> This is part 1 of 2 of the full documentation (pages 1–100 of 161).
> The content is paginated: fetch every part to see all of it.
> Next part: https://docs.nosto.com/ugc/llms-full.txt/1
> Page index: https://docs.nosto.com/ugc/llms.txt

# Introduction

Nosto's UGC enables engaging social media-driven creations with little effort.

Since the early days of [Nosto](https://www.nosto.com/), we've tried to make it easy to customize the look and feel of integrations with out-of-the-box features. There are many websites, apps, and engaging experiences that utilize rich, social media awesomeness via Nosto without writing an additional line of code.

For those who need that extra bit, or who have an uber dev team who is itching to apply their craft, Nosto has exposed some of its internal components for customer consumption.

The [Nosto](https://www.nosto.com/)'s UGC Techdocs is divided into 6 main sections:

## Guides

To get you up to speed quicker, or if you want to see some of the potential uses of Nosto's UGC API, we've prepared some [guides](/ugc/guides) for you.

## API Docs

For those more tech-inclined, you may want to dive right into the API documentation, and we've divided it into the four sections below.

#### JavaScript

If you are looking to add your creative touch to Widgets, Event screens, or Emails via the front end, this section will get you there.

[JavaScript API Info](/ugc/api-docs/javascript)

#### Content API

With powerful features and lightning speed, Nosto's UGC Content API allows you to simplify your UGC integration.

[Content API Info](/ugc/api-docs/content)

#### REST API

Looking to use Nosto's UGC data interfaces to integrate into your existing or upcoming application's backend? REST assured, we've got you covered.

[REST API Info](/ugc/api-docs/rest)

#### Webhooks

Would you like for your application to be notified when something important happens in your Stack? We can hook you up.

[Webhooks API Info](/ugc/api-docs/webhooks)

## Widgets

Understand what widgets are and when to use them

[Widgets Info](https://developer.stackla.com/widgets)

## Enterprise Tools

Find a full list of Enterprise products available within your Nosto's UGC License.

[Enterprise Tools Info](https://developer.stackla.com/enterprise)

## Commerce Tools

Leverage the power of Nosto's UGC to help drive the most out of your eCommerce site.

[Commerce Info](https://developer.stackla.com/commerce)

## Analytics

Discover how to effectively report on the impact of Nosto's UGC in your favorite Analytics tool.

[Analytics Info](https://developer.stackla.com/analytics)

If you have questions or need assistance please let us know via email at <support@nosto.com>.


# Guides

To demonstrate and help you get started, the following are some examples of common uses of Nosto's UGC to enrich an existing website or application.

#### Note about guide formatting

The structure of the guides is mostly these three parts:&#x20;

1. Introduction to the use case
2. In-depth code or analysis
3. Next steps or further exercises

In addition, we've capitalized words when they refer to specific Nosto's UGC concepts to make them stand out more. For example, the word Tag is capitalized when talking about a UGC Tag, as opposed to a tag in the context of clothing, a tag in the context of word clouds, or a tag in the context of graffiti.

```javascript
<script type="text/javascript">(function (d, id) { 
    var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; 
    if (el) el.dataset.initTimestamp = (new Date()).getTime(); 
    if (d.getElementById(id)) 
        return; t = d.createElement('script'); 
        t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; 
        t.id = id; (d.getElementsByTagName('head')[0] || 
        d.getElementsByTagName('body')[0]).appendChild(t); 
    }(document, 'stackla-widget-js'));
</script>
```


# Analytics


# Tracking UGC on Adobe Analytics

* [Overview](#overview)
* [Basic Tracking](#basic-tracking)
  * [Basic Tracking Code](#basic-tracking-code)
    * [jQuery.getscript Sample Code](#jquery-getscript-sample-code)
    * [Stackla.loadJS Sample Code](#stackla-loadjs-sample-code)
  * [Track iFrames Load](#track-iframes-load)
    * [Sample Parent Page Code](#sample-parent-page-code)
    * [Sample Code for Widget](#sample-code-for-widget)
* [Event/Link Tracking](#event-link-tracking)
  * [Global Widgets Events API](#global-widgets-events-api)
    * [Example](#global-widgets-events-api-example)
  * [Adobe Analytic Definitions / Prerequisites](#adobe-analytic-definitions-prerequisites)
    * [s.tl() Function](#adobe-analytic-definitions-prerequisites-function)
    * [Structure](#adobe-analytic-definitions-prerequisites-structure)
    * [Variables](#adobe-analytic-definitions-prerequisites-variables)
  * [Examples](#event-link-tracking-example)
    * [UserClick - Track Exit Link](#userclick-track-exit-link)
    * [userClick - Track Exit Link + Populate Variables](#userclick-track-exit-link-populate-varaibles)
    * [productActionClick - Track Custom Link](#productactionclick-track-custom-link)
* [Campaign Tracking](#campaign-tracking)
  * [Product Tags](#product-tags)
  * [Custom Campaign Tags](#custom-campaign-tags)

## Overview

This document provides an overview of the different approaches which can be taken by a client who uses Adobe Analytics as their Web Traffic Analytics tool.

The document outlines the options and level of tracking available within the tool for a client to take advantage of.

[Back to Top](#top)

## Basic Tracking

### Basic Tracking Code

For clients who wish to reference the standard Analytics code directly on their Widget, they can simply use the jQuery call `jQuery.getscript` or the `Stackla.loadJS` call to achieve this.

Both calls will fetch the relevant external JS file and load it whenever the Widget loads, however the jQuery script will load the code in sequential order, whilst the Nosto's UGC call will load all external JS files simultaneously.

#### jQuery.getscript Sample Code

```
$.getScript('ttps://<jsfile>.js')
    .then($.getScript('https://<jsfile>.js'))
    .then($.getScript('https://<jsfile>.js'))
    .then(function () {
        // Do something after the external files are loaded
    });
```

#### Stackla.loadJS Sample Code

```
Stackla.loadJS([
    'https://<jsfile>.js',
    'https://<jsfile>.js',
    'https://<jsfile>.js'
]).then(function() {
    // Do something after the external files are loaded
});
```

**Please Note**: Nosto's UGC renders as an iFrame on the Parent Page upon which it has been embedded. As such, duplicating the same code on both the Parent Page and iFrame may result in double counting within your Adobe Analytics account.

[Back to Top](#top)

### Track iFrames Load

When the Nosto's UGC Widget renders on a page it renders as an iFrame, this means if a client wants, they can use postMessage within the Nosto's UGC Widget and create an event listener on event "message" on the parent page to record if the Widget has in-fact loaded.

The framework of the code that would need to be used is provided below:

#### Sample Parent Page Code

```html
<script>
    function listenMessage(e) {
        if(e.data == "Iframe is loaded"){
            alert(e.data);
            /*Place Adobe Analytics Code*/
        }
    }

    if (window.addEventListener) {
        window.addEventListener("message", listenMessage, false);
    } else {
        window.attachEvent("onmessage", listenMessage);
    }
</script>
```

#### Sample Code for Widget

```
window.top.postMessage('Iframe is loaded','http://url');
```

The above code should be used in conjunction with the Nosto's UGC Javascript API - Inline Tile API Callbacks.

[Back to Top](#top)

## Event/Link Tracking

Nosto's UGC, through it’s Global Widgets Events API, provides an number of events for clients to subscribe to in order to more accurately track user interactions.

### Global Widgets Events API

The Global Widgets Events API is a Javascript API that is only available in Vertical Fluid, Horizontal Fluid, Waterfall, Carousel and Mosaic widgets.

The Event is available via the `window.Stackla.WidgetManager` global variable.

#### Example

```
Stackla.WidgetManager.on('tileExpand', function (e, data) {
    // Maybe you need to filter when you have multiple widgets on the same page.
    if (data.widgetId === 9999) {
        // do something...
    }
});
```

To find out more information around these `events` please use [this guide](/ugc/api-docs/javascript/widgets/reference#events)

[Back to Top](#top)

### Adobe Analytic Definitions / Prerequisites

The level of detail / granularity that the client wishes to record in their Adobe Analytics environment will determine whether any pre-configuring is required prior to tracking the Nosto's UGC Global Widgets API events.

Adobe Analytics has three core variables which can be used to track information based upon events at a more granular detail. These variables are:

* Custom Conversion Variables (**eVars**)
* Custom Traffic Variables (**sProps**)
* Success **Events**

For the purpose of the tracking Events from the Nosto's UGC Widget, the Variables eVars and Events will be used.

It is recommended that these configurations are defined within the client’s Adobe Analytics instance before implementing any triggers within Stackla.

#### s.tl() Function

This guide leverages outlines a number of call using the function `s.tl()`. This function has been identified as appropriate for a number of the use-cases relating to tracking Events from a Nosto's UGC Widget and reporting them within Adobe Analytics.

This function is not the only function that can be used by a client, and is more a recommendation versus a prescription. Outlined below is the structure and variables relating to this function which are relevant for the examples in this guide.

#### Structure

```
s.tl(this,linkType,linkName, variableOverrides, doneAction)
```

#### Variables

| Variable                                                                                      | Description                                                                                                                                                                                |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `this`                                                                                        | The first argument should always be set either to this (default) or true. The argument refers to the object being clicked; when set to "this," it refers to the HREF property of the link. |
| `linkType`                                                                                    |                                                                                                                                                                                            |
| `linkType` has three possible values, depending on the type of link that you want to capture. |                                                                                                                                                                                            |

* File Download - `d`
* Exit Links - `e`
* Custom Links - `o`

\| | `linkName` | This can be any custom value, up to 100 characters. This determines how the link is displayed in the appropriate report. | | `variablesOverrides` | (Optional, pass null if not using) This lets you change variable values for this single call, It is similar to other AppMeasurement libraries. | | `doneAction` | An optional parameter to specify a navigation action to execute after the track link call completes when `useForcedLinkTracking` is enabled. |

[Back to Top](#top)

### Examples

The following examples will generate a trackable event from the Expanded Tile view of the Nosto's UGC Widget that can be passed to Adobe Analytics.

Each of these examples have a series of assumptions which have been made on the Adobe Analytics end. The assumptions will need to be customised to work for a client’s specific use-case.

#### userClick - Track Exit Link

This example leverages Adobe Analytics function `s.tl()`. The structure of this function is listed under the Adobe Analytics Definitions / Prerequisites.

**Assumptions**

Client wants to track whenever a visitor clicks on a `UserName` (and is then sent to an the Source Network) that it tracks as an Exit Link in Adobe Analytics. No `eVars` or Events will be tracked as part of this code.

**Variables**

* `linkType` - Exit Link
* `linkName` - Widget-UserNameClick

**Sample code**

```
Stackla.WidgetManager.on('userClick', function (e, data) {
    s.tl(this,'e','Widget-UserNameClick');
});
```

[Back to Top](#top)

#### userClick - Track Exit Link + Populate Variables

This example leverages Adobe Analytics function `s.tl()`. The structure of this function is listed under the Adobe Analytics Definitions / Prerequisites.

**Assumptions**

Client wants to track whenever a visitor clicks on a UserName (and is then sent to an the Source Network) that it tracks as an Exit Link in Adobe Analytics. Client also wants to append additional information to Event and related eVars.

For the example, the Event has been defined as `event1` and the `eVars` as `eVar45` and `eVar46`

**Variables**

* `linkType` - Exit Link
* `linkName` - Widget-UseNameClick
* `linkTrackEvents` - event1
* `eVar45` - WidgetID
* `eVar46` - TileID

**Sample Code**

```
Stackla.WidgetManager.on('userClick', function (e, data) {
    s.linkTrackVars= s.linkTrackVars+',eVar45,eVar46,events'; s.events='event1'; s.linkTrackEvents='event1'; s.eVar45='WidgetID: ' + data.widgetId; s.eVar46='TileID: ' + data.tileData._id.$id;
            s.tl(this,'e','Widget-UserNameClick');
});
```

[Back to Top](#top)

#### productActionClick - Track Custom Link

This example leverages Adobe Analytics function `s.tl()`. The structure of this function is listed under the Adobe Analytics Definitions / Prerequisites.

**Assumptions**

Client wants to track whenever a visitor clicks on a productActionLink that it tracks as an Custom Link in Adobe Analytics. Client also wants to append additional information to Event and related eVars.

For the example, the Event has been defined as `event1` and the `eVars` as `eVar45` and `eVar46`

**Variables**

* `linkType` - Custom Link
* `linkName` - Widget-UseNameClick
* `linkTrackEvents` - event1
* `eVar45` - WidgetID
* `eVar46` - TileID

**Sample Code**

```
Stackla.WidgetManager.on('productActionClick', function (e, data) {
    s.linkTrackVars= s.linkTrackVars+',eVar45,eVar46,events'; s.events='event1'; s.linkTrackEvents='event1'; s.eVar45='WidgetID: ' + data.widgetId; s.eVar46='TileID: ' + data.tileData._id.$id;
            s.tl(this,'o','Widget-UserNameClick');
});
```

[Back to Top](#top)

## Campaign Tracking

One of the features of the Adobe Analytics tool is the ability to track the flow of a user through different sites and pages within a campaign.

The Campaign functionality enables clients to track conversion of pre-defined campaigns/goals within the tool.

### Product Tags

Nosto's UGC’s Product Tags enable clients to specify a URL and a Call to Action (CTA) which can be rendered as a Social Commerce or ShopSpot output on the Visual UGC Widgets.

For clients which simply want to track all clicks on these output and associate them with Nosto's UGC, the client can simply interest the Campaign ID `cid` to the end of the respective URLs, and then each time this is clicked, Adobe Analytics will be updated.

The structure of the URL will simply be:

```
https://<clientdomain>/<producturl>?cid=<campaignID>
```

This approach works for clients who want to use the same Campaign ID across all Nosto's UGC Widget outputs.

[Back to Top](#top)

### Custom Campaign Tags

For clients who want to have a more customised Campaign Tag (ie. Because they want to track on a per Widget basis or because they want the Parent Page to influence the Tag), Nosto's UGC’s JS API provides the ability for a client to either fetch variables from the Parent Page or alternatively declare a variable per Widget.

[Back to Top](#top)


# Tracking Widget interactions with Google Analytics

* [Overview](#overview)
* [Key Concepts](#key-concepts)
* [The Fun Part](#the-fun-part)
  * [Getting Started](#getting-started)
  * [Getting to know the Global Widget Events API](#getting-to-know-the-global-widget-events-api)
  * [Sending data to Google Analytics](#sending-data-to-google-analytics)
* [Conclusion](#conclusion)

## Overview

Nosto's UGC provides a Google Analytics plugin that allows you to track common user interactions via Google Event tracking.

The data sent to Google Analytics is pre-set and configuration is limited to choosing which Widgets you want to track and the specific interactions you want to track on them.

In this guide we will use the Nosto's UGC [JavaScript API](/ugc/api-docs/javascript) to customise the data that is sent to Google Analytics for the events we choose to track.

[Back to Top](#top)

## Key Concepts

### Google Analytics Events

[Events](https://support.google.com/analytics/answer/1033068?hl=en) are user interactions with content that can be tracked independently from a web page or screen load.

Data related to these interactions can be sent to Google Analytics.

[Back to Top](#top)

## The Fun Part

### Getting Started

In this example we will need to do the following:

* Use the [Global Widget Events API](/ugc/api-docs/javascript/widgets/reference#parent-page) to find out what data Nosto's UGC Widgets make available for the user interactions that we choose to track.
* Use Google Analytics event tracking methods to send some of this data to Google Analytics.

[Back to Top](#top)

### Getting to know the Global Widget Events API

Nosto's UGC’s [Global Widget Events API](/ugc/api-docs/javascript/widgets/reference#parent-page) provides a number of events that you can subscribe to in order to track user interactions.

Today we’ll work with two:

1. tileExpand - triggered when a user clicks a Tile to view it in a lightbox
2. productActionClick - triggered when a user clicks a “Buy” button on a Tile (Social Commerce feature)

First we’ll look at what data these events makes available, then we’ll configure Google Analytics event tracking to send some of this data to Google Analytics when users trigger these events.

To start, we’ll crack open the Custom Code Editor on our Widget, switch to the Expanded Tile tab and add the following code.

Adding this code in the Expanded Tile JS editor instead of directly on the parent page means Nosto's UGC will load this code for us when Stackla.WidgetManager global variable is available, avoiding the need to check and re-check for this variable before executing our code.

```
Stackla.WidgetManager.on('tileExpand', function (e, data) {
    // Log all the available data to the console
    console.log(data);
});
```

With this, we’ll get a dump in the console of all the data associated with the `tileExpand` event that Nosto's UGC makes available.

This data is contained inside an aptly named `data` object. Let’s fetch some stuff from there and send it to Google Analytics via event tracking.

[Back to Top](#top)

### Sending data to Google Analytics

Before we look into this further, we'll need a way to establish how this data is being sent to GA. As the ga() calls do not use the current Nosto's UGC Google Analytics plugin Tracking ID or data, we'll need to include this in the Widget or the parent page with the tracking ID:

```
 (function(i, s, o, g, r, a, m) {
        i.GoogleAnalyticsObject = r;
        i[r] = i[r] || function() {
            (i[r].q = i[r].q || []).push(arguments);
        }, i[r].l = 1 * new Date();
        a = s.createElement(o),
            m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        m.parentNode.insertBefore(a, m);
    })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');

 ga('create', 'UA-XXXXXX-X', 'auto'); 
 //CHANGE THE X TO the Tracking ID
```

Once this is done we can replace the console log with a Google Analytics Event method which has [this format](https://developers.google.com/analytics/devguides/collection/analyticsjs/events#implementation):

```
ga('send', 'event', [eventCategory], [eventAction], [eventLabel], [eventValue], [fieldsObject])
```

The `eventCategory`, `eventAction` and `eventLabel` values correspond with columns in our Google Analytics Event reports and we’ll replace them with data from our data object.

We’ll leave `eventValue` blank as it is not required. We’ll also omit `fieldsObject` for now.

Here goes…

```
Stackla.WidgetManager.on('tileExpand', function (e, data) {
    ga('send', 'event', 'Widget (' + data.widgetId +') Filter (' + data.filterId + ')', 'Tile expand', 'tile_id: ' + data.tileData._id.$id + ', source: ' + data.tileData.source + ', media: ' + data.tileData.media);
});
```

Here’s a breakdown of the above:

For `eventCategory` we have included “Widget (Widget ID here), Filter (Filter ID here)”. That way we can report on each Widget/Filter combination on our website.

`eventAction` describes the user interaction that has taken place - ‘Tile expand’ seems an accurate label.

Our eventLabel is where most of our data will be sent. In this case we’ve included things like the Tile ID, the source (network) and the media type. This will allow us to answer questions like “which media type (image, video, text) attracts the most clicks?”.

When we log in to Google Analytics, hit the Reporting tab and go to Behaviour > Events > Top Events we’ll see something like this:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/llJvSscmJcZgqcmVncM3/ga-event.gif)

Let’s track another event…

```
Stackla.WidgetManager.on('productActionClick', function (e, data) {
    ga('send', 'event', 'Widget (' + data.widgetId +') Filter (' + data.filterId + ')', 'Buy button click', 'tile_id: ' + data.tileData._id.$id + ', source: ' + data.tileData.source + ', media: ' + data.tileData.media + ', sku: ' + data.productTag.ext_product_id);
});
```

This event will track clicks on a product buy button on a Nosto's UGC Tile (productActionClick).

We’ll send the same data that we did for the tileExpand event but we’ll also append the product sku to the eventLabel. Product sku is stored in the ext\_product\_id field on a Tile and made available in the data.productTag object for this specific event.

By drilling down on “Buy button click” events in GA, we can filter by product sku to see which Tiles are driving the most traffic to our purchase pages.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/NX8YLPDEzalKqmw6du8l/buy-button-clicks.jpg)

[Back to Top](#top)

## Conclusion

By leveraging the events and data made available by the [Global Widget Events API](/ugc/api-docs/javascript/widgets/reference#parent-page) we can customise the data sent to Google Analytics.

[Back to Top](#top)


# Direct Uploader


# How to add Custom Data to Direct Uploader

* [Overview](#overview)
* [Available options](#steps)

## Overview

Out of the box, Nosto's UGC Direct Uploader form offers a great solution for clients who wish to collect media (Image / Video) submissions, whilst also collecting key pieces of data (ie. Name, Email Address). This works great when the contributor is unknown prior to them contributing or when you want to force the user to re-provide this information.

Sometimes however you may already have information about this user, and rather than requiring them to populate these field, you wish to pass information from the parent page directly into this GoConnect form.

This guide outlines the two approaches which can be taken to achieve this. The first is to provide the passed data into the GoConnect embed code. This works well when this information is known at the point of load, however it does limit us if we wish to update this information on the fly. This is where the second approach can overcome this, leveraging Nosto's UGC Javascript API to pass in these values on an as needs basis.

[Back to Top](#top)

## Available options

### Approach 1: Append custom data to the Embed Code

1. There are two places within the GoConnect form where custom data can be appended. The first is as plain text which is rendered in any part of the GoConnect HTML, the second is as a pre-populated value within one of the fields. As such, to help the form know where this data should live, Nosto's UGC has two custom data prefix types which need to be included in order for the GoConnect form to know where the custom data will go.Plain text values should have the prefix "data-custom-" (ie. data-custom-username) whilst values for the fields should have the prefix "data-field-" (ie. data-field-firstname"). It is important for the field data that the unique parameter (ie. firstname) is consistent with the field names specified in your templates. Both these values should be appended to the DIV class part of the GoConnect embed code as per the screenshot below.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/MHujbpie8Jo3jREyam0Y/data-par-3.png)

2. Once we have defined the custom data within the embed code the next step is to tell Nosto's UGC where to inject this data. We do this within the GoConnect templates. To find the template section, simply open the GoConnect Form you are customising, select Configure and press 'Custom Template' on the relevant tab you wish to inject these values.From here we can inject into the Template where we would like this code to render.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ByjIUfFYcHvNCuosNm9N/data-par-2.png)

3. Now to render the custom plain text values, we need to enclose the custom data with three braces on either side and include the value *custom.*. Parameter is the unique identifier we have defined in the embed code. For example, using are example of data-custom-username, we would want to add the following code \_{custom.username}\_As defined earlier, the custom field text defined in the embed code is the same as the parameter used in the template. Using the screenshot below, the parameter in the template is "firstname" which is consistent with the code we put in the embed code - "data-field-firstname".

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/qtK17npCJL4hiY248tsU/data-par-1.png)

4. Once we are happy with our template structure, we can now render the embed code. Using the example provided above, on the second tab of our GoConnect form, we would see something similar to the below:![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Hmo7SqvveEekgGP95AyB/js-lib-1.png)

### Approach 2: Append Custom Data via the Javascript API

The above approach works great if we have a scenario when we are able to predefine those custom data values for our GoConnect Widget. Where it struggles though is when we need / want to dynamically change these values. Using the first approach, we would have to refresh the entire GoConnect form, however that is a less than desirable user experience, as such, this is where Nosto's UGC Javascript API can help.

Now if we want to update the custom data text displayed in the HTML of the GoConnect form, the call we wish to use is below:

```

        Stackla.GoConnect.setText(placeholderName, newPlaceholderValue)
    
```

Taking the example we created in the first approach, we can take the above and change the data-custom-username we defined earlier from mariano.carpenter to MARIANO using the JS Console in Google Chome

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/LKT6mBWiMv7Zvmc0khLa/js-lib-2.png)

Now if its the field data we want to change, the call we make is slightly different with us looking to change the FieldValue versus the PlaceholderValue.

```

Stackla.GoConnect.setField(fieldName, newFieldValue)
    
```

Again, taking the example we created in the first approach, we can take the above and change the data-field-firstname we defined earlier from Mariano to MARIANO using the JS Console in Google Chome

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/RVwiw3QKlgRgWPAuQryk/js-lib-3.png)

### Approach 3: Using Custom Data for Input Fields

If we have a scenario where we need to inject or pass values from GoConnect Widget to the form's text field or hidden field.

We can opt this approach to pass dynamic or static data to the form's text fields, or hidden fields. Using this approach, just the value gets changed and either is displayed to the user or you can create a hidden field or make an text field hidden to make sure the values never be seen to the user, this approach is pretty handy and can be used in conjunction with the above 2 approaches.

First of all, you need to inject the dynamic or static property the same way as defined in above approaches,

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/8BjWDEnqcRfo1ovYtDNq/Screenshot-2023-01-27-at-11.13.35-am-700x188.png)

Secondly, to add this variable into the text field or hidden field, you can use the variable - {{{customvalue.registrationid}}}, as shown in the image below

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/xRufhlfePYMbAsiaZTZG/Screenshot-2023-01-27-at-11.10.14-am-700x273.png)

This way, we can add the value to text fields

### Widget Expiration : TTL Option (optional)

* The Default expiry time of widget elements is 60 seconds, and in most of the cases you do not need to adjust the TTL,
* There are some specific scenarios where you must specify the TTL option,

**TTL Scenario**

* If you are passing a dynamic custom variable as suggested in Approach 1st & 3rd, then you can use TTL option, as shown in the image below

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/JhJTbIfsateQkUASZwwl/Screenshot-2023-02-06-at-11.35.36-am-700x114.png)

* `data-ttl` option is in seconds and this is optional and if not provided will default to 60 seconds

### Widget Custom Uploaded Data

* The data punched-in will gets to the Curate -> Content section, and can be seen by clicking three dots - ... -> Tile Details -> Data as shown in image
* custom\_input holds the data passed via the custom variable using Approach 1 & 3

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/rY20Pbn4iHin9YPvVtIG/Screenshot-2023-02-06-at-11.51.46-am-700x245.png)

[Back to Top](#top)


# How to capture Custom Data on Direct Uploader

## Overview

Often, you would like to capture additional data from your audience via the Direct Uploader widget for your events or competitions. This guide will show you how to setup custom inputs on your Direct Uploader widget. First thing first, navigate to the Direct Uploader management screen - Aggregate > Direct Uploader, select your favourite Direct Uploader widget. When you are on your Direct Uploader widget, click "Configure" tab, and choose a template where you would like to add your own custom input fields.

## Adding Custom fields

Direct Uploader accepts input fields that are prefixed with "custom-data-" and followed by the name of the input field. Note: The name of the input field must contain alphanumeric or underscore characters **only**. e.g `custom-data-**gender**` is correct, `custom-data-**your-gender**` is incorrect. See following sample code, which captures Company, Gender and About from the audience

```

<div class="row">
    <div class="col-md-12">
        <div class="st-form-group">
            <label>Company</label>
            <input type="text" class="st-form-control" name="custom-data-company" value="" placeholder="Company" />
        </div>
    </div>
</div>
<div class="row">
    <div class="col-md-12">
        <div class="st-form-group">
            <label>Gender</label>
            <select name="custom-data-gender" class="st-form-control">
                <option></option>
                <option value="male">Male</option>
                <option value="female">Female</option>
            </select>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-md-12">
        <div class="st-form-group">
            <label>Tell us more about yourself</label>
            <textarea name="custom-data-more_about_yourself" class="st-form-control"></textarea>
        </div>
    </div>
</div>
    
```

Save your settings to see additional fields appear on your Direct Uploader widget or click on the preview.

\</br/> When content come through from your Direct Uploader widget, your custom input will be captured in the `__external_data` column on the Nosto's UGC **Tile** object.

```
// Tile Object
{
    ...
    "\_\_external\_data": {
        "\_id": { "$id": "123" }
        "firstname": "John",
        "lastname": "Smith",
        "email": "John.Smith@stackla.com",
        "ip": "0.0.0.0",
        "terms\_and\_conditions\_url": "http://your-t-and-c.com",
        "terms\_and\_conditions": 1,
        "company": "Nosto",
        "gender": "Male",
        "more\_about\_yourself": "I love Visual UGC"
    },
    ...
}
```

#### Custom validation

Now that you have added your custom input fields, It's time to add some validation to those input fields. By default, your custom input will be validated against our server by the following rules:

* Field name must not exceed 1024 characters
* Input value must not exceed 1024 characters
* Input must not contain HTML elements
* Message field supports up to 32000 characters
* Custom\_input field supports up to 250 characters

However, You can also provide your own validation by adding a callback in the Custom JS tab

```

        window.Stackla.GoConnectCallbacks = {
            onBeforeSave: function (formData, errorMessagesFromServer) {

                if (typeof(formData['custom-data-company']) !== 'undefined' && !formData['custom-data-company']) {

                    errorMessagesFromServer['custom-data-company'] = 'Hey, please enter a company!';

                    return false;

                } else {

                    return true;

                }

            }
        };
    
```

Note the callback **onBeforeSave** will be passed with 2 parameters when the beforeSave event is fired.

* **formData** is the data to be sent to Nosto's UGC for validation before save happens.
* **errorMessagesFromServer** is an error object Nosto's UGC server returns when data is validated by Nosto's UGC. If you would like to add your own validation logic, simply adding for modifying this object.
* Remember to **return true** or **false** from your callback to indicate whether or not to go ahead with saving the data to Nosto's UGC

#### Returning Custom Data from Nosto's UGC

In Nosto'sual UGC, we take security seriously, custom inputs from your Direct Uploader widget will be **hidden** from all Nosto's UGC public endpoints. However, there are some situations where you would like certain input to be exposed to the public, for example, you are capturing "company" from your Direct Uploader widget, and you want "company" data to be displayed on your widget. You can achieve this by adding **data-permission="public"** to your "company" input on the Direct Uploader widget prior to your Direct Uploader widget go live.

When you adding **data-permission="public"** data attribute to your custom inputs, you indicate to the Direct Uploader widget that these fields will be exposed to the public. Visual UGC will capture your public data in the **external\_data** column on your Visual UGC **Tile** object.

```
// Tile Object
{
    ...,
    "external_data": {
        "company": "Visual UGC"
    }
    ...,
}

// Custom Widget JS - Display "company" data on your Widget
Callbacks.prototype.onCompleteJsonToHtml = function (tileObject, t) {
    if (t && t.external_data && t.external_data.company) {
        tileObject.append(t.external_data.company);
    }
    return tileObject;
};
```

*Note that you can find your public data in the **external\_data** column on the Visual UGC Tile Object, external\_data is a subset of the **\_\_external\_data** column. **\_\_external\_data** on the other hand is private and it is hidden from all public endpoints by default, you can only access it on Moderation View or from Visual UGC API*


# How to automatically tag data on Direct Uploader

## Overview

Often, you would like to automatically tag your content from your audience via the Direct Uploader widget for your events or competitions. This guide will show you how to setup a custom tag field on your Direct Uploader widget. First thing first, navigate to the Direct Uploader management screen - Aggregate > Direct Uploader, select your favourite Direct Uploader widget. When you are on your Direct Uploader widget, click "Configure" tab, and choose a template where you would like to add your own custom input fields.

## Adding Custom fields

Specify a field with the name custom-data-tags. The value of this field will be used to tag your content. It should match a tag name (case-insensitive) in your stack .

```

<div class="row">
    <div class="col-md-12">
        <div class="st-form-group">
            <label>Animal breed</label>
            <select name="custom-data-tags" class="st-form-control">
                 <option value="" disabled selected>Select your animal breed</option>
                 <option value="Domestic Cat">Domestic Cat</option>
                 <option value="Poodle">Poodle</option>
                 <option value="Husky">Husky</option>
            </select>     
            
        </div>
    </div>
</div>
```

Save your settings to see if the field appear on your Direct Uploader widget or click on the preview.

#### Want to stay up to date with all the latest Stackla developer news? [Sign up!](https://github.com/Stackla/docs/blob/master/docs/signup/README.md)


# How to Customize error messages on Direct Uploader

## Overview

When submitting data to the GoConnect widget, all the error messages returned from the server can be customized. This is achieved by defining a mapping object **window\.Stackla.GoConnectCustomMessages**, and add it to the **Custom Javascript** Tab on the GoConnect widget configuration page.

The following code provides a mapping between the server response messages and your customized messages. paste below javascript code on your Custom Javascript Tab.

```
`window.Stackla.GoConnectCustomMessages = {
fileUpload: {
    prompt: 'ここにあなたのイメージをドラッグ',
    tip: '最大サイズ600×600',
    uploading: '上傳',
    uploaded: '完成上傳',
    cancel_upload: '取消上傳',
    remove_file: '刪除文件'
},
responseMessages: {
    video: {
        not_empty: '視頻不能為空',
        length_too_long: '視頻長度過長',
        size_too_small: '視頻大小太小',
        size_too_large: '視頻大小過大',
        invalid_extension: '視頻擴展名無效',
        invalid_mime_type: '視頻mime類型不支持',
        upload_incomplete: '上傳不完整',
        transcoder_rejected: '代碼轉換器拒絕'
    },
    image: {
        please_wait: 'お待ちください',
        not_empty: 'あなたのイメージをアップロードしてください',
        wait_a_short_time: '短い時間を待ちます',
        success: '成功',
        invalid_image_file: '無効なイメージファイル',
        invalid_image_type: '無効なイメージタイプ',
        exceed_max_size: '最大サイズを超えます',
        exceed_max_dimension: '最大サイズを超えました',
        below_min_dimension: '分寸法以下',
        error_process_image: 'エラー処理イメージ'
    },
    comment: {
        not_empty: 'コメントを入力してください',
        max_length: '最大の長さ',
        html_not_allow: '許可しないHTML'
    },
    firstname: {
        not_empty: 'コメントを入力してください',
        max_length: '最大の長さ',
        html_not_allow: '許可しないHTML'
    },
    lastname: {
        not_empty: 'コメントを入力してください',
        max_length: '最大の長さ',
        html_not_allow: '許可しないHTML'
    },

    email: {
        not_empty: 'コメントを入力してください',
        max_length: '最大の長さ',
        html_not_allow: '許可しないHTML',
        invalid_email: '無効なEメール'
    },
    terms_and_conditions: {
        not_empty: 'コメントを入力してください'
    }
}
```

};\`

As this mapping object is saved, the error messages on your GoConnect widget will be shown as you customized it:\
(**note:** If particular message is not supplied by this mapping object, GoConnect widget will use the default message from the server instead)

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/sjNty0qZLL6J7sOZVeRQ/goconnect-customize-messages.png)


# How to Track Direct Uploader form submissions with Google Analytics (Universal & GA 4)

* [Overview](#overview)
* [Key Concepts](#key-concepts)
* [The Fun Part](#the-fun-part)
  * [Getting Started](#getting-started)
  * [Getting to know the Global Widget Events API](#getting-to-know-the-global-widget-events-api)
  * [Sending data to Google Analytics](#sending-data-to-google-analytics)

## Overview

Nosto's UGC provides a Google Analytics plugin that allows you to track common user interactions via Google Event tracking.

The data sent to Google Analytics is pre-set and currently limited to interactions with Onsite Widgets and Email Campaigns.

In this guide, we will showcase how you can use the Direct Uploader custom code editor to track in Google Analytics when someone submits a form.

[Back to Top](#top)

## Key Concepts

### Google Analytics Events

[Events](https://support.google.com/analytics/answer/9356037?hl=en\&sjid=5023917053232407279-AP) are user interactions with content that can be tracked independently from a web page or screen load.

Data related to these interactions can be sent to Google Analytics.

[Back to Top](#top)

## The Fun Part

### Getting Started

In this example we will need to do the following:

* Create a Direct Uploader form
* Use Google Tag Manager and Google Analytics event tracking methods to send some of this data to Google Analytics.

This guide can be used for both Universal Analytics and Google Analytics 4.

[Back to Top](#top)

### Sending data to Google Analytics

Before we look into this further, we'll need a way to establish how this data is being sent to Google Analytics via Google Tag Manager. As the gtag() calls do not use the current Nosto's UGC Google Analytics plugin Tracking ID or data, we'll need to include this in the Direct Uploader Javascript or the parent page with the tracking ID or Google Tag ID / Measurement ID. In the example below we are including firstname, lastname, and email as custom parameters for the event

```
window.Stackla.GoConnectCallbacks = {
 onBeforeSave: function (formData, errorMessagesFromServer) {
 if(typeof(formData['custom-data-programs']) !== 'undefined' && formData['custom-data-programs'] == 'Select from the drop-down menu') {
    errorMessagesFromServer['custom-data-programs'] = 'Please select a program';
    return false;
 } else {
   gtag('event', 'nosto_ugc_upload_form_submission',
{
'app_name': 'Nosto - UGC',
'firstname': $("[name='firstname']").val(),
'lastname': $("[name='lastname']").val(),
'email': $("[name='email']").val(),
}
);
 return true;
 }    }
};var head = document.getElementsByTagName('head')[0];var script = document.createElement('script');script.setAttribute('async', '');script.setAttribute('src', 'https://www.googletagmanager.com/gtag/js?id=X-XXXXX');head.appendChild(script);window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'X-XXXXX'); 
```

Please change the X-XXXXX in the code above to Tracking Id for Universal Analytics or Google Tag Id / Measurement ID for Google Analytics 4. You can find more details [here](https://support.google.com/analytics/answer/9539598?hl=en).

[Back to Top](#top)


# Data Templates


# Creating a Data Template

* [Overview](#overview)
* [Building the Templates](#the-fun-part)
* [Conclusion](#conclusion)

## Overview

Nosto's UGC offers the ability for clients to append their own Custom Data to any Tile or Tag on the Platform, allowing for clients to enrich the content on their Stack with additional data.

This custom data is appended to the Tile or Tag data and is available to be rendered through any Nosto's UGC output, plus the Nosto's UGC API.

In this guide we are going to build two Custom Data templates, one for Tiles, and one for Tags, which we can then leverage via the Nosto Admin Portal.

[Back to Top](#top)

## Building the Templates

### Getting Started

For the following guide we are going to create two simple Data Templates within our Stack. The use-cases for each are outlined below:

* Add a response to a Question on a Tile
* Add alternative Pricing to a Product Tile

### Data Templates for Tiles

First step is to define our template. As outlined on the [Data Templates](https://github.com/Stackla/docs/blob/master/enterprise/datatemplates/README.md) page, you will need to define a **Template Name**, **Title** and **Scope** before you can start building out the form.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SC1lZtHIAFLfXINkKcoj/Screen-Shot-2018-02-12-at-8.44.04-am.png)

For the purpose of this guide, we are going to give the Data Template form the **Name** and **Title** of 'Q\&A Template'. From here we are going to select **Scope**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dAAn8a94soeSZzzXDdHj/Screen-Shot-2018-02-12-at-8.44.16-am.png)

We will select the **Scope** 'Tiles', and click on the **More Settings** button to limit this only to **Text** tiles.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Up2tB6N7DgccLEiiT2UT/Screen-Shot-2018-02-12-at-8.44.35-am.png)

The **Scope** builder works very similar to **Advanced Tag Builder** on the Platform. We are going to enter a very simple query of `Media = Text`

From here we can define our **Schema** and **Options**. Nosto's UGC Data Templates use a framework called [Alpaca](http://www.alpacajs.org) to define these elements, with the JSON Schema defining how the data is stored in Nosto's UGC, and Options defining how it is rendered in the form.

For this guide, we want to store two attributes, the Answer to the question (Text Area) and the author of the response (Picklist). As such we will put the following code in:

**Schema:**

```
{
    "type":"object",
    "properties": {
        "response": {
            "type":"string"
        },
        "author": {
            "type":"string",
            "enum": [
                "Iestyn_Harris",
                "Lee_Briers",
                "Jonathan_Davies",
		"Ian_Watson",
		"Kerion_Cunningham"
            ]
        }
    }
}
```

**Options:**

```
{

    "fields": {
        "response": {
            "type":"textarea",
            "label": "Answer"
        },
        "author": {
            "type":"select",
            "label": "Author",
            "optionLabels": [
                "Iestyn Harris",
                "Lee Briers",
                "Jonathan Davies",
		"Ian Watson",
		"Kerion Cunningham"
            ]
        }
    }
}
```

We can now hit Save on our Data Template, and then go to **Curate Content** to access the Data Template form.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/fzX3tYlxQqEd3X8Y9DZs/templatedropdown.jpg)

The option will be available via the overflow menu on the respective Tile. Once clicked on, the Data Template form will appear and can be edited by the User.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/fmfRWqyFZmchhSoBntxP/Screen-Shot-2018-02-12-at-9.14.10-am.png)

### Data Templates for Tags

The process for building a Data Template for Tags is very similar to Tiles. The only real difference is the Scope.

As per Data Templates for Tiles, we will start by defining our **Name** and **Title**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SC1lZtHIAFLfXINkKcoj/Screen-Shot-2018-02-12-at-8.44.04-am.png)

For the purpose of this guide, we are going to give the Data Template form the **Name** and **Title** of 'Additional Settings'. From here we are going to select **Scope**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dAAn8a94soeSZzzXDdHj/Screen-Shot-2018-02-12-at-8.44.16-am.png)

We will select the **Scope** 'Tags', and click on the **More Settings** button to limit this only to **Product** tags.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/CMynmZs5dYtizKc8KkgL/Screen-Shot-2018-02-12-at-9.17.42-am.png)

The query we will enter here is simply `Tag Type = Product`

We can now our **Schema** and **Options**. For this guide, we will want to offer a series of sizes and colours

**Schema:**

```
{
    "type":"object",
    "properties": {
        "currency": {
            "type":"string",
            "enum": [
                "AUD",
                "GBP",
                "USD",
		"NZD",
		"Euro"
            ]
        },
		"price": {
            "type":"string"
        }
    }
}
```

**Options:**

```
{

    "fields": {
        "currency": {
            "type":"select",
            "label": "Currency",
            "optionLabels": [
                "Australian Dollars",
                "British Pounds",
                "US Dollars",
		"New Zealand Dollars",
		"Euros"
            ]
        },
        "price": {
            "type":"text",
            "label": "Price"
        }
    }
}
```

From here we can hit Save. To view the additional fields for our Tags, we simply go to **Manage Tags** under Curate and it will be available as Tab option.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/k8uPx4gGM7FyJYpt6aZW/Screen-Shot-2018-02-12-at-9.29.33-am.png)

[Back to Top](#top)

## Next Steps

Once we've built our Custom Data templates, we can start populating them, and rendering this information on our outputs.

[Back to Top](#top)


# Rights via Registration


# Capture Custom Data on Rights by Registration Form

## Overview

In this guide we explore the general concept of adding custom data collection fields to the Rights by Registration form.

This guide focuses on the simplest and effective way of addings these fields and how to access this data within your Stack.

## Adding the Custom Fields

Go to **UGC Settings > Rights via Registration** and follow the steps to link your account with your relevant X and/or Instagram accounts and specify your Rights by Registration message.

Once complete, you should now have access to see the ‘Customer Facing Pages’ section of the Rights by Registration configuration page.

Scroll down to the second page (User Information Snippet) and click on the Edit button to access the mustache code. Here you can start adding your custom fields.

Rights by Registration accepts any field names that are prefixed with "custom-data-" and followed by a keyword, Note: The keyword must only contain alphanumeric or underscore characters. e.g custom-data-gender

For the purpose of this example, we have added an additional text field, a checkbox, radio button and a pick list to the existing fields.

The code we have used to add these additional fields is displayed below:

```
<div class="st-form-group">
<text{{!}}area name="custom-data-comments3" class="st-form-control" data-smartlabel="true"
data-input-name="Additional Text Field"
placeholder="Additional Text Field (optional) "></text{{!}}area>
</div>
		
<div class="st-form-group">
<input type="checkbox" name="custom-data-extra-checkbox" value="1" class="st-form-control"
data-input-name="Extra check-box"
placeholder="Extra check-box (optional)" />
Extra check-box (optional)
</div>
		
<div class="st-form-group">
<input type="radio" name="custom-data-radio" class="st-form-control"
data-input-name="Radio 1" value="Radio 1" checked />
Radio 1
<input type="radio" name="custom-data-radio" class="st-form-control"
data-input-name="Radio 2" value="Radio 2" />
Radio 2
</div>
		
<div class="st-form-group">
<select name="custom-data-picklist" class="st-form-control" >
<option value="Option 1">Option 1</option>
<option value="Option 2">Option 2</option>
<option value="Option 3">Option 3</option>
<option value="Option 4">Option 4</option>
</select>
</div>
```

Save your settings and you should be able to see the additional fields in your Rights via Registration form.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/CCRl26tzXNJux2C6VqZT/form.png)

When your audiences submit this content, it will be available under the claimed\_data column on the data table.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/k8Xl4hohFSL70cB7ZzHp/data.png)

The data will also be available through the CSV download for your Rights via Registration instance.


# Styling Rights via Registration Form

* [Using the Template Editor](#using-the-editor)
* [HTML Structures](#html-structure)
* [Sample Style](#sample-style)
* [Template Engine](#template-methods)

### Using the Template Editor

You can use the Template Editor to change any CSS styling or HTML semantics for the Registration Form, The button on the top right allows switching between the Edit mode and the Preview Mode. After editing the CSS or the HTML semantics on the Edit mode, You can switch back to the Preview mode to preview your custom changes.

You can upload your image assets to stackla through the Custom Assets button, Once your image has been uploaded, copy the image url and embed it to your registration form

![Display Options](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/hm1R6kKHTB6TIZH1ogCb/editor.png)

If you want to revert back to the original setting, you can click the Revert Draft button. please be aware this will overwrite all your changes.

### HTML Structures

Below outlines the HTML Structure used in the Registration Form

````html
<!-- Mustache Template -->
<script type="text/html" class="claim-template">
    <div>
        <h1>Registration Form Title</h1>
        <form>
            <!-- Tiles container -->
            <div class="tiles">
                <!-- One Tile -->
                <div class="tile">
                    <!-- The Tile Network -->
                    <div class="">
                        <!-- image -->
                        <img class="tile-image"/>
                        <!-- Body -->
                        <div class="tile-body">
                            <!-- User -->
                            <div class="tile-user-info">
                                <a class="tile-avatar"></a>
                                <div class="tile-user"></div>
                            </div>
                            <!-- Message -->
                            <div class="tile-caption"></div>
                            <!-- Date Time -->
                            <div class="tile-source-timestamp"></div>
                        </div>
                    </div>
                </div>
            </div>
        </form>
    </div>
</script>

![Display Options](images/html-semantic.png)

### Masonry Style

Below sample code shows a Registration Form in Masonry Style. We have added Custom CSS on top of the template, and changed the HTML structure of the Tile to display User profile first.

```html
<script type="text/html" class="claim-template">
    <link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300' rel='stylesheet' type='text/css'>
    <style>
        body{
            font-family: 'Open Sans Condensed', sans-serif;
            font-weight:normal;
            font-style:normal
        }
        .claim-wrapper {
            width: 100%!important;
            padding: 0px!important;
        }
        .tile-list {
            column-gap: 10px;
            column-count: 4;
        }
        .tile {
            display: inline-block;
            width: 100%;
        }
        .select-checkbox {
            position: absolute!important;
            top: 0px;
            right: 0px;
        }
    </style>
    <div data-bind="visible: success() === -1">
        <h1 class="text-center">We have found the following photos that are belonging to your Instagram Account, Select your best photos to enter the competition.</h1>

        <form>
            <span class="error-message" data-bind="text: errorMessage()"></span>
            <div class="text-center" style="padding: 20px">
                <a class="st-btn st-btn-primary" data-bind="click: function() {selectToggle(); return true;}">Select All</a>
                <a data-bind="attr:{'class':getButtonClasses()}, click: function(d,e) { initClaimTiles() }" data-button-submit="register">
                    <span>Register</span>
                </a>
            </div>

            <div data-bind="foreach: tiles, attr:{'class':getListClasses()}">
                <!-- Tile -->
                <div data-bind="attr:{'class':getTileClasses()}">
                    <div class="tile-body">
                        <!-- User Profile -->
                        <div class="tile-user-info clearfix">
                            <a class="tile-avatar" data-bind="attr:{'href':getAvatarUrl()}">
                                <img width="30" height="30" data-bind="attr:{'src':avatar()}">
                            </a>
                            <div class="tile-user">
                                <a class="tile-user-top" data-bind="attr:{'href':getAvatarUrl()}">
                                    <span data-bind="text:name()"></span>
                                </a>
                                <a class="tile-user-bottom" data-bind="attr:{'href':getAvatarUrl()}">
                                    <span data-bind="text:user()"></span>
                                </a>
                            </div>

                            <!-- Select Checkbox -->
                            <input type="checkbox" class="st-form-control select-checkbox" data-bind="checked: selected, click: function() {$parent.updateSelection(); return true;}" />
                        </div>

                        <img class="tile-image" data-bind="attr: {src: image()}, visible: image()">

                        <!-- Image -->
                        <div class="tile-caption" data-bind="visible: message()">
                            <p data-bind="text:message()"></p>
                        </div>

                        <!-- Time -->
                        <div class="tile-source-timestamp" data-bind="visible: source\_created\_at()">
                            <span data-bind="text:getTimeAgoPhrase()"></span>
                        </div>
                    </div>
                </div>
            </div>

            <div class="st-form-group">
                {{#claimData.tcurl}}
                <label class="terms-and-conditions">
                    <input type="checkbox" class="st-form-control" data-bind="checked: termAndConditions, click: function() {updateSelection(); return true;}" />
                    I agree to the
                    <a href="{{claimData.tcurl}}" target="\_blank">Terms and Conditions</a>.
                </label>
                {{/claimData.tcurl}}
            </div>

        </form>
    </div>
    <scr{{i}}ipt>
    window.Stackla = window.Stackla || {};
    window.Stackla.tileListData = {{{json}}};
    </scr{{i}}ipt>
</script>
````

![Display Options](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/0kzRoQrdORR6tmQ5R6GW/masonry.png)

### Template Methods

Rights by Registration uses Mustache as the Rendering Template Engine, and Knockout JS as the View Model Logic. Below outlines the Knockout method calls and properties in the Mustache templates.

| Knockout JS View Model Methods | Description                                            |
| ------------------------------ | ------------------------------------------------------ |
| success()                      | Check if we should render the registration form        |
| multiple()                     | Check if multiple tiles available for registration     |
| selectToggleVisible()          | is toggle button visible                               |
| foreach: tiles                 | Iterate through each tiles                             |
| getListClasses()               | get css classes for the tiles container                |
| showTile()                     | showing current tile                                   |
| showSelected()                 | Show checkbox for current tile                         |
| $parent.updateSelection        | Select Current Tile (used in the tile block)           |
| getTileClasses()               | Get CSS classes for Tile                               |
| image()                        | Referring to the image field in the Tile               |
| getAvatarUrl()                 | Get Avatar URL                                         |
| avatar()                       | Referring to the avatar field in the Tile              |
| name()                         | Referring to the Name field in the Tile                |
| user()                         | Referring to the User field in the Tile                |
| message()                      | Referring to the message field in the Tile             |
| source\_created\_at()          | Referring to the source\_created\_at field in the Tile |
| getTimeAgoPhrase()             | Get Human readable Time                                |
| termAndConditions()            | Is term and conditions configured                      |
| getButtonClasses()             | get CSS classes for the Submission Button              |
| initClaimTiles()               | Submit the Form to Nosto's UGC                         |
| collectData()                  | Submit Additional Data to Nosto's UGC                  |
| dataCollectionError()          | Get error message for Submitting Additional Data       |
| errorMessage()                 | Get Error Messages                                     |

| Mustache Template Properties  | Description                                                                 |
| ----------------------------- | --------------------------------------------------------------------------- |
| claimData.tcurl               | the configured terms and condition url                                      |
| claimData.brand\_url\_success | the configured value for successful page                                    |
| claimData.brand\_url\_failure | the configured value for successful page                                    |
| claimData.source\_username    | the configured value for successful page                                    |
| claimData.source\_username    | the configured value for successful page                                    |
| dataToCollect.firstname       | the configrations for the first name field (*enabled, mandatory, optional*) |
| dataToCollect.lastname        | the configrations for the last name field                                   |
| dataToCollect.email           | the configrations for the email field                                       |
| dataToCollect.postcode        | the configrations for the post code field                                   |
| dataToCollect.comments        | the configrations for the comments field                                    |
| dataToCollect.comments2       | the configrations for the comments2 field                                   |
| dataToCollect.opt\_in         | the configrations for the opt\_in field                                     |


# REST API


# Caching REST API results for optimization

## Overview

When data fetched from Nosto's UGC via API is required for every page view, it can be expensive to request every impression. Often the data does not need to be the latest, especially if it's known that the data does not change often. In this guide, we will explore methods to reduce API consumption (reducing the risk of exceeding the API rate limit), while utilizing the data at the same time.

By using the cache method, we will end up having a faster web response and REST API's rate limits will not limit us to serve our busy web page to clients.

In this guide, we will use Memcached in a demo PHP application to implement a caching strategy for Filter data.

### A note on cache methods

There are several cache methods that we can use and your mileage will certainly vary.

Most languages and frameworks have similar patterns for caching locally (e.g. local files or RAM such as [PHP-APC](http://php.net/manual/en/book.apc.php) or [node-cache](https://www.npmjs.com/package/node-cache)), as well as drivers for distributed caching systems such as [memcached](http://memcached.org). Further to this, edge server caching applications such as [Varnish](https://www.varnish-cache.org) can cache the result before it even hits your web server, or web server extensions can add the capability to Apache or nginx.

In each of these, the pattern is similar: rather than hitting the Nosto's UGC API every time data is required from a resource, store the response for some time in a cache and use the cached version until that period expires for all requests.

In the case of local cache, the data is stored on each web server. While good for single-server implementations, once you exceed one application server, and since each server will have its own cache, you run the risk of using different versions in different requests.

Distributed cache systems such as memcached resolve the above problem and allow a single cache to be accessed from multiple sources. For example, each one of your 5 application servers can have their application access the identical source for the cached data.

If you wish to step to a higher level, you can store the web server response itself in a cache and serve that to visitors via your web server or reverse proxy of some kind.

## Key Concepts

### memcached

Memcached is a general-purpose distributed memory caching system.

### Filters API

Filters API provides an endpoint to obtain the filters that are available in the Stack.

### Example Application

In this application, we will utilize memcached to cache the response of Nosto's UGC API in raw format and generate an output of it.

## The Fun Part

In this guide we will do the following:

* Install and configure memcached
* Generate uncached requests
* Generate cached requests

### Install and configure memcached

In this guide we won't be going through installing it as it comes in many variants, however, if you're not familiar there is great info on the Memcached[ Wiki](https://code.google.com/p/memcached/wiki/NewStart) and [PHP Manual](http://php.net/manual/en/book.memcached.php).

### Generate uncached requests

```

<?php
// Fetch filters from Nosto's UGC REST API
$filters = getFiltersFromStackla();
echo '<select name="filters">';
foreach ($filters as $id => $name) {
    echo "<option value=\"{$id}\">{$name}</option>";
}
echo '</select>';
// The End
```

Execute the above code several times, and you will notice that every request generates hits on the Nosto's UGC API, and the API console will indicate that there are now fewer requests available.

### Generate cached requests

#### Note on getFiltersFromStackla()

Before we do anything, we assume that there is a function named `getFiltersFromStackla` that retrieves the raw JSON from the Stack and processes it into an associative array (id=>name). Details on how to acquire the Filters are covered in [this guide](/ugc/guides/onsite-widgets/render-widget-filters-dynamically), although the effort to wrap the task in the `getFiltersFromStackla` is left as a separate exercise.

#### Connect to and query Memcached

The following code is a sample of how to connect to the memcached server.

```
$mc = new Memcache();
// Modify this for your server
$mcHost = 'localhost';
$mcPort = 11211;
$mcPresist = true;
$mcWeight = 1;
$mcTimeout = 1;

// Add a Memcached server to the connection pool
$mc->addServer($mcHost, $mcPort, $mcPresist, $mcWeight, $mcTimeout);

// Connect to Memcached server
$mc->connect($mcHost, $mcPort);
```

Now that we have a connection, we can query for particular data, and if not found, store the data itself. memcached is a key-value store, so we will need a unique key to store the data.

```
if ($mc->get('filters')) {
    // Get cached value
    $filters = $mc->get('filters');
} else {
    // Fetch filters from Nosto's UGC REST API
    $filters = getFiltersFromStackla();
    // Cache filters for the next 30 seconds
    $mc->set('filters', $filters, time() + 30);
}
```

```

<?php
$mHost = 'localhost';
$mPort = 11211;
$mPresist = TRUE;
$mWeight = 1;
$mTimeout = 1;
$m = new Memcache();
// Add a Memcached server to the connection pool
$m->addServer ( $mHost, $mPort, $mPresist, $mWeight, $mTimeout);
// Connect to the Memcached server
$m->connect($mHost, $mPort);
// Check if the filters result is in Memcached
if ($m->get('filters')) {
    // Get cached value
    $filters = $m->get('filters');
} else {
    // Fetch filters from Nosto's UGC REST API
    $filters = getFiltersFromStackla();
    // Cache filters for 30 seconds minute
    $m->set('filters', $filters, time() + 30);
}
echo '<select name="filters">';
foreach ($filters as $id => $name) {
    echo "<option value=\"{$id}\">{$name}</option>";
}
echo '</select>';
// The End
```

After executing this code, you will notice that the first request reduces the allowed requests in the console, however for the following 30 seconds, subsequent hits do not. This is the effect of the cache.

## Summary and Next Steps

This example can be further expanded with the following activities:

* Caching other Nosto's UGC endpoints
* Caching Filter content after dynamically changing Filter settings (personalized content)


# Posting content into Nosto via Tile API

## Overview

Aside from aggregating from social networks, one thing not to be overlooked is Nosto's UGC ability to collect crowdsourced content that is not off social media. In this guide, we explore how to programmatically push content to Nosto's UGC via a UGC Post Feed Term.

We will create a simple one page microsite which will feature a HTML form to POST data to a backend processor, which will in turn push the data to Nosto's UGC via Tile API.

## Key concepts

### Tile

In your Stack, the name Tile is given to a piece of content that has been saved and made available for moderation. In this case it will be the resulting data posted to your Stack.

### Nosto's UGC Post Feed Term

This is a special type of Term (not to be confused with the Nosto's UGC Post Term) that you can use to draw content submitted via Tile API through. This allows you or your Stack admin to configure automatic moderation for the content, as well as other rules on how to treat the content. In the majority of cases, the API client performs most of the moderation and programmatic categorising of content, but some of these specific pieces can be controlled by an admin so that a code change is not needed.

### Example Application

When running a campaign that collects user-generated content (UGC), it's often useful to offer a form for users to post content directly, as well as accept content via Social Media. In this example, we will create a small PHP application that will serve as a View showing the microsite application, and contain a form that will accept a message of fan-love towards [Bon Iver](http://boniver.org), your favorite band.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Create the Term
2. Create the microsite View
3. Create the backend app
4. Moderate the posted content

### Create the Term

Creating a new Nosto's UGC **Post Feed term** is done in the same manner as creating any other term. Go to **Discovery > Terms** and click **Create Term**.

In the New Term screen, select the pink **Nosto icon** as the Term Network

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FKGFlHooNfF8sAkPTkoTL%2FScreenshot%202024-05-23%20at%2010.04.04%20am.png?alt=media&amp;token=891a8da0-01dd-4af3-a091-22a6256d3491" alt=""><figcaption></figcaption></figure>

On the **Term type will be** select "**Post**" as the Type

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FkkAAvPFiYn3pR9u6Xvwn%2FScreenshot%202024-05-27%20at%203.06.36%20pm.png?alt=media&amp;token=089f2ab4-611d-4384-a68c-333f42f268da" alt=""><figcaption></figcaption></figure>

You can name the term anything you like; however entering something familiar to the purpose will help you find it later in the list.

The "Display Name" field will show up in the case that the name parameter is not provided in the POST (this was not a mandatory field in the past, but it is now).

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FBdF2ofeGrLBin9qfbOfu%2FScreenshot%202024-05-27%20at%203.09.33%20pm.png?alt=media&amp;token=c320217f-a3cd-4e1d-8a17-29fdd81d8482" alt=""><figcaption></figcaption></figure>

In the moderation settings for the term you can choose whether or not you want to auto-Publish the content, or if you would like to Queue it for later curation.

### Create the microsite View

Creating the View is super easy. All we need to do is create a page with a form to collect the user's Name and Message. That form will be posted to the backend via AJAX (helped along by jQuery) to the controller we will build in the backend.

### Create the backend app

The backend app is really simple. We take the POSTed data and pass it to the API via cURL. That's all we need to do!

### Moderate the posted content

Load up the page that holds the View and you should see a form like below.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ldJOk0GWlI0S2Hhz9EQN/about_to_post.png)

After hitting "Send!", the message will be pushed to Nosto's UGC and available in your moderation queue.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FWHN6Xzo80IlQTZ0tPAeC%2FScreenshot%202024-05-27%20at%203.14.20%20pm.png?alt=media&amp;token=e46b2474-b46a-46fc-9e68-09df6c4dcd37" alt=""><figcaption></figcaption></figure>

Note about automatic moderation of posts

When content is submitted to Nosto's UGC, you can select what the default action with the post is upon ingestion, even by the type of post it is (i.e. Text-only, Image, Video). This means that you can choose to queue user-generated content for moderation by a human being, or if you're brave allow the content to be directly posted to the Stack.

This can be set on the Term's Moderation settings.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FL34KrDziNX8Y7TcbUxys%2FScreenshot%202024-05-27%20at%203.51.15%20pm.png?alt=media&amp;token=99e1f94b-6711-46f2-b2e6-8695fb9a3d96" alt=""><figcaption></figcaption></figure>

## Summary and Next Steps

This example can be further expanded with the following activities:

* Using Nosto's UGC Webhooks to trigger events when content is posted
* Displaying posted content in a Widget for voting
* Posting media such as images or videos instead of just text type (see [this guide](/ugc/guides/rest-api/posting-images-into-stackla-via-tile-api) for an example)


# Posting images into Nosto via Tile API

## Overview

In [a previous guide](/ugc/guides/rest-api/posting-content-into-stackla-via-tile-api), we addressed how to post content directly into Nosto's UGC via API. In this guide, we will expand on that guide and ingest image content into the same Stack.

If you have not already, please consult the [Posting content into Nosto via Tile API](/ugc/guides/rest-api/posting-content-into-stackla-via-tile-api) guide.

## Key concepts

### Text, Image, Video and HTML Tiles

Nosto's UGC Tiles can come in many shapes and sizes: text-only, image with message, video with a title and message or a HTML tile for rich text and imagery. There are also future types reserved for expansion.

### Example Application

The [previous example](/ugc/guides/rest-api/posting-content-into-stackla-via-tile-api) built an application that had a Term configured, and a small app that sent messages of fan-love to a faux Bon Iver fan page. We will simply extend on that applicaiton, so there are no changes needed to the Term or the Stack itself. If you have not already, please consult the [Posting content into Nosto via Tile API](/ugc/guides/rest-api/posting-content-into-stackla-via-tile-api) guide for more detailes on how to build these.

**Note about pre-requisites**

This example uses the [PHP GD library](http://php.net/manual/en/book.image.php), however the code is easily adaptable to a different library if needed, or can be removed if GD is not available.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Modify the microsite View
2. Modify the backend app
3. Moderate the posted content

### Modify the microsite View

The old view sent data back to the [backend](#app-backend) via AJAX. Since we need to send a file this time, we will use a simple form POST instead, to keep things simple.

The output will resemble something like the following.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/OztTzBdzbQiZnrf2BP4r/blank_form.png)

#### [index.html](https://github.com/Stackla/docs/blob/master/docs/media/guides/assets/05/index.html)

````

### Create the backend app

The backend is very similar to what was used in the previous guide, except we are also accepting plugins and performing resizing (via the [ResizeImage class](#resizeimage-class), adapted from [here](http://www.paulund.co.uk/resize-image-class-php)).

#### Note on upload permissions

The uploaded file will be processed and saved into the path specified by the `$uploadsDir` variable, in the following code set to `./uploads` path. The permissions of this path needs to be set correctly to allow the application to save the processed file (i.e. for the user that the PHP script is executing as) in the path. For Apache, this user (and group) will often be `www-data`, so giving read, write and execute permissions to that user and group (i.e. `www-data:www-data`) will often suffice. Your mileage will vary, depending on your server settings (you can find some more information [here](http://serverfault.com/questions/357108/what-permissions-should-my-website-files-folders-have-on-a-linux-webserver)). The following commands should help for your development environment.

```
# From your application path
sudo chown www-data:www-data ./uploads
sudo chmod 755 ./uploads
```

#### [process.php](/docs/media/guides/assets/05/process.php)

``

#### [ResizeImage.class.php](/docs/media/guides/assets/05/ResizeImage.class.php)

`

### Moderate the posted content

Load up the page that holds the View and you should see a form like below.

![](images/about_to_post-1.png)

After hitting "Send!", the message will be pushed to Nosto's UGC and available in your moderation queue.

![](images/posted.png)

#### Note about automatic moderation of posts

When content is submitted to Nosto's UGC, you can select what the default action with the post is upon ingestion, even by the type of post it is (i.e. Text-only, Image, Video). This means that you can choose to queue user-generated content for moderation by a human being, or if you're brave allow the content to be directly posted to the Stack.

This can be set on the Term's Moderation settings.

![](images/moderation_settings.png)

## Summary and Next Steps

This example can be further expanded with the following activities:

- Using Nosto's UGC Webhooks to trigger events when content is posted
- Displaying posted content in a Widget for voting
- Posting videos or HTML posts
````


# Onsite Widgets (Classic)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

## Overview

Nosto's UGC widgets have been developed to provide a variety of key features that make them the best choice for displaying content on a web page. Used by 90% of clients. Serving 16 million requests a day.

## Usage

**Key Features**

* Speed & flexibility of development within the Stackla platform.
* Easy customization of CSS and JS.
* Access to a powerful internal [JavaScript API](/ugc/api-docs/javascript).
* Optimized responsive display.
* Built-in content freshness detection.
* Speed and robustness of content delivery.
* Produces simple and reusable HTML embed code.
* Extensible with maps and direct upload widgets.
* Dynamic content request options using filters and tags.
* Customizable content polling frequency.
* No API rate limits.
* Advanced features such as Product highlights and Likes.
* Reporting on tile and widget performance via Google Analytics.
* Tile recommendation data to help make moderation easier.

**Designed for**

* Displaying content on websites and browser-based apps.


# Blank Canvas

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}


# How to Use the Blank Canvas to Create a Twitter Count Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

* [Introduction](#introduction)
* [The common approach](#common-approach)
* [Blank canvas approach](#bc-approach)
* [Steps](#steps)
* [Conclusion](#conclusion)
* [Widget code for reference](#code-ref)

## Introduction

Blank Canvas provides a fairly flexible way to display the information ingested in Visual UGC and allows us to create unique widgets. Sometimes the desired outcome is not to display the actual user generated content, but to use the ingested items count to unlock discounts (or any sort of information) when those numbers hit some sort of thresholds. This was the case of one of our clients: they wanted to create a progress bar and unlock five offers in a particular event.

[Back to Top](#top)

## The Common Approach

Usually this is achieved by creating a custom application that uses the API to retrieve a summary from within a certain term or filter. This has a set of cons:

1. To host a custom app implies more work and higher costs.
2. It may imply using a database.
3. The developer uses only the API and doesn’t leverage the widgets editor.

[Back to Top](#top)

## Blank Canvas approach

Advantages:

1. Faster development
2. No hosting needed

[Back to Top](#top)

## Steps

1. The first step may look like a workaround but it is valid still and reflects the flexibility of the platform. By default, Nosto's UGC provides the UGC data as the pieces of information to render. This is not what we need for this particular case, so we can ignore/remove this callback all together: *Stackla.loadTilesByFilter(function (tiles) {…});*\
   Instead, we will use our own API call:

```

$(document).ready(function() {
    $.get('https://12345678.execute-api.ap-southeast-2.amazonaws.com/staging', function( resp ) {
        ...
    });
});
```

3. That particular endpoint refers to Nosto's UGC API: *<https://api.Stackla.com/api/filters/123456/summaries?aggregated\\_by=default\\&stack={your\\_stack}\\&access\\_token={your\\_access\\_token}>*

   It was proxied with AWS' API Gateway and cached with AWS' Cloud Front. This was done in order to prevent Nosto's UGC API to be hit more than 450 times during the 15 minutes time window. That method of proxing is reliable, easy to set up and cheap.
4. The returned summary needs to be parsed/manipulated and then we can invoke the following method:

```

Stackla.render({...});
```

Where the parameter is a valid JSON element.

7. The “layout” provided by the blank canvas framework will be rendered with the provided information. Please refer to *<https://mustache.github.io/>* to understand about the templating language.
8. The CSS can now be edited to style the widget and make it responsive and nice-looking.
9. If there are some resources, we can use the media library provided by Stackla. By doing so, we avoid using a third party system to host the images, fonts or any file we may need.

[Back to Top](#top)

## Conclusion

The development process was quick and straightforward, even though this was the first time we used the Blank Canvas Widget in this way.

```

Stackla.GoConnect.setText(placeholderName, newPlaceholderValue)
```

[Back to Top](#top)

## Widget code for reference

### Javascript

```

var threshold1 = 1000, threshold2 = 2000, threshold3 = 5000;
var initialDegrees = 0;
var animTime = 1000;
var totalStages = 4;

Number.prototype.format = function (n, x) {
    var re = '\d(?=(\d{' + (x || 3) + '})+' + (n > 0 ? '\.' : '$') + ')';
    return this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$&,');
};


$(document).ready(function () {
    $.get('https://abcdef123456.cloudfront.net', function (resp) {
        if (resp.errors.length === 0) {
            var nTweets = 0;
            if(typeof resp.data !== 'undefined' && typeof resp.data[0] !== 'undefined' && typeof resp.data[0].quantity !== 'undefined') {
                nTweets = resp.data[0].quantity;
            }

            //Mocked!
            //nTweets = 500;
            Stackla.render({
                extra: {
                    tweets: nTweets,
                    offer: [
                        {
                            num: 1,
                            enabled: nTweets >= threshold1,
                            threshold: threshold1.format(),
                            name: "Offer 1",
                            description: ["Promo 1","Description promo 1"],
                            clickthrough: "https://www.fake-host.com/offers/12345"
                        },
                        {
                            num: 2,
                            enabled: nTweets >= threshold2,
                            threshold: threshold2.format(),
                            name: "Offer 2",
                            description: ["Promo 2","Description promo 2"],
                            clickthrough: "https://www.fake-host.com/offers/23456"
                        },
                        {
                            num: 3,
                            enabled: nTweets >= threshold3,
                            threshold: threshold3.format(),
                            name: "Offer 3",
                            description: ["Promo 3","Description promo 3"],
                            clickthrough: "https://www.fake-host.com/offers/34567"
                        }
                    ]
                }
            });
            if (nTweets  0.33) {
            percComplete = 0.33 + 1 / (threshold2 - threshold1) * (tweets - threshold1) * 0.33;
            if (percComplete > 0.66) {
                percComplete = 0.66 + 1 / (threshold3 - threshold2) * (tweets - threshold2) * 0.66;
                if (percComplete > 1) {
                    percComplete = 1;
                }
            }
        }
        var timePerStage = parseInt(animTime / step);
        window.setTimeout(function () {
            animateRecursive(0, step, timePerStage);
        }, 0);
    }, 1000);
};
```

[Back to Top](#top)

### CSS

```

body {
    border: 0;
    overflow: hidden;
}
.container {
    overflow: hidden;
    h1 {
        margin-top: 2px;
    }
    .offers-cont {
        margin: 0 auto;
    }
    .clearfix {
        overflow: auto;
    }
    .offer {
        float: left;
        width: 362px;
        height: 275px;
        background-color: #343433;
        color: #666565;
        margin: 5px;
        padding: 10px;
        text-align: center;
        .enabled {
            position: relative;
            width: 100%;
            height: 100%;
        }
        h1 {
            margin-top: 15px;
            margin-bottom: 10px;
        }
        .btn-cont {
            position: absolute;
            bottom: 30px;
            padding: 10px;
            border: 1px solid #FFF;
        }
        .desc {
            margin-top: 12px;
        }
        &.enabled {
            background-color: #db0626;
            color: #FFF;
            a, a:active, a:hover {
                color: #FFF;
                text-decoration: none;
                font-size: 1.3em;
                padding: 0;
                margin: 0;
            }
        }
    }
    .offer-1,
    .offer-2,
    .offer-3 {
        position: absolute;
        text-align: center;
        .separator {
            margin-left: auto;
            margin-right: auto;
        }
    }
    .offers-cont {
        margin-top: 60px;
    }
    .separator {
        background-color: #db0626;
        height: 4px;
        width: 55px;
        margin: 15px 0;
    }
    .stages {
        overflow: hidden;
        max-width: 700px;
        width: 100%;
        margin: 0 auto;
        transition: background-image 1.5s ease;
        background: url('https://web-assets.stackla.com/someclient.stackla.comhttp://sdp-media.dev/images/customimages/2016-11/1479441831/stage1.png') top center;
        background-size: contain;
        &.stage-2 {
            background-image: url('https://web-assets.stackla.com/someclient.stackla.comhttp://sdp-media.dev/images/customimages/2016-11/1479441822/stage2.png');
        }
        &.stage-3 {
            background-image: url('https://web-assets.stackla.com/someclient.stackla.comhttp://sdp-media.dev/images/customimages/2016-11/1479441818/stage3.png');
        }
        &.stage-4 {
            background-image: url('https://web-assets.stackla.com/someclient.stackla.comhttp://sdp-media.dev/images/customimages/2016-11/1479441810/stage4.png');
        }
    }
}
```

[Back to Top](#top)

### Layout

```


<div class="track clearfix">
    <div class="container">
    {{#extra.offer}}
            <div class="offer-legend offer-{{num}}">
                <h1>{{name}}</h1>
                <div class="separator"></div>
                <div class="desc">{{threshold}} tweets to unlock</div>
            </div>
    {{/extra.offer}}
    <div class="stages">
            <div></div>
        </div>
        <div class="offers-cont clearfix">
    {{#extra.offer}}
    <div class="offer o{{num}} {{^enabled}}disabled{{/enabled}} {{#enabled}}enabled{{/enabled}}">
    {{^enabled}}
                    <div class="disabled">
                        <h1>{{name}}</h1>
                        <img src="https://s3-ap-southeast-2.amazonaws.com/client/assets/gray-lock.png" />
                        <div class="desc">Will be unlocked<br/>after {{threshold}} tweets</div>
                    </div>
    {{/enabled}}
    {{#enabled}}
                    <div class="enabled">
                        <h1>{{name}}</h1>
                        <div class="desc">
    {{#description}}
    {{.}}<br/>
    {{/description}}
    </div>
                        <div class="btn-cont">
                            <a href="{{clickthrough}}" target="_blank">Claim now</a>
                        </div>
                    </div>
    {{/enabled}}
    </div>
    {{/extra.offer}}
    </div>
    </div>
</div>
```

[Back to Top](#top)


# Creating an Auto-Scrolling Carousel using Blank Canvas

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

* [Overview](#overview)
* [Layout](#layout)
* [Tile](#tile)
* [CSS](#css)
* [Javascript](#javascript)
* [Final Result](#final)

## Overview

Nosto's UGC Blank Canvas Widget allows users to very quickly and easily design their own Widget templates to suit their organisation's requirements.

In this guide we are going to create a Carousel Widget, which has been optimised for Tablet and Mobile devices and leverages [Swiper by idangero.us](http://idangero.us/swiper) to create not only a smooth scroll, but also some auto-scrolling behaviour.

In the guide we are going to move through each of the Mustache Partials to build the final Widget.

[Back to Top](#top)

## Layout

First Step is to build out our Layout partial. Here we are going to load the SVG elements for the left and right arrow and then define the container which will hold the Tiles.

```
<section class="some no-touchevents">
    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;" baseProfile="tiny" version="1.2">
        <symbol id="icon-arrow-left" viewBox="0 0 64 64">
            <title>Arrow Left</title>
            <desc>Scroll Left</desc>
            <polygon points="43,12 36,8 20,32 36,56 43,52 30,32 "></polygon>
        </symbol>
        <symbol id="icon-arrow-right" viewBox="0 0 64 64">
            <title>Arrow Right</title>
            <desc>Scroll Right</desc>
            <polygon points="20,12 27,8 43,32 27,56 20,52 33,32 "></polygon>
        </symbol>
    </svg>
    <main>
        <section>
            <div class="container" id="mouseovercapture">
                <div class="swiper">
                    <div class="some__slider-container swiper-container swiper-container-horizontal swiper-container-rtl" dir="rtl">
                        <ul class="swiper-wrapper some__track">
                            {{#tiles}}
                                <li class="swiper-slide" style="width: 275px; margin-left: 15px;">
                                {{>tpl-tile}}
                                </li>
                            {{/tiles}}
                        </ul>
                    </div>
                    <div class="swiper-navigation">
                        <div class="previous">
                            <svg class="icon">
                                <use xlink:href="#icon-arrow-right"></use>
                            </svg>
                        </div>
                        <div class="next">
                            <svg class="icon">
                                <use xlink:href="#icon-arrow-left"></use>
                            </svg>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </main>
</section>
```

[Back to Top](#top)

## Tile

Next step is defining our Tile partial. In this Widget, for our Tiles we simply wish to load the relevant Tile image in the background, and then on mouse-over include the Content Creator, the Tile Message and provide a link back to the original Tile on Social Media.

```
<a target="_blank" href="{{original_url}}" class="tile swiper-lazy swiper-lazy-loaded" style="background-image: url('{{image}}');">
	<p><span class="account">@{{user}}</span>{{{message}}}</p>
</a>
```

[Back to Top](#top)

## CSS

Next step is our styling. A number of the below styling elements are influenced from the [Swiper by idangero.us](http://idangero.us/swiper) documentation and can be customised to suit your design preferences.

```
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');	
body {
	font-family: 'Open Sans', sans-serif;
	height: 250px;
}
.swiper-slide,
    .swiper-wrapper {
        width: 100%;
        height: 100%;
        position: relative
    }
    .swiper .swiper-navigation .next,
    .swiper-container-no-flexbox .swiper-slide {
        float: left
    }
    .swiper-container {
        margin: 0 auto;
        position: relative;
        overflow: hidden;
        z-index: 1
    }
    .swiper-container-vertical>.swiper-wrapper {
        -ms-flex-direction: column;
        -webkit-flex-direction: column;
        flex-direction: column
    }
    .swiper-wrapper {
        z-index: 1;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        transition-property: -webkit-transform;
        transition-property: transform;
        transition-property: transform, -webkit-transform;
        box-sizing: content-box
    }
    .swiper-container-android .swiper-slide,
    .swiper-wrapper {
        -webkit-transform: translate3d(0, 0, 0);
        -ms-transform: translate3d(0, 0, 0);
        transform: translate3d(0, 0, 0)
    }
    .swiper-container-multirow>.swiper-wrapper {
        -webkit-box-lines: multiple;
        -moz-box-lines: multiple;
        -ms-flex-wrap: wrap;
        -webkit-flex-wrap: wrap;
        flex-wrap: wrap
    }
    .swiper-container-free-mode>.swiper-wrapper {
        transition-timing-function: ease-out;
        margin: 0 auto
    }
    .swiper-slide {
        -webkit-flex-shrink: 0;
        -ms-flex: 0 0 auto;
        -ms-flex-negative: 0;
        flex-shrink: 0
    }
    .swiper-container-autoheight,
    .swiper-container-autoheight .swiper-slide {
        height: auto
    }
    .swiper-container-autoheight .swiper-wrapper {
        -ms-flex-align: start;
        -webkit-align-items: flex-start;
        -ms-grid-row-align: flex-start;
        align-items: flex-start;
        transition-property: height, -webkit-transform;
        transition-property: transform, height;
        transition-property: transform, height, -webkit-transform
    }
    .swiper-container .swiper-notification {
        position: absolute;
        left: 0;
        top: 0;
        pointer-events: none;
        opacity: 0;
        z-index: -1000
    }
    .swiper-wp8-horizontal {
        -ms-touch-action: pan-y;
        touch-action: pan-y
    }
    .swiper-wp8-vertical {
        -ms-touch-action: pan-x;
        touch-action: pan-x
    }
    .swiper-button-next,
    .swiper-button-prev {
        position: absolute;
        top: 50%;
        width: 27px;
        height: 44px;
        margin-top: -22px;
        z-index: 10;
        cursor: pointer;
        background-size: 27px 44px;
        background-position: center;
        background-repeat: no-repeat
    }
    .swiper-button-next.swiper-button-disabled,
    .swiper-button-prev.swiper-button-disabled {
        opacity: .35;
        cursor: auto;
        pointer-events: none
    }
    .swiper-pagination {
        position: absolute;
        text-align: center;
        transition: .3s;
        -webkit-transform: translate3d(0, 0, 0);
        -ms-transform: translate3d(0, 0, 0);
        transform: translate3d(0, 0, 0);
        z-index: 10
    }
    .swiper-pagination.swiper-pagination-hidden {
        opacity: 0
    }    
    .swiper-container-coverflow .swiper-wrapper,
    .swiper-container-flip .swiper-wrapper {
        -ms-perspective: 1200px
    }
    .swiper-container-cube,
    .swiper-container-flip {
        overflow: visible
    }
    .swiper-container-cube .swiper-slide,
    .swiper-container-flip .swiper-slide {
        pointer-events: none;
        -webkit-backface-visibility: hidden;
        backface-visibility: hidden;
        z-index: 1
    }
    .swiper-container-cube .swiper-slide .swiper-slide,
    .swiper-container-flip .swiper-slide .swiper-slide {
        pointer-events: none
    }
    .swiper-container-cube .swiper-slide-active,
    .swiper-container-cube .swiper-slide-active .swiper-slide-active,
    .swiper-container-flip .swiper-slide-active,
    .swiper-container-flip .swiper-slide-active .swiper-slide-active {
        pointer-events: auto
    }
    .swiper-container-cube .swiper-slide-shadow-bottom,
    .swiper-container-cube .swiper-slide-shadow-left,
    .swiper-container-cube .swiper-slide-shadow-right,
    .swiper-container-cube .swiper-slide-shadow-top,
    .swiper-container-flip .swiper-slide-shadow-bottom,
    .swiper-container-flip .swiper-slide-shadow-left,
    .swiper-container-flip .swiper-slide-shadow-right,
    .swiper-container-flip .swiper-slide-shadow-top {
        z-index: 0;
        -webkit-backface-visibility: hidden;
        backface-visibility: hidden
    }
    .swiper-container-cube .swiper-slide {
        visibility: hidden;
        -webkit-transform-origin: 0 0;
        -ms-transform-origin: 0 0;
        transform-origin: 0 0;
        width: 100%;
        height: 100%
    }
    .swiper-container-cube.swiper-container-rtl .swiper-slide {
        -webkit-transform-origin: 100% 0;
        -ms-transform-origin: 100% 0;
        transform-origin: 100% 0
    }
    .swiper-container-cube .swiper-slide-active,
    .swiper-container-cube .swiper-slide-next,
    .swiper-container-cube .swiper-slide-next+.swiper-slide,
    .swiper-container-cube .swiper-slide-prev {
        pointer-events: auto;
        visibility: visible
    }
    .swiper-container-cube .swiper-cube-shadow {
        position: absolute;
        left: 0;
        bottom: 0;
        width: 100%;
        height: 100%;
        background: #000;
        opacity: .6;
        -webkit-filter: blur(50px);
        filter: blur(50px);
        z-index: 0
    }
    .swiper-container-fade.swiper-container-free-mode .swiper-slide {
        transition-timing-function: ease-out
    }
    .swiper-container-fade .swiper-slide {
        pointer-events: none;
        transition-property: opacity
    }
    .swiper-container-fade .swiper-slide .swiper-slide {
        pointer-events: none
    }
    .swiper-container-fade .swiper-slide-active,
    .swiper-container-fade .swiper-slide-active .swiper-slide-active {
        pointer-events: auto
    }
    .swiper-scrollbar {
        border-radius: 10px;
        position: relative;
        -ms-touch-action: none;
        background: rgba(0, 0, 0, .1)
    }
    .swiper-container-horizontal>.swiper-scrollbar {
        position: absolute;
        left: 1%;
        bottom: 3px;
        z-index: 50;
        height: 5px;
        width: 98%
    }
    .swiper-container-vertical>.swiper-scrollbar {
        position: absolute;
        right: 3px;
        top: 1%;
        z-index: 50;
        width: 5px;
        height: 98%
    }
    .swiper-scrollbar-drag {
        height: 100%;
        width: 100%;
        position: relative;
        background: rgba(0, 0, 0, .5);
        border-radius: 10px;
        left: 0;
        top: 0
    }
    .swiper-scrollbar-cursor-drag {
        cursor: move
    }
    
    @-webkit-keyframes swiper-preloader-spin {
        100% {
            -webkit-transform: rotate(360deg)
        }
    }
    @keyframes swiper-preloader-spin {
        100% {
            -webkit-transform: rotate(360deg);
            transform: rotate(360deg)
        }
    }
    html:after {
        content: "breakpoint-getComputedStyleTest";
        display: none
    }
    & {
        overflow-x: hidden;
        background: #eee;
        color: #000;
        font-family: 'Open Sans', sans-serif;
		font-size: 5px;
        line-height: 1.667em;
        margin: 0;
        padding: 70px 0 100px
    }
    &:after {
        content: "none";
        display: none
    }
    & .js-breakpoint-none {
        position: absolute
    }
    &.whitebg {
        background: #fff
    }
    *,
    :after,
    :before {
        box-sizing: border-box;
        -moz-box-sizing: border-box
    }
    @media (min-width: 480px) {
        &:after {
            content: "w480";
            display: none
        }
        & .js-breakpoint-w480 {
            position: absolute
        }
    }
    @media (min-width: 768px) {
        &:after {
            content: "w768";
            display: none
        }
        & .js-breakpoint-w768 {
            position: absolute
        }
    }
    @media (min-width: 980px) {
        &:after {
            content: "w980";
            display: none
        }
        & .js-breakpoint-w980 {
            position: absolute
        }
    }
    @media (min-width: 1024px) {
        &:after {
            content: "w1024";
            display: none
        }
        & .js-breakpoint-w1024 {
            position: absolute
        }
    }
    .swiper {
        position: relative
    }
    .swiper .swiper-container {
        width: 100%
    }
    .swiper .swiper-container .swiper-wrapper {
        list-style: none;
        padding: 0;
        margin: 0;
        will-change: transform;
        transition-timing-function: cubic-bezier(.25, .46, .45, .94)
    }
    .swiper .swiper-container .swiper-wrapper .swiper-slide {
        width: 240px;
        position: relative;
        transition: -webkit-transform .9s ease-out;
        transition: transform .9s ease-out;
        transition: transform .9s ease-out, -webkit-transform .9s ease-out;
        -webkit-transform: scale(.92);
        -ms-transform: scale(.92);
        transform: scale(.92)
    }
    @media (min-width: 480px) {
        .swiper .swiper-container .swiper-wrapper .swiper-slide {
            -webkit-transform: scale(1);
            -ms-transform: scale(1);
            transform: scale(1)
        }
    }
    .swiper .swiper-container .swiper-wrapper .swiper-slide.swiper-slide-active {
        -webkit-transform: scale(1);
        -ms-transform: scale(1);
        transform: scale(1)
    }
    .swiper .swiper-navigation {
        display: none;
        position: absolute;
        left: -65px;
        right: -65px;
        top: 50%;
        -webkit-transform: translateY(-50%);
        -ms-transform: translateY(-50%);
        transform: translateY(-50%)
    }
    @media (min-width: 480px) {
        .swiper .swiper-navigation {
            display: block
        }
    }
    .swiper .swiper-navigation .next.swiper-button-disabled,
    .swiper .swiper-navigation .previous.swiper-button-disabled,
    .swiper .tile .swiper-lazy-preloader {
        display: none
    }
    .swiper .swiper-navigation .next,
    .swiper .swiper-navigation .previous {
        cursor: pointer;
        font-size: 34px;
        fill: #a5a5a5;
        width: 75px;
        will-change: fill;
        transition: fill .33s ease-out
    }
    .swiper .swiper-navigation .next .icon,
    .swiper .swiper-navigation .previous .icon {
        width: 100%
    }
    .swiper .swiper-navigation .next:hover,
    .swiper .swiper-navigation .previous:hover {
        fill: #999
    }
    .swiper .swiper-navigation .previous {
        float: right
    }
    .swiper .tile.swiper-lazy-loading .swiper-lazy-preloader {
        display: block
    }
    .tile .buttonplay,
    &.no-touchevents .tile p {
        will-change: opacity;
        transition: opacity .25s ease-out
    }
    .tile {
        display: block;
        position: relative;
        width: 100%;
        height: 100%;
        text-align: left;
        color: #000;
        background: #fff;
        overflow: hidden;
        background-size: cover;
        text-decoration: none
    }
    &.no-touchevents .tile:hover p {
        opacity: 1
    }
    &.no-touchevents .tile:hover .buttonplay {
        opacity: 0
    }
    &.no-touchevents .tile p {
        opacity: 1;
        color: #000;
    }
    &.no-touchevents .tile.swiper-lazy p {
        opacity: 0
    }
    &.no-touchevents .tile.swiper-lazy:hover:after {
        color: #b8b9ba
    }
    &.no-touchevents .tile.swiper-lazy:hover p {
        opacity: 1
    }
    .tile:before {
        content: "";
        display: block;
        padding-bottom: 100%
    }
    .tile span,
    .tile svg {
        pointer-events: none
    }
    .tile .buttonplay {
        fill: #fff;
        position: absolute;
        top: 50%;
        left: 50%;
        margin: -40px 0 0 -40px;
        width: 80px;
        height: 80px
    }
    .tile p {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
        padding: 15px;
        direction: ltr;
        background: rgba(255, 255, 255, .8);
        height: 100%;
        font-size: 18px;
        line-height: 1.33;
        transition: opacity .2s;
        opacity: 0
    }
    .tile p .account,
    .tile p .date {
        display: none
    }
    .swiper-container.small .tile p {
        font-size: 16px
    }
    @media (min-width: 768px) {
        .swiper-container.small .tile p {
            padding: 10px
        }
        .tile p {
            padding: 25px
        }
        .tile p .account {
            display: block;
            font-weight: 700;
            margin: 0 0 10px;
            text-transform: uppercase
        }
        .tile p .date {
            display: block;
            font-size: 15px
        }
    }
    .container {
        max-width: 1275px;
        margin: 0 auto;
        padding: 0 12.5px
    }
    @media (min-width: 480px) {
        .container {
            padding: 0 65px
        }
    }
    .nowrap {
        white-space: nowrap
    }
    .hidden {
        display: none
    }
p {
	font-size: 14px !important;
}
```

[Back to Top](#top)

## Javascript

Finally is the Javascript. First step is loading the [Swiper by idangero.us](http://idangero.us/swiper) library. For this Widget we have loaded it from [cdnjs](https://cdnjs.com) and then defined our variables such as where to load from, speed of scroll and the mobile breakpoints.

```
Stackla.loadJS(['https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.3.1/js/swiper.min.js']).then(function() {
	Stackla.loadTilesByFilter(function (tiles) {
		Stackla.render({tiles: tiles});
		var swiper = new Swiper ('.some__slider-container', {
        slidesPerView: 4,
        spaceBetween: 15,
        centeredSlides: !1,
        slideToClickedSlide: !1,
        lazyLoading: !0,
        preloadImages: !1,
        nextButton: ".next",
        prevButton: ".previous",
        observer: !0,
        watchSlidesProgress: !0,
        watchSlidesVisibility: !0,
        updateOnImagesReady: !0,
        speed: 400,
        autoplay: 3e3,
        autoplayDisableOnInteraction: !1,
        loop: !0,
        breakpoints: {
            1024: {
                slidesPerView: 3
            },
            768: {
                slidesPerView: 2,
                centeredSlides: !1,
                spaceBetween: 15,
                slideToClickedSlide: !1
            },
            480: {
                slidesPerView: 1.5,
                centeredSlides: !0,
                spaceBetween: 0,
                slideToClickedSlide: !0
            }
        }
    });
	console.log(swiper);
	});
});
```

[Back to Top](#top)

## Final Result

The final result should look similar to the Widget below with the auto-scrolling Carousel being responsive for Tablet and Mobile outputs as well.

```html
<script type="text/javascript">
    (function (d, id) { var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; 
        if (el) el.dataset.initTimestamp = (new Date()).getTime(); 
        if (d.getElementById(id)) return; t = d.createElement('script'); 
        t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; 
        t.id = id; (d.getElementsByTagName('head')[0] 
                || d.getElementsByTagName('body')[0]).appendChild(t); 
    }
    (document, 'stackla-widget-js'));</script>
```


# Creating Gallery Widget by Using the Blank Canvas Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

* [Overview](#overview)
* [Step 1 - Settings](#step1)
* [Step 2 - Coding for Layout and Tile Templates](#step2)
* [Step 3 - Coding for CSS](#step3)
* [Step 4 - Coding for JavaScript](#step4)
* [Step 5 - Refinement](#step5)
* [Result - The Gallery Widget](#result)

## Overview

If you need a widget that Nosto's UGC hasn't supplied out of the box yet, Blank Canvas allows you to create your widget from scratch. In this guide, we will show you how to create a Gallery widget with Blank Canvas step by step. You can learn more information about the Blank Canvas Widget in the following guide: [Creating Your Own Widget Type by Using the Blank Canvas Widget](/ugc/guides/onsite-widgets/blank-canvas/creating-your-own-widget-type-by-using-the-blank-canvas-widget).

## Step 1 - Settings

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Js0BbxJp50IEIMDDSsvU/create-new-widget-page-1.png)

If the widget is enabled in your stack, you will see the extra widget type, Blank Canvas, appear in your Widget creation page.

Just click the Create Widget button to start.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Js0BbxJp50IEIMDDSsvU/create-new-widget-page-1.png)

Before you start getting creative with the Blank Canvas you will need to set it up properly to ensure that it all works.

The **Selected Filter** is the most important setting among them as it involved the data that you can fetch from Stackla.

## Step 2 - Coding for Layout and Tile Templates

Now let's start writing some HTML. Blank Canvas introduces the [Mustache](https://mustache.github.io) template engine and the Mustache Partials feature. In the following example, we will use partial template **tpl-tile** to render **tiles** objects. The **tiles** objects must be prepared in the Custom JS Editor - which we will talk about later.

### Sample Code

#### Layout

```

<div class="track">
    <div class="container" id="gallery">
        {{#tiles}}
            {{>tpl-tile}}
        {{/tiles}}
        <div class="clear"></div>
    </div>
</div>
```

#### Tile

The following Tile template will be included by the **{{>tpl-tile}}** syntax above. We will use classnames **.g-tile**, **.g-caption**, **.g-photo**, **.g-img** to style with CSS in the next section.

```

<div class="g-tile">
    <div class="g-caption">
        {{#emoji}}{{{message}}}{{/emoji}}
    </div>
    {{#image}}
    <div class="g-photo">
        <img src="{{image}}" class="g-img" height="360"/>
    </div>
    {{/image}}
</div>
```

In the above sample code, we created a tile template that will display the image and caption with the **id**, **emoji**, **message**, and **image** properties. We have provided quite a few Tile properties so that you can assemble your own structure easily. Please check the [Tile Properties section in the API documentation](/ugc/api-docs/javascript/widgets/blank-canvas#helper-methods-tile-properties) for more details.

To preview the result of HTML template, we need to get data using JavaScript. Click on the **fork** button on the JavaScript panel, you will get the minimal code needed.

```

Stackla.loadTilesByFilter(function (tiles) {
    // Render the layout template
    Stackla.render({tiles: tiles});
});
```

Click on the button "Update Preview", you should see plain stacked images and captions.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ROa2Kjzmd9867wMSK1kb/step2-preview.png)

## Step 3 - Coding for CSS

All of the CSS rules reside within the iframe, so you don't need to worry about any conflicts with your page. Please make use of the **@import** directive to import the external CSS stylesheets you need.

### Sample Code

```

@import url(https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Rajdhani);

body {
  font-family: 'Open Sans Condensed', sans-serif;
}

.g-tile {
    position:relative;
    float:left;
    height: 360px;
    overflow: hidden;
    background: #eee;
}
.g-tile .emoji {
    height: 1em;
}
.g-img {
    height: 360px;
}
.g-caption {
    position:absolute;
    left: 0;
    right:0;
    bottom:0;
    background: rgba(0,0,0,0.5);
    color: #fff;
    padding: 15px;
    line-height: 1.5;
    display:none;
}
.g-tile:hover .g-caption {
    display:block;
}
.clear {
    clear: both;
}
    
```

Click on button "Update Preview", you should see images aligned in rows.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/GUYE4dDIbsZKha5yTGSU/step3-preview.png)

## Step 4 - Coding for JavaScript

We want photos to fill each row's space, so we'll need some help from Javascript. We will use a library [Freewall](http://vnjs.net/www/project/freewall) to help us do the job.

By applying **Freewall** plugin to our widget's container(#gallery), we will get a gallery layout. Please note that the cellH (360) is the same as height property in CSS.

```


Stackla.loadJS([
    'https://cdnjs.cloudflare.com/ajax/libs/freewall/1.0.5/freewall.min.js',
    'https://unpkg.com/imagesloaded@4.1/imagesloaded.pkgd.min.js'
]).then(function(){
    // Load tiles data from selected filter
    Stackla.loadTilesByFilter(function (tiles) {

    // Render the layout template
    Stackla.render({tiles: tiles});
        var wall = new Freewall("#gallery");
        wall.reset({
            selector: '.g-tile',
            cellW: 30,
            cellH: 30,
            gutterX : 10,
            gutterY : 10,
            cacheSize : false,
            onResize: function() {
                wall.fitWidth();
            }
        });

        // images have loaded
        $('#gallery').imagesLoaded().done(function() {
            wall.fitWidth();

            var height = $('#gallery').height();
            // set ifrmae height
            Stackla.postMessage('resize', {height: height+'px'});

            // for scroll bar appear;
            $(window).trigger("resize");
        });
    });
});


```

Click on the button "Update Preview", you should see now **tiles** have filled all spaces of each row, but not images. We will fix it on the next step.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/qDDUmfyKPdL3aPsnFXNb/step4-preview.png)

## Step 5 - Refinement

We want images to fill each tile as well. So we set images to a background, and make it fill by CSS (background-size: cover). The tag remains but hidden to let **Freewall** calculate layout by images width.

### Sample Code

```

<div class="g-tile">
    <div class="g-caption">
        {{#emoji}}{{{message}}}{{/emoji}}
    </div>
    {{#image}}
    <div class="g-photo"
         style="background-image: url({{image}})">
        <img src="{{image}}" class="g-img"/>
    </div>
    {{/image}}
</div>
```

```

@import url(https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Rajdhani);

body {
  font-family: 'Open Sans Condensed', sans-serif;
}

.g-tile {
    position:relative;
    float:left;
    height: 360px;
    overflow: hidden;
}
.g-tile .emoji {
    height: 1em;
}
.g-img {
    height: 360px;
}
.g-photo {
    background-size: cover;
    background-repeat: no-repeat;
}
.g-photo img{
    visibility: hidden;
}
.g-caption {
    position:absolute;
    left: 0;
    right:0;
    bottom:0;
    background: rgba(0,0,0,0.5);
    color: #fff;
    padding: 15px;
    line-height: 1.5;
    display:none;
}
.g-tile:hover .g-caption {
    display:block;
}
.clear {
    clear: both;
}
    
```

## Result - The Gallery Widget

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dRyf46INCkJ4uTr8XWWa/step5-preview.png)

After applying all the Layout, Tile, CSS, and JS code by the above code, you should get a Gallery Widget [(Live demo in JS Bin)](http://output.jsbin.com/zicake/1).

That's it! Get your code and embed your Gallery Widget!!

## Live Demo!

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```


# Creating a simple Hover effect using Blank Canvas

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

* [Overview](#overview)
* [Sample #1](#card1)
* [Sample #2](#card2)
* [Sample #3](#card3)
* [Sample #4](#card4)
* [Sample #5](#card5)
* [Sample #6](#card6)
* [Sample #7](#card7)
* [Sample #8](#card8)

## Overview

Nosto's UGC Blank Canvas Widget allows users to very quickly and easily design their own Widget templates to suit their organisation's requirements. Included on this page is several simple templates which can be used by customers who want a different way to render image and text content Tiles via Stackla.

Each of the designs have been inspired from [LittleSnippets.net](http://littlesnippets.net), an online blog which contains hundreds of free CSS3/HTML code snippets.

The code and samples below have been optimised for predominately Instagram content, however it can be easily adjusted based upon your requirements.

[Back to Top](#top)

## Sample #1

In this first sample we are introducing an Angle Line treatment which is present both on the Tile rest state and expands once the user hovers over the respective tile. In this design we introduce the Content Creator and the Tile text on mouse-over.

**Output**

```javascript
<script type="text/javascript">
    (function (d, id) { 
    var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; 
    if (el) el.dataset.initTimestamp = (new Date()).getTime(); 
    if (d.getElementById(id)) 
    return; t = d.createElement('script'); 
    t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; 
    t.id = id; (d.getElementsByTagName('head')[0] || 
    d.getElementsByTagName('body')[0]).appendChild(t); 
    }(document, 'stackla-widget-js'));
</script>

```

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <figcaption>
    <h3>@{{user}}</h3>
    <p>{{message}}</p>
  </figcaption>
  <a href="{{original_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro);
@import url(https://fonts.googleapis.com/css?family=Teko:700);
.tile {
  background-color: #fff;
  color: #ffffff;
  display: inline-block;
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  margin: 10px 5px;
  max-width: 400px;
  min-width: 400px;
  max-height: 400px;
  min-height: 400px;
  overflow: hidden;
  position: relative;
  text-align: right;
  width: 100%;
}
.tile *,
.tile *:before,
.tile *:after {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.45s ease;
  transition: all 0.45s ease;
}
.tile img {
  backface-visibility: hidden;
  height: 400px;
  vertical-align: top;
}
.tile:before,
.tile:after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  content: '';
  background-color: #b81212;
  opacity: 0.5;
  -webkit-transition: all 0.45s ease;
  transition: all 0.45s ease;
}
.tile:before {
  -webkit-transform: skew(30deg) translateX(80%);
  transform: skew(30deg) translateX(80%);
}
.tile:after {
  -webkit-transform: skew(-30deg) translateX(70%);
  transform: skew(-30deg) translateX(70%);
}
.tile figcaption {
  position: absolute;
  top: 0px;
  bottom: 0px;
  left: 0px;
  right: 0px;
  z-index: 1;
  bottom: 0;
  padding: 20px 20px 20px 40%;
}
.tile figcaption:before,
.tile figcaption:after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #b81212;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.7);
  content: '';
  opacity: 0.5;
  z-index: -1;
}
.tile figcaption:before {
  -webkit-transform: skew(30deg) translateX(100%);
  transform: skew(30deg) translateX(100%);
}
.tile figcaption:after {
  -webkit-transform: skew(-30deg) translateX(90%);
  transform: skew(-30deg) translateX(90%);
}
.tile h3,
.tile p {
  margin: 0;
  opacity: 0;
  letter-spacing: 1px;
}
.tile h3 {
  font-family: 'Teko', sans-serif;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2em;
  text-transform: uppercase;
}
.tile p {
  font-size: 1.0em;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
}
.tile:hover h3,
.tile.hover h3,
.tile:hover p,
.tile.hover p {
  -webkit-transform: translateY(0);
  transform: translateY(0);
  opacity: 0.9;
}
.tile:hover:before,
.tile.hover:before {
  -webkit-transform: skew(30deg) translateX(30%);
  transform: skew(30deg) translateX(30%);
  -webkit-transition-delay: 0.05s;
  transition-delay: 0.05s;
}
.tile:hover:after,
.tile.hover:after {
  -webkit-transform: skew(-30deg) translateX(20%);
  transform: skew(-30deg) translateX(20%);
}
.tile:hover figcaption:before,
.tile.hover figcaption:before {
  -webkit-transform: skew(30deg) translateX(50%);
  transform: skew(30deg) translateX(50%);
  -webkit-transition-delay: 0.15s;
  transition-delay: 0.15s;
}
.tile:hover figcaption:after,
.tile.hover figcaption:after {
  -webkit-transform: skew(-30deg) translateX(40%);
  transform: skew(-30deg) translateX(40%);
  -webkit-transition-delay: 0.1s;
  transition-delay: 0.1s;
}
```

[Back to Top](#top)

## Sample #2

In this sample we introduce a basic hover once the user interacts with the respective tile. Here the treatment sees the hover come from the base of the tile and expand vertically, revealing the additional Tile details.

**Output**

**Tile Markup**

```
<figure class="tile"><img src="{{image}}" alt="sample87"/>
  <figcaption>
    <h3>@{{user}}</h3>
	  <h5><center>{{message}}<br><br>{{source}}</center></h5>
  </figcaption><a href="{{original_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Raleway);
.tile {
  font-family: 'Raleway', sans-serif;
  position: relative;
  display: inline-block;
  overflow: hidden;
  margin: 10px 8px;
  min-width: 230px;
  max-width: 315px;
  max-height: 315px; 
  width: 100%;
  color: #ffffff;
  font-size: 16px;
  text-align: left;
}
.tile * {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.25s ease;
  transition: all 0.25s ease;
}
.tile:before {
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: 10px;
  right: 10px;
  top: 100%;
  content: '';
  background-color: rgba(51, 51, 51, 0.9);
  -webkit-transition: all 0.25s ease;
  transition: all 0.25s ease;
  -webkit-transition-delay: 0.25s;
  transition-delay: 0.25s;
}
.tile img {
  vertical-align: top;
  height: 315px;
  backface-visibility: hidden;
}
.tile figcaption {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.tile h3,
.tile h5 {
  margin: 0;
  opacity: 0;
  letter-spacing: 1px;
}
.tile h3 {
  -webkit-transform: translateY(-100%);
  transform: translateY(-100%);
  text-transform: uppercase;
  font-weight: 400;
  -webkit-transition-delay: 0.05s;
  transition-delay: 0.05s;
  margin-bottom: 5px;
}
.tile h5 {
  font-weight: normal;
  background-color: #ae895d;
  text-transform: uppercase;
  padding: 3px 10px;
  margin: 20px;
  -webkit-transform: translateY(-100%);
  transform: translateY(-100%);
  -webkit-transition-delay: 0s;
  transition-delay: 0s;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
}
.tile:hover:before,
.tile.hover:before {
  top: 10px;
  -webkit-transition-delay: 0s;
  transition-delay: 0s;
}
.tile:hover h3,
.tile.hover h3,
.tile:hover h5,
.tile.hover h5 {
  -webkit-transform: translateY(0);
  transform: translateY(0);
  opacity: 1;
}
.tile:hover h3,
.tile.hover h3 {
  -webkit-transition-delay: 0.3s;
  transition-delay: 0.3s;
}
.tile:hover h5,
.tile.hover h5 {
  -webkit-transition-delay: 0.2s;
  transition-delay: 0.2s;
}
```

[Back to Top](#top)

## Sample #3

In this sample we are using CSS to create a block transition effect on the hover-over before revealing the Content Creator and Tile message.

**Output**

**Tile Markup**

```
<figure class="tile"><img src="{{image}}" alt="{{message}}"/>
  <figcaption>
    <h2>@{{user}}</h2>
    <p>{{message}}</p>
  </figcaption>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,500,900);
@import url(https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css);
.tile {
  font-family: 'Source Sans Pro', Arial, sans-serif;
  position: relative;
  float: left;
  overflow: hidden;
  margin: 10px 1%;
  min-width: 350px;
  max-width: 350px;
  max-height: 350px;
  min-height: 350px; 
  width: 100%;
  color: #ffffff;
  text-align: center;
  font-size: 16px;
}
.tile * {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.35s ease;
  transition: all 0.35s ease;
}
.tile img {
  opacity: 1;
  height: 350px;
}
.tile:after,
.tile:before,
.tile figcaption:after,
.tile figcaption:before {
  background: #0a0a0a;
  width: 25%;
  position: absolute;
  content: '';
  opacity: 0;
  -webkit-transition: all 0.3s steps(4);
  transition: all 0.3s steps(4);
  z-index: 1;
  bottom: 100%;
  top: 0;
}
.tile:before {
  left: 0;
  -webkit-transition-delay: 0;
  transition-delay: 0;
}
.tile:after {
  left: 25%;
  -webkit-transition-delay: 0.1s;
  transition-delay: 0.1s;
}
.tile figcaption:before {
  left: 50%;
  -webkit-transition-delay: 0.2s;
  transition-delay: 0.2s;
  z-index: -1;
}
.tile figcaption:after {
  left: 75%;
  -webkit-transition-delay: 0.3s;
  transition-delay: 0.3s;
  z-index: -1;
}
.tile figcaption {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2;
  padding: 30px;
}
.tile h2,
.tile p,
.tile .icons {
  margin: 0;
  width: 100%;
  opacity: 0;
}
.tile h2 {
  font-weight: 900;
  text-transform: uppercase;
}
.tile p {
  font-weight: 300;
}
.tile .icons {
  position: absolute;
  bottom: 30px;
  left: 0;
  width: 100%;
}
.tile i {
  padding: 0px 10px;
  display: inline-block;
  font-size: 24px;
  color: #ffffff;
  text-align: center;
  opacity: 0.8;
}
.tile i:hover {
  opacity: 1;
}
.tile:hover:after,
.tile.hover:after,
.tile:hover:before,
.tile.hover:before,
.tile:hover figcaption:after,
.tile.hover figcaption:after,
.tile:hover figcaption:before,
.tile.hover figcaption:before {
  bottom: 0;
  opacity: 0.8;
}
.tile:hover figcaption h2,
.tile.hover figcaption h2,
.tile:hover figcaption p,
.tile.hover figcaption p,
.tile:hover figcaption .icons,
.tile.hover figcaption .icons {
  -webkit-transition-delay: 0.4s;
  transition-delay: 0.4s;
}
.tile:hover figcaption h2,
.tile.hover figcaption h2,
.tile:hover figcaption .icons,
.tile.hover figcaption .icons {
  opacity: 1;
}
.tile:hover figcaption p,
.tile.hover figcaption p {
  opacity: 0.7;
}
body {
  height: 380px; 
}
```

[Back to Top](#top)

## Sample #4

For this Sample, we are presenting the User Handle and the Network source on the rest view, and then introducing the Tile text on hover with a series of lines.

In this sample we introduce a basic hover once the user interacts with the respective tile. Here the treatment sees the hover come from the base of the tile and expand vertically, revealing the additional Tile details.

**Output**

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <div class="title">
    <div>
      <h2>@{{user}}</h2>
      <h4>{{source}}</h4>
    </div>
  </div>
  <figcaption>
    <p>{{message}}</p>
  </figcaption>
  <a href="{{original_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Raleway:400,500,700);
@import url(https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css);
figure.tile {
  font-family: 'Raleway', Arial, sans-serif;
  position: relative;
  float: left;
  overflow: hidden;
  margin: 10px 1%;
  min-width: 400px;
  max-width: 400px;
  min-height: 400px;
  max-height: 400px; 
  width: 100%;
  color: #ffffff;
  text-align: center;
  font-size: 16px;
  background-color: #000000;
}
figure.tile *,
figure.tile *:before,
figure.tile *:after {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.55s ease;
  transition: all 0.55s ease;
}
figure.tile img {
  max-width: 100%;
  backface-visibility: hidden;
  vertical-align: top;
  opacity: 0.9;
}
figure.tile .title {
  position: absolute;
  top: 58%;
  left: 25px;
  padding: 5px 10px 10px;
}
figure.tile .title:before,
figure.tile .title:after {
  height: 2px;
  width: 400px;
  position: absolute;
  content: '';
  background-color: #ffffff;
}
figure.tile .title:before {
  top: 0;
  left: 10px;
  -webkit-transform: translateX(100%);
  transform: translateX(100%);
}
figure.tile .title:after {
  bottom: 0;
  right: 10px;
  -webkit-transform: translateX(-100%);
  transform: translateX(-100%);
}
figure.tile .title div:before,
figure.tile .title div:after {
  width: 2px;
  height: 300px;
  position: absolute;
  content: '';
  background-color: #ffffff;
}
figure.tile .title div:before {
  top: 10px;
  right: 0;
  -webkit-transform: translateY(100%);
  transform: translateY(100%);
}
figure.tile .title div:after {
  bottom: 10px;
  left: 0;
  -webkit-transform: translateY(-100%);
  transform: translateY(-100%);
}
figure.tile h2,
figure.tile h4 {
  margin: 0;
  text-transform: uppercase;
}
figure.tile h2 {
  font-weight: 400;
}
figure.tile h4 {
  display: block;
  font-weight: 700;
  background-color: #ffffff;
  padding: 5px 10px;
  color: #000000;
}
figure.tile figcaption {
  position: absolute;
  bottom: 42%;
  left: 25px;
  text-align: left;
  opacity: 0;
  padding: 5px 60px 5px 10px;
  font-size: 0.8em;
  font-weight: 500;
  letter-spacing: 1.5px;
}
figure.tile figcaption p {
  margin: 0;
}
figure.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
figure.tile:hover img,
figure.tile.hover img {
  zoom: 1;
  filter: alpha(opacity=35);
  -webkit-opacity: 0.35;
  opacity: 0.35;
}
figure.tile:hover .title:before,
figure.tile.hover .title:before,
figure.tile:hover .title:after,
figure.tile.hover .title:after,
figure.tile:hover .title div:before,
figure.tile.hover .title div:before,
figure.tile:hover .title div:after,
figure.tile.hover .title div:after {
  -webkit-transform: translate(0, 0);
  transform: translate(0, 0);
}
figure.tile:hover .title:before,
figure.tile.hover .title:before,
figure.tile:hover .title:after,
figure.tile.hover .title:after {
  -webkit-transition-delay: 0.15s;
  transition-delay: 0.15s;
}
figure.tile:hover figcaption,
figure.tile.hover figcaption {
  opacity: 1;
  -webkit-transition-delay: 0.2s;
  transition-delay: 0.2s;
}
body {
  min-height: 430px; 
}
```

[Back to Top](#top)

## Sample #5

Simple hover effect, with a negative treatment.

**Output**

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <figcaption>
	<h3>{{message}}</h3><br><br>
    <h3>@{{user}}</h3>
    <h4>{{source}}</h4>
  </figcaption>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Montserrat:200);
.tile {
  font-family: 'Montserrat', Arial, sans-serif;
  position: relative;
  display: inline-block;
  overflow: hidden;
  margin: 8px;
  min-width: 230px;
  max-width: 315px;
  max-height: 315px; 
  width: 100%;
  color: #fff;
  text-align: left;
  font-size: 16px;
  background: #000;
}
.tile *,
.tile:before,
.tile:after {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.4s ease;
  transition: all 0.4s ease;
}
.tile img {
  max-width: 100%;
  height: 315px;
  backface-visibility: hidden;
  vertical-align: top;
}
.tile:before,
.tile:after {
  position: absolute;
  top: 20px;
  right: 20px;
  content: '';
  background-color: #fff;
  z-index: 1;
  opacity: 0;
}
.tile:before {
  width: 0;
  height: 1px;
}
.tile:after {
  height: 0;
  width: 1px;
}
.tile figcaption {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 15px 20px;
}
.tile h3,
.tile h4 {
  margin: 0;
  font-size: 1.1em;
  font-weight: normal;
  opacity: 0;
}
.tile h4 {
  font-size: .8em;
  text-transform: uppercase;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
}
.tile:hover img,
.tile.hover img {
  zoom: 1;
  filter: alpha(opacity=20);
  -webkit-opacity: 0.2;
  opacity: 0.2;
}
.tile:hover:before,
.tile.hover:before,
.tile:hover:after,
.tile.hover:after {
  opacity: 1;
  -webkit-transition-delay: 0.25s;
  transition-delay: 0.25s;
}
.tile:hover:before,
.tile.hover:before {
  width: 40px;
}
.tile:hover:after,
.tile.hover:after {
  height: 40px;
}
.tile:hover h3,
.tile.hover h3,
.tile:hover h4,
.tile.hover h4 {
  opacity: 1;
}
.tile:hover h3,
.tile.hover h3 {
  -webkit-transition-delay: 0.3s;
  transition-delay: 0.3s;
}
.tile:hover h4,
.tile.hover h4 {
  -webkit-transition-delay: 0.35s;
  transition-delay: 0.35s;
}
body {
  text-align: center;
}
```

[Back to Top](#top)

## Sample #6

The hover treatment in this example sees the Image shrink and disappear to the right on hover-over.

**Output**

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <figcaption>
    <h3>@{{user}}</h3>
    <p>{{message}}</p><a href="{{original_url}}" target="_blank" class="read-more">View Post</a>
  </figcaption>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Raleway:400,500,800); 
figure.tile { 
  font-family: 'Raleway', Arial, sans-serif; 
  position: relative; 
  float: left; 
  overflow: hidden; 
  margin: 10px 1%; 
  min-width: 350px; 
  max-width: 350px; 
  max-height: 350px; 
  min-width: 350px; 	
  width: 100%; 
  color: #ffffff; 
  text-align: left; 
  background-color: #07090c; 
  font-size: 16px; 
} 
figure.tile * { 
  -webkit-box-sizing: border-box; 
  box-sizing: border-box; 
  -webkit-transition: all 0.35s ease; 
  transition: all 0.35s ease; 
} 
figure.tile img { 
  height: 350px; 
  -webkit-transition-delay: 0.2s; 
  transition-delay: 0.2s; 
  backface-visibility: hidden; 
} 
figure.tile figcaption { 
  position: absolute; 
  top: 50%; 
  left: 0; 
  width: 100%; 
  -webkit-transform: scale(0.5) translate(0%, -50%); 
  transform: scale(0.5) translate(0%, -50%); 
  -webkit-transform-origin: 50% 0%; 
  -ms-transform-origin: 50% 0%; 
  transform-origin: 50% 0%; 
  z-index: 1; 
  opacity: 0; 
  padding: 0 30px; 
} 
figure.tile h3, 
figure.tile p { 
  line-height: 1.5em; 
} 
figure.tile h3 { 
  margin: 0; 
  font-weight: 800; 
  text-transform: uppercase; 
} 
figure.tile p { 
  font-size: 0.8em; 
  font-weight: 500; 
  margin: 0 0 15px; 
} 
figure.tile .read-more { 
  border: 2px solid #ffffff; 
  padding: 0.5em 1em; 
  font-size: 0.8em; 
  text-decoration: none; 
  color: #ffffff; 
  display: inline-block; 
} 
figure.tile .read-more:hover { 
  background-color: #ffffff; 
  color: #000000; 
} 
figure.tile:hover img, 
figure.tile.hover img { 
  -webkit-animation: tile 0.45s linear forwards; 
  animation: tile 0.45s linear forwards; 
  -webkit-animation-iteration-count: 1; 
  animation-iteration-count: 1; 
} 
figure.tile:hover figcaption, 
figure.tile.hover figcaption { 
  -webkit-transform: scale(1) translate(0, -50%); 
  transform: scale(1) translate(0, -50%); 
  opacity: 1; 
  -webkit-transition-delay: 0.35s; 
  transition-delay: 0.35s; 
} 
@keyframes tile { 
  50% { 
    -webkit-transform: scale(0.8) translateX(0%); 
    transform: scale(0.8) translateX(0%); 
    opacity: 0.5; 
  } 
  100% { 
    -webkit-transform: scale(0.8) translateX(150%); 
    transform: scale(0.8) translateX(150%); 
    opacity: 0.5; 
  } 
}
```

[Back to Top](#top)

## Sample #7

Simple horizontal slide effect on hover-over.

**Output**

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <figcaption>
    <h2>@{{user}}</h2>
    <h3>{{source}}</h3>
    <p>{{message}}</p>
  </figcaption>
  <a href="{{original_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Slabo+27px);
@import url(https://fonts.googleapis.com/css?family=Lato);
.tile {
  font-family: 'Lato', sans-serif;
  position: relative;
  float: left;
  overflow: hidden;
  margin: 10px 1%;
  min-width: 230px;
  max-width: 315px;
  min-height: 315px;
  max-height: 315px;
  width: 100%;
  color: #ffffff;
  text-align: left;
  font-size: 16px;
  background-color: #1A1A1A;
}
.tile * {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.45s ease;
  transition: all 0.45s ease;
}
.tile img {
  vertical-align: top;
  max-width: 100%;
  backface-visibility: hidden;
}
.tile figcaption {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
  padding: 30px;
  background-color: #202123;
  -webkit-transform: translateX(100%);
  transform: translateX(100%);
  -webkit-box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
  box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
}
.tile h2,
.tile h3,
.tile p {
  margin: 0;
}
.tile h2,
.tile h3 {
  font-family: 'Slabo 27px', serif;
  line-height: 1.2em;
}
.tile h2 {
  font-size: 1.9em;
  color: #35ADF9;
}
.tile h3 {
  color: #EBEBEB;
  font-size: 1.3em;
  font-weight: normal;
  letter-spacing: 1px;
}
.tile p {
  border-top: 1px solid rgba(255, 255, 255, 0.2);
  font-size: 0.9em;
  margin-top: 12px;
  padding: 12px 0 15px;
  line-height: 1.5em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
}
.tile:hover > img,
.tile.hover > img {
  -webkit-transform: translateX(100%);
  transform: translateX(100%);
}
.tile:hover figcaption,
.tile.hover figcaption {
  -webkit-transform: translateX(0%);
  transform: translateX(0%);
}
```

[Back to Top](#top)

## Sample #8

Simple vertical reveal more on the respective tiles.

**Output**

**Tile Markup**

```
<figure class="tile"><img src="{{image}}" alt="{{message}}"/>
  <figcaption>
    <h3>@{{user}}</h3>
    <h5>{{source}}</h5>
    <blockquote>
      <p>{{message}}</p>
    </blockquote>
  </figcaption><a href="{{source_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Lato);
@import url(https://fonts.googleapis.com/css?family=Oswald);
@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css);
.tile {
  font-family: 'Lato', Arial, sans-serif;
  position: relative;
  display: inline-block;
  overflow: hidden;
  margin: 8px;
  min-width: 250px;
  max-width: 310px;
  max-height: 310px; 	
  width: 100%;
  background-color: #000000;
  color: #ffffff;
  text-align: left;
  font-size: 16px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.15);
}
.tile * {
  -webkit-transition: all 0.35s;
  transition: all 0.35s;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.tile img {
  height: 350px; 
  vertical-align: top;
}
.tile figcaption {
  position: absolute;
  height: 75px;
  left: 15px;
  right: 15px;
  bottom: 15px;
  overflow: hidden;
  padding: 15px;
  background-color: rgba(0, 0, 0, 0.75);
}
.tile h3 {
  font-family: 'Oswald';
  text-transform: uppercase;
  font-size: 20px;
  font-weight: 400;
  line-height: 24px;
  margin: 3px 0;
}
.tile h5 {
  font-weight: 400;
  margin: 0;
  text-transform: uppercase;
  color: #bbb;
  letter-spacing: 1px;
}
.tile blockquote {
  padding: 0;
  margin: 0;
  font-style: italic;
  font-size: 1em;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}
.tile:hover figcaption,
.tile.hover figcaption {
  height: calc(85%);
}
```

[Back to Top](#top)


# Creating a Word Cloud using Blank Canvas

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).
{% endhint %}

* [Overview](#overview)
* [Layout](#layout)
* [CSS](#css)
* [Javascript](#javascript)
* [Final Result](#final)

## Overview

Nosto's UGC Blank Canvas Widget allows users to very quickly and easily design their own Widget templates to suit their organisation's requirements.

In this guide we are going to create a fixed-dimension Word Cloud, leveraging [jQCloud](http://mistic100.github.io/jQCloud) which will also auto-refresh after a set period.

In the guide we are going to move through each of the Mustache Partials to build the final Widget.

[Back to Top](#top)

## Layout

For the Layout Partial we will be simply defining the relevant classes where we will want to render the Word Cloud. The class names we will be used as inline with what our version of [jQCloud](http://mistic100.github.io/jQCloud) (2.0.3) recommends.

```
<div class="wall first">
	<div class="tagwrap">
		<div class="wall__tags"></div>
	</div>
</div>
```

For this particular Blank Canvas there is no need to actually reference the Tiles partial as we won't be actually loading any individual pieces of UGC.

[Back to Top](#top)

## CSS

Following the definition of the Layout Partial, we can now start to define the styling of our Word Cloud. In here the most important items we have defined relates to the various sizes and colours of the elements in the Word Cloud (Elements: `div.jqcloud span.jqcloud-word.w1 - div.jqcloud span.jqcloud-word.w10`) and the overarching Styling for the Wall.

```
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,700');

/**
* Tag Colours & Weights
*/
div.jqcloud span.jqcloud-word.w1 { color:#888 }
div.jqcloud span.jqcloud-word.w2 { color:#444; font-weight: 700; }
div.jqcloud span.jqcloud-word.w3 { color:#666; font-size: 35px; }
div.jqcloud span.jqcloud-word.w4 { color:#000; font-size: 38px; }
div.jqcloud span.jqcloud-word.w5 { color:#888; font-size: 40px; }
div.jqcloud span.jqcloud-word.w6 { color:#e63650; font-size: 42px;}
div.jqcloud span.jqcloud-word.w7 { color:#444; font-size: 44px; }
div.jqcloud span.jqcloud-word.w8 { color:#222; font-size: 46px; }
div.jqcloud span.jqcloud-word.w9 { color:#000; font-size: 48px; }
div.jqcloud span.jqcloud-word.w10 { color:#e63650; font-weight: 700; font-size: 50px;}

/**
* Styling for Wall
*/
.wall {
	display: inline-block;
	position: absolute;
	top: 0;
	background: #fff;
}
.wall.first {
   left: 20px;
}
.tagwrap {
	background: #fff;
}
body, html {
 	margin: 0;
	padding: 0;
	height: 700px;
	background: #fff;
}

.jqcloud {
	font-size: 20px;	
}

.jqcloud-word{
	text-transform: uppercase;
	font-family: 'Open Sans', sans-serif;
	font-weight: 300;
}

.jqcloud-word::before {
	content: '#'
}
```

[Back to Top](#top)

## Javascript

Finally comes the most important part, loading the [jQCloud](http://mistic100.github.io/jQCloud) Javascript Framework. For this example, we have used jQCloud 2.0.3, however if you use earlier or later versions the behaviour maybe slightly different. You will need to download the jQuery JS file, host it and then update the below code to reference it in order for the below Javascript to work.

Now in our Javascript we are defining how frequently we would like our Widget to refresh, the size of our Widget and the delay for loading each element. For this same, we've set it to refresh twice after 1000ms and have set the Widget size to 850px wide by 700px tall.

```
var refreshTime =  1 * 1000;
	refreshed = false; 

 Stackla.loadJS([
	 "jqcloud.js"
 ]).then(function () {
	 
	 // Load tiles data from selected filter
	var refreshCloud = function () {
		Stackla.loadTilesByFilter(function (tiles) {
			if (!refreshed) Stackla.render({tiles: tiles});
			var results = [];
			var hashtags = tiles.forEach(function(post){	
				var retrievedHashtags = getHashTags(post.caption);
				if(retrievedHashtags.length){
					results.concat(retrievedHashtags);
					retrievedHashtags.forEach(function(tag){
						results.push(tag);
					});
				}

			}, {
				limit:2
			});
			var words = processResults(results);
			
			if (!refreshed) {
				setTimeout(function () { 
					$('.wall.first .wall__tags, .wall__tags_clone').jQCloud(words, {
					width: 850,
					height: 700,
					delay: 2,
					encodeURI:true,
					afterCloudRender: function () {}
				});
				}, 1000);
				refreshed = true;
			}
		});
	};
	 
	 // get the cloud
	 refreshCloud();
	 
	 // set an interval to refresh
	 setInterval(refreshCloud, refreshTime);
 });

function getHashTags(inputText) {  
    var regex = /(?:^|\s)(?:#)([a-zA-Z\d]+)/gm;
    var matches = [];
    var match;

    while ((match = regex.exec(inputText))) {
        matches.push(match[1]);
    }
    return matches;
}
function findWithAttr(array, attr, value) {
    for(var i = 0; i < array.length; i += 1) {
        if(array[i][attr].toLowerCase() === value.toLowerCase()) {
            return i;
        }
    }
    return -1;
}

function processResults(results){
	var words = [];
	results.forEach(function(tag){
		var wordIndex = findWithAttr(words, "text", tag);
		if(wordIndex < 0){
			// if there's no match
			var word = {
				text : ''+tag,
				weight : 1
			};
			words.push(word);
		}else{
			words[wordIndex].weight +=1;
		}
	});
	return words;
}
var that = this;
```

[Back to Top](#top)

## Final Result

The Final Result will look something like below. It is important to note, that depending on the Filter you are using, how much content you have associated with it, and the variation of words will all have an impact on the end design.

jQCloud will ignore words which are only ever mentioned once, and if it cannot differentiate the occurrence of certain words, it may skip some of the size classes we defined in the CSS.

```html
<script type="text/javascript">(function (d, id) 
{ var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; 
    if (el) el.dataset.initTimestamp = (new Date()).getTime();
    if (d.getElementById(id)) return; t = d.createElement('script'); 
    t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; 
    t.id = id; (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t); }
(document, 'stackla-widget-js'));
</script>
```


# Creating Your Widget by Using the Blank Canvas

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

* [Overview](#overview)
* [Step 1 - Getting a Canvas with Single Click!](#step1)
* [Step 2 - Super Simple Settings!](#step2)
* [Step 3 - Custom Code Editors](#step3)
* [Step 4 - Coding for Layout and Tile Templates](#step4)
* [Step 5 - Coding for CSS](#step5)
* [Step 6 - Coding for JavaScript](#step6)
* [Result - The Boilerplate Widget](#result)
* [Bonus - Creating the Horizontal Masonry Widget](#bonus)

## Overview

Though Nosto's UGC provides several different types of widgets and has the great ability of customization, we still can't satisfy some of our customers' unique needs. For example, some customers want to use the [Horizontal Masonry](http://isotope.metafizzy.co/layout-modes/masonryhorizontal.html), which Nosto's UGC doesn't have. This then leads to our customer to using hack solutions or forces them to build from scratch which can take a lot of effort. That's not okay for both Nosto's UGC and our clients.

To resolve this, we have provided a new widget type. Blank Canvas allows you to create your widget from scratch whilst providing developers with some great utility functions and tools to achieve their solution efficiently.

In this tutorial, we will show you how to create a Blank Canvas widget step by step.

## Step 1 - Getting a Canvas with Single Click!

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/n8DbhwOgMnx9z5m6j8HX/create-new-widget-page.png)

Once the widget is available to you, you will see an extra widget type, Blank Canvas, in your widget creation page.

It doesn't have any JavaScript, CSS, or HTML by default. That's the reason why you can only see the prompt text in the preview widget area.

Just click the **Create Widget** button to start! It's easy!!

## Step 2 - Super Simple Settings!

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/vPpAso15oFefVw7W7VJ1/settings.png)

Before you start to put your imagination and innovative ideas onto the canvas, it's suggested to set up it correctly.

In Blank Canvas, we only provide very basic setting options. That is the Activate Widget, Name, Select Filter, and the Powered by Nosto's UGC Logo fields.

The **Selected Filter** is the most important setting among them. It involves with the data you can fetch from Nosto's UGC.

## Step 3 - Custom Code Editors

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/zAwYon5Tosb8tKwp194v/code-editors-1.png)

Unlike other widgets which have the CSS and JavaScript editors, the Blank Canvas widget provides the other **Layout** and **Tile** editors so that you can write code in [Mustache](https://mustache.github.io).

## Step 4 - Coding for Layout and Tile Templates

### Understanding the Mustache Partials

If you are familiar with the [Mustache](https://mustache.github.io) template engine, you probably know the [Partials](https://mustache.github.io/mustache.5.html#Partials). The Partials feature just makes it easier to **modulize your templates**.

Nosto's UGC introduces the Mustache Partials feature. You can add, remove, or rename any templates. **The only necessary one is the Layout**. You can remove the Tile template if you think you can finish everything in one template.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/S6IyTQ9nW0CH3wBI7NvV/mustache-templates-structure.png)

The above diagram illustrates the Layout template which includes Header, Navigation, Tile (inside the loop), and Map templates. We've created the following widget with this templates structure. See? You basically can create a micro-site with our Blank Canvas. Awesome right?

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```

Note the above example just demonstrates the ability to use multiple templates. There is no interaction between photos and map.

### Sample Code

You can copy and paste the following code as your boilerplate. Or you can just click the **Fork** link (at the top-right corner) to get them directly.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ukYpyZ6t0yr5PdhupmZn/fork.png)

#### Layout

```
<div class="track">
    <div class="container">
        {{#tiles}}
            {{>tpl-tile}}
        {{/tiles}}
    </div>
</div>
```

The following Tile template will be included by the **{{>tpl-tile}}** syntax. The **tiles** is an array collection containing tile objects. It must be prepared in the Custom JS Editor - which \[we will talk about later]\(#js-stackla.loadTilesByFilter).

#### Tile

```
<div class="tile">
    <div class="hd">#{{id}}</div>
    <div class="bd">
        {{#emoji}}{{{message}}}{{/emoji}}
    </div>
    {{#image}}
    <div class="ft">
        <img src="{{image}}" width="120"/>
    </div>
    {{/image}}
</div>
```

In the above sample code, we use **id**, **emoji**, **message**, and **image** properties. We have provide quiet a few Tile properties so that you can assemble your own structure easily. Please check the [Tile Properties section in the API documentation](/ugc/api-docs/javascript/widgets/blank-canvas#helper-methods-tile-properties) for more details.

## Step 5 - Coding for CSS

All the CSS rules reside within the iframe, so you don't need to worry about any conflicts with your page.

Please make use of the **@import** directive to import the external CSS stylesheets you need.

### Sample Code

Again, you can click the **Fork** link to grab the following code.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/2QOp8tyUlSfaxrDjQSIU/css-fork.gif)

```
// Tips: You can use @import rule to load the CSS files you need
// @import url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css);

@import url(https://fonts.googleapis.com/css?family=Open+Sans);
body {
    background: #ccc;
    padding: 10px 0 0 10px;
    font-family: 'Open Sans', sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
.tile {
    background: #fff;
    border: solid 1px #ccc;
    color: #666;
    font-size: 13px;
    padding: 10px;
    margin-right: 10px;
    margin-bottom: 10px;
    .hd {
        margin-bottom: 10px;
    }
    .bd {
        color: #333;
        font-size: 16px;
    }
    &-emoji {
        margin-right: 2px;
        vertical-align: middle;
        width: 16px;
    }
}
```

## Step 6 - Coding for JavaScript

Blank Canvas provides a simple SDK, which allows you to boost the development speed. Let's learn this SDK by reading the following boilerplate code.

### Used JavaScript Libraries

The Blank Canvas currently has the following JavaScript libraries installed.

* **jQuery**: You can access it by using the `$` global variable. The current version is 1.11.0.
* **lodash**: You can access it by using the `_` global variable. The current version is 3.10.1.
* **Mustache.js**: You can access it by using the `Mustache` global variable. The current version is 0.8.1.
* **twemoji**: You can access it by using the `twemoji` global variable. The current version is 2.
* **jquery.cookie**: You can access it by using the `jQuery.fn.cookie`.
* **lazyload**: You can access it by using the `Lazyload` global variable.
* **js-xss**: You can access it by using the `filterXSS` global variable.
* **twitter-text**: You can access it by using the `twttr.txt` global variable. The current version is 1.13.0

### Global Setting Object

You can access some basic information about this widget from the `Stackla.widgetOptions` global variable. Please check the [API documentation](/ugc/api-docs/javascript/widgets/blank-canvas#setting-object) for more details.

### JavaScript SDK Methods

I would like to highlight the three used methods which are S**tackla.loadJS**, S**tackla.loadTilesByFilter**, and the S**tackla.render**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/H84uE3ryqMeW9ZL0Gd7A/js-sample-code.png)

#### Stackla.loadJS

This method helps you to **load the external JavaScript files parallelly and execute by order**. It has better performance than using the multiple tags. Please check the How to Load External JS and CSS into Widgets to get more details.

It supports both the callback-style and the promise-style.

```javascript
Stackla.loadJS([
    'https://foo.com/lib1.js',
    'https://foo.com/lib2.js',
    'https://foo.com/lib3.js',
], function () {
    // The callback after lib1.js, lib2.js, and lib3.js are loaded.
});
```

#### Stackla.loadTilesByFilter

This method helps you to **load the data from the selected filter**, which reduces a lot of work relating to API requests and pinned tiles ordering.

It supports both the callback-style and the promise-style.

```
Stackla.loadTilesByFilter()
.then(function (tiles) {
     // You will get the tiles collection data.
});
```

The only argument of the callback function is the tiles data which we [mentioned above](#layout-tiles).

#### Stackla.render

This method helps you to render the Mustache templates you defined. After all content is rendered, it will automatically resizes the belonging iframe to proper height.

```
//Stackla.loadTilesByFilter(function (tiles) {
    // Render the layout template
    Stackla.render({tiles: tiles});
//});
```

The only parameter is the data object which you want pass to the Mustache template.

#### Sample Code - Gathering Everything Together

Now hope you fully understand how these methods work.

```
/* global $, Stackla */

// // Tips: Load the JavaScript libraries you need
// Stackla.loadJS([
//     'https://rawgit.com/desandro/jquery-bridget/master/jquery-bridget.js',
//     'https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.2/isotope.pkgd.js',
//     'https://rawgit.com/metafizzy/isotope-masonry-horizontal/master/masonry-horizontal.js'
// ]).then(function () {
//     // You can trigger the Stackla.loadTilesByFilter after all dependencies are loaded.
// });

// Load tiles data from selected filter
Stackla.loadTilesByFilter(function (tiles) {
    // Render the layout template
    Stackla.render({tiles: tiles});
});
```

## Result - The Boilerplate Widget

After applying all the Layout, Tile, CSS, and JS code by the above boilerplate code, you should get a [simple widget](http://output.jsbin.com/xikelan). It's not fancy at all. However, it's just easy enough for you to understand how Blank Canvas works quickly.

***

## Bonus - Creating the Horizontal Masonry Widget

You are probably one of our clients who wants to use the Horizontal Masonry Widget. Now you can use our Blank Canvas Widget to achieve it!

### Layout

The Layout is the same as the boilerplate code.

```
<div class="track">
    <div class="container">
        {{#tiles}}
            {{>tpl-tile}}
        {{/tiles}}
    </div>
</div>
```

### Tile

Tile is also pretty much the same as the boilerplate code. We just add an `tile--double` class when the `isDouble` property of the tile object is valid.

```
<div class="tile{{#isDouble}} tile--double{{/isDouble}}">
    <img src="{{image}}" width="120" class="tile-image"/>
    <div class="tile-content">
        {{#emoji}}{{{message}}}{{/emoji}}
    </div>
</div>
```

### CSS

We want the content to be displayed only when the user hovers over the tile. This effect is done by the CSS.

```
@import url(https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Rajdhani);

.track {
    overflow: auto;
    position: relative;
}

.container {
    position: absolute;
}

* {
  -webkit-box-sizing: border-box;
     -moz-box-sizing: border-box;
          box-sizing: border-box;
}

/* inherit height from window */
html, body { height: 100%; }

body {
  font-family: 'Open Sans Condensed', sans-serif;
}



.container {
  background: #000;
  border: 2px solid #333;
  border-width: 2px 0 0 2px;
  height: 604px;
}

/* clear fix */
.container:after {
  content: '';
  display: block;
  clear: both;
}

.tile {
    float: left;
    width: 200px;
    height: 200px;
    background: #fff;
    border: 2px solid #333;
    border-width: 0 2px 2px 0;
    overflow: hidden;
    position: relative;
    &-content {
        color: #fff;
        background: rgba(0, 0, 0, 0.7);
        padding: 10px;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        opacity: 0;
        transition: opacity 0.8s linear;
        &:hover {
            opacity: 1;
        }
    }
    &-emoji {
        width: 16px;
        height: 16px;
    }
    &-image {
        position: absolute;
        width: 100%;
        height: 100%;
        object-fit: cover;
    }
    &--double {
        width: 400px;
        height: 400px;
    }
}
```

### JavaScript

We use `Stackla.loadJS` to load the three required libraries. In the example below, I have randomly chosen maximum four tiles to append the `isDouble` attributes which optimizes the visualization.

```
Stackla.loadJS([
    'https://rawgit.com/desandro/jquery-bridget/master/jquery-bridget.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.2/isotope.pkgd.js',
    'https://rawgit.com/metafizzy/isotope-masonry-horizontal/master/masonry-horizontal.js'
]).then(function () {
    Stackla.loadTilesByFilter(render);
});

function render(tiles) {
    var specialTile;
    _.times(4, function () {
        specialTile = _.sample(tiles);
        specialTile.isDouble = true;
    });

    Stackla.render({tiles: tiles});

    $('.container').isotope({
        layoutMode: 'masonryHorizontal',
        itemSelector: '.tile',
        masonryHorizontal: {
            rowHeight: 100
        }
    });

    Stackla.postMessage('resize', {height: 610});
}
```

## Live Demo!

That's it! It's time for you to create a brand new widget to amaze your users!!

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```


# Bind your own Events

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/typescript-api-guides/bind-your-own-events).
{% endhint %}

* [Introduction](#introduction)
* [Creating your Custom Event](#custom-events)

## Introduction

Nosto's UGC Global Widgets API empowers users to feed their favorite Analytics tools with useful interaction information from their UGC Widgets based on common user behavior.

These common events relate to items such as Expanding a Tile, Loading more Content, or Clicking on a Shopspot link, and are based on the areas our users have wanted to track the most.

Now whilst these ‘Events’ cover the most common areas a client may wish to track, there are always scenarios where you need to track the less common areas and thus need to be able to create your events.

This guide provides a few examples as to how a user can create their custom events.

[Back to Top](#top)

## Creating your Custom Event

Now for this guide, we are going to focus on tracking the Left / Right arrows that are available on the Carousel Widget on the In-Line tile.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/4vjwbwmWaPU5e3qYHzhL/screen1.png)

In this use case, we want to understand whether or not people are clicking on these buttons to see either new content or previous content.

Now as these buttons aren’t offered as part of Nosto's UGC standard [Global Widgets Event API](/ugc/api-docs/javascript/widgets/reference#parent-page), we are going to create our Events by binding a click function to the CSS classes associated with these buttons (`.track-controller.prev` & `.track-controller.next`)

To do this, we would look to implement code similar to the below in the Custom Javascript section on the In-Line tile:

```

window.setTimeout(function (){
	$('.track-controller.prev').on('click', function () {
		parent.postMessage('inlinePrevClicked', "*");
    });
	$('.track-controller.next').on('click', function () {
		parent.postMessage('inlineNextClicked', "*");
    });
}, 1000);
```

As you will see in the code above, we are defining the CSS class (`.track-controller.prev`), and the function (ie. `Click`) and as well we are posting a message to our parent page (`postMessage>`).

By posting to our parent page, we can both test whether or not our custom events are working in the console, but can then consider sending these insights to an Analytics platform like Google Analytics or Adobe Analytics.

Now naturally, despite the fact we are posting a message to our parent page, unless our parent page is listening to it, it won’t achieve much, so we will need to add something on this page to allow us to test.

The easiest way to do this is to add the below code to the Custom JS on the Expanded Tile (which loads on the parent page versus in an iFrame like the In-Line Tile).

```

window.addEventListener("message", function (e) {
	if (e.data === 'inlinePrevClicked') {
		console.log('Click Previous');
	} else if (e.data === 'inlineNextClicked') {
		console.log('Click Next');
	}
}, false);
```

In the above code, we are looking for the messages we’ve sent (defined as `inlinePrevClicked` and `inlineNextClicked`) and then have defined what we want to push to our Console log.

Once added we should be able to Save our Widget and see the events in our Console log on a browser like Google Chrome.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Lg9ighwkAr5KLiG37Jnl/screen2.png)

Great, so now that we’ve done that, why don’t we also create a custom event to track the Left / Right arrows on the Expanded Tile?

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ks8LRJNkssMZaaBstz57/screen3.png)

Now naturally as we are updating the Expanded Tile, we don’t need to specify any listening events, which means we can just define the CSS class, and the function and then add a log message in the console.

As such, we can look to add something similar to the below to our Custom JS section on the Expanded Tile:

```

$tackla(document).on('click', '.stacklapopup-arrow-left', function () {
	console.log('Click Expanded Previous');
});
$tackla(document).on('click', '.stacklapopup-arrow-right', function () {
	console.log('Click Expanded Next');
});
```

Once added, we can then simply save and test.

[Back to Top](#top)


# Creating a Grid Widget from Waterfall

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

* [Overview](#overview)
* [Step 1 - Widget Settings](#step-1)
* [Step 2 - Understanding the Waterfall widget](#step-2)
* [Step 3 - Custom CSS](#step-3)
* [Step 4 - Custom JavaScript](#step-4)
* [Step 5 - Refinement](#step-5)
* [Result - A Custom Grid Widget](#result)

## Overview

If you need specfic widget interactions, but you are keen on using either our Grid widget or Masonry widget (which do not support all Nosto's UGC interactions), there is now a way to do so! In this guide, we will show you how to create a Grid widget by customizing a Waterfall widget. *One thing to keep in mind with this custom widget: it will function differently than our standard Grid widgets, which are built to fill a specific space. Due to the Waterfall's "Load More" functionality, this widget will expand in height upon loading more content.*

## Step 1 - Settings

Once you create your new Waterfall widget, you'll need to set it up properly to ensure everything works with the customizations.

* The **Selected Filter** is the most important setting among them as it involves the data that you can fetch from Stackla.
* We can use the **Max Tile Width** setting to mimic the Grid/Masonary's sizing options (small/medium/large).
* **Margins** can be set to your liking, however, **10px** will match the OOB Grid widget's settings.

In order to more closely resemble the Grid widgets, we will avoid enabling any **Inline Tile** options *(don't worry, these can all be enabled on the Expanded Tile)*. !\[]\(<https://web-assets.stackla.com/zachdrago.stackla.comhttp://sdp-media.dev/images/customimages/2017-04/1492567097/screen-shot-1.png> =300x)

## Step 2 - Understanding the Waterfall widget

Before jumping into the customizations, we first need to understand how the Waterfall widget is built. This will make it much easier to customize with our code.

### Widget Layout

The layout below demonstrates the wrappers of our Widget tiles. ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/rVAyUsgN7w5mQaBRzNrA/waterfall-layout-structure.png) Check the complete Widget structure from our [Waterfall Guide](/ugc/guides/onsite-widgets/styling-waterfall-widget#layout-structure)

### Tile Layout

The Waterfall widget is mostly composed by tiles - as such it is much easier to customize the Waterfall widget when you are familiar with it's structure. ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/DUEyiLaZQ8nJ0QS3Aphn/waterfall-tile-structure.png) ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/rt3U677jzn9BEaS8vW3C/screen-shot.png) *The diagram above only shows until the 4th level. Check the complete Tile structure from our* [*Waterfall Guide*](/ugc/guides/onsite-widgets/styling-waterfall-widget#tile-structure)

### Code Editors

In this guide we will be using the custom code editors to modify both your CSS and JavaScript. The CSS editor supports LESS CSS pre-processor. That means you can use both traditional CSS syntax or the better LESS syntax.

Additionally, you will want to **fork** the code from the top-right corner of each editor pane. This loads the boilerplate code from the Nosto's UGC codebase copy & paste from our Developer Portal. ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dasVWDxdtekT0mvupoPY/screen-shot-2.png)

## Step 3 - Coding for JavaScript

Let's begin with the Custom JavaScript. From the **onCompleteJsonToHtml** callback, we can target the DOM elements of the tile, and easily manipulate them.

**Step 1**

We will first use this to create a new element to hold all of our hover tile data. We'll then prepend this to the **tile-content**. Additionally, we will reposition the **tile-source** to the bottom of the tile-content.

```
onCompleteJsonToHtml: function ($tile, tileData) {
    // FIND THE TILE-CONTENT ELEMENT
    // AND PREPEND OUR NEW HOVER ELEMENT CALLED TILE-HOVER
    var $content = $tile.find('.tile-content');
    $content.prepend('<div class="tile-hover"></div>');

    // REPOSITION TILE-SOURCE TO BOTTOM OF TILE-CONTNENT
    $content.append($tile.find('.tile-source'));

    return $tile;
},
```

*You will not yet see any visible changes .*

**Step 2**

Next we will move the **tile-caption** into our new tile-hover element. We will also create and append a new **tile-user** element to hold the post's user handle.

```
onCompleteJsonToHtml: function ($tile, tileData) {
    // FIND THE TILE-CONTENT ELEMENT
    // AND PREPEND OUR NEW HOVER ELEMENT CALLED TILE-HOVER
    var $content = $tile.find('.tile-content');
    $content.prepend('<div class="tile-hover"></div>');

    // REPOSITION TILE-SOURCE TO BOTTOM OF TILE-CONTENT
    $content.append($tile.find('.tile-source'));

    // FIND TILE-HOVER ELEMENT
    // AND MOVE TILE-CAPTION INTO IT
    var $hover = $tile.find('.tile-hover');
    $hover.append($tile.find('.tile-caption'));

    // CREATE AND APPEND A NEW TILE-USER ELEMENT
    // MOVE POST'S USER INFO TO TILE-USER ELEMENT
    $hover.append('<div class="tile-user"></div>');
    $tile.find('.tile-user').append($tile.find('.tile-user-bottom'));

    return $tile;
},
```

Let's go ahead and click "Preview" to see these changes. You will notice that the tile-caption and the tile-user info is now above the tile-image. This is ok, as we will be using CSS to hide and style this later. ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/bVutReSOtHfZfT84Y5vM/screen-shot-4.png)

**Step 3**

Finally, we will remove all of the leftover tile elements that don't need to be displayed: \* Tile Avatar \* Tile Belt \* Tile Tags \* Tile Interactions \* Twitter Buttons

```
onCompleteJsonToHtml: function ($tile, tileData) {

    // FIND THE TILE-CONTENT ELEMENT
    // AND PREPEND OUR NEW HOVER ELEMENT CALLED TILE-HOVER
    var $content = $tile.find('.tile-content');
    $content.prepend('<div class="tile-hover"></div>');

    // REPOSITION TILE-SOURCE TO BOTTOM OF TILE-CONTNENT
    $content.append($tile.find('.tile-source'));

    // FIND TILE-HOVER ELEMENT
    // AND MOVE TILE-CAPTION INTO IT
    var $hover = $tile.find('.tile-hover');
    $hover.append($tile.find('.tile-caption'));

    // CREATE AND APPEND A NEW TILE-USER ELEMENT
    // MOVE POST'S USER INFO TO TILE-USER ELEMENT
    $hover.append('<div class="tile-user"></div>');
    $tile.find('.tile-user').append($tile.find('.tile-user-bottom'));

    // REMOVE ALL LEFTOVER ELEMENTS THAT AREN'T NEEDED
    $tile.find('.tile-timestamp').remove();
    $tile.find('.tile-belt').remove();
    $tile.find('.tile-avatar-wrapper').remove();
    $tile.find('.tile-twitter-intent').remove();
    $tile.find('.tile-interactions').remove();
    $tile.find('.tile-tags').remove();

    return $tile;
},
```

Let’s go ahead and click “Preview” to see these changes one more time. You will notice that the leftover elements have been removed.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/bO1bNyYi6IneDi1T6P01/screen-shot-5.png)

**Step 4**

The last thing that we need to do, is resize the tiles so they appear as perfect squares.

From the **onCalculateDimensions** callback we will need to add the following JavaScript:

```
onCalculateDimensions: function ($tile) {
    var tileWidth = $tile.width();
    $tile.height(tileWidth);
},
```

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/1WeFVor5nkCBSGR4GFwA/screen-shot-7.png)

We now have all of the tile elements in place for our CSS. Let's Save your work up to now, and move on to the CSS.

## Step 4 - Coding for CSS

All of the CSS rules reside within the iframe, so you don't need to worry about any conflicts with your page. *NOTE: You can make use of the **@import** directive to import the external CSS stylesheets you need, etc.*

**Step 1**

Let's begin our CSS customizations by forking the CSS boilerplate, and adding our new tile-hover and tile-user elements to it, as well as a few others to help mimic the Grid widgets: \* tile-hover \* tile-caption links \* tile-user \* tile-user-bottom

**New CSS boilerplate**

```
// Tips: You can use @import rule to load the CSS files you need
// @import url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css);
// @import url(https://fonts.googleapis.com/css?family=Open+Sans);

.tile {

    // Content
    &-content {

    }

    // Hover
    &-hover {

    }

    // Image
    &-image-wrapper {

    }

    // Video
    &-video-wrapper {

    }

    // Avatar
    &-avatar-wrapper {

    }
    &-avatar-link {

    }
    &-avatar-placeholder {

    }
    &-avatar-image {

    }

    // Belt (Source + User + Timestamp)
    &-belt {

    }

    // Title
    &-title {

    }

    // Caption
    &-caption,
    &-html {

    }

    // Caption Links (remove hover affect from links)
    &-caption a:hover {

    }

    // Source
    &-source {

    }

    // User
    &-user {

    }

    &-user-bottom {

    }

    // Timestamp
    &-timestamp {

    }

    // Interactions (Like/Dislike/Vote/Sharing)
    &-interactions {

    }

    // Tags
    &-tags {

    }

    // Twitter Intention
    &-twitter-intent {

    }
}
```

**Step 2**

Next we will add the necessary styles to our adjusted elements (this will replace the current boilerplate code, so feel free to copy & paste the whole thing).

```
// Tips: You can use @import rule to load the CSS files you need
// @import url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css);
// @import url(https://fonts.googleapis.com/css?family=Open+Sans);

.tile {
    border: none;

    // Content
    &-content {
        height: 100%;
        margin: 0;
    }

    // Hover
    &-hover {
        opacity: 0;
        transition: opacity 1s;
        background: rgba(0, 0, 0, .7);
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 0;
        z-index: 10;
        padding: 15px;
    }

    // Image
    &-image-wrapper {
        height: 100% !important;
        overflow: hidden;
    }

    // Video
    &-video-wrapper {

    }

    // Avatar
    &-avatar-wrapper {

    }
    &-avatar-link {

    }
    &-avatar-placeholder {

    }
    &-avatar-image {

    }

    // Belt (Source + User + Timestamp)
    &-belt {

    }

    // Title
    &-title {

    }

    // Caption
    &-caption,
    &-html {
        height: 172px;
        line-height: 1.5;
        position: absolute;
        bottom: 50px;
        top: 0;
        left: 0;
        right: 0;
        display: -webkit-box;
        -webkit-line-clamp: 8;
        -webkit-box-orient: vertical;
        overflow: hidden;
        text-overflow: ellipsis;
        padding: 15px;
    }

    // Caption Links
    // REMOVE HOVE EFFECT FROM LINKS WITHIN CAPTION
    &-caption a:hover {
        text-decoration: none;
        color: #ffffff;
    }

    // Source
    &-source {
        color: #ffffff;
        z-index: 100;
        bottom: 16px;
        font-size: 16px;
        left: 15px;
        overflow: hidden;
        position: absolute;
        right: 15px;
        text-overflow: ellipsis;
        vertical-align: middle;
        white-space: nowrap;
        line-height: initial;
        margin-right: 0;
        width: initial;
    }

    // User
    &-user {
        bottom: 15px;
        font-size: 16px;
        left: 15px;
        overflow: hidden;
        position: absolute;
        right: 15px;
        text-overflow: ellipsis;
        vertical-align: middle;
        white-space: nowrap;
    }

    &-user-bottom {
        display: inline-block;
        width: auto;
        white-space: nowrap;
        margin-left: 22px;
    }

    // Timestamp
    &-timestamp {

    }

    // Interactions (Like/Dislike/Vote/Sharing)
    &-interactions {

    }

    // Tags
    &-tags {

    }

    // Twitter Intention
    &-twitter-intent {

    }
}
```

**Step 3**

Add the CSS to enable our tile hover effect. *This can be added to the very top or very bottom of the custom CSS; outside of the current boilerplate.*

```
.tile:hover {
    .tile-hover {
        opacity: 1;
    }
}
```

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/HoVIshowrjYSy7iHbNL0/screen-shot-8.png)

Save your changes, and close the code editors!

## Step 5 - Refinement

Now that we have all of the custom code in place, and our Grid widget built, you will want to refine some of the widget settings to better fit your needs. \* **Settings > Max Tile Width:** Using this setting, you can adjust the tile sizes to better resemble the Grid's settings (small, medium, large). \* **Appearance > Tiles:** Here you will want to adjust the text to fit your tile size. \* *UGC Text:* \* Color: #ffffff \* Text Size: 16 \* *Username* \* Color: #ffffff \* Text Size: 13 \* *User Handle* \* Color: #ffffff \* Text Size: 13 \* *Link Color* \* Color: #ffffff

## Result - A Grid Widget

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ILWdqg0fyQO7x15MDRN4/screen-shot-9.png)

That's it! Get your code and embed your Grid Widget! [(Live demo in JS Bin)](http://output.jsbin.com/mocaroh).


# Create a Q\&A Widget using Data Templates

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

* [Introduction](#introduction)
* [Build the Data Template](#build-the-template)
* [Setup In-Line Tile](#setup-inline-tile)
* [Setup Expanded Tile](#setup-expanded-tile)
* [Next Steps](#conclusion)

## Introduction

Nosto's UGC offers the ability for clients to append their own Custom Data to any Tile or Tag on the Platform, allowing for clients to enrich the content on their Stack with additional data.

This custom data, which is then appended to the Tile or Tag, can be used through Nosto's UGC outputs including Widgets, Event Screens and the Nosto's UGC API.

In this guide we are going to create a simple Question & Answer (Q\&A) Widget, allowing Moderators to add, using Data Templates, answers to questions which have been submitted.

[Back to Top](#top)

## Build the Data Template

First step in the process is to build a Data Template. For the purpose of this guide, we are going to create a simple, one field template for Tiles, where we can add an 'answer' to a question we've aggregated into our Stack.

To find out how to create a Data Template for Tiles, we recommend following this [guide](/ugc/guides/data-templates/creating-data-template) first.

For this example, our **Schema** and **Options** will be similar to the below:

**Schema:**

```
{
    "type":"object",
    "properties": {
        "response": {
            "type":"string"
        }
    }
}
```

**Options:**

```
{
    "fields": {
        "response": {
            "type":"textarea",
            "label": "Answer"
        }
    }
}
```

Once we have built out the template, we can start populating our template via the Curate Content section of your Stack. To do this, simply pick a Tile within your Curate Content screen, click on the Action Button and from the overflow menu your Data Template should be available.

*Note: It is recommended that you provide an answer for at least one tile before customising your Widget*

[Back to Top](#top)

## Setup In-Line Tile

Once we've setup our template, we can now start to customise our Widgets so that the answers will render on both the In-Line and Expanded Tile.

For this guide we are going to customise a Waterfall Widget.

To begin, we will need to append the below Custom Javascript to the Widget. This will add both an 'Answer' title to the base of the Tile, plus the relevant response we specified in the data template. It will also only render a Tile, if it has a Data Template response in it, meaning if this information is empty, the Tile won't appear on the Widget, even if it is published.

**Custom Javascript:**

```
onCompleteJsonToHtml: function ($tile, tileData) {
		$tile.find('.tile-caption').append("<br/><br/>Answer:");
		if (tileData && tileData.custom_fields.response) {			
			var response = tileData.custom_fields.response;
			$tile.find('.tile-caption').append('<br/>'+response+'<br/><br/>');
		} 
		console.log('[onCompleteJsonToHtml]', $tile, tileData);
		return $tile;
    },
```

Once added and saved, it should render similar to the below on the In-Line Tile:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Ys3Yl8t4g8QaUuzuobGD/Screen-Shot-2018-03-26-at-9.52.05-am.png)

[Back to Top](#top)

## Setup Expanded Tile

Next step is adding these responses to the Expanded Tile. Again, like on the In-Line tile, we are going to append an 'Answer' value to the base of the Expanded Tile, and then add the relevant response from the Data Template below.

**Custom Javascript:**

```
.on('expandedTileOpen', function (e, data) {
		var response = data.tileData.custom_fields.response;
		var $p = $tackla('<p></p>').html(response.replace(/docs/(\r|\n)/g, '<br />'));
        	console.log('response', $p, $tackla('.stacklapopup-caption').length);
		$tackla('.stacklapopup-caption').append("<br/><br/>Answer:");
		$tackla('.stacklapopup-caption').append($p);
    })
```

Once added and saved, it should render similar to the below on the Expanded Tile:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/QkghYTfVSqnVRFWb65NM/Screen-Shot-2018-03-26-at-9.55.27-am.png)

[Back to Top](#top)

## Next Steps

From here, we can look to either style our data template content to make it stand out from the ingested Social Content, or we can look to include more values / insights.

[Back to Top](#top)


# Displaying a Widget in a Mobile App

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

In this guide we explore the general concept of displaying a Nosto's UGC Widget into within an Android and iOS Mobile Application. This will enable you to render social media content inside of your app without a lot of hassle.

This guide focuses on the simplest and effective way of displaying the Widget through the use of WebViews.

## Key Concepts

### Parent Page

The page which will host the embedded UGC Widget and will be referenced by the relevant mobile app web view.

### Android Studio

Android Studio is an integrated development environment (IDE) for developing on the Android platform.

### XCode

XCode is an integrated development environment (IDE) for developing on the iOS platform.

## Requirements

To ensure our widgets function correctly, they must have access to the DOM. You can enable DOM Storage in your web view by following the instructions at this [link](https://developer.android.com/reference/android/webkit/WebSettings#setDomStorageEnabled\(boolean\))

## The Fun Part

In this guide we will do the following:

* Configure your Widget Parent Page
* Configure WebView on an iOS Mobile App via XCode
* Configure WebView on an Android Mobile App via Android Studio

### Configure your Widget Parent Page

Your Parent Page will be the hosted page that will contain your Embedded UGC Widget.

For the purposes of this guide, it is assumed that either a Fluid Horizontal or Fluid Vertical Widget will be used.

In order to ensure your Widget renders correctly when referenced from your Mobile Application it is important that you include the following META information in your page header.

```
<head>
    <meta name="apple-mobile-web-app-capable" content="YES"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui"/>
    <meta name="HandheldFriendly" content="true"/>
</head>
```

This information will tell the WebView the scale of the page, that it has been optimised for handheld devices and in the case of iOS that it is Apple Mobile App Capable.

The second step is ensure that there is no unnecessary padding or white space around your Widget. To remove this, please include the following CSS on your parent page:

```
<style>
    body {
        padding: 0;
        margin: 0;
    }
    body,
    .stacklahfw,
    .stacklahfw iframe {
        max-width: 100%;
    }
</style>
```

This CSS will remove the default padding that exists on all HTML pages, as well renders the UGC Widget at 100% the page width.

### Add WebView to your iOS Mobile App

For the purpose of this guide, we will create a brand new iOS Mobile Application on XCode.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/j96fzhG0jyJZr4p56PLf/image07.png)

Create a New Application with the following attributes:

* Template: Single View Application
* Application Name: WebView
* Language: Objective-C
* Devices: Universal
* Storyboards: True

And select where you would like to save the application files on your computer.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/lKFIPOCfTz9D7qf6ys4D/image00.png)

Once setup you will notice on there left there will be a series of pre-generated files. These files will be called AppDelegate, ViewController and Main.Storyboard.

Both AppDelegate and ViewController will have a .h and .m version of the file.

For the purpose of app building, .h is the header file (the file that tells the app what to do), and .m is the implementation file (the file that does what the header told it to do).

#### Add the WebView to the Storyboard

The first step is adding the WebView to the Main.storyboard file. Upon selecting the file, you will be presented with a screen similar to below:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/g9oj8zAKqWcifOMtWRM9/image04.png)

You will also be presented with an updated object list (located on the right hand side).

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/R8N9GFRV2XTZo9WyrDZZ/image03.png)

From this list find WebView and drag onto your Controller Canvas.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/bca8ntpuaXuSwJjMqVYl/image02.png)

This will open up the View Controller Scene within XCode. From here, make sure `View > Web View` is selected.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/F1RY1P77yfj0C367Eawf/image06.png)

This will allow you to tweak the settings for the WebView within your Mobile Application.

Make sure within your settings you have ‘Scale Page to Fit’ selected.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/AMy4pX0xxaRkfIxUO6kq/image01.png)

#### Update ViewController.m

Next step is updating `ViewController.m`. Select the file from the left hand side. From the code that loads up, paste the following under the value `#import "ViewController.h"`.

```
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.frame];
    webView.scalesPageToFit = YES;
    NSString *urlAddress = @"URL of a web page which has a UGC Widget";
    NSURL *url = [NSURL URLWithString:urlAddress];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [webView loadRequest:request];
    webView.contentMode = UIViewContentModeScaleAspectFit;
    webView.scalesPageToFit = YES;
    webView.autoresizingMask=(UIViewAutoresizingFlexibleHeight |UIViewAutoresizingFlexibleWidth);
    [self.view addSubview:webView];
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end
```

The section highlighted in yellow is where you will need to specify the URL to the page hosting your Widget.

Once done, save and build your Mobile Application.

### Add WebView to your Android Mobile App

For the purpose of this guide, we will create a brand new Android Mobile Application on Android Tools.

From Android Tools, select New Project and fill out the following attributes:

* Application Name: WebView Application
* Target Android Devices: Phone & Tablet
* Minimum SDK: API 15: Android 4.0.3 (IceCreamSandwich)
* Mobile Activity: Blank Activity
* Company Domain: Stackla

And select where you would like the application saved on your Computer.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/bZ8ZXqCHNIBiAJCihpW2/image05.png)

Once setup you will notice on there left there will be a series of pre-generated files. The more important files to any Android App are:

* **src/main/java**. Android Java source code.
* **src/main/res**. Resources used by the native application.
* **src/main/res/drawable-type**. Image resources used by the native application.
* **src/main/res/layout**. XML layout files that define the structure of UI components.
* **src/main/res/values**. Dimensions, strings, and other values that you might not want to hard-code in your application.
* **src/main/AndroidManifest.xml**. The manifest file defines what's included in the application: activities, permissions, themes, and so on.

#### Add the WebView to the Main Activity Layout

Open the file `res/layout/activity_main.xml` and add the following code to the base of the document.

```
<WebView
        android:id="@+id/activity_main_webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
```

#### Update Main Activity Java Source Code:

Open the file `java/stackla/webviewapplication/MainActivity.java` and add the following highlighted code to the document.

```
package stackla.webviewapplication;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebSettings;
import android.webkit.WebView;


public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView mWebView = (WebView) findViewById(R.id.activity_main_webview);

        WebSettings webSettings = mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        mWebView.setInitialScale(1);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setLoadWithOverviewMode(true);
        mWebView.getSettings().setUseWideViewPort(true);
        mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
        mWebView.setScrollbarFadingEnabled(false);

        mWebView.loadUrl("URL to Widget Parent Page");
    }

    // ... rest of the code
}
```

This code ensures that the standard Android WebSettings and WebView kit are imported.

* `import android.webkit.WebSettings;`
* `import android.webkit.WebView;`

Whilst this code determines the rendering of the app within the Web View.

* `WebView mWebView = (WebView) findViewById(R.id.activity_main_webview);`
* `WebSettings webSettings = mWebView.getSettings();`
* `webSettings.setJavaScriptEnabled(true);`
* `mWebView.setInitialScale(1);`
* `mWebView.getSettings().setJavaScriptEnabled(true);`
* `mWebView.getSettings().setLoadWithOverviewMode(true);`
* `mWebView.getSettings().setUseWideViewPort(true);`
* `mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);`
* `mWebView.setScrollbarFadingEnabled(false);`

This final line of code tells the Mobile Application what URL to load:

* `mWebView.loadUrl("URL to Widget Parent Page");`

#### Update Android Manifest

Open the file /manifests/AndroidManifest.xml and add the following line of code right after the parameter `<application>`.

```
<uses-permission android:name="android.permission.INTERNET" />
```

#### Update Build Tool Version

If you see error "Cannot resolve symbol R", you might need to downgrade the build tool version.

Please open the file `build.gradle` and change `buildToolsVersion` to `"21.0.1"`

Once done, save and build your Mobile Application.

## Best Practices for Web Apps

It is important to have a valid DOCTYPE and viewport in the parent page to prevent unexpected behaviours from different browsers. The article below listed some web apps best practices. Please ensure the web app follows the best practices at all times.

[Best Practices for Web Apps](http://developer.android.com/guide/webapps/best-practices.html)

## Summary and Next Steps

This example can be further expanded with the following activities:

* Customise Widget to suit the look and feel of your current app
* Integrate the above to switch Filters or Widgets within the Web Views


# Dynamically Specify Products to Display in Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/widget-creation-guides/dynamically-specify-products-to-display-in-widget).
{% endhint %}

## Overview

Nosto's UGC Social Commerce product is a very powerful resource to an eCommerce site's developer's toolkit. Not only can Nosto's UGC provide direct linking between social media content and a product (or even SKU), those same relationships can be leveraged by the eCommerce CMS or page to specify the content that should be surfaced. By modifying the embed code to specify the products, categories or brands, it is a great complement to the Filter associated with a Widget.

This guide will explore how to filter the widget to be displayed in a Product, Category or Brand pages by only showing content related to those pages.

## Key concepts

### Dynamic Filtering

Dynamic Filtering unlocks special features of Nosto's UGC, outlined in this guide. You will need to have this feature enabled to complete this guide.

### Social Commerce

The Social Commerce product unlocks special features of Nosto's UGC to create Product Tags and other behaviour outline in this guide.

### Product Tags

Like other Tag types in Nosto's UGC, they can be attached to a Tile, in either a 1-to-1 or 1-to-many fashion.

### Widget

Widgets that are embedded for Stacks with Dynamic Filtering enabled have the ability to have their filters augmented with specific Tags.

### Filter

Nosto's UGC Filters drive the content being displayed in the Widget. Dynamic Filtering features allow Tags to be specified to overwrite the Tags chosen for the Filter.

### Example Application

In this guide we will not be creating an application, but really just an example snippet being modified by an imaginary eCommerce system. This translates to following the pattern of a view being passed a variable containing data about the products or product groups we are interested in and the rendering of the widget.

We are assuming that the Stack already has content in it that has been tagged with the relevant Product Tags and that each Product Tag has a Category and/or Brand associated to it.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Create a Filter
2. Create a Widget
3. Review relevant tags
4. Prepare embed code for Product page
5. Prepare embed code for Category page
6. Prepare embed code for Brand page

### Create a Filter

Creating a Filter in Nosto's UGC is super easy and outlined in [this Knowledgebase article](http://help.nosto.com/en/articles/5793303-how-to-create-a-filter). Assuming that we would like to only showcase product images, select the following options:

* Name it "Products"
* Set it to sort by "Latest"
* We only want pictures, so we set the Image Type to "Image"

The rest of the options can be left to default.

### Create a Widget

We will be using one Widget that will be customised via the eCommerce system's view. For this reason we just need one Widget and will augment its embed code to specify the product, category or brand to be used.

Create a Waterfall Widget and set its filter to "Products". Save and observe the preview with your content.

### Review relevant Tags

To demonstrate the use of Product Tags, we assume that there are already 2 product tags that we will use for tagging with the following details:

1. **Tag of type:** Product, **ID:** 101, **Name:** "Blue T-Shirt", **URL:** "<http://example.com/product/blue-t-shirt>", **External Product ID:** "13456743", **Category:** \["T-Shirt"], **Brand:** Nike
2. **Tag of type:** Product, **ID:** 102, **Name:** "Black T-Shirt", **URL:** "<http://example.com/product/black-t-shirt>", **External Product ID:** "17456941", **Category:** \["T-Shirt"], **Brand:** Patagonia

To view these details, enter the Admin Portal > Engage > Commerce.

We are also assuming that content has been previously tagged with them.

### Prepare embed code for Product Page

There are many different ways in which eCommerce systems generate product pages and product listings. We are assuming a very simple CMS pattern that has one generic view for a product page in this case for the product "Blue T-Shirt"

When deploying the embed code for a product page, products can be specified in two ways:

1. To use their Visual UGC IDs: 101, 102 and 103; or
2. To use their internal (external to Visual UGC) ID, prefixed with "ext:" which are: "ext:13456743", "ext:17456941"

In the embed code the products are specified by adding a `data-tags` parameter to the code and outputting the products as a comma separated list.

```
<div id="stackla-widget">
    <!-- BEGIN: Your widget code -->
    <div class='stacklafw' data-tags='ext:13456743' data-id='1234' data-hash='aabbccddeeff' data-ct='' data-alias='stackla-developer.stackla.com' data-ttl="30" ></div>
    <script type='text/javascript'>
        (function (d, id) {
            if (d.getElementById(id)) return;
            var t = d.createElement('script');
            t.type = 'text/javascript';
            t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
            t.id = id;
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
        }(document, 'stacklafw-js'));
    </script>
    <!-- END: Your widget code -->
</div>
```

Specifying the data string above as `ext:13456743` would result in the same output if the products were specified using the Visual UGC product tag id `101.`Visual UGC will interpret the `ext:` protocol as specifying the External Product ID and anything else as a Visual UGC Tag ID.

**Note about specifying Tags:**

You may specify up to 10 Tags and the Tags that you specify will overwrite any specified for the Filter. Further you can specify a combination of Product and Content Tags.

### Prepare embed code for Category Page

There are many different ways in which eCommerce systems generate category pages. We are assuming a very simple CMS pattern that has one generic view for a category page in this case for the category "T-Shirt"

When deploying the embed code for a category page, you will need to specify the category as saved in the product tag

In the embed code the categories are specified by adding a `data-categories` parameter to the code and outputting the categories as a comma separated list.

```html
<div id="stackla-widget">
<!-- BEGIN: Your widget code -->
<div class='stacklafw' **_data-categories='T-Shirt'_** data-id='1234' data-hash='aabbccddeeff' data-ct='' data-alias='stackla-developer.stackla.com' data-ttl="30" ></div>
<script type='text/javascript'>
(function (d, id){
if (d.getElementById(id)) return;
var t = d.createElement('script');
t.type = 'text/javascript';
t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
t.id = id;
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
}(document, 'stacklafw-js'));

</script>
<!-- END: Your widget code -->
</div>
```

Using this dynamic filter will result in the widget only showing content that was tagged with products that match the "T-Shirt" category.

**Note about specifying Categories:**

You may specify up to 10 Categories.

### Prepare embed code for Brand Page

There are many different ways in which eCommerce systems generate brand pages. We are assuming a very simple CMS pattern that has one generic view for a category page in this case for the category "Nike"

When deploying the embed code for a brand page, you will need to specify the brand as saved in the product tag

In the embed code the categories are specified by adding a `data-brand` parameter to the code and outputting the categories as a comma separated list.

```html
<div id="stackla-widget">
    <!-- BEGIN: Your widget code -->
    <div class='stacklafw' data-brand='Nike' data-id='1234' data-hash='aabbccddeeff' data-ct='' data-alias='stackla-developer.stackla.com' data-ttl="30" ></div>
    <script type='text/javascript'>
        (function (d, id) {
            if (d.getElementById(id)) return;
            var t = d.createElement('script');
            t.type = 'text/javascript';
            t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
            t.id = id;
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
        }(document, 'stacklafw-js'));
    </script>
    <!-- END: Your widget code -->
</div>
```

Using this dynamic filter will result in the widget only showing content that was tagged with products that match the "Nike" brand.

**Note about specifying Brands:**

You may specify up to 10 Brands.


# Dynamically specify what Tile to display in a Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/widget-creation-guides/dynamically-specify-tile-to-display-in-widget).
{% endhint %}

* [Introduction](#introduction)
* [Key Concepts](#key-concepts)
* [The Fun Part](#fun-part)
* [Creating a Widget](#widget)
* [Find a Tile ID](#tile-id)
* [Widget code for reference](#embed-code)
* [End Result](#end-result)

## Introduction

Through Nosto's UGC Dynamic Filtering functionality, customers have the ability to customise the outputs of their Widgets based upon their relevant use-case by overwriting the Filter and/or the Tags associated with that Filter to generate a more relevant output.

Now whilst the ability to change what content is shown at a Filter or Tag level is incredibly useful for clients attempting to generate relevant outputs for their eCommerce site or generate a personalised display via their DMP, there will of course be times where a client needs to get more granular and specific around what content they wish to display on their outputs.

This is where Nosto's UGC ability to specify exactly which Content Tiles are displayed in a Widget can become extremely useful.

This guide will explore a simple use-case of where a client wishes to display a single Tile in their Widget.

[Back to Top](#top)

## Key Concepts

### Dynamic Filtering

Dynamic Filtering unlocks special features of Nosto'sNosto's UGC, outlined in this guide. You will need to have this feature enabled to complete this guide.

### Widgets

Widgets that are embedded for Stacks with Dynamic Filtering enabled have the ability to overwrite what Tiles are rendered in their Widgets.

### Tile ID

The Tile ID is a unique 24 character alphanumeric unique ID generated by Nosto's UGC for every pieces of content aggregated onto the Platform.

[Back to Top](#top)

## The Fun Part

In this example we will need to do the following:

1. Creating a Widget
2. Finding a Tile ID
3. Updating the Widget Embed Code

[Back to Top](#top)

## Creating the Widget

Similar to our guide on [Dynamically Specifying Products to Display in a Widget](/ugc/guides/onsite-widgets/dynamically-specify-products-to-display-in-widget) we will only need to create one Widget for this scenario, and will simply augment its embed code to specify which Tile(s) to display.

As such, simply create a Waterfall Widget, leave its filters to ‘Latest’ and Save. The preview should show you a Widget with a series of Tiles in it.

[Back to Top](#top)

## Find a Tile ID

There are a number of ways in which you can find a Tile’s Tile ID. Ranging from using the REST API, to Webhooks to simply viewing the Tile data in the Curate Content section of Stackla.

For this guide, we are just going to collect it manually via the Curate Content section of Stackla.

To source, simply find the tile you wish to get the ID for, click on the overflow menu and select ‘Data’.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/IGxOZcHNnoWdRGABnMLf/screen1-1.png)

This will load up the Tile Data and we can grab the ID from the Data modal. In the below example, the ID of the tile is `599e0c3de09ca7e44bf290f1`

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/0H5px9uBGbLW76NHJiBG/screen2-1.png)

[Back to Top](#top)

## Update the Widget Embed Code

As we mentioned earlier, we will be using a default Waterfall Widget, leveraging the latest filter for this example. As such, we will be presented with embed code similar to the below:

```html
<div class="stackla-widget" data-alias="<stackname>.stackla.com" data-hash="<hash>" data-id="<id>" data-ttl="30" style="width: 100%; overflow: hidden;"></div>
<script type="text/javascript">
(function (d, id) {
    var t, el = d.scripts[d.scripts.length - 1].previousElementSibling;
    if (el) el.dataset.initTimestamp = (new Date()).getTime();
    if (d.getElementById(id)) return;
    t = d.createElement('script');
    t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
    t.id = id;
    (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t);
}(document, 'stackla-widget-js'));
</script>
```

Now to change what content is displayed in the Widget, all we need to do is add the parameter data-tile-id to the DIV section of the embed code and then include the Tile ID(s) that we wish to render.

For example, to showcase the one tile which we collected earlier, we would add `data-tile-id=”599e0c3de09ca7e44bf290f1”`, and of course if we wanted to showcase two Tiles we would simply add a comma between the Tile IDs.

[Back to Top](#top)

## End Result

Rendered below is the Nosto's UGC Waterfall Widget rendering the single Tile as listed in this guide:

```html
<script type="text/javascript">(function (d, id) { var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; if (el) el.dataset.initTimestamp = (new Date()).getTime(); if (d.getElementById(id)) return; t = d.createElement('script'); t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```


# How to add a title / subtitle to a widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides/how-to-add-a-title-sub-title-to-a-widget).
{% endhint %}

## Pre-Requisites

* If you are not familiar with customizing widgets using CSS & Javascript, we suggest you start [here](https://developer.stackla.com/guides/styling-widget-expanded-tile/) first as some of those details will come in handy.

## Overview

Nosto's UGC offers the ability to create advanced customizations to your widgets to match your brand needs.

In this guide, we are going explain how you can add a Title & Sub-Title directly to your widgets to ensure if a widget hides in your website because it doesn't have enough content to be displayed the entire header hides with it.

Please note that this customization is currently not supported for Story widgets.

## Getting Started

Different widget templates require slightly different Javascript to add a Header section to them. Below you can find the different examples for each template type.

### **Waterfall & Carousel Widgets:**

#### Create a Widget

Create a Waterfall or Carousel Widget and leave all the settings as per default.

#### Add a Title & Sub-Title

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
var widgetTitle = 'Get Inspired!';
var widgetSubTitle = 'See what others have to share about #mybrand';

$.extend(Callbacks.prototype, {
  onCompleteRenderTiles: function($addedTiles, addedData) {

    if (!document.querySelector("span.ugc-title")) {
      var widgetHeader = document.createElement("span");
      var widgetHeaderWrapper = document.createElement("div");
      widgetHeader.innerHTML = widgetTitle;
      widgetHeader.classList.add('ugc-title');
      widgetHeaderWrapper.classList.add('ugc-headline');
      widgetHeaderWrapper.insertBefore(widgetHeader, widgetHeaderWrapper.firstChild);
      var bodyElement = document.body;
      bodyElement.insertBefore(widgetHeaderWrapper, bodyElement.firstChild);
    }
    var subTitleWrapper = document.createElement("div");
    var subTitle = document.createElement("div");
    subTitle.innerHTML = widgetSubTitle;
    subTitleWrapper.classList.add("ugc-widget-subtitle");
    subTitleWrapper.insertBefore(subTitle, subTitleWrapper.firstChild);
    widgetHeaderWrapper.after(subTitleWrapper);
  }
});
```

* Then click Save.

#### Style your Title & Sub-Title

Customise the style of your Title & Sub-Title using the CSS section of the Inline Title Custom Code Editor.

#### Sample

The following is an example of a waterfall widget with custom Title & Sub-Title:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/mVM64TguPeZwxmEp4dPE/Screen-Shot-2023-01-19-at-2.32.42-pm.png)

### **Nightfall, Quadrant, Grid, Slider, Masonry and Direct Uploader Widgets:**

#### Create a Widget

Create a Nightfall, Quadrant, Grid, Slider, Masonry or Direct Uploader Widget and leave all the settings as per default.

#### Add a Title & Sub-Title

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
$(document).on('widget:ready', function(e, instance) {

  //====================== // Customisable Strings //====================== 
  var widgetTitle = 'Get Inspired'; 
  var widgetSubTitle = 'See what others have to share about #mybrand';
  
  instance.on('render', function(e, $appendRoot, listData) {
    if (!document.querySelector("span.ugc-title")) {
      var widgetHeader = document.createElement("span");
      var widgetHeaderWrapper = document.createElement("div");
      widgetHeader.innerHTML = widgetTitle;
      widgetHeader.classList.add('ugc-title');
      widgetHeaderWrapper.classList.add('ugc-headline');
      widgetHeaderWrapper.insertBefore(widgetHeader, widgetHeaderWrapper.firstChild);
      var bodyElement = document.body;
      bodyElement.insertBefore(widgetHeaderWrapper, bodyElement.firstChild);
      var subTitleWrapper = document.createElement("div");
      var subTitle = document.createElement("div");
      subTitle.innerHTML = widgetSubTitle;
      subTitleWrapper.classList.add("ugc-widget-subtitle");
      subTitleWrapper.insertBefore(subTitle, subTitleWrapper.firstChild);
      widgetHeaderWrapper.after(subTitleWrapper);
    }
  });
});
```

* Then click Save

#### Style your Title & Sub-Title

Customise the style of your Title & Sub-Title using the CSS section of the Inline Title Custom Code Editor.

#### Sample

The following is an example of a Grid widget with custom Title & Sub-Title:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/lv262tY3nHg7pIHMuU0H/Screen-Shot-2023-01-19-at-3.06.25-pm.png)

### **Blank Canvas Widgets:**

#### Create a Widget

Create a Blank Canvas Widget and leave all the settings as per default.

#### Add a Title & Sub-Title

* Open the Custom Code editor and and under the Javascript section add the following code:

```
Stackla.loadTilesByFilter(function(tiles) {
  Stackla.render({
    tiles: tiles
  }); 
  
  //====================== // Customisable Strings //====================== 
  var widgetTitle = 'Get Inspired'; var widgetSubTitle = 'See what others have to share about #mybrand';
  var widgetHeader = document.createElement("span");
  var widgetHeaderWrapper = document.createElement("div");
  
  widgetHeader.innerHTML = widgetTitle;
  widgetHeader.classList.add('ugc-title');
  widgetHeaderWrapper.classList.add('ugc-headline');
  widgetHeaderWrapper.insertBefore(widgetHeader, widgetHeaderWrapper.firstChild);
  var bodyElement = document.body;
  bodyElement.insertBefore(widgetHeaderWrapper, bodyElement.firstChild);
  var subTitleWrapper = document.createElement("div");
  var subTitle = document.createElement("div");
  subTitle.innerHTML = widgetSubTitle;
  subTitleWrapper.classList.add("ugc-widget-subtitle");
  subTitleWrapper.insertBefore(subTitle, subTitleWrapper.firstChild);
  widgetHeaderWrapper.after(subTitleWrapper);
});
```

* Then click Save

#### Style your Title & Sub-Title

Customise the style of your Title & Sub-Title using the CSS section of the Custom Code Editor.


# How to localize the load more button on widgets

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/typescript-api-guides/how-to-localise-the-load-more-button-on-widgets).
{% endhint %}

## Pre-Requisites

* If you are not familiar with customizing widgets using CSS & Javascript, we suggest you start [here](https://developer.stackla.com/guides/styling-widget-expanded-tile/) first as some of those details will come in handy.

## Overview

Nosto's UGC offers the ability to create advanced customizations to your widgets to match your brand needs.

In this guide, we are going explain how you can localize the Load More button when you use the same widgets in different websites/regions.

Please note that this customization is currently not supported for Story widgets.

## Getting Started

#### Create a Widget

Create a new  Widget that supports the Load More button and leave all the settings as per default.

#### Localize the Load More button

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
var searchParams = new URLSearchParams(window.location.search);
var tagGroup = searchParams.get("tag_group");
Strings.load_more = 'Expand';

if (tagGroup && tagGroup === 'AU') {
Strings.load_more = 'Expand More';
}
if (tagGroup && tagGroup === 'FR') {
Strings.load_more = 'Charger Plus';
}
if (tagGroup && tagGroup === 'PT') {
Strings.load_more = 'Continuar';
}
```

* In the code above, update the tagGroup to match the different Product Feed Group Names for your account. You can find those by going to **UGC Settings > Product Feeds**.
* Then click **Save**.

####


# How to overlay existing Google Map with the UGC Map Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

* [Overview](#overview)
* [Creating a Map Widget](#creating-your-first-map-widget)
* [Changing the look and feel](#changing-look-and-feel)
* [Overlaying the map on your existing Google Map](#overlaying-the-map-on-your-existing-google-map)
* [Final Demo](#final-demo)

## Overview

Map widget provides a visualisation of where content is happening.

In this tutorial, we will show you how to create a Map Widget and overlay it on top of your existing Google Map.

## Creating your first Map widget

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/AupbhzNjj22f0RWsnmPW/create-new-widget.png)

Once the Maps plugin is installed, you will see a new widget type in the widget creation page.

Simply choose Map and click **Create Widget**

And don't forget to name your widget.

### Connecting to a content widget

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/JiDqGaUuzJkdRImsJwXQ/change-display-type.png)

By default, Map is simply a map that shows where the content is created. However, you can connect the Map widget with one of our content widgets, e.g. a Waterfall widget. To do so, change the display type from \[Map only] to \[Map + Feed] then select a content widget to connect with.

Once the widgets are connected, the content widget gets notified when a user clicks on the pin on the Map widget.

### Previewing the connected widgets

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/nIJqjvkkpqKtqFOKBvrH/map-on-jsbin.png)

To view the connected widgets, save your current settings first then go to the Embed Code tab. If you chose to use an existing widget as content widget, you might have already got the widget embed code running in your page. If that's the case, simply choose \[Map Only] and copy and paste the embed code to your page. If the content isn't in your page yet, then choose \[Map + Content Widget] and copy and paste the embed code to your page.

## Changing the look and feel

### Choosing a different layer provider

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/wabFpJNi7Z6xTw9Cuhxt/choose-provider.png)

The Map widget uses [Leaflet](https://leafletjs.com) as map rendering service and currently supports various map layer providers. The current out-of-box providers and Open Street Maps, Carto DB, Mapzen and Google Maps.

For example, instead of using the default layer, you can use the Dark Matter theme from CartoDB.

### Fine tuning look and feel

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/uNTlJb5oKsQXD5s1niYj/provider-type-settings.png)

After a provider is selected, you can change the look and feel such as pin colour or map color. Different map providers might have slightly different options.

### Previewing the modified map

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ZGUXaAm5joNlKGlwiBVV/changed-to-dark-matters.png)

The map layer has changed to CartoDB Dark Matters and the pin colour is also changed.

## Overlaying the map on your existing Google Map

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/hCc3aFOniFX4xDwMGiC3/overlay-on-google-map.png)

If you have already got a Google Map running on your site, you can also make the map feature overlay on top of your existing Google Map.

Below is the sample code to

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>JS Bin</title>
</head>
<body>
<!-- Google Map element -->
<div id="map" style="height: 500px"></div>

<!-- Change the inline style to hide the map widget -->
<div class="stackla-widget" data-id="10811" data-hash="579ab61508113" data-ct=""
     data-alias="georgezhao.stackla.com" data-ttl="30" style="width: 100%; overflow: hidden; height: 500px; display: none;"></div>
<script type="text/javascript">
    (function (d, id) {
        if (d.getElementById(id)) return;
        var t = d.createElement('script');
        t.type = 'text/javascript';
        t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
        t.id = id;
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
    }(document, 'stackla-widget-js'));
</script>
<div class="stackla-widget" data-id="10243" data-hash="577b624a5b11d" data-ct=""
     data-alias="georgezhao.stackla.com" data-ttl="30" style="width: 100%; overflow: hidden;"></div>
<script type="text/javascript">
    (function (d, id) {
        if (d.getElementById(id)) return;
        var t = d.createElement('script');
        t.type = 'text/javascript';
        t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
        t.id = id;
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
    }(document, 'stackla-widget-js'));
</script>

<script>
    // Custom code to overlay Map widget with Google Map
    function init() {
        if (!window.Stackla) {
            // wait until the Nosto's UGC object is ready
            setTimeout(init, 100);
            return;
        }
        initMap();
    }

    function initMap() {
        var coordinateSydney = {lat: -33.8568, lng: 151.2153};
        var map = new google.maps.Map(document.getElementById('map'), {zoom: 11, center: coordinateSydney});

        var options = {
            /**
             * @param {HTMLElement} dom
             * @param statistics
             * @returns {*}
             */
            decoratePin: function (dom, statistics) {
                // you can access the following statistics before a pin is rendered
                var avgLocation = statistics.avg_location;
                var quantity = statistics.quantity;
                var geohash = statistics.geohash;

                // you can modify or add listeners before a pin is rendered
                return dom;
            }
            // please see the table below for all available options
        };

        // Nosto's UGC code
        var mapWidget = Stackla.WidgetManager.getWidgetInstances()[0];
        mapWidget.bindGoogleMap(map, options);
    }
</script>
<script src="https://maps.googleapis.com/maps/api/js?callback=init"></script>
</body>
</html>
```

## Available Options

| Option Name | Type                                                      | Description                                                                                                                                                                          |
| ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| decoratePin | Function (pin:HTMLElement, statistics:Object):HTMLElement | A function provided by the developer to modify the pin DOM before it is rendered. It accepts the pre-generated DOM and developer can return a modified DOM.                          |
| pinOnClick  | Function (geohash:String):void                            | A function provided by the developer to override the on-click behaviour. If this function is provided, the click action on the pin will no longer trigger the Change-Geohash action. |

## Final Demo

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>

<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js')); </script>

 
<script>function init() { if (!window.Stackla) { setTimeout(init, 100); return; } initMap(); } <div></div> function initMap() { var coordinateSydney = {lat: -33.8568, lng: 151.2153}; var map = new google.maps.Map(document.getElementById('map'), {zoom: 11, center: coordinateSydney}); <div></div> // Nosto's UGC code var mapWidget = Stackla.WidgetManager.getWidgetInstances()[0]; mapWidget.bindGoogleMap(map); }</script>

<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAZmmYJnG5-kRdV95bN-JFTTeOIyV_5Op8&callback=init"></script>
```


# How to use Filter and search in a Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

When Stackla widgets are shown, often a custom menu will be built. The menu will drive the Filter-changing in the widget and often perform other functions too, not necessarily related to Stackla.

In this guide, we will build a case where we can trigger the widget filter by clicking on a menu.

## Key concepts

### Tile

In your Stack, the name Tile is given to a piece of content that has been saved and made available for moderation. It could be a Tweet, an Instagram photo or a YouTube video, amongst others. It could also be a user-generated post to your photo competition.

### Widget

A Stackla Widget will be created to demonstrate the use of the [JavaScript API](/ugc/api-docs/javascript) to switch the filters being displayed.

### Filter

Stackla Filters drive the content being displayed in the Widget, so they are a key resource used for the menu. The [REST API](/ugc/api-docs/rest) will be used to grab them from

### JavaScript API

The [JavaScript API](/ugc/api-docs/javascript) is will be used to talk to the Widget on the site and get it to switch the Filter being displayed.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Configure Filters & Tags
2. Enable 'Allow Filter Tags to be overwritten'
3. Enable 'Enable Text Search'
4. Create an application to grab the Filter data from Stackla

### Configure Filters

For this example, we will have some filters to choose from: Latest, Twitter and Instagram. Remove the other Filters, or if you're starting from scratch, create them so that they sort by Latest, with Twitter and Instagram filtering by the Twitter and Instagram networks respectively.

### Create the Example Application

To simplify the example, we will create a simple HTML page and will have clickable filters and tags listing.

### Lets jump to the geek's mode to see how it works.

Lets add the widget embed code to the page.

```
<div class="widget">
    <div class='stacklafw' data-id='9999' data-hash='abcd123efgh5678' data-ct='' data-alias='myawesomestack.stackla.com' data-ttl="30" ></div>
    <script type="text/javascript">
        (function (d, id) {
            if (d.getElementById(id)) return;
            var t = d.createElement('script');
            t.type = 'text/javascript';
            t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
            t.id = id;
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
        }(document, 'stacklafw-js'));
    </script>
</div>
```

To see this example work, we will need to add this. This *widgetId* will be use to target selected widget.

```
<script> window.widgetId = 9999; // manually added </script>
```

### Filters

Let's add clickable filters listing, you will notice the filter-id are attached to the listing, it will be used to do the javascript filtering.

**note:** don't forget to change the filter-id to your filter-id.

```
<nav class="filters">
    <div>
        <ul class="nav nav-pills">
            <li class="col-xs-4 col-sm-4 col-md-4 col-lg-2" data-filter="1031"><a href="#latest">Latest</a></li>
            <li class="col-xs-4 col-sm-4 col-md-4 col-lg-2" data-filter="1035"><a href="#twitter">Twitter</a></li>
            <li class="col-xs-4 col-sm-4 col-md-4 col-lg-2" data-filter="1034"><a href="#gratest">Greatest</a></li>
        </ul>
    </div>
</nav>
```

This is how we are going to drive the clickable filters listing. We call the **Stackla.WidgetManager** class to apply the selected filtering.

```
$('.filters a').click(function(){
    var filter = $(this).parent();
    $('.filters li').removeClass('active');
    filter.addClass('active');
    Stackla.WidgetManager.changeFilter(window.widgetId, filter.attr('data-filter'));
});
```

### Keyword / Full Text Search

You will need to request from [Stackla Support](http://support.stackla.com/) to enable the 'Text Search' option in your Stack. By default, this feature is off.

Let's build the keyword input form and add JavaScript to it.

```
<form id="search-form" action="#">
    <input type="text" name="keyword"> <button type="submit">search</button>
</form>
```

```
var refSearch = null;
function search(keyword) {
    if (refSearch) {
       clearTimeout(refSearch);
    }
    refSearch = setTimeout(function(){
        console.log('keyword', keyword);

        if (keyword.length > 0 && keyword.length < 3) {
            return;
        }
        StacklaFluidWidget.searchFilter(window.widgetId, keyword);
    }, 100);
}

$('#search-form').submit(function(event){
    search($(this).find('input[name="keyword"]').val());
    return false;
});
```

![Filters and Keyword search](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FH2HswJrGFYKanrUwwbJz%2Ffilters-and-search.png?alt=media\&token=3b1d7f02-2af0-499f-9a87-aceace1e6027)

## Summary and Next Steps

This was a very basic example to demonstrate some use of the Widgets Filtering on the same page. You can expand this example to:

* Build a better menu for switching
* Use dynamic generated filters for flexibility
* Use keyword validation


# How to change click on inline tile behaviour to redirect to PDP in a Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

Nosto's UGC offers the ability to create advanced customizations to your widgets to match your brand needs.

In this guide, we are going explain how you can change the click though behaviour of an inline tile to direct users to PDP.

## Getting Started

#### Create a Widget

Create a new Widget and set the click through behaviour to No clickthrough.

#### For Grid, Masonry, Nightfall, Slider and Quadrant widgets.

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
$(document).on('widget:ready', function (e, instance) {
    instance
        .on('beforeAppend', function (e, $tile, tileData, $appendRoot) {
            const productTags = tileData.tags_extended.filter(tag => tag.type === 'product' && tag.custom_url);
            if (productTags && productTags.length) {
                $tile[0].onclick = () => window.top.location.href = productTags[0].custom_url;
            }
        });
});
```

* Then click **Save**.

#### For Waterfall and Carousel widgets.

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
$.extend(Callbacks.prototype, {
    onCompleteJsonToHtml: function($tile, tileData) {
        const productTags = tileData.tags_extended.filter(tag => tag.type === 'product' && tag.custom_url);
        if (productTags && productTags.length) {
            $tile[0].onclick = () => window.top.location.href = productTags[0].custom_url;
        }

        return $tile;
    }
});
```

* Then click **Save**.

In the code above, the first product's url of each tile is used to redirect the user to the PDP page. If you want to use a different product url by product name, please change the way of how to get productTags variable by:

```
const productTags = tileData.tags_extended.filter(tag => tag.type === 'product' && tag.tag === '{productName}')
```


# Styling cross-sellers on Grid and Carousel Widgets

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

## Pre-Requisites

* If you are not familiar with customising widgets using CSS, we suggest you start [here](/ugc/guides/onsite-widgets/styling-widget-expanded-tile) first as some of those details will come in handy.
* Cross-Sellers on UGC widgets require the subscription of the [Nosto Product Recommendations](https://www.nosto.com/products/product-recommendations) Module.
* Cross-Sellers are only supported on Grid and Carousel widgets.

## Overview

When using Nosto Product Recommendations as cross-sellers in UGC widgets, you combine the power of social proof with recommendations to drive your conversion rate by increasing relevance and boost average order value.

This guide will explore how to style the layout of how cross-sellers are displayed in widgets.

## Getting Started

### Tag Content with Tags

Start by going to the Curate view, apply shopspots to 4 or 5 tiles and publish those tiles.

### Create a Filter

Creating a Filter in Nosto's UGC is really simple as outlined in [this Knowledgebase article](https://help.nosto.com/en/articles/5793303-how-do-i-create-a-filter). Assuming that we would like to only showcase content with products, select the following options:

* Name it "Cross-Sellers"
* Set it to sort by "Latest"
* Select the filter "Shopspots" and choose the "Show Shopspots" option

The rest of the options can be left to default.

### Create a Widget

Create a Grid or Carousel Widget and set its filter to "Cross-Sellers". Save and observe the preview with your content.

### Update the Widget Expanded Tile CSS

Open the Custom Code editor and click Expanded Tile. Here some examples of cross-sellers styling that you can customise:

#### Color of the ribbon (e.g. Grey):

```
.stacklapopup-products-item-image-recommendation-label {
```

#### Position of the ribbon (e.g. at the bottom):

```
.stacklapopup-products-item-image-recommendation-label {
```

#### Color, Font & Text of the Cross-Seller label (e.g. Label "Pair With", Color "white", Font "Lato, sans-serif)

```
.stacklapopup-products-item-image-recommendation-label:after {
content: "Pair with";
color: white;
font: 400 10px/18px "Lato, sans-serif";
}
```

#### Color, Font & Text of the See Recommendations label (e.g. Label "show me recommendations", Color "#2C2C2C", Font "Lato, sans-serif)

```
.stacklapopup-products-amount-over-3:after {
content: "see recommendations:";
color: #2C2C2C;
font: 400 10px/18px "Lato, sans-serif";
}
```

## Sample

The following is an example of a grid widget with customised cross-sellers:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/D8M4cagSNvLrPmpnUYPU/Screen-Shot-2022-05-26-at-3.11.23-pm.png)


# How to Load External JS and CSS into Widgets

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/typescript-api-guides/loading-external-js-and-css-libraries-to-widgets).
{% endhint %}

* [Overview](#overview)
* [Loading the External CSS](#loading-the-external-css)
* [Loading the External JavaScript](#loading-the-external-javascript)
  * [jQuery.getScript](#jquery-getscript)
  * [Stackla.loadJS](#stackla-loadjs)
* [Tips](#tips)
  * [1. Getting Stackla.loadJS Example with a Click](#getting-stackla-loadjs-example-with-a-click)
  * [2. Available Hosts for the Wanted External Files](#available-hosts-for-the-wanted-external-files)

## Overview

This article will demonstrate how to load external JavaScript and CSS into your widgets using our custom code editors.

## Loading the External CSS

Loading the external CSS is very straight-forward. You just need to use the CSS `@import`.

### Example

You can write the following code in your Custom CSS code editor to load [Bootstrap](http://getbootstrap.com) and [Font Awesome](http://fontawesome.io) libraries.

```
@import url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.min.css);
@import url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css);
```

[Back to Top](#top)

## Loading the External JavaScript

Loading the external JavaScript is a bit more tricky because it involves the **dependency order**.

### jQuery.getScript

You can use the `$.getScript` method to load external JavaScript files one after the other in your preferred order. The following example loads **jquery-bridget**, **isotope**, and **masonry-horizontal** one by one as the order is critical.

#### Example

```
$.getScript('https://rawgit.com/desandro/jquery-bridget/master/jquery-bridget.js')
    .then($.getScript('https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.2/isotope.pkgd.js'))
    .then($.getScript('https://rawgit.com/metafizzy/isotope-masonry-horizontal/master/masonry-horizontal.js'))
    .then(function () {
        // Do something after the external files are loaded
    });
```

### Stackla.loadJS

The disadvantage of the previous code snippet is that **the preceding JavaScript blocks the next one from loading**. (The jquery-bridget blocks the isotope and the isotope blocks masonry-horizontal.) If one of them is a slow asset, your widget rendering could be slower than what you expect.

Thus, we introduce the `Stackla.loadJS` method to fix this issue. It **starts to load all JavaScript files altogether** but **executes them in order**. The following is the example.

#### Example

```
Stackla.loadJS([
    'https://rawgit.com/desandro/jquery-bridget/master/jquery-bridget.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.2/isotope.pkgd.js',
    'https://rawgit.com/metafizzy/isotope-masonry-horizontal/master/masonry-horizontal.js'
]).then(function() {
    // Do something after the external files are loaded
});
```

[Back to Top](#top)

## Tips

### 1. Getting Stackla.loadJS Example with a Click

When you click the **Fork** links in the Code Editor, the generated code will include the usage of `Stackla.loadJS` example.

![Fork](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Q7Tx3ijrgvpHdZyoQJKn/fork-1.png)

### 2. Available Hosts for the Wanted External Files

You may be wondering what the public URLs are for your wanted libraries. You may use some of the following resources.

* [**cdnjs**](https://cdnjs.com): All of the popular libraries such as Bootstrap, Font Awesome and Animated.css are all available on cdnjs.com.
* [**RawGit**](http://rawgit.com): RawGit serves raw files directly from GitHub with proper Content-Type headers. That means, you can place your libraries as the GitHub repositories and load it with the rawgit!
* **Host your files with Nosto's UGC**: This feature is not available unless requested, to enable the "CSS/JS Upload" in the Media Library you will need to contact [Visual UGC Support](http://support.stackla.com).

[Back to Top](#top)

Please note - not all of our code editors allow you to load external JS and CSS. This is currently only available for our Widget editors.


# Profiling Widget Performance

* [Overview](#overview)
* [Facts](#facts)
* [Benefits](#benefits)
* [Steps](#steps)
* [Recommendation](#recommendation)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

After you embed the Nosto's UGC widget into your web page, you may want to evaluate the widget load time. The article provides a step-by-step tutorial to help you to write a profiler.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/pmKee0QeW0bn88Mn7mTv/sample-profiler.png)

## Facts

* The Nosto's UGC Widget loads asynchronously. Unlike the default behavior of `<script src="..."></script>`, it doesn't block your page from loading.
* Widget loading is heavily affected by other page assets which load before the embed code (`fluid-embed.js`). When you find that the Nosto's UGC widget loads slowly, check if there are slow assets which are requested before the embed code on your page. This could be any slow images, CSS, or JavaScript files before the embed code.

## Benefits

If you are able to report these profiling results back to us when logging a support ticket on performance issues, it will make it easier for us to investigate and assist you with this issue.

## Steps

### 1. Paste the following code before the tag

We have to get the initial time from the page and compare it with the different benchmarks coming from multiple events.

```
(function () {
    window.Stackla = window.Stackla || {};

    // All the benchmarks
    Stackla.profilerRecords = [];

    // Method for adding a benchmark
    Stackla.addProfilerItem = function (label, finish, start) {
        var duration;
        if (start) {
            duration = (finish - start) / 1000 + ' secs';
            Stackla.profilerRecords.push([
                '<span class="title">' + label + '</span>',
                '<span class="content">' + duration + '</span>'
            ].join(''));
        } else {
            Stackla.profilerRecords.push(label);
        }
    };

    // Page Init At
    Stackla.pageInit = new Date();
    Stackla.addProfilerItem('Page Init: ' + Stackla.pageInit);

    // Page Load
    window.addEventListener('load', function () {
        Stackla.pageLoad = new Date();
        Stackla.addProfilerItem('Page Load: ', Stackla.pageLoad, Stackla.pageInit);
    });

    // DOMReady
    document.addEventListener('DOMContentLoaded', function () {
        Stackla.pageReady = new Date();
        Stackla.addProfilerItem('Page Ready: ', Stackla.pageReady, Stackla.pageInit);
    });

    // Widget Finish Load
    window.addEventListener('message', function (e) {
        var data = JSON.parse(e.data),
            listEl = document.createElement('ul');

        if (data.action !== 'initComplete') {
            return;
        }

        Stackla.widgetLoad = new Date();
        Stackla.addProfilerItem('Widget Finish Load: ', Stackla.widgetLoad, Stackla.pageInit);

        Stackla.profilerRecords.forEach(function (record, i) {
            var itemEl = document.createElement('li');
            itemEl.innerHTML = record;
            listEl.appendChild(itemEl);
        });

        listEl.setAttribute('id', 'stackla-widget-profiler');

        // Output the Profiler
        document.body.appendChild(listEl);
    });
}());
```

### 2. Update your Embed Code

The following is the general embed code with additional 6 lines. Please follow the comments.

```html
<div class="stackla-widget" data-id="XXXX" data-hash="XXXX" data-ct=""
     data-alias="XXXX.stackla.com" data-ttl="30" style="width: 100%; overflow: hidden;"></div>
<script type='text/javascript'>
(function (d, id) {
     var t, el = d.scripts[d.scripts.length - 1].previousElementSibling;
    el.dataset.initTimestamp = (new Date()).getTime();
    if (d.getElementById(id)) return;

     // ADD THE FOLLOWING 2 LINES HERE
    Stackla.widgetBeforeLoad = new Date();
    Stackla.addProfilerItem('Widget Embed Start: ', Stackla.widgetBeforeLoad, Stackla.pageInit);

    t = d.createElement('script');
    t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
    t.id = id;

    // ADD THE FOLLOWING 4 LINES HERE
    t.addEventListener('load', function () {
        Stackla.widgetLoad = new Date();
        Stackla.addProfilerItem('Widget Embed Finish: ', Stackla.widgetLoad, Stackla.widgetBeforeLoad);
    });

    (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t);
}(document, 'stackla-widget-js'));
</script>
```

### Step 3 - Add the style sheet

Let's make the profiler more readable by adding some rules to your CSS stylesheet.

```
/* #stackla-widget-profiler (start) */
#stackla-widget-profiler {
    right: 0;
    font-family: 'Avenir Next', 'Open Sans', 'PT Sans', 'DejaVu Sans', 'Bitstream Vera Sans', Verdana, sans-serif;
    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
    position: fixed;
    top: 0;
    width: 400px;
    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
    z-index: 2;
    background: #eee;
    border: solid 1px #999;
    font-size: 13px;
    font-family; 'OpenSans'
    -moz-border-radius-bottomright: 5px;
    -webkit-border-bottom-right-radius: 5px;
    border-bottom-right-radius: 5px;
    text-align: left;
}
#stackla-widget-profiler {
    margin: 0;
    padding: 0;
}
#stackla-widget-profiler li {
    border-bottom: solid 1px #999;
    padding: 3px;
    margin: 0;
    list-style-type: none;
}
#stackla-widget-profiler li:last-child {
    border-bottom: none;
}
#stackla-widget-profiler li .content {
    color: #a00;
    text-align: right;
}
/* #stackla-widget-profiler (end) */
```

## Useful trick - Make use of JSBin

Ideally your profiling page would sit on your own web server. However, you can choose to use an online JavaScript prototyping tool, such as JSBin to assist you if this is not possible. By copy-pasting all the server-generated HTML source code with the following line after the tag, you can get a similar test page.

```
<base href="YOUR-BASE-URL-FOR-ALL-ASSETS"/>
```

The examples of the following section are using JSBin.

## Recommendation

We recommend you make profilers for the following scenarios.

* A blank page which only has the Nosto's UGC Widget ([Example](http://jsbin.com/zivuzosuva/1/edit?html,css,output)).
* Your current page without the Nosto's UGC Widget ([Example](http://jsbin.com/nolosegiqa/edit?html,css,output)).
* Your current page with the Nosto's UGC Widget ([Example](http://jsbin.com/qeqojefese/edit?html,css,output)).

This way you can get several different benchmarks to identify the problem.


# Re-targeting with Widgets and Facebook Pixel

* [Overview](#overview)
* [Generate your Pixel](#generate-pixel)
* [Decide what to send to Facebook](#decide-what-to-send)
* [Track a View Content Event](#viewcontent)
* [Track a Custom Event](#customevent)
* [Conclusion](#conclusion)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

Behaviour Re-Targeting (also known as behavioural re-marketing, or simply, retargeting) is an important and regularly use component of most brand's online advertising strategy. Through Behaviour Re-Targeting, brands will target specific advertising to users based upon their previous Internet actions, such as going to a particular page, clicking on a certain link or engaging with a Nosto's UGC Widget.

In this guide, we will look at how you can use Nosto's UGC Widgets as part of your Facebook Pixel re-targeting strategy, and the types of data points you can leverage to deliver more relevant and engaging ads back on either Facebook, Facebook Messenger or Instagram.

[Back to Top](#top)

## Generate your Facebook Pixel

The first step in this process is to ensure that you have a Facebook Pixel setup. If you have not yet setup a Facebook Pixel, you will need to go into Facebook Adverts and select Pixels.

From here, Facebook will ask you to name your Pixel and then provide you with the relevant embed code for your page. Please ensure this code is included on the same page(s) you have your Nosto's UGC Widget(s) on.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/5mwunxFsbkT2Ds1vRrL7/Screen-Shot-2018-03-26-at-11.29.37-am.png)

[Back to Top](#top)

## Decide what to send to Facebook

Facebook Pixel offers 9x standard *Event Types* which a Customer can register an event against, plus offer the ability for a customer to create their own Custom Event type. The standard Event Types are:

* `ViewContent` - Register an Event when a Key page has been viewed by the End User
* `Search` - Register an Event based upon the End User using a Search prompt on a page
* `AddToCart` - Register an Event based upon the End User adding a Product to their cart
* `AddToWishList` - Register an Event based upon the End User adding a Product to their Wish List
* `InitateCheckout` - Register an Event when an End User enters the checkout flow in the purchase process
* `AddPaymentInfo` - Register an Event when an End User gets to the Payment Page in the purchase process
* `Purchase` - Register an Event when the purchase process has been completed
* `Lead` - Register an Event when a user completes a 'Sign Up' process
* `CompleteRegistration` - Register an Event when a user completes a Registration form

Now naturally some of these Event Types are better used later on in a user's journey on a website, so for the purpose of this guide, we are going to use `ViewContent`, sending information across whenever a user Expands a Tile on a Widget.

We are also going to create a Custom Event to track whenever clicks on a Product Link on our Expanded Tile.

## Track a View Content Event

Now to send information to Facebook from the Nosto's UGC Widget, we are going to leverage the [Global Widgets API](/ugc/api-docs/javascript/widgets/reference#parent-page) to instigate the Facebook Event.

To start, we will use the Global Widget API Event Type, `TileExpand`. This event type fires whenever someone clicks on an Inline Tile and thus opens the Expanded Tile. To do this, we will need to add the following code to our Nosto's UGC Widget, using the Custom Javascript section on the Expanded Tile.

```
Stackla.WidgetManager.on('tileExpand', function (e, data) {
    fbq('track', 'ViewContent');
});
```

Now this will send the below information to Facebook. Useful if you only want to track and re-target based upon a User interacting with your Widget, but also quite light on in terms of the relevant data you are sending back.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/VojUsTumsM1Xgin2fs5G/pixel1.png)

As such, we are going to expand the code to now include other attributes such as Widget ID (`data.widgetId`), Filter ID (`data.filterId`), Tile ID (`data.tileData._id.$id`), Tile Media Type (`data.tileData.media`), Tile Source Network (`data.tileData.source`) and the Tags associated with the Tiles (`data.tileData.tags`). To do this we would update the code to the below:

```
Stackla.WidgetManager.on('tileExpand', function (e, data) {
    fbq('track', 'ViewContent', {widgetid: data.widgetId, filterid: data.filterId, tileid: data.tileData._id.$id, media: data.tileData.media, source: data.tileData.source, tags: data.tileData.tags});
});
```

This would then send the following to Facebook:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/uBaAaBpFaIJdbaHDaP0b/pixel2.png)

This additional information we can then look to leverage if we want to go into the world of Custom Audience targeting.

[Back to Top](#top)

## Track a Custom Event

So we now know how to send information to Facebook when it correlates to one of their standard events, but what if we want to track something that doesn't fit the mould? This is where Facebook's Custom Event function becomes useful.

Now for this use-case we want to track whenever a user clicks on a Product listing on our Widget, and send across the relevant External Product ID (`data.productTag.ext_product_id`) to Facebook Pixel. To do this, we would simply add this code to the Custom Javascript section on the Expanded Tile within the Custom Code editor on our Widget.

```
Stackla.WidgetManager.on('productActionClick', function (e, data) {
    fbq('trackCustom', 'ShopEvent', {sku: data.productTag.ext_product_id });
});
```

If we wanted to extend this and include other data, such as Widget ID (`data.widgetId`), Filter ID (`data.filterId`), Tile ID (`data.tileData._id.$id`) and Tile Media Type (`data.tileData.media`), we could do this as well by adjusting the code as follows:

```
Stackla.WidgetManager.on('productActionClick', function (e, data) {
    fbq('trackCustom', 'ShopEvent', {widgetid: data.widgetId, filterid: data.filterId, tileid: data.tileData._id.$id, media: data.tileData.media, sku: data.productTag.ext_product_id });
});
```

[Back to Top](#top)

## Custom Audiences

Now that we are sending custom data to our Pixel, and have validated that everything is working as expected, we can now jump into the process of building our Custom Audiences using the Nosto's UGC data we have defined in our events.

To start, all we need to do is go to Facebook Adverts > Pixel > Settings and pick 'Build Custom Audience'.

From here we can set as many or as few conditions as we want and build as many different audiences as we want, similar to the example below where we are using Tag IDs.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/5WzxKggAS3P1JU9RztQd/pixel3.png)

From there, once all setup, we can start building new, more relevant and engaging ads for both Facebook and Instagram and target away.

[Back to Top](#top)

## Conclusion

The above guide focuses on just a few possible use-cases, however some other options you can consider include tracking events when users Search on a Widget (Event: `SearchCompleteRegistration` or `Lead`) or of course tracking interactions such as Votes, Shopspot clicks or so on.

[Back to Top](#top)


# Render Widget filters dynamically

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen).

**Note: This feature is unique to Classic widgets**
{% endhint %}

## Overview

When Nosto's UGC widgets are shown, often a custom menu will be built. The menu will drive the Filter-changing in the widget and often perform other functions too, not necessarily related to Stackla.

In this example we will investigate how Nosto's UGC [REST API](https://github.com/Stackla/docs/blob/master/docs/api-docs/rest/README.md) can be consumed to grab the data related to Filters from the Stack.

## Key concepts

### Widget

A Nosto's UGC Widget will be created to demonstrate the use of the [JavaScript API](/ugc/api-docs/javascript) to switch the filters being displayed.

### Filter

Nosto's UGC Filters drive the content being displayed in the Widget, so they are a key resource used for the menu. The [REST API](https://github.com/Stackla/docs/blob/master/docs/api-docs/rest/README.md) will be used to grab them from the Stack.

### REST API

The [REST API](https://github.com/Stackla/docs/blob/master/docs/api-docs/rest/README.md) is used to acquire the Filter resources from the Stack. In other words, we will use it to read which Filters the Stack has configured.

### JavaScript API

The [JavaScript API](/ugc/api-docs/javascript) is will be used to talk to the Widget on the site and get it to switch the Filter being displayed.

### Example Application

To demonstrate the reading of data and rendering of the menu we will create a new PHP application. The application will effectively consume the Filters resource from the Stack and according to its data, render a menu. The menu itself will be a simple rendering of the Filter data that we receive.

You will need to have a working web server active and Internet connectivity to <https://api.stackla.com/> from the server to be able to consume the [REST API](https://github.com/Stackla/docs/blob/master/docs/api-docs/rest/README.md).

We are also assuming that the Stack already has content in it which you will need to tag, as explained later on.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Configure the relevant Filters
2. Create an application to grab the Filter data from Nosto's UGC

### Configure Filters

For this example, we will have three filters to choose from: Latest, Twitter and Instagram. Remove the other Filters, or if you're starting from scratch create them so that they sort by Latest, with Twitter and Instagram filtering by the Twitter and Instagram networks respectively.

Because the filters themselves will give us a different range of content, we will not be using any Tags to achieve this further.

### Create the Example Application

Grabbing Filter data from Nosto's UGC is a really simple task. In fact, all it really involves is consuming a RESTful resource: filters. In other words, you will need to perform a HTTP GET operation on `https://api.stackla.com/api/filters` (with some minor mods, as outlined in the following section). The URL can be retargeted towards your specific stack, which always has an alias as a subdomain on the `stackla.com` domain. For example, stack `myawesomestack` will have an alias as <https://myawesomestack.stackla.com/>, no matter what other URLs you may use to access.

**Note:** if you choose to use `api.stackla.com` instead, you will need to pass your Stack name as a `stack` query parameter, e.g. `https://api.stackla.com/api/filters?stack=myawesomestack`

The following code will simply render a HTML page, and use some inline PHP (please don't judge too harshily) to render a menu as a HTML list.

#### Grabbing RESTfully

The following code can be used to grab the JSON data from the API. XML data can be acquired by passing the "xml" extension next to the filters resource.

**A note about** [**file\_get\_contents**](http://php.net/manual/en/function.file-get-contents.php)**:**

If the below request was to fail (4xx, 5xx and not 200), file\_get\_contents will not give you the body of the message. This will make it hard to detect or debug errors returned by the API. For this example, we will use file\_get\_contents as it's the easiest to demo, but you may instead consider using REST libraries in your PHP framework or [cURL](http://au2.php.net/manual/en/book.curl.php).

```
$requestUrl = 'https://api.stackla.com/api/filters.json?access_token=1234abcd5678efgh9012ijkl3456mnop&stack=myawesomestack';
$filtersData = file_get_contents($requestUrl);

// If data is returned, convert it into an object
if($filtersData) {
    // If data is returned, convert it into an object
    $filters = json_decode($filtersData)->data;
} else {
    // We got no data -- panic and create an empty array.
    $filters = array();
}
```

Once we have some data in the `filters` object we can loop through them and output in a menu such as a select dropdown.

```
<select onchange="navigateByFilter(this.value);">
    <?php foreach($filters as $filter): ?>
    <option value="<?=$filter->id?>"><?=$filter->name?></option>
    <?php endforeach; ?>
</select>
```

#### Changing Filters via embed code

A Widget's set Filter can be overwritten by the HTML on the embed code by using the `data-filter` attribute. In the code for the `div` tag, set the attribute with the ID of the Filter. For example, this code sets the Filter ID to be 9999.

```html
<div class='stacklafw' data-id='1234' data-filter="9999" data-hash='aabbccddeeff' data-ct='' data-alias='stackla-developer.stackla.com' data-ttl="30"></div>
<script type='text/javascript'>
    (function (d, id) {
        if (d.getElementById(id)) return;
        var t = d.createElement('script');
        t.type = 'text/javascript';
        t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
        t.id = id;
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
    }(document, 'stacklafw-js'));
</script>
```

#### Changing Filters via JavaScript

When a Fluid Widget is on the page, a `StacklaFluidWidget` object exists that can be referenced. If the widget is the only instance of the specific widget on the page, we can use the `changeFilter` method with the Widget's ID to switch the Filter by the Filter's ID.

```
var widgetId = 1234;
var filterId = 5678;
StacklaFluidWidget.changeFilter(widgetId, filterId);
```

#### site.php

This is the listing of all of the above combined into one file to serve.

```php
<?php
// This will be your API Key (see API console if you're not sure)
$apiKey = '1234abcd5678efgh9012ijkl3456mnop';

// This will be your Stack Name
$stackName = 'myawesomestack';
$stackUrl = 'https://api.stackla.com/';
$apiUri = 'api/filters';

// The REST endpoint and query URL will be at the following URL.
// Opening the full URL in a web browser should already yield a results to
// demonstrate the use. Note that Visual UGC returns JSON by default. If we
// were to require XML, we can add the extension to the $apiUri above,
// e.g. $apiUri = 'api/filters.xml'
// The final URL should be something like:
//     https://api.stackla.com/api/filters?access_token=1234abcd5678efgh9012ijkl3456mnop&stack=myawesomestack
$requestUrl = $stackUrl . $apiUri . '?access_token=' . $apiKey . '&stack=' . $stackName;

// Note about file_get_contents:
// If the below request was to fail (4xx, 5xx and not 200), file_get_contents
// will not give you the data. This will make it hard to detect errors returned
// by the API. For this example, we will use file_get_contents as it's the
// easiest to demo.
$filtersData = file_get_contents($requestUrl);

if($filtersData) {
    // If data is returned, convert it into an object
    $filters = json_decode($filtersData)->data;
} else {
    // We got no data -- panic and create an empty array.
    $filters = array();
}
?>
<!doctype html>
<html>
<head>
    <title>My Awesome Stack</title>
    <script>
        function navigateByFilter(filterId) {
            // Set the following widgetId with the correct ID of the widget being used.
            var widgetId = '1234';
            StacklaFluidWidget.changeFilter('1234', filterId);
        }
    </script>
</head>
<body>
    <h1>Welcome to the awesomest Stack on the planet!</h1>
    <p>To change between my family photos and the different locations I've been
        to, use the dropdown menu below.</p>
    <select onchange="navigateByFilter(this.value);">
        <?php foreach($filters as $filter): ?>
        <option value="<?=$filter->id?>"><?=$filter->name?></option>
        <?php endforeach; ?>
    </select>

    <div id="stackla-widget">
        <!-- BEGIN: Your widget code -->
        <!-- Paste your widget code in the area below. Keep in mind your data-id attribute -->
        <div class='stacklafw' data-id='1234' data-hash='aabbccddeeff' data-ct='' data-alias='stackla-developer.stackla.com' data-ttl="30" ></div>
        <script type='text/javascript'>
            (function (d, id) {
                if (d.getElementById(id)) return;
                var t = d.createElement('script');
                t.type = 'text/javascript';
                t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js';
                t.id = id;
                (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t);
            }(document, 'stacklafw-js'));
        </script>
        <!-- END: Your widget code -->
    </div>
</body>
</html>
```

## Summary and Next Steps

This was a very basic example to demonstrate some use of the REST API and Widgets JavaScript API on the same page. You can expand this example to:

* Build a better menu for switching
* Use the public display options or naming pattern of Filters to control whether the Filter should be available for display
* Use a naming pattern for the filters


# Styling Carousel Widget

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

* [Overview](#overview)
* [Use the Code Editor](#use-the-code-editor)
* [Layout Structure](#layout-structure)
* [Tile Structure](#tile-structure)
* [Sample](#sample)

## Overview

This article will instruct you on how to custom style a Carousel Widget using Custom CSS. If you're looking to customise a widget using our Javascript API - you can find the documentation [here](/ugc/api-docs/rest/reference/widgets).

## Use the Code Editor

You can use the Code Editor to modify both your Custom CSS and JavaScript. The CSS editor supports [LESS CSS pre-processor](http://lesscss.org). That means you can use both traditional CSS syntax or the better LESS syntax.

### Fork from Nosto's UGC

You can now also click the **fork** link at the top-right corner of each editor pane. This brings the boilerplate code from the Nosto's UGC codebase so that you don't need to write from scratch or copy & paste from our Developer Portal.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/KVbC7AC4GiP5r5AykLnn/how-custom-code.png)

## Layout Structure

The below Layout demonstrates the wrappers of our Widget tiles.

|

#### Diagram

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/NjjJD5nQcf5GgcNj4BZF/carousel-layout-structure.png)

|

#### Code

```
<!DOCTYPE html>
<html class="js no-touch small-screen-width">
<head>...</head>
<body class="cf-latest">
<div class="track ps-container">
<ul class="widget-list" id="container">
<!-- All tiles will be appeneded to here -->
</ul>
<div class="spinner-overlay">
<div class="spinner"></div>
</div>
<div class="ps-scrollbar-x-rail">
<div class="ps-scrollbar-x"></div>
</div>
<div class="ps-scrollbar-y-rail">
<div class="ps-scrollbar-y"></div>
</div>
<div class="track-controller prev">
<i class="fs fs-arrow-left2"></i>
</div>
<div class="track-controller next">
<i class="fs fs-arrow-right2"></i>
</div>
</div>
</body>
</html>
```

\| | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Possible Scenarios

* Changing the background style by setting `body` selector.
* Changing the tile container style by setting `#container` selector.
* Changing the **AJAX indicator** style by setting `.spinner-overlay` and `.spinner` selectors.
* Changing the **Scroll bars** style by setting `.ps-scrollbar*` selectors.
* Changing the **Prev/Next Nav Arrow** style by setting `.track-controller` selectors.

## Tile Structure

The Carousel widget is mostly composed by tiles - as such it is much easier to customize the Carousel widget when you are familiar with it's structure.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Rb3oV7hhCSXZQnBzZ7za/carousel-tile-structure.png" alt="" data-size="original"></p> | <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/wi7PznkjUXxwhQaZZyN7/carousel-tile-example.png" alt="" data-size="original"></p> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Code

The diagram above only shows until the 4th level. Check the complete Tile structure by referencing the following code.

```
<li class="tile">
    <div class="tile-content">
        <div class="tile-image-wrapper">
            <img class="tile-image"/>
            <div class="tile-media-loading"/>
        </div>
        <div class="tile-avatar-wrapper">
            <div class="tile-avatar-placeholder">
                <img/>
            </div>
            <a class="tile-avatar-link">
                <img class="tile-avatar-image"/>
            </a>
        </div>
        <div class="tile-belt">
            <div class="tile-source"></div>
            <div class="tile-timestamp">...</div>
            <div class="tile-user-info">
                <div class="tile-user">
                    <a class="tile-user-top">
                        <div class="tile-source social-source"></div>
                        <span class="tile-user-name">...</span>
                    </a>
                    <a class="tile-user-bottom">
                        <span class="tile-user-handle">...</span>
                    </a>
                </div>
            </div>
        </div>
        <div class="tile-details">
            <div class="tile-caption">
                <div>
                    <p>...</p>
                </div>
            </div>
            <div class="tile-interactions">
                <span class="tile-interaction-like">
                    <a class="tile-interaction-link"></a>
                </span>
                <span class="tile-interaction-dislike">
                    <a class="tile-interaction-link"></a>
                </span>
                <span class="tile-interaction-share">
                    <a class="tile-interaction-link"></a>
                </span>
                <ul class="tile-sharing-expanded">
                    <li>
                        <a class="tile-share-button facebook"></a>
                        <div class="fs fs-facebook"></div>
                    </li>
                    <li>
                        <a class="tile-share-button twitter"></a>
                        <div class="fs fs-twitter"></div>
                    </li>
                    <li>
                        <a class="tile-share-button pinterest"></a>
                        <div class="fs fs-pinterest"></div>
                    </li>
                    <li>
                        <a class="tile-share-button gplus"></a>
                        <div class="fs fs-googleplus"></div>
                    </li>
                    <li>
                        <a class="tile-share-button linkedin"></a>
                        <div class="fs fs-linkedin"></div>
                    </li>
                    <li>
                        <a class="tile-share-button email"></a>
                        <div class="fs fs-envelope"></div>
                    </li>
                    <li>
                        <a class="tile-share-close"></a>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</li>
```

## Sample

The following is an example of a customized widget.

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```

You can use the following CSS/LESS code as your Custom CSS boilerplate.

```less
@import url(https://fonts.googleapis.com/css?family=Lato);

html, body {
    background: #000;
}

body {
    padding: 0;
    margin: 5px 0;
}

.tile {
    border: none;
    border-radius: 4px;
    margin-left: 5px;
    margin-bottom: 10px;
    padding-left: 0;
    padding-right: 0;
    &-image {
        border-radius: 4px 4px 0 0;
    }
    &-avatar-wrapper {
        top: 6px;
        position: relative;
    }
    &-belt {
        background-color: #eee;
        border-radius: 0 0 4px 4px;
        margin-top: -20px;
        padding: 25px 15px;
        text-shadow: 0 1px 0 rgba(255, 255, 255, 1);
    }
    &-caption {
        border-radius: 0 0 4px 4px;
        font-family: 'Raleway', sans-serif;
        font-size: 18px !important;
        line-height: 1.5;
        padding: 0 15px;
    }
    &-details {
        .image &,
        .video & {
            height: 0;
            overflow: hidden;
            padding: 0;
            top: auto;
            bottom: 111px;
            transition: height .3s ease-in,
                        padding .4s linear,
                        opacity .6s linear;
        }
        .image:hover &,
        .video:hover & {
            padding: 20px;
            height: 306px;
        }
    }
}

.track-controller{
    color: #ffffff;
    background-color: #000000;
    border-radius: 0;
    opacity: 0.5;
    transition: opacity .3s ease;
    &:hover{
        opacity: 1;
    }
    &.prev{
        left: 0;
    }
    &.next{
        right: 0;
    }
}
```


# Styling Grid Widget

* [Overview](#overview)
* [Use the Code Editor](#use-the-code-editor)
* [Layout Structure](#layout-structure)
* [Tile Structure](#tile-structure)
* [Sample](#sample)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

## Overview

This article will instruct you on how to custom style a Grid Widget using Custom CSS. If you're looking to customise a widget using our Javascript API - you can find the documentation [here](/ugc/api-docs/rest/reference/widgets).

## Use the Code Editor

You can use the Code Editor to modify both your Custom Tile, Custom CSS and JavaScript. The CSS editor supports [LESS CSS pre-processor](http://lesscss.org). That means you can use both traditional CSS syntax or the better LESS syntax.

### Fork from Nosto's UGC

You can now also click the **fork** link at the top-right corner of each editor pane. This brings the boilerplate code from the Nosto's UGC codebase so that you don't need to write from scratch or copy & paste from our Developer Portal.

### Custom Tile

Currently the Custom Tile editor is only available in the Grid, Masonry and the Blank Canvas widgets. Unlike other widgets, you are free to change the tile HTML structure according to your need. However, you will need to learn the template syntax [Mustache.js](https://mustache.github.io). You can check which [tile properties](/ugc/api-docs/javascript/widgets/blank-canvas#helper-methods-tile-properties) are available.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/GACvdslosSHDRCofM21s/how-custom-code-1.png)

## Layout Structure

The below Layout demonstrates the wrappers of our Widget tiles.

|

#### Diagram

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/UaE0u6NncgA8msZKLbVc/layout-structure.png)

|

#### Code

```
<!DOCTYPE html>
<html>
<head>...</head>
<body class="cf-latest">
<div class="track">
<div class="container">
<!-- All tiles will be appeneded to here -->
</div>
</div>
</body>
</html>
```

\| | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Possible Scenarios

* Changing the background style by setting `body` selector.
* Changing the widget margin style by setting `.track` selector.
* Changing the tile container style by setting `.container` selector.

## Tile Structure

The Grid Widget is mostly composed of tiles - as such it is much easier to customize the Grid Widget when you are familiar with it's structure.

Below demostrates the default structure. You can change the tile structure using the custom code editor.

|

#### Code

```
<div class="tile">
<div class="tile-detail">
<div class="tile-caption">...</div>
<div class="tile-user">
<i class="tile-user-source fs fs-instagram"></i>
<a class="tile-user-link">
<span class="tile-user-handle">...</span>
</a>
</div>
</div>
<div class="tile-image"></div>
<div class="tile-source tile-source-instagram">
<i class="fs fs-instagram"></i>
</div>
</div>








Sample
The following is an example of a customized widget.
<script type="text/javascript">(function (d, id) { var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; if (d.getElementById(id)) return; t = d.createElement('script'); t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
You can use the following CSS/LESS code as your Custom CSS boilerplate.
@import url('https://fonts.googleapis.com/css?family=Lobster|Raleway:200,400');.tile {    border-radius: 8px;    -webkit-filter: grayscale(100%);    &:hover {        opacity: 1;        -webkit-filter: none;        .tile-caption {            opacity: 1;        }        .tile-detail {            background-color: rgba(0, 0, 0, 0.2);        }    }    // Overlay    &-detail {        border-radius: 8px;        background-color: transparent;        font-weight: bold;        opacity: 1;        text-shadow: 1px 0 1px rgba(0, 0, 0, 0.7);    }    // Caption    &-caption {        opacity: 0;        font-family: Raleway;        font-weight: bold;        transition: opacity 1s;    }    // User    &-user {        text-shadow: 1px 0 1px rgba(0, 0, 0, 0.7);    }    &-user-handle {        font-size: 20px;        font-family: Lobster;    }    // Source    &-user,    &-source {        font-size: 24px;    }}
```


# Styling Masonry Widget

* [Overview](#overview)
* [Use the Code Editor](#use-the-code-editor)
* [Layout Structure](#layout-structure)
* [Tile Structure](#tile-structure)
* [Sample](#sample)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).

{% endhint %}

## Overview

This article will instruct you on how to custom style a Masonry Widget using Custom CSS. If you're looking to customise a widget using our Javascript API - you can find the documentation [here](/ugc/api-docs/rest/reference/widgets).

## Use the Code Editor

You can use the Code Editor to modify both your Custom Tile, Custom CSS and JavaScript. The CSS editor supports [LESS CSS pre-processor](http://lesscss.org). That means you can use both traditional CSS syntax or the better LESS syntax.

### Fork from Nosto's UGC

You can now also click the **fork** link at the top-right corner of each editor pane. This brings the boilerplate code from the Nosto's UGC codebase so that you don't need to write from scratch or copy & paste from our Developer Portal.

### Custom Tile

Currently the Custom Tile editor is only available in the Grid, Masonry and the Blank Canvas widgets. Unlike other widgets, you are free to change the tile HTML structure according to your need. However, you will need to learn the template syntax [Mustache.js](https://mustache.github.io). You can check which [tile properties](/ugc/api-docs/javascript/widgets/blank-canvas#helper-methods-tile-properties) are available.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SokGuYRruYcq011Zc5iE/how-custom-code-1.png)

## Layout Structure

The below Layout demonstrates the wrappers of our Widget tiles.

|

#### Diagram

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/5dOR48N1mmcGGXhUFoie/layout-structure.png)

|

#### Code

```
<!DOCTYPE html>
<html>
<head>...</head>
<body class="cf-latest">
<div class="track">
<div class="container">
<!-- All tiles will be appeneded to here -->
</div>
</div>
</body>
</html>
```

\| | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Possible Scenarios

* Changing the background style by setting `body` selector.
* Changing the widget margin style by setting `.track` selector.
* Changing the tile container style by setting `.container` selector.

## Tile Structure

The Masonry Widget is mostly composed of tiles - as such it is much easier to customize the Masonry Widget when you are familiar with it's structure.

Below demostrates the default structure. You can change the tile structure using the custom code editor.

|

#### Code

```
<div class="tile">
<div class="tile-detail">
<div class="tile-caption">...</div>
<div class="tile-user">
<i class="tile-user-source fs fs-instagram"></i>
<a class="tile-user-link">
<span class="tile-user-handle">...</span>
</a>
</div>
</div>
<div class="tile-image"></div>
<div class="tile-source tile-source-instagram">
<i class="fs fs-instagram"></i>
</div>
</div>








Sample
The following is an example of a customized widget.
<script type="text/javascript">(function (d, id) { var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; if (d.getElementById(id)) return; t = d.createElement('script'); t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
You can use the following CSS/LESS code as your Custom CSS boilerplate.
@import url('https://fonts.googleapis.com/css?family=Lobster|Raleway:200,400');.tile {    border-radius: 8px;    -webkit-filter: grayscale(100%);    &:hover {        opacity: 1;        -webkit-filter: none;        .tile-caption {            opacity: 1;        }        .tile-detail {            background-color: rgba(0, 0, 0, 0.2);        }    }    // Overlay    &-detail {        border-radius: 8px;        background-color: transparent;        font-weight: bold;        opacity: 1;        text-shadow: 1px 0 1px rgba(0, 0, 0, 0.7);    }    // Caption    &-caption {        opacity: 0;        font-family: Raleway;        font-weight: bold;        transition: opacity 1s;    }    // User    &-user {        text-shadow: 1px 0 1px rgba(0, 0, 0, 0.7);    }    &-user-handle {        font-size: 20px;        font-family: Lobster;    }    // Source    &-user,    &-source {        font-size: 24px;    }}
```


# Styling Waterfall Widget

* [Overview](#overview)
* [Use the CSS Code Editor](#use-the-code-editor)
* [Layout Structure](#layout-structure)
* [Tile Structure](#tile-structure)
* [Sample](#sample)
* [Helpful Resource](#helpful-resource)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

## Overview

This article will instruct you on how to custom style a Waterfall Widget using Custom CSS. If you're looking to customise a widget using our Javascript API - you can find the documentation [here](/ugc/api-docs/rest/reference/widgets).

## Use the Code Editor

You can use the Code Editor to modify both your Custom CSS and JavaScript. The CSS editor supports [LESS CSS pre-processor](http://lesscss.org). That means you can use both traditional CSS syntax or the better LESS syntax.

### Fork from Nosto's UGC

You can now also click the **fork** link at the top-right corner of each editor pane. This brings the boilerplate code from the Nosto's UGC codebase so that you don't need to write from scratch or copy & paste from our Developer Portal.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/jxe2rlqpHKFy9Hk0baoP/how-custom-code.png)

## Layout Structure

The below Layout demonstrates the wrappers of our Widget tiles.

|

#### Diagram

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/K19PcSYY7bN31HDarHFA/waterfall-layout-structure.png)

|

#### Code

```
<!DOCTYPE html>
<html class="csstransforms csstransforms3d csstransitions js no-touch">
<head>...</head>
<body>
<div class="track">
<div id="container" class="container isotope">
<!-- All tiles will be appeneded to here -->
</div>
<a class="button js-load-more">Load more content</a>
<div class="spinner-overlay">
<div class="spinner"></div>
</div>
</div>
</body>
</html>
```

\| | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Possible Scenarios

* Changing the background style by setting `body` selector.
* Changing the tile container style by setting `#container` selector.
* Changing the **Load More Data** style by setting `.js-load-more` selector.
* Changing the **AJAX indicator** style by setting `.spinner-overlay` and `.spinner` selectors.

## Tile Structure

The Waterfall widget is mostly composed by tiles - as such it is much easier to customise the Waterfall widget when you are familiar with it's structure.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/oKpwun5SstTBed3Pg4lY/waterfall-tile-structure.png" alt="" data-size="original"></p> | <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Ih7gbYZCgpke0S4FAvTS/waterfall-tile-example.png" alt="" data-size="original"></p> |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Code

The diagram above only shows until the 4th level. Check the complete Tile structure by referencing the following code.

```
<div class="tile ">
    <div class="tile-content">
        <div class="tile-image-wrapper">
            <img class="tile-image">
            <div class="tile-media-loading">
                <span class="tile-media-loading-icon"></span>
            </div>
        </div>
        <div class="tile-avatar-wrapper">
            <div class="tile-avatar-placeholder">
                <img>
            </div>
            <a class="tile-avatar-link">
                <img class="tile-avatar-image"/>
            </a>
        </div>
        <div class="tile-belt">
            <div class="tile-source tile-source-twitter">
                <i class="fs fs-twitter"></i>
            </div>
            <div class="tile-timestamp"></div>
            <div class="tile-user-info">
                <div class="tile-user">
                    <a class="tile-user-top">
                        <span class="tile-user-name"></span>
                    </a>
                    <a class="tile-user-bottom">
                        <span class="tile-user-handle"></span>
                    </a>
                </div>
            </div>
        </div>
        <div class="tile-caption"></div>
        <div class="tile-tags">
            <a class="tile-tag"></a>
        </div>
        <div class="tile-interactions">
            <span class="tile-interaction-like">
                <a class="tile-interaction-link">
                    <i class="fs fs-thumbs-up"></i>
                </a>
                <span class="tile-interaction-count"></span>
            </span>
            <span class="tile-interaction-dislike">
                <a class="tile-interaction-link">
                    <i class="fs fs-thumbs-down"></i>
                </a>
                <span class="tile-interaction-count"></span>
            </span>
            <span class="tile-interaction-vote">
                <a class="tile-interaction-link">
                    <i class="fs fs-triangle-up"></i>
                </a>
                <span class="tile-interaction-count"></span>
            </span>
            <span class="tile-interaction-share">
                <a class="tile-interaction-link">
                    <i class="fs fs-share"></i>
                </a>
                <ul class="tile-sharing-expanded">
                    <li>
                        <a class="tile-share-button">
                            <div class="fs fs-facebook"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-button twitter">
                            <div class="fs fs-twitter"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-button pinterest">
                            <div class="fs fs-pinterest"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-button gplus">
                            <div class="fs fs-googleplus"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-button linkedin">
                            <div class="fs fs-linkedin"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-button email">
                            <div class="fs fs-envelope"></div>
                        </a>
                    </li>
                    <li>
                        <a class="tile-share-close">
                            <i class="fs fs-close"></i>
                        </a>
                    </li>
                </ul>
            </span>
        </div>
        <div class="tile-twitter-intent">
            <a class="tile-twitter-reply">
                <i class="fs fs-reply"></i>
            </a>
            <a class="tile-twitter-retweet">
                <i class="fs fs-retweet"></i>
            </a>
            <a class="tile-twitter-favorite">
                <i class="fs fs-heart"></i>
            </a>
        </div>
    </div>
</div>
```

## Sample

The following is an example of a customized widget.

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```

You can use the following CSS/LESS code as your Custom CSS boilerplate.

```
@import url(https://fonts.googleapis.com/css?family=Lato);

html, body {
    background: #000;
}

.js-load-more {
    background: rgba(51, 102, 153, 0.8);
    border-radius: 4px 4px 0 0;
    bottom: 0;
    display: inline-block;
    font-size: 12px;
    font-weight: bold;
    left: ~"calc(50% - 68px)";
    position: absolute;
    text-transform: uppercase;
    transition: background 1s linear;
    &:hover {
        background: #0198cf;
    }
}

.tile {
    border: none;
    border-radius: 4px;
    &-content {
        margin: 0;
    }
    &-image-wrapper {
        margin-bottom: -60px;
        &:after {
            background: rgba(255, 255, 255, 0.2);
            bottom: 0;
            content: '';
            left: 0;
            position: absolute;
            right: 0;
            top: 0;
            transition: background 0.3s linear;
        }
        &:hover {
            &:after {
                background: rgba(255, 255, 255, 0);
            }
        }
    }
    &-image {
        border-radius: 4px 4px 0 0;
    }
    &-avatar-wrapper {
        top: 14px;
        position: relative;
    }
    &-belt {
        background-color: #eee;
        padding: 15px;
        text-shadow: 0 1px 0 rgba(255, 255, 255, 1);
    }
    &-tags {
        padding: 0 15px;
    }
    &-caption {
        font-family: 'Raleway', sans-serif;
        font-size: 18px !important;
        line-height: 1.5;
        padding: 0 15px;
    }
    &-twitter-intent {
        padding-bottom: 15px;
    }
}
```

## Helpful Resource

You can download a layered PSD to demonstrate our widget [here](https://github.com/Stackla/docs/blob/master/docs/media/guides/assets/20/stackla-waterfall-wireframe-2016.psd.zip).


# Styling Widget Expanded Tile

* [Overview](#overview)
* [Use the CSS Code Editor](#use-the-css-code-editor)
* [Layout Structure](#layout-structure)
* [Tile Structure](#tile-structure)
* [Sample](#sample)
* [Helpful Resource](#helpful-resource)

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

## Overview

This article will instruct you on how to custom style the Expanded Tile for our Widgets using Custom CSS. If you're looking to customise a widget using our Javascript API - you can find the documentation [here](/ugc/api-docs/rest/reference/widgets).

## Use the CSS Code Editor

You can use the CSS Code Editor to modify your Widget Expanded Tile CSS. It supports [LESS CSS pre-processor](http://lesscss.org). That means you can use both traditional CSS syntax or the better LESS syntax.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/vNchaEj0bfne9Hzd1bxD/how-custom-code-2.png)

### Tips

The Expanded Tile actually exists in the parent page instead of Visual UGC's iframe. That means you can write custom CSS in your own page rather than in our Code Editor. However, we recommend that you use our editor for ease of management.

## Container Structure

The below diagram demonstrates the container of our Widget Expanded Tile. It contains the required HTML elements for the background mask and tile centerizing.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/LRj3ErssnlWSsK2EINI1/expanded-tile-layout-structure.png" alt="" data-size="original"></p> |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/4xGOn5OQgxbaius0Sw6Q/expanded-tile-layout-sample.png" alt="" data-size="original"></p>   |

### Possible Scenarios

* Changing the mask style by setting `.stacklapopup-bg` selector.
* Changing the maximum width of Expanded Tile by setting `.stacklapopup-content-wrap` selector.
* Changing the close button style by setting `.stacklapopup-close` selector.

### Tips

#### Reset

You can break the Expanded Tile styling when you set rules for common tags such as `div` or `span`. To avoid this, you can manually apply the following CSS rules:

```
.stacklapopup-wrap * {
    margin: 0;
    padding: 0;
}
```

#### z-index

You may have some overlays with z-index values in your page. To display these correctly you may need to overwrite the default z-index values.

```
.stacklapopup-bg {
    z-index: 1042; /* Default value */
}
.stacklapopup-wrap {
    z-index: 1043; /* Default value */
}
```

## Tile Structure

The Expanded Tile has two different layouts which are **Portrait** and **Landscape**.

### Portrait

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/UNfWFpuEhya0lYIhI4Kf/expanded-tile-portrait-structure.png" alt="" data-size="original"></p> | <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Bk6UJxxwDF1vWOQeKrsP/expanded-tile-portrait-sample.png" alt="" data-size="original"></p> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Landscape

Landscape has more HTML elements than Portrait for layout purpose. They are `.stacklapopup-panel`, `.stacklapopup-panel-left` and `.stacklapopup-panel-right`. All the inner blocks share the same structure as the Portrait layout.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/uJN7CwnT5XAQtnpqxK3Y/expanded-tile-landscape-structure.png" alt="" data-size="original"></p> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/IyMqXEegwofXlMvqGIzg/expanded-tile-landscape-sample.png" alt="" data-size="original"></p>   |

### Code

The diagrams above only show up to the 4th level. Check the complete Expanded Tile structure by referencing the following code.

```
<div class="stacklapopup-wrap stacklapopup-close-btn-in stacklapopup-auto-cursor stacklapopup-ready">
    <div class="stacklapopup-container stacklapopup-s-ready stacklapopup-inline-holder">
        <div class="stacklapopup-content">
            <div class="stacklapopup-content-wrap instagram image stacklapopup-horizontal stacklapopup-landscape stacklapopup-has-author stacklapopup-has-timestamp stacklapopup-has-caption stacklapopup-has-interactions">
                <div class="stacklapopup-panel">
                    <div class="stacklapopup-panel-left">
                        <div class="stacklapopup-image-wrapper stacklapopup-js-zoom">
                            <div class="stacklapopup-image-wrapper-inner">
                                <div class="stacklapopup-image-loading">
                                   <img class="stacklapopup-lazyload">
                                </div>
                            </div>
                        </div>
                    </div><!-- .stacklapopup-panel-left (end) -->
                    <div class="stacklapopup-panel-right">
                        <div class="stacklapopup-user-info-wrapper">
                            <div class="stacklapopup-user-info">
                                <span class="stacklapopup-avatar-wrapper">
                                    <a class="stacklapopup-avatar-link">
                                       <img class="stacklapopup-avatar-image">
                                    </a>
                                </span>
                                <span class="stacklapopup-source stacklapopup-source-instagram">
                                    <i class="fs fs-instagram"></i>
                                </span>
                                <a class="stacklapopup-user-link">
                                    <div class="stacklapopup-user-top">
                                        <span class="stacklapopup-user-name"></span>
                                    </div>
                                    <div class="stacklapopup-user-bottom">
                                        <span class="stacklapopup-user-handle"></span>
                                    </div>
                                </a>
                            </div>
                        </div>
                        <div class="stacklapopup-caption">
                            <p class="stacklapopup-caption-paragraph"></p>
                        </div>
                        <div class="stacklapopup-interactions-wrapper">
                            <div class="stacklapopup-interactions">
                                <span class="stacklapopup-interaction-like">
                                    <a class="stacklapopup-interaction-link on">
                                        <i class="fs fs-thumbs-up"></i>
                                    </a>
                                    <span class="stacklapopup-interaction-count"></span>
                                </span>
                                <span class="stacklapopup-interaction-dislike">
                                    <a class="stacklapopup-interaction-link">
                                        <i class="fs fs-thumbs-down"></i>
                                    </a>
                                    <span class="stacklapopup-interaction-count"></span>
                                </span>
                                <span class="stacklapopup-interaction-vote">
                                    <a class="stacklapopup-interaction-link">
                                        <i class="fs fs-triangle-up"></i>
                                    </a>
                                    <span class="stacklapopup-interaction-count"></span>
                                </span>
                                <span class="stacklapopup-interaction-comment ">
                                    <a class="stacklapopup-interaction-link js-stacklapopup-comment-button">
                                        <i class="fs fs-comment"></i>
                                    </a>
                                </span>
                            </div>
                        </div>
                        <div class="stacklapopup-footer">
                            <div class="stacklapopup-timestamp"></div>
                        </div>
                    </div><!-- .stacklapopup-panel-right (end) -->
                </div><!-- .stacklapopup-panel (end) -->
                <div class="stacklapopup-comments-wrap">
                    <div class="stacklapopup-comments-user clearfix">
                        <ul class="stacklapopup-comments-list"></ul>
                        <form>
                            <textarea class="stacklapopup-comments-user-textarea"></textarea>
                            <input class="stacklapopup-comments-user-input">
                            <button type="button" class="stacklapopup-comments-user-button">
                                <i class="fs fs-arrow-right6"></i>
                            </button>
                            <div class="stacklapopup-comments-user-message">
                                <i class="stacklapopup-comments-user-message-icon fs fs-success"></i>
                            </div>
                        </form>
                    </div><!-- .stacklapopup-comments-wrap (end) -->
                </div>
                <button class="stacklapopup-arrow stacklapopup-arrow-left">
                    <i class="stacklapopup-arrow-icon fs fs-arrow-left2"></i>
                </button>
                <button  class="stacklapopup-arrow stacklapopup-arrow-right">
                    <i class="stacklapopup-arrow-icon fs fs-arrow-right2"></i>
                </button>
                <button class="stacklapopup-close"></button>
            </div><!-- .stacklapopup-content-wrap (end) -->
        </div><!-- .stacklapopup-content (end) -->
        <div class="stacklapopup-preloader"></div>
    </div><!-- .stacklapopup-container (end) -->
</div><!-- .stacklapopup-wrap (end) -->
```

## Sample

The following is an example of a customized Expanded Tile.

\<br/>\<p> You can use the following CSS/LESS code as your Custom CSS boilerplate

```
@import url(https://fonts.googleapis.com/css?family=Satisfy);

.stacklapopup {
    /* Mask */
    &-bg {
        background: #fff;
        opacity: 0.5;
    }
    &-content-wrap {
        border-radius: 10px;
        border: solid 1px #ccc;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
    }
    &-user-info-wrapper {
        background: #eee;
        border-radius: 10px 10px 0 0;
        color: #333;
        display: block;
        margin-bottom: 0;
        padding: 10px;
        text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7);
    }
    &-caption {
        font-family: 'Satisfy', cursive;
        font-size: 36px;
    }
    &-tags {
        font-size: 14px;
        font-weight: bold;
    }
    &-interactions {
        margin: 0 10px;
    }
    &-footer {
        background: #eee;
        border-radius: 0 0 10px 10px;
        color: #333;
        padding: 10px;
        text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7);
    }
    &-timestamp {
        font-weight: bold;
    }
    &-preloader {
        display: none;
    }
}

/* Overwrite some special rules from Portrait layout */
.stacklapopup-portrait {
    &.stacklapopup-content-wrap {
        padding: 0;
    }
    .stacklapopup-image-wrapper,
    .stacklapopup-video-wrapper {
        margin: 0;
    }
}
```

## Helpful Resource

You can download a layered PSD to demonstrate our Expanded Tile layout [here](https://github.com/Stackla/docs/blob/master/wp-content/uploads/2017/12/stackla-lightbox-wireframe-2016.psd.zip).


# Styling Widget Shopspots

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [NextGen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides).
{% endhint %}

## Overview

This article will instruct you on customizing Shopspot style in both Tiles and Expanded Tiles (a.k.a Lightbox).

## Shopspots in Tiles

You can style Shopspots in tiles via the Custom CSS tab in Nosto Admin Portal.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/987eLrtBDcy8DKrD1Gb0/tile-shopspot-before-customization.png)

### HTML Structure

The structure is pretty simple. It uses `.tile-shopspot` as namespace.

```
<span class="tile-shopspot">
  :before
  <i class="tile-shopspot-icon"></i>
</span>
```

Be aware that our default style also makes use of the `:before` pseudo class for inner border. Remember to reset it if you don't need it.

### Sample CSS

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/VKEz8HwbWSommhB84OXu/tile-shopspot-after-customization.png)

By pasting the following code into your **Custom CSS** in Nosto'sNosto's UGC Widget setting, you can change the default style of Tile Shopspot.

```
@import 'https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css';

.tile-shopspot {
    background-color: rgba(0, 0, 0, 0.5);
    border: solid 2px #fff;
}
.tile-shopspot-icon {
    content: 'f219' !important;
    background: none;
    font-family: FontAwesome;
    font-size: 20px;
    line-height: 36px;
}
```

## Shopspots in Expanded Tile (a.k.a Lightbox)

Since Expanded Tile resides in your page instead of Nosto's UGC Widget iframe, you will need to put your customized stylesheet **in your own page**, not via the Nosto Admin Portal.

The following snapshot illustrates the style of default Expanded Tile.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SIv02a2tWA4y07dZTsqN/lightbox-shopspot-before-customization.png)

### HTML Structure

A Shopspot element (`.stacklapopup-shopspot`) wraps both **icon** (`.stacklapopup-shopspot-icon`) and **flyout** (`.stacklapopup-shopspot-flyout`). The following diagram shows detailed HTML structure with CSS class names.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/TTBiIewbRSXguro5hk5B/lightbox-shopspot-html-structure.png)

The following is the sample HTML source code, which has the same architecture with the above diagram.

```
<div class="stacklapopup-shopspot">
    <i class="stacklapopup-shopspot-icon"></i>
    <div class="stacklapopup-shopspot-flyout">
        <div class="stacklapopup-shopspot-flyout-image">
            <img src="...">
        </div>
        <div class="stacklapopup-shopspot-flyout-text">
            <h2 class="stacklapopup-shopspot-flyout-title">...</h2>
            <div class="stacklapopup-shopspot-flyout-price">...</div>
            <p class="stacklapopup-shopspot-flyout-description">...</p>
            <a class="stacklapopup-shopspot-flyout-button" href="..." target="_blank">
                <i class="stacklapopup-shopspot-flyout-button-icon"></i>
                <span class="stacklapopup-shopspot-flyout-button-text">...</span>
            </a>
        </div>
    </div>
</div>
```

### How Does Flyout Work?

Our default style restricts the dimension to `46x46` and sets `overflow:hidden` style so the user can't see the flyout by default. When the user hovers to an icon, the hover class (`.stacklapopup-shopspot-hover`) will be attached which sets `overflow: visible` and displays the flyout.

### Sample CSS

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/lTZMyW1R1Wku3cP0CXG9/lightbox-shopspot-after-customization.png)

By pasting the following code into one of stylesheets of your website, you can get the above customized style.

```
@import 'https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css';

.stacklapopup-shopspot-wrapper .stacklapopup-shopspot {
    background-color: rgba(0, 0, 0, 0.5);
    border: solid 2px #fff;
    -webkit-font-smoothing: antialiased;
}
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-hover {
    box-shadow: none;
}
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-icon:before {
    background: none;
    content: 'f219' !important;
    font-family: FontAwesome;
    font-size: 24px;
    font-style: normal;
    line-height: 46px;
}
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-flyout-button,
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-flyout-button:link,
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-flyout-button:hover {
    background-color: #000;
}
.stacklapopup-shopspot-wrapper .stacklapopup-shopspot-flyout-button-icon {
    display: none;
}
```

### Shopspot Animation

The default style sets `top: -24px;` and `left: calc(100% -24px);` so that Shopspot flies from top-right corner. The default Shopspot dimension is 24px \* 24px.

#### Flying from Left-Top Corner

You can simply change the `top` and `left` properties to `-24px` to make it flying from top-left corner.

```
.stacklapopup-shopspot {
    top: -24px;
    left: -24px;
}
```

#### Removing Animation

You can remove the animation by using the following CSS code.

```
.stacklapopup-shopspot {
    top: auto;
    left: auto;
    transition: none;
}
```

#### Replacing with Fade-in

You can use CSS3 Animation to achieve many effects. The following fade-in effect is just an example.

```
@-webkit-keyframes fade-in {
    0% {opacity: 0;}
    100% {opacity: 1;}

}
@keyframes fade-in {
    0% {opacity: 0;}
    100% {opacity: 1;}
}
.stacklapopup-shopspot {
    top: auto;
    left: auto;
    transition: none;
    -webkit-animation: fade-in 0.5s;
    animation: fade-in 0.5s;
}
```


# Using Web Fonts in Widgets

{% hint style="warning" %}
You are reading the **Classic Widget Documentation**

Classic widgets are a previous widget version for onsite widgets created before September 23rd.

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is **Classic** or **NextGen** widget.

You can read the [Nextgen widget documentation here](/ugc/guides/widgets-nextgen/styling-guides/using-webfonts-in-widgets).
{% endhint %}

* [Overview](#overview)
* [Step 1 - Load Fonts](#step1)
  * [Via the Custom CSS](#via-css)
  * [Via the Custom JavaScript](#via-js)
    * [Tips - Enabling Web Fonts](#webfonts)
    * [What's the differences](#differences)
* [Step 2 - Use Fonts](#step2)
* [Sample](#sample)

## Overview

This article will instruct you on how to use web fonts from different providers such as [Google](https://www.google.com/fonts), [Typekit](https://typekit.com), [Fonts.com](http://www.fonts.com) or [Fontdeck](http://fontdeck.com).

## Step 1 - Load Fonts

You will need to download the font files before using them. Let's take a look at some different approaches to load fonts onto our web page.

### Via the Custom CSS

The easiest way to use Web Fonts is via the CSS @import statement. You can get the required CSS code from the mentioned providers and paste it into Nosto's UGC Custom CSS Code Editor.

Let's take a look at Google Fonts as an example. After you select the fonts you need, you can go to the **Use** page and get the required code from the @import tab.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/MUIRc37v36K0KblGhEZj/css-import-sample.png)

Paste the code into the Nosto's UGC Custom CSS Editor.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/51Ps35gsJ9r3ZaH1sypp/css-import-editor-sample.png)

### Via the Custom JavaScript

An alternative way to load font files is via Javascript. Let's take a look at Google Fonts as an example again.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dWcdX7mDLzvzqjVcoKJv/load-via-javascript-sample.png)

Paste the code into the Nosto's UGC Custom JavaScript Editor. Please note the `<script/>` tags must be removed.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/1V9zbHah1hHzZ6TwinF7/load-via-javascript-editor-sample.png)

#### Tips - Enabling Web Fonts

By enabling the **Web Fonts** option in Appearance tab, you just need to configure the `WebFontConfig` global variable without loading the Web Font Loader library manually.

| ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/RoXd9HdtsLqoTPSfitfq/load-via-javascript-editor-webfont-sample.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/RzJhDeae88Bof7haLE8A/load-via-javascript-editor-simple-sample.png) |
| -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |

Note the **Web Fonts** config option currently is only available in Waterfall, Carousel, and Vertical Fluid widgets. And it only affects the inline tiles, not the expanded tile.

#### What's the Differences?

You may wonder why we need the more complicated JavaScript method. This is because the Web Font Loader gives us additional control. The following code snippet illustrates minimizing the flash of styled text by setting `sessionStorage`.

```
WebFontConfig = {
    // other options and settings...
    active: function() {
        sessionStorage.font = true;
    }
}
```

For more information, check the official [Web Font Loader](https://github.com/typekit/webfontloader) repository. We also recommend you to read the [Loading Web Fonts with the Web Font Loader](https://css-tricks.com/loading-web-fonts-with-the-web-font-loader) article on the [CSS-Tricks](https://css-tricks.com) website.

## Step 2 - Use Fonts

After loading fonts properly, all you need to do is add the font name to your CSS styles. For example:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/hozgnNs9Bv5U96EO0mYP/use-fonts-sample.png)

## Sample

In the following example, we are going to use the Custom Javascript editor to load multiple web fonts from different font providers. Note that we have enabled the Web Fonts option in the Appearance tab. We have also enabled sessionStorage to avoid the flash issue.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/RoXd9HdtsLqoTPSfitfq/load-via-javascript-editor-webfont-sample.png)

### Inline Tile Custom JavaScript

Here we specify how to use the "Open Sans Condensed" font from Google and a TypeKit kit containing the "Industry" font.

```
WebFontConfig = {
    google: {
        families: [
            'Open+Sans+Condensed:300:latin'
        ]
    },
    typekit: {
        id: 'YOUR KIT ID'
    },
    active: function() {
        sessionStorage.font = true;
    }
};
```

Note that TypeKit is a paid service, you need to register by yourself and use it on Nosto's UGC widgets.

### Inline Tile Custom CSS

```
.tile-user {
    font-family: 'Open Sans Condensed', sans-serif;
}
.tile-caption {
    font-family: 'industry', sans-serif;
}
```

### Expanded Tile Custom CSS

To demonstrate the Expanded Tile Custom CSS we've used the CSS @import method to load the "Open Sans Condensed" and "Rajdhani" fonts from Google Fonts.

```
@import url(https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Rajdhani);

.stacklapopup-user-info {
    font-family: 'Open Sans Condensed', sans-serif;
}
.stacklapopup-caption {
    font-family: 'Rajdhani', sans-serif;
}
```

Currenly we don't provide the Web Font Loader in the Expanded Tile. You can only load the Web Fonts from CSS @import.

### Widget with Customised Web Fonts

```html
<script type="text/javascript">(function (d, id) { if (d.getElementById(id)) return; var t = d.createElement('script'); t.type = 'text/javascript'; t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```


# Onsite Widgets (NextGen)

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

## Overview

Nosto's UGC widgets have been developed to provide a variety of key features that make them the best choice for displaying content on a web page. Used by 90% of clients. Serving 16 million requests a day.

## Usage

**Key Features**

* Speed & flexibility of development within the UGC Platform.
* Easy customization of CSS and JS, and now includes Typescript support!
* Access to a powerful Typescript SDK.&#x20;
* Optimized responsive display.
* Built-in content freshness detection.
* Speed and robustness of content delivery.
* Produces simple and reusable HTML embed code.
* Dynamically embedded into a web component without the need of an IFrame!
* Reporting on tile and widget performance via Google Analytics.


# Widget Creation Guides


# Creating gallery widget by using the blank canvas

* [Overview](#overview)
* [Step 1 - Settings](#step1)
* [Step 2 - Coding for Layout and Tile Templates](#step2)
* [Step 3 - Coding for CSS](#step3)
* [Step 4 - Coding for JavaScript](#step4)
* [Step 5 - Refinement](#step5)
* [Result - The Gallery Widget](#result)

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/blank-canvas/creating-gallery-widget-by-using-the-blank-canvas-widget) here.
{% endhint %}

## Overview

If you need a widget that Nosto's UGC hasn't supplied out of the box yet, Blank Canvas allows you to create your widget from scratch. In this guide, we will show you how to create a Gallery widget with Blank Canvas step by step. You can learn more information about the Blank Canvas Widget in the following guide: [Creating Your Own Widget Type by Using the Blank Canvas Widget](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-own-widget-type-by-using-the-blank-canvas-widget).

## Step 1 - Settings

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-ab96f41c131cda67a24a0dc7357b97c68bf32070%2Fcreate-new-widget-page.png?alt=media)

If the widget is enabled in your stack, you will see the extra widget type, Blank Canvas, appear in your Widget creation page.

Just click the Create Widget button to start.

Before you start getting creative with the Blank Canvas you will need to set it up properly to ensure that it all works.

The **Selected Filter** is the most important setting among them as it involved the data that you can fetch from Stackla.

## Step 2 - Coding for Layout and Tile Templates

Now let's start writing some HTML. Blank Canvas introduces the [Mustache](https://mustache.github.io) template engine and the Mustache Partials feature. In the following example, we will use partial template **tpl-tile** to render **tiles** objects. The **tiles** objects must be prepared in the Custom JS Editor - which we will talk about later.

### Sample Code

#### Layout

```
<div class="ugc-tiles grid grid-inline">
    {{#tiles}}
    {{>tpl-tile options=../options.config}}
    {{/tiles}}
</div>
<load-more />
```

#### Tile

The following Tile template will be included by the **{{>tpl-tile}}** syntax above. We will use classnames **.g-tile**, **.g-caption**, **.g-photo**, **.g-img** to style with CSS in the next section.

```

{{#tile class="grid-item"}}
  {{#ifAutoPlayVideo media options.tile_options.auto_play_video }}
    <div class="tile">
      {{playVideo this "100%" "100%"}}
    </div>
  {{else}}
    <div class="tile" data-background-image="{{image_thumbnail_url}}">
      <div class="icon-section">
        <div class="top-section">
          {{#each attrs}}
            {{#ifequals this "instagram.reel"}}
              <div class="content-icon icon-reel"></div>
            {{/ifequals}}
          {{/each}}
          {{#ifHasProductTags this}}
            <div class="shopping-icon icon-products"></div>
          {{/ifHasProductTags}}
        </div>
        <div class="center-section">
          {{#ifequals media "video"}}
            <div class="icon-play"></div>
          {{/ifequals}}
        </div>
        <div class="bottom-section">
          <tile-tags tile-id="{{id}}" variant="dark" mode="swiper" context="grid-inline"></tile-tags>
          <div class="network-icon icon-{{source}}"></div>
        </div>

        <shopspot-icon tile-id={{id}} />
      </div>

      <div class="tile-image-wrapper">
        {{#ifAutoPlayVideo media options.tile_options.auto_play_video }}
          {{playVideo this "100%" "100%"}}
        {{else}}
          <shopspot-icon tile-id={{id}}></shopspot-icon>
          {{#lazy image_thumbnail_url }}{{/lazy}}
        {{/ifAutoPlayVideo}}
        <div class="swiper-lazy-preloader"></div>
        <div class="description overlay">
          <div class="caption">
            <div class="caption-paragraph">{{message}}</div>
          </div>
        </div>
      </div>
    </div>
  {{/ifAutoPlayVideo}}
{{/tile}}
```

In the above sample code, we created a tile template that will display the image and caption with the **id**, **emoji**, **message**, and **image** properties. We have provided quite a few Tile properties so that you can assemble your own structure easily. Please check the [Tile Properties section in the API documentation](/ugc/api-docs/javascript/widgets/blank-canvas#helper-methods-tile-properties) for more details.

To preview the result of HTML template, we need to get data using JavaScript. Click on the **fork** button on the JavaScript panel, you will get the minimal code needed.

Click on the button "Update Preview", you should see plain stacked images and captions.

## Step 3 - Coding for CSS

All of the CSS rules reside within the Shadow DOM, so you don't need to worry about any conflicts with your page. Please make use of the **@import** directive to import the external CSS stylesheets you need.

### Sample Code

```

#nosto-ugc-container {
  background-color: var(--widget-background);
  padding: var(--margin);

  .tile {
    background-size: cover;
    background-position: center;
    height: 100%;
    width: 100%;
    position: relative;

    .icon-play {
      top: 43%;
      left: 0;
      right: 0;
      margin: auto;
    }

    .tile-image-wrapper {
      position: relative;
      width: 100%;
      overflow: hidden;
      border-radius: 0;
    }
    
    .tile-image-wrapper img {
        width: 100%;
        display: block;
        transition: transform 0.3s ease;
        border-radius: 0;
    }
    
    .description.overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.7);
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        opacity: 0;
        transition: opacity 0.4s ease;
        z-index: 10;
    }
    
    .caption {
        max-width: 90%;
    }
    
    .caption-paragraph {
        font-size: 14px;
        line-height: 1.4;
        overflow: hidden;
        display: -webkit-box;
        -webkit-line-clamp: 4;
        -webkit-box-orient: vertical;
        text-overflow: ellipsis;
    }
    
    .caption-paragraph a {
        color: #ffcc00;
        text-decoration: none;
        font-weight: bold;
    }

    .tile-image-wrapper:hover .description.overlay {
        opacity: 1;
    }
  }
}

    
```

Click on button "Update Preview", you should see images aligned in rows.

## Result - The Gallery Widget

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dRyf46INCkJ4uTr8XWWa/step5-preview.png)

After applying all the Layout, Tile, CSS, and JS code by the above code, you should get a Gallery Widget [(Live demo in JS Bin)](http://output.jsbin.com/zicake/1).

That's it! Get your code and embed your Gallery Widget!!

## Live Demo!

```html
<div id="ugc-widget" data-ttl="300" data-hash="681063c4163ef" data-id="64527" data-hash="681063c4163ef" data-id="64527" data-title="Grid - Gallery Widget Test" data-ttl="300" data-tag-group=""></div>
<script>
    (() => {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = 'https://widget-ui.teaser.stackla.com/core.js';
        script.onload = (() => {
            // You can customise the widget by passing in parameters to the object below.
            const widget = new window.ugc.widget({});
            widget.init();
        });
        document.body.appendChild(script);
    })();
</script>
```


# Creating your own widget type using blank canvas

* [Overview](#overview)
* [Step 1 - Getting a Canvas with Single Click!](#step1)
* [Step 2 - Super Simple Settings!](#step2)
* [Step 3 - Custom Code Editors](#step3)
* [Step 4 - Coding for Layout and Tile Templates](#step4)
* [Step 5 - Coding for CSS](#step5)
* [Step 6 - Coding for JavaScript](#step6)
* [Result - The Boilerplate Widget](#result)
* [Bonus - Creating the Horizontal Masonry Widget](#bonus)

{% hint style="warning" %}
You are reading the NextGen DocumentationYou are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-own-widget-type-by-using-the-blank-canvas-widget) here.
{% endhint %}

## Overview

Though Nosto's UGC provides several different types of widgets and has the great ability of customization, we still can't satisfy some of our customers' unique needs. For example, some customers want to use the [Horizontal Masonry](http://isotope.metafizzy.co/layout-modes/masonryhorizontal.html), which Nosto's UGC doesn't have. This then leads to our customer to using hack solutions or forces them to build from scratch which can take a lot of effort. That's not okay for both Nosto's UGC and our clients.

To resolve this, we have provided a new widget type. Blank Canvas allows you to create your widget from scratch whilst providing developers with some great utility functions and tools to achieve their solution efficiently.

In this tutorial, we will show you how to create a Blank Canvas widget step by step.

## Step 1 - Getting a Canvas with Single Click!

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-ab96f41c131cda67a24a0dc7357b97c68bf32070%2Fcreate-new-widget-page.png?alt=media)

Once the widget is available to you, you will see an extra widget type, Blank Canvas, in your widget creation page.

It doesn't have any JavaScript, CSS, or HTML by default. That's the reason why you can only see the prompt text in the preview widget area.

Just click the **Create Widget** button to start! It's easy!!

## Step 2 - Super Simple Settings!

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-2a02b16848439368b40f52a6af8e9755129657d7%2Fsettings.png?alt=media)

Before you start to put your imagination and innovative ideas onto the canvas, it's suggested to set up it correctly.

In Blank Canvas, we only provide very basic setting options. That is the Activate Widget, Name, Select Filter, and the Powered by Nosto's UGC Logo fields.

The **Selected Filter** is the most important setting among them. It involves with the data you can fetch from Nosto's UGC.

## Step 3 - Custom Code Editors

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-9eacd668842791a493f3c152b6a154156c3c057f%2Fcode-editor.png?alt=media)

Unlike other widgets which have the CSS and JavaScript editors, the Blank Canvas widget provides the other **Layout** and **Tile** editors so that you can write code in [Mustache](https://mustache.github.io).

## Step 4 - Coding for Layout and Tile Templates

### Understanding the Mustache Partials

If you are familiar with the [Mustache](https://mustache.github.io) template engine, you probably know the [Partials](https://mustache.github.io/mustache.5.html#Partials). The Partials feature just makes it easier to **modulize your templates**.

Nosto's UGC introduces the Mustache Partials feature. You can add, remove, or rename any templates. **The only necessary one is the Layout**. You can remove the Tile template if you think you can finish everything in one template.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/S6IyTQ9nW0CH3wBI7NvV/mustache-templates-structure.png)

The above diagram illustrates the Layout template which includes Header, Navigation, Tile (inside the loop), and Map templates. We've created the following widget with this templates structure. See? You basically can create a micro-site with our Blank Canvas. Awesome right?

```html
<div id="ugc-widget" data-ttl="300" data-hash="6808801ecc16b" data-id="64507" data-hash="6808801ecc16b" data-id="64507" data-title="Blank Canvas" data-ttl="300" data-tag-group=""></div>
<script>
    (() => {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = 'https://widget-ui.teaser.stackla.com/core.js';
        script.onload = (() => {
            // You can customise the widget by passing in parameters to the object below.
            const widget = new window.ugc.widget({});
            widget.init();
        });
        document.body.appendChild(script);
    })();
</script>
```

Note the above example just demonstrates the ability to use multiple templates. There is no interaction between photos and map.

### Sample Code

You can copy and paste the following code as your boilerplate. Or you can just click the **Reset Code** link (at the top-right corner) to get them directly.

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-1b8f8031503c427fc60340f6c14a9fc0a7c0610e%2Fscreen-shot-3.png?alt=media)

#### Layout

```
<div class="ugc-tiles grid grid-inline">
    {{#tiles}}
    {{>tpl-tile}}
    {{/tiles}}
</div>
<load-more />
```

The following Tile template will be included by the **{{>tpl-tile}}** syntax. The **tiles** is an array collection containing tile objects. It must be prepared in the Custom JS Editor - which \[we will talk about later]\(#js-stackla.loadTilesByFilter).

#### Tile

```
{{#tile class="grid-item"}}
<div class="tile" data-background-image="{{image_thumbnail_url}}">
  <div class="icon-section">
    <div class="top-section">
      {{#each attrs}}
      {{#ifequals this "instagram.reel"}}
      <div class="content-icon icon-reel"></div>
      {{/ifequals}}
      {{/each}}
      {{#ifHasProductTags this}}
      <div class="shopping-icon icon-products"></div>
      {{/ifHasProductTags}}
    </div>
    <div class="center-section">
      {{#ifequals media "video"}}
      <div class="icon-play"></div>
      {{/ifequals}}
    </div>
    <div class="bottom-section">
      <tile-tags tile-id="{{id}}" variant="dark" mode="swiper" context="grid-inline"></tile-tags>
      <div class="network-icon icon-{{source}}"></div>
    </div>

    <shopspot-icon tile-id={{id}} />
  </div>
</div>
{{/tile}}
```

## Step 5 - Coding for CSS

All the CSS rules reside within the Shadow DOM, so you don't need to worry about any conflicts with your page.

Please make use of the **@import** directive to import the external CSS stylesheets you need.

### Sample Code

Again, you can click the **Fork** link to grab the following code.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/2QOp8tyUlSfaxrDjQSIU/css-fork.gif)

```
.widget-icon{display:block}.close{width:24px;height:24px;background-image:url("https://templates.stackla.com/assets/svg/close-black.svg");background-repeat:no-repeat}.close-white{width:26px;height:26px;background-image:url("https://templates.stackla.com/assets/svg/close-white.svg");background-repeat:no-repeat}.chevron-left{width:40px;height:40px;background-image:url("https://templates.stackla.com/assets/svg/chevron-left.svg");background-repeat:no-repeat}.chevron-left-inline{width:36px;height:36px;background-image:url("https://templates.stackla.com/assets/svg/chevron-left-inline.svg");background-repeat:no-repeat}.chevron-right{width:40px;height:40px;background-image:url("https://templates.stackla.com/assets/svg/chevron-right.svg");background-repeat:no-repeat}.chevron-right-inline{width:36px;height:36px;background-image:url("https://templates.stackla.com/assets/svg/chevron-right-inline.svg");background-repeat:no-repeat}.icon-share{width:20px;height:20px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/share-white.svg");background-repeat:no-repeat}.icon-share.light{background:url("https://templates.stackla.com/assets/svg/share-black.svg");height:13px;width:13px;background-repeat:no-repeat}.icon-instagram-share{width:34px;height:34px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/instagram-share.svg");background-repeat:no-repeat}.icon-x-share{align-items:center;background-color:#000;border-radius:50%;display:flex;justify-content:center;height:34px;position:relative;width:34px}.icon-x-share::after{content:"";position:absolute;width:19px;height:19px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/x-share.svg");background-repeat:no-repeat}.icon-linkedin-share{width:34px;height:34px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/linkedin-share.svg");background-repeat:no-repeat}.icon-facebook-share{width:34px;height:34px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/facebook-share.svg");background-repeat:no-repeat}.icon-pinterest-share{width:34px;height:34px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/pinterest-share.svg");background-repeat:no-repeat}.icon-email-share{width:37px;height:30px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/email-share.svg");background-repeat:no-repeat}.chevron-up{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/chevron-up.svg");background-repeat:no-repeat}.chevron-down{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/chevron-down.svg");background-repeat:no-repeat}.back-arrow{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/back-arrow.svg");background-repeat:no-repeat}.icon-vimeo{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/vimeo.svg");background-repeat:no-repeat}.icon-vimeo.small{width:12px;height:12px}.icon-instagram,.icon-instagram.large,.icon-instagram.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/instagram.svg");background-repeat:no-repeat}.icon-instagram.black,.icon-instagram.large.black,.icon-instagram.medium.black{background-image:url("https://templates.stackla.com/assets/svg/instagram-black.svg")}.icon-instagram.small,.icon-instagram.large.small,.icon-instagram.medium.small{width:12px;height:12px}.icon-twitter,.icon-twitter.large,.icon-twitter.medium{display:flex;width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/twitter.svg");background-repeat:no-repeat}.icon-twitter.black,.icon-twitter.large.black,.icon-twitter.medium.black{background-image:url("https://templates.stackla.com/assets/svg/x-black.svg")}.icon-twitter.small,.icon-twitter.large.small,.icon-twitter.medium.small{width:12px;height:12px}.icon-linkedin,.icon-linkedin.large,.icon-linkedin.medium{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/linkedin.svg");background-repeat:no-repeat}.icon-linkedin.black,.icon-linkedin.large.black,.icon-linkedin.medium.black{background-image:url("https://templates.stackla.com/assets/svg/linkedin-black.svg")}.icon-linkedin.small,.icon-linkedin.large.small,.icon-linkedin.medium.small{width:12px;height:12px}.icon-tiktok,.icon-tiktok.large,.icon-tiktok.medium{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/tiktok.svg");background-repeat:no-repeat}.icon-tiktok.black,.icon-tiktok.large.black,.icon-tiktok.medium.black{background-image:url("https://templates.stackla.com/assets/svg/tiktok-black.svg")}.icon-tiktok.small,.icon-tiktok.large.small,.icon-tiktok.medium.small{width:12px;height:12px}.icon-facebook,.icon-facebook.large,.icon-facebook.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/facebook.svg");background-repeat:no-repeat}.icon-facebook.black,.icon-facebook.large.black,.icon-facebook.medium.black{background-image:url("https://templates.stackla.com/assets/svg/facebook-black.svg")}.icon-facebook.small,.icon-facebook.large.small,.icon-facebook.medium.small{width:12px;height:12px}.icon-reel,.icon-reel.large,.icon-reel.medium{width:12px;height:12px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/reel.svg");background-repeat:no-repeat}.icon-reel.small,.icon-reel.large.small,.icon-reel.medium.small{width:12px;height:12px}.icon-products{width:14px;height:14px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/products.svg");background-repeat:no-repeat}.icon-products.small{width:10px;height:10px}.icon-products-bg,.icon-products-bg.large,.icon-products-bg.medium{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/products-bg.svg");background-repeat:no-repeat}.icon-products-bg.small,.icon-products-bg.large.small,.icon-products-bg.medium.small{width:18px;height:18px}.icon-youtube,.icon-youtube.large,.icon-youtube.medium{width:22px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/youtube.svg");background-repeat:no-repeat}.icon-youtube.black,.icon-youtube.large.black,.icon-youtube.medium.black{background-image:url("https://templates.stackla.com/assets/svg/youtube-black.svg")}.icon-youtube.small,.icon-youtube.large.small,.icon-youtube.medium.small{width:12px;height:12px}.icon-youtube-short,.icon-youtube-short.large,.icon-youtube-short.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/youtube-short.svg");background-repeat:no-repeat}.icon-youtube-short.small,.icon-youtube-short.large.small,.icon-youtube-short.medium.small{width:12px;height:12px}.icon-flickr,.icon-flickr.large,.icon-flickr.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/flickr.svg");background-repeat:no-repeat}.icon-flickr.black,.icon-flickr.large.black,.icon-flickr.medium.black{background-image:url("https://templates.stackla.com/assets/svg/flickr-black.svg")}.icon-flickr.small,.icon-flickr.large.small,.icon-flickr.medium.small{width:12px;height:12px}.icon-weibo,.icon-weibo.large,.icon-weibo.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/weibo.svg");background-repeat:no-repeat}.icon-weibo.black,.icon-weibo.large.black,.icon-weibo.medium.black{background-image:url("https://templates.stackla.com/assets/svg/weibo-black.svg")}.icon-weibo.small,.icon-weibo.large.small,.icon-weibo.medium.small{width:12px;height:12px}.icon-pinterest,.icon-pinterest.large,.icon-pinterest.medium{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/pinterest.svg");background-repeat:no-repeat}.icon-pinterest.black,.icon-pinterest.large.black,.icon-pinterest.medium.black{background-image:url("https://templates.stackla.com/assets/svg/pinterest-black.svg")}.icon-pinterest.small,.icon-pinterest.large.small,.icon-pinterest.medium.small{width:12px;height:12px}.icon-rss,.icon-rss.large,.icon-rss.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/rss.svg");background-repeat:no-repeat}.icon-rss.black,.icon-rss.large.black,.icon-rss.medium.black{background-image:url("https://templates.stackla.com/assets/svg/rss-black.svg")}.icon-rss.small,.icon-rss.large.small,.icon-rss.medium.small{width:12px;height:12px}.icon-tumblr,.icon-tumblr.large,.icon-tumblr.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/tumblr.svg");background-repeat:no-repeat}.icon-tumblr.small,.icon-tumblr.large.small,.icon-tumblr.medium.small{width:12px;height:12px}.icon-stackla,.icon-stackla.large,.icon-stackla.medium,.icon-sta_feed,.icon-sta_feed.large,.icon-sta_feed.medium{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/stackla.svg");background-repeat:no-repeat}.icon-stackla.small,.icon-stackla.large.small,.icon-stackla.medium.small,.icon-sta_feed.small,.icon-sta_feed.large.small,.icon-sta_feed.medium.small{width:12px;height:12px}.icon-snapchat,.icon-snapchat.large,.icon-snapchat.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/snapchat.svg");background-repeat:no-repeat}.icon-snapchat.small,.icon-snapchat.large.small,.icon-snapchat.medium.small{width:12px;height:12px}.icon-spotify,.icon-spotify.large,.icon-spotify.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/spotify.svg");background-repeat:no-repeat}.icon-spotify.small,.icon-spotify.large.small,.icon-spotify.medium.small{width:12px;height:12px}.icon-hootsuite,.icon-hootsuite.large,.icon-hootsuite.medium{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/hootsuite.svg");background-repeat:no-repeat}.icon-hootsuite.small,.icon-hootsuite.large.small,.icon-hootsuite.medium.small{width:12px;height:12px}.icon-prev{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/prev.svg");background-repeat:no-repeat}.icon-prev-white{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/prev-white.svg");background-repeat:no-repeat}.icon-prev-circle{width:40px;height:40px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/prev-circle.svg");background-repeat:no-repeat}.icon-next{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/next.svg");background-repeat:no-repeat}.icon-next-white{width:16px;height:16px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/next-white.svg");background-repeat:no-repeat}.icon-next-circle{width:40px;height:40px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/next-circle.svg");background-repeat:no-repeat}.icon-play{width:2em;height:2em;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/play.svg");background-repeat:no-repeat}.icon-upload{background-image:url("https://templates.stackla.com/assets/svg/upload.svg");height:15px;width:15px;background-size:contain;background-repeat:no-repeat}.icon-picture{background-image:url("https://templates.stackla.com/assets/svg/picture.svg");background-size:contain;background-repeat:no-repeat;height:18px;width:18px;position:absolute}.icon-tag{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/tag.svg");background-repeat:no-repeat}.icon-tag.small{width:18px;height:18px}.icon-video-play{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/video-play.svg");background-repeat:no-repeat}.icon-video-pause{width:18px;height:18px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/video-pause.svg");background-repeat:no-repeat}.icon-video-volume{width:12px;height:12px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/video-volume.svg");background-repeat:no-repeat}.icon-video-mute{width:12px;height:12px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/video-mute.svg");background-repeat:no-repeat}.down-arrow{width:8px;height:7px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/down-arrow.svg");background-repeat:no-repeat}.up-arrow{width:8px;height:7px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/up-arrow.svg");background-repeat:no-repeat}.down-arrow-thin{width:24px;height:24px;background-size:contain;background-image:url("https://templates.stackla.com/assets/svg/down-arrow-thin.svg");background-repeat:no-repeat}.tile>.tile-loading,.loading{border-radius:50% !important;animation:spin 2s linear infinite;position:absolute;z-index:10;border:5px solid #f3f3f3;border-top:5px solid #3498db;border-bottom:5px solid #3498db;width:30px !important;height:30px !important;top:calc(50% - 30px);left:calc(50% - 30px)}.expanded-tile-overlay{position:fixed;width:100vw;height:100vh;inset:0;background-color:rgba(0,0,0,.9);z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center}.story-overlay{display:none;position:absolute;background-color:rgba(0,0,0,.4);width:var(--tile-size);height:var(--tile-size);border-radius:50%;z-index:10}@media(min-width: 577px)and (max-width: 992px){.expanded-tile-overlay{align-items:flex-start;overflow:hidden}}@media(max-width: 576px){.expanded-tile-overlay{overflow:hidden}}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-button{align-content:center;border-radius:0;height:40px;padding:8px 16px;margin:24px 0 0;width:auto;background-color:var(--cta-button-background-color) !important;color:var(--cta-button-font-color) !important;font-size:var(--cta-button-font-size) !important}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker-text,expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker-text b,expanded-tiles:not(:empty) add-to-cart #variant-container p,expanded-tiles:not(:empty) add-to-cart #variant-container p span{color:#333;font-size:12px;font-weight:400;padding:0}expanded-tiles:not(:empty) add-to-cart #variant-container div:not(.ugc-add-to-cart-sizepicker .ugc-add-to-cart-sizepicker-btn){flex-direction:row;align-items:baseline}expanded-tiles:not(:empty) add-to-cart #variant-container div p::after{content:":"}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-other-variant-selector{background:none;border:none;color:#333;cursor:pointer;font-size:12px;margin:0;padding:0}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker-text{margin:0;padding:0;padding:8px 0}expanded-tiles:not(:empty) add-to-cart #variant-container p{margin:18px 0 8px}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker{display:flex;gap:12px}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker-ring{height:24px;width:24px;margin:0}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-colorpicker-inner{width:15.6px;height:15.6px;left:24.5%;top:24%}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-sizepicker{display:flex;gap:10px}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-sizepicker-btn{border-radius:0;cursor:pointer;font-family:"Open Sans",sans-serif;font-size:12px;font-weight:400;height:34px;margin:0}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-sizepicker-btn[selected=true]{border:2px solid #4083b9}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-sizepicker-btn:not([enabled=true]){border:1px solid #dcdcdc;background:#f2f2f2;color:#b9b9b9;cursor:not-allowed;text-decoration:line-through}expanded-tiles:not(:empty) add-to-cart .ugc-add-to-cart-sizepicker-btn:not([enabled=true])::before{content:none}expanded-tiles:not(:empty) ugc-products{--product-price-font-color: $black;--product-title-font-color: $white;--product-description-font-color: $black;display:flex;flex-direction:column;border-top:1px solid #f4f4f4;padding-top:16px}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header{display:flex;margin-bottom:0}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header .stacklapopup-products-header{margin-bottom:0}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header .stacklapopup-products-item-header{display:none}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header .stacklapopup-products-item-header.stacklapopup-products-item-active{display:flex;flex-direction:column;justify-content:flex-start;gap:16px}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header .stacklapopup-products-item-header .stacklapopup-products-item-price{color:var(--product-price-font-color);font-size:14px;font-weight:400;line-height:12px;margin-bottom:8px}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-header .stacklapopup-products-item-header .stacklapopup-products-item-title{color:var(--product-title-font-color);font-size:14px;font-weight:500;line-height:16px;text-decoration:none}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper{display:flex;margin-top:24px;position:relative}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper::-webkit-scrollbar{display:none}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper.arrows-hidden .swiper-nav-icon{display:none !important}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper.arrows-hidden .swiper-expanded-product-recs{width:100% !important}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs{width:calc(90% - 20px)}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-wrapper{width:calc(100% - 20px);max-width:initial;max-height:initial}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper{width:84.9px !important;height:110px !important;position:relative;cursor:pointer;display:inline-block;padding:2px}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper:hover .stacklapopup-products-item{opacity:1}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item{display:flex;flex-shrink:0;height:100%;align-items:center}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item.stacklapopup-products-item-active img{border:.5px solid #9f9f9f;opacity:1}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item .stacklapopup-products-item-image{opacity:.4;border-radius:3px;display:flex;align-items:center;width:100%;height:calc(100% - 2px)}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item-image-recommendation-label{display:flex;width:69px;height:16px;justify-content:center;align-items:center;flex-shrink:0;background:#000;border-radius:14px;position:absolute;top:0;left:10px;z-index:2}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item-image-recommendation-label p{color:#fff;font-size:8px;font-style:normal;font-weight:350;line-height:160%;text-align:center;display:flex;align-items:center}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-expanded-product-recs .swiper-slide.stacklapopup-product-wrapper .stacklapopup-products-item-image-recommendation-label p .icon-like{height:9px;width:9px;display:inline-block;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%229%22%20height%3D%229%22%20viewBox%3D%220%200%209%209%22%20fill%3D%22none%22%3E%3Cmask%20id%3D%22mask0_2185_4584%22%20style%3D%22mask-type%3Aalpha%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%220%22%20y%3D%220%22%20width%3D%229%22%20height%3D%229%22%3E%3Crect%20width%3D%229%22%20height%3D%229%22%20fill%3D%22%23D9D9D9%22/%3E%3C/mask%3E%3Cg%20mask%3D%22url%28%23mask0_2185_4584%29%22%3E%3Cpath%20d%3D%22M6.75%207.875H2.625V3L5.25%200.375L5.71875%200.84375C5.7625%200.8875%205.79844%200.946875%205.82656%201.02188C5.85469%201.09688%205.86875%201.16875%205.86875%201.2375V1.36875L5.45625%203H7.875C8.075%203%208.25%203.075%208.4%203.225C8.55%203.375%208.625%203.55%208.625%203.75V4.5C8.625%204.54375%208.61875%204.59062%208.60625%204.64062C8.59375%204.69063%208.58125%204.7375%208.56875%204.78125L7.44375%207.425C7.3875%207.55%207.29375%207.65625%207.1625%207.74375C7.03125%207.83125%206.89375%207.875%206.75%207.875ZM3.375%207.125H6.75L7.875%204.5V3.75H4.5L5.00625%201.6875L3.375%203.31875V7.125ZM2.625%203V3.75H1.5V7.125H2.625V7.875H0.75V3H2.625Z%22%20fill%3D%22white%22/%3E%3C/g%3E%3C/svg%3E");background-size:cover;margin-right:2px}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-exp-product-recs-button-prev.swiper-button-prev{left:0}expanded-tiles:not(:empty) ugc-products .stacklapopup-product-images-wrapper .swiper-exp-product-recs-button-next.swiper-button-next{right:0}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content{display:none}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content.stacklapopup-products-item-active{display:flex;flex-direction:column}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content .stacklapopup-products-item-button-wrap{all:unset;position:relative;width:100%}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content .stacklapopup-products-item-button-wrap .stacklapopup-products-item-button{display:inline-block;font-weight:500;line-height:14px;text-align:center;text-decoration:none;width:100%;background-color:var(--cta-button-background-color);color:var(--cta-button-font-color);font-size:var(--cta-button-font-size)}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content .stacklapopup-products-item-button-wrap .stacklapopup-products-item-button.disabled{background:#c4c4c4;font-weight:bold;cursor:default;color:#9d9d9d}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content .stacklapopup-products-item-description-wrapper{overflow:hidden auto;width:100%}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-content .stacklapopup-products-item-description-wrapper .stacklapopup-products-item-description{color:var(--product-description-font-color);font-size:12px;font-weight:300;line-height:160%;text-overflow:ellipsis;overflow-y:scroll;display:-webkit-box;margin:10px 0 16px;scrollbar-width:thin;scrollbar-color:#e6e6e6 #fff}expanded-tiles:not(:empty) ugc-products .loader{width:50px;aspect-ratio:1;border-radius:50%;background:radial-gradient(farthest-side, #b4b2af 94%, #000) top/8px 8px no-repeat,conic-gradient(#000 30%, #928f8a);mask:radial-gradient(farthest-side, #000 calc(100% - 8px), #000 0);animation:l13 1s infinite linear}expanded-tiles:not(:empty) ugc-products .top-section{margin-bottom:14px}expanded-tiles:not(:empty) ugc-products .top-section .down-arrow-thin{cursor:pointer}@keyframes l13{100%{transform:rotate(1turn)}}expanded-tiles:not(:empty) ugc-products .recommendations-text{color:#2c2c2c;font-size:10px;font-weight:400;text-decoration:underline;text-underline-offset:2px}expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-button,expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-button-wrap{align-content:center;display:flex;flex-direction:column;height:40px}@media only screen and (max-width: 992px){expanded-tiles:not(:empty) ugc-products .stacklapopup-products-item-button-wrap{bottom:unset;position:unset}}html{background:var(--widget-background)}expanded-tiles{--expanded-tiles-background: #fff}expanded-tiles:not(:empty){min-height:88vh;display:flex;flex-direction:row;justify-content:center;background-color:rgba(0,0,0,0);text-rendering:auto;max-width:1002px;max-height:712px;position:relative}expanded-tiles:not(:empty) :host{transition:ease all .5s}expanded-tiles:not(:empty) .play-icon{z-index:9;display:inline-block;width:50px;height:50px;background-image:url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none"><circle cx="15" cy="15" r="15" fill="%23BCBBBC"/><path d="M19.5 14.1336C20.1667 14.5185 20.1667 15.4808 19.5 15.8657L13.5 19.3298C12.8333 19.7147 12 19.2335 12 18.4637L12 11.5355C12 10.7657 12.8333 10.2846 13.5 10.6695L19.5 14.1336Z" fill="black"/></svg>');background-size:contain;position:absolute;top:calc(50% - 15px);left:calc(50% - 15px)}expanded-tiles:not(:empty) .hidden{display:none !important}expanded-tiles:not(:empty) .back{display:none;background:#fff;position:relative;height:56px;align-items:center;padding-left:16px;border-radius:5px 5px 0 0;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper{--swiper-navigation-top-offset: calc(50% - 20px);display:flex;place-items:center;width:100vw;flex-direction:column}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper{max-width:895px;max-height:90vh}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide{align-items:flex-start;height:fit-content}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide ugc-products{max-width:323px}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide div.image-filler{background-position:top;background-repeat:no-repeat;background-size:cover;position:absolute;inset:0}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide div.image-filler.blurred{filter:blur(20px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .exit{position:absolute;display:flex;right:5%;top:-15px;z-index:2;justify-content:end}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel{overflow:hidden;display:grid;height:100%;position:relative;max-width:879px;min-height:88vh;max-height:880px;background:var(--expanded-tiles-background);border-radius:var(--expanded-tile-border-radius);grid-template-columns:minmax(0, 517px) minmax(0, 363px);grid-template-rows:1fr}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left{min-height:300px;min-width:unset;display:flex;place-items:center center;background-position:center;background-repeat:no-repeat;background-size:cover;overflow:hidden;position:relative;color:#fff;max-width:517px}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left:has(>.video-content){align-items:stretch}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper{display:flex;position:relative;height:100%;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper .image-wrapper-inner{display:flex;align-items:center;justify-content:center;width:100%;height:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper .image-wrapper-inner div.image{display:flex;position:relative}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper .image-wrapper-inner div.image img.image-element{display:flex;width:100%;height:100%;margin-bottom:0}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper .image-wrapper-inner:has(>.video-content){align-items:stretch}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .image-wrapper .image-wrapper-inner .video-content{display:flex;flex-grow:1;border:none;margin:0;padding:0;z-index:1}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left video{display:flex;width:100%;height:100%;z-index:10;position:relative}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .video-content-wrapper{display:flex;width:100%;height:100%;place-items:center;justify-content:center}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .video-fallback-content{display:flex;height:100%;justify-content:center;align-items:center}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .video-fallback-content.hidden{display:none}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .icon-section{display:flex;width:100%;height:100%;position:absolute}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .content-text,expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left .content-html{display:flex;white-space:break-spaces;mix-blend-mode:difference;padding:20px}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping{height:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .tile-product.swiper-slide{height:100%;align-items:center}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .tile-product-panel{display:flex;align-items:center;justify-content:center;overflow:hidden}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .tile-product-panel .carousel-grouping-img{min-width:100%;min-height:100%;object-fit:cover;object-position:var(--image-position);height:70vw}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .expand-control{width:60px;height:9px;margin-left:21px;margin-bottom:8px;display:inline-flex;padding:4px;justify-content:center;align-items:center;gap:4px;border-radius:25px;opacity:.7;background-color:#fff;cursor:pointer}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .expand-control.collapsed{visibility:hidden}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .expand-control .expand-control-label{color:#201c1f;font-size:12px;font-style:normal;font-weight:500;line-height:12px}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .swiper-pagination{bottom:var(--swiper-pagination-bottom, 20px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left carousel-grouping .swiper-pagination-bullet-active{background-color:#fff}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right{display:flex;padding:20px;overflow-y:scroll;scrollbar-width:none;max-height:80vh}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right .panel-right-wrapper{display:flex;overflow:hidden auto;scrollbar-width:none;width:100vw}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right .content-wrapper{display:flex;height:100%;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right .content-wrapper .content-inner-wrapper{gap:8px;display:flex;flex-direction:column;height:100%;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right .content-wrapper .content-inner-wrapper .tile-tags{display:var(--tags-display-expanded);margin:0 16.4px 8.4px 0;width:100%}@media only screen and (max-width: 992px){expanded-tiles:not(:empty){margin:0;max-width:unset;max-height:unset;height:100vh;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .exit{display:none !important}expanded-tiles:not(:empty) .expanded-tile-wrapper .back{display:flex}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper{overflow-y:scroll;scrollbar-width:none}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper{max-height:calc(100vh - 50px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide ugc-products{max-width:calc(100vw - 40px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel{grid-template-columns:1fr;width:100%;max-height:unset;overflow:hidden;margin-bottom:56px}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left,expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right{width:auto}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left{max-width:100vw}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right{max-width:100vw;overflow:visible;margin-bottom:10px}}@media only screen and (min-width: 577px)and (max-width: 992px){expanded-tiles:not(:empty) .expanded-tile-wrapper{flex-direction:column}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper{max-height:unset;max-width:100vw;height:auto}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide{border-radius:0}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide .panel{grid-template-rows:587px fit-content(50%) !important}}@media only screen and (max-width: 992px){expanded-tiles:not(:empty){margin:0;max-width:unset;max-height:unset;height:100vh;width:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .exit{display:none !important}expanded-tiles:not(:empty) .expanded-tile-wrapper .back{display:flex}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper{overflow-y:scroll;scrollbar-width:none}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper{max-height:calc(100vh - 50px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide ugc-products{max-width:calc(100vw - 40px)}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel{grid-template-columns:1fr;width:100%;max-height:unset;overflow:hidden;margin-bottom:56px}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left,expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right{width:auto}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-left{max-width:100vw}expanded-tiles:not(:empty) .expanded-tile-wrapper .panel .panel-right{max-width:100vw;overflow:visible;margin-bottom:10px}}@media only screen and (max-width: 576px){expanded-tiles:not(:empty) .expanded-tile-wrapper{height:100%}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper{max-width:unset;overflow-y:scroll;scrollbar-width:none}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper{max-width:100vw}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .swiper-slide{height:100vh}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .panel{height:100vh;grid-template-columns:minmax(100%, 1fr) !important;grid-template-rows:auto !important}expanded-tiles:not(:empty) .expanded-tile-wrapper .swiper .swiper-wrapper .panel .panel-right{overflow:visible}}.swiper{display:flex;height:100%;width:100%}.swiper .swiper-slide{align-items:center;position:relative;display:flex;justify-content:center;overflow:hidden}.swiper .swiper-slide:not(.swiper-initialized) .swiper-slide>.ugc-tile>img{opacity:0}.swiper-button-next,.swiper-button-prev{top:unset}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.swiper-button-prev::after,.swiper-button-next::after{content:unset !important;--swiper-navigation-size: 20px}@media(max-width: 576px){.swiper .swiper-wrapper{max-height:100vh}}@media(max-width: 992px){.swiper-button-next.btn-lg,.swiper-button-prev.btn-lg{display:none !important}}@media(min-width: 992px){.swiper-button-next,.swiper-button-prev{display:flex}}#nosto-ugc-container .ugc-tile{cursor:pointer;max-width:400px}#nosto-ugc-container .ugc-tile .video-content{display:flex;object-fit:cover;object-position:top}.grid-inline tile-tags,expanded-tiles tile-tags{--swiper-navigation-sides-offset: 0;--swiper-navigation-top-offset: 3px;align-items:center;display:var(--tags-display-inline);position:relative;overflow:hidden;height:fit-content;width:100%}.grid-inline tile-tags[context=expanded],expanded-tiles tile-tags[context=expanded]{display:var(--tags-display-expanded)}.grid-inline tile-tags .mask-left,expanded-tiles tile-tags .mask-left{mask-image:linear-gradient(to right, transparent 10%, black 25%)}.grid-inline tile-tags .mask-right,expanded-tiles tile-tags .mask-right{mask-image:linear-gradient(to left, transparent 10%, black 25%)}.grid-inline tile-tags .mask-both,expanded-tiles tile-tags .mask-both{mask-image:linear-gradient(to right, transparent 5%, black 15%, black 85%, transparent 95%)}.grid-inline tile-tags[context*=inline] .swiper-tags,expanded-tiles tile-tags[context*=inline] .swiper-tags{margin-left:initial;width:100%}.grid-inline tile-tags[context*=inline][navigation-arrows=true] .swiper-tags,expanded-tiles tile-tags[context*=inline][navigation-arrows=true] .swiper-tags{width:100%}.grid-inline tile-tags:has(.swiper-tags-button-prev:not(.swiper-button-disabled)) .swiper-tags,expanded-tiles tile-tags:has(.swiper-tags-button-prev:not(.swiper-button-disabled)) .swiper-tags{mask-image:linear-gradient(to right, transparent 5%, black 25%)}.grid-inline tile-tags:has(.swiper-tags-button-next:not(.swiper-button-disabled)) .swiper-tags,expanded-tiles tile-tags:has(.swiper-tags-button-next:not(.swiper-button-disabled)) .swiper-tags{mask-image:linear-gradient(to left, transparent 5%, black 25%)}.grid-inline tile-tags:has(.swiper-tags-button-prev:not(.swiper-button-disabled)):has(.swiper-tags-button-next:not(.swiper-button-disabled)) .swiper-tags,expanded-tiles tile-tags:has(.swiper-tags-button-prev:not(.swiper-button-disabled)):has(.swiper-tags-button-next:not(.swiper-button-disabled)) .swiper-tags{mask-image:linear-gradient(to right, transparent 5%, black 15%, black 85%, transparent 95%)}.grid-inline tile-tags .swiper-button-disabled,expanded-tiles tile-tags .swiper-button-disabled{display:none}.grid-inline tile-tags .swiper-tags-button-prev,expanded-tiles tile-tags .swiper-tags-button-prev,.grid-inline tile-tags .swiper-tags-button-next,expanded-tiles tile-tags .swiper-tags-button-next{position:relative}.grid-inline tile-tags .swiper-tags-button-prev .swiper-nav-icon,expanded-tiles tile-tags .swiper-tags-button-prev .swiper-nav-icon,.grid-inline tile-tags .swiper-tags-button-next .swiper-nav-icon,expanded-tiles tile-tags .swiper-tags-button-next .swiper-nav-icon{width:12px;height:12px}.grid-inline tile-tags .swiper-tags-button-prev::after,expanded-tiles tile-tags .swiper-tags-button-prev::after,.grid-inline tile-tags .swiper-tags-button-next::after,expanded-tiles tile-tags .swiper-tags-button-next::after{content:""}.grid-inline tile-tags .tile-tags-wapper,expanded-tiles tile-tags .tile-tags-wapper{flex-direction:column;width:100%}.grid-inline tile-tags .tile-tags-wapper .tile-tags,expanded-tiles tile-tags .tile-tags-wapper .tile-tags{display:var(--tags-display-inline);align-items:center;z-index:2;flex-direction:row;gap:var(--tags-gap)}.grid-inline tile-tags .tile-tags-wapper .tile-tag,expanded-tiles tile-tags .tile-tags-wapper .tile-tag,.grid-inline tile-tags .swiper-tags .swiper-slide,expanded-tiles tile-tags .swiper-tags .swiper-slide{display:inline-flex;background:var(--tile-tag-background, "#D6D4D5");border-radius:3px;font-size:10px;font-style:normal;font-weight:400;padding:2px 4px;line-height:16px;text-wrap:nowrap;max-width:fit-content}.grid-inline tile-tags .tile-tags-wapper .tile-tag a,expanded-tiles tile-tags .tile-tags-wapper .tile-tag a,.grid-inline tile-tags .swiper-tags .swiper-slide a,expanded-tiles tile-tags .swiper-tags .swiper-slide a{color:#000;text-decoration:none}.grid-inline tile-tags .tile-tags-wapper .tile-tag a .tag-title,expanded-tiles tile-tags .tile-tags-wapper .tile-tag a .tag-title,.grid-inline tile-tags .swiper-tags .swiper-slide a .tag-title,expanded-tiles tile-tags .swiper-tags .swiper-slide a .tag-title{margin:0 6px;display:flex}.grid-inline tile-tags[variation=small][context*=inline] .swiper-tags .swiper-slide,expanded-tiles tile-tags[variation=small][context*=inline] .swiper-tags .swiper-slide{max-width:100%}.grid-inline tile-tags .tile-tags-wapper .tile-tag,expanded-tiles tile-tags .tile-tags-wapper .tile-tag{padding:2px 4px}.grid-inline tile-tags .swiper-tags .swiper-slide,expanded-tiles tile-tags .swiper-tags .swiper-slide{padding:2px 0}.grid-inline tile-tags[variant=dark] .tile-tags .tile-tag,expanded-tiles tile-tags[variant=dark] .tile-tags .tile-tag,.grid-inline tile-tags[variant=dark] .swiper-tags .swiper-slide,expanded-tiles tile-tags[variant=dark] .swiper-tags .swiper-slide{background:var(--tile-tag-inline-background, color(srgb 0 0 0 / 0.4));border-radius:25px;line-height:12px}.grid-inline tile-tags[variant=dark] .tile-tags .tile-tag a,expanded-tiles tile-tags[variant=dark] .tile-tags .tile-tag a,.grid-inline tile-tags[variant=dark] .swiper-tags .swiper-slide a,expanded-tiles tile-tags[variant=dark] .swiper-tags .swiper-slide a{color:#fff}load-more{cursor:pointer}load-more #buttons{display:flex;place-content:center;margin-top:20px;margin-bottom:20px}load-more #buttons .load-more{display:flex}.grid-inline tile-content,expanded-tiles tile-content{display:flex}.grid-inline tile-content .tile-content-wrapper,expanded-tiles tile-content .tile-content-wrapper{--max-lines: 7;display:flex;flex-direction:column;height:100%;gap:24px;width:100%}.grid-inline tile-content .tile-content-wrapper .description,expanded-tiles tile-content .tile-content-wrapper .description{display:flex;flex-direction:column;overflow:hidden;flex:1 1 auto;width:100%}.grid-inline tile-content .tile-content-wrapper .description .caption,expanded-tiles tile-content .tile-content-wrapper .description .caption{display:flex;line-height:1.2rem;overflow:hidden;flex:1 1 auto}.grid-inline tile-content .tile-content-wrapper .description .caption .caption-paragraph,expanded-tiles tile-content .tile-content-wrapper .description .caption .caption-paragraph{display:flex;display:-webkit-box;-webkit-line-clamp:var(--lines);color:var(--text-tile-font-color);line-clamp:7;-webkit-box-orient:vertical;font-size:var(--text-tile-font-size);font-weight:300;line-height:16px;-webkit-box-pack:end;height:fit-content;max-height:80px;margin-bottom:16px;text-overflow:ellipsis;overflow:hidden;overflow-y:scroll;scrollbar-width:none;-ms-overflow-style:none}.grid-inline tile-content .tile-content-wrapper .description .caption .caption-paragraph::-webkit-scrollbar,expanded-tiles tile-content .tile-content-wrapper .description .caption .caption-paragraph::-webkit-scrollbar{display:none}.grid-inline tile-content .tile-content-wrapper .user-info,expanded-tiles tile-content .tile-content-wrapper .user-info{display:flex;align-items:center}.grid-inline tile-content .tile-content-wrapper .user-info .user-link,expanded-tiles tile-content .tile-content-wrapper .user-info .user-link{display:flex;flex-direction:column;margin-right:auto;text-decoration:none;gap:8px}.grid-inline tile-content .tile-content-wrapper .user-info .user-link .user-name,expanded-tiles tile-content .tile-content-wrapper .user-info .user-link .user-name{font-size:var(--text-tile-user-name-font-size);color:var(--text-tile-user-name-font-color)}.grid-inline tile-content .tile-content-wrapper .user-info .user-link .user-handle,expanded-tiles tile-content .tile-content-wrapper .user-info .user-link .user-handle{font-size:var(--text-tile-user-handle-font-size);color:var(--text-tile-user-handle-font-color)}.grid-inline tile-content .tile-content-wrapper .user-info .avatar-wrapper,expanded-tiles tile-content .tile-content-wrapper .user-info .avatar-wrapper{margin-right:16px;display:flex}.grid-inline tile-content .tile-content-wrapper .user-info .avatar-wrapper .avatar-link,expanded-tiles tile-content .tile-content-wrapper .user-info .avatar-wrapper .avatar-link{display:flex}.grid-inline tile-content .tile-content-wrapper .user-info .avatar-wrapper .avatar-link>img,expanded-tiles tile-content .tile-content-wrapper .user-info .avatar-wrapper .avatar-link>img{width:40px;height:40px;border-radius:50%}.grid-inline tile-content .tile-content-wrapper .header,expanded-tiles tile-content .tile-content-wrapper .header{display:flex;flex-direction:row;width:100%}.grid-inline tile-content .tile-content-wrapper .header share-menu,expanded-tiles tile-content .tile-content-wrapper .header share-menu{display:flex;margin-left:auto;margin-right:10px;align-items:center}.grid-inline tile-content .tile-content-wrapper .header time-phrase,expanded-tiles tile-content .tile-content-wrapper .header time-phrase{display:flex;margin-left:auto;align-items:center}.grid-inline tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper,expanded-tiles tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper{display:flex;flex-direction:column;margin-left:auto;padding-top:4px;padding-bottom:2px;align-items:end}.grid-inline tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon,expanded-tiles tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon{display:flex;flex-direction:row;align-items:center}.grid-inline tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon .icon-products,expanded-tiles tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon .icon-products{margin-right:6px}.grid-inline tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon share-menu,expanded-tiles tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper .share-menu-products-icon share-menu{display:flex;margin-left:auto;margin-right:0;align-items:center}.grid-inline tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper time-phrase,expanded-tiles tile-content .tile-content-wrapper .header .share-menu-timephrase-wrapper time-phrase{margin-top:auto}.grid-inline tile-content .tile-content-wrapper.light .user-info .user-link,expanded-tiles tile-content .tile-content-wrapper.light .user-info .user-link{color:#000}.grid-inline tile-content .tile-content-wrapper.dark .caption .caption-paragraph,expanded-tiles tile-content .tile-content-wrapper.dark .caption .caption-paragraph{color:#fff}.grid-inline tile-content .tile-content-wrapper.dark .user-info .user-link,expanded-tiles tile-content .tile-content-wrapper.dark .user-info .user-link{color:#fff}.grid-inline tile-content .tile-content-wrapper.dark .user-info .user-link .user-handle,expanded-tiles tile-content .tile-content-wrapper.dark .user-info .user-link .user-handle{color:#fff}.grid-inline tile-content .tile-content-wrapper.dark-weight .user-info .user-link,expanded-tiles tile-content .tile-content-wrapper.dark-weight .user-info .user-link{color:#fff;font-weight:600}.grid-inline tile-content .tile-content-wrapper.dark-weight .user-info .user-link .user-handle,expanded-tiles tile-content .tile-content-wrapper.dark-weight .user-info .user-link .user-handle{color:#fff;font-weight:400}.grid-inline tile-content .tile-content-wrapper.custom .user-info .user-link .user-name,expanded-tiles tile-content .tile-content-wrapper.custom .user-info .user-link .user-name{color:var(--text-tile-user-name-font-color);font-weight:600}.grid-inline tile-content .tile-content-wrapper.custom .user-info .user-link .user-handle,expanded-tiles tile-content .tile-content-wrapper.custom .user-info .user-link .user-handle{color:var(--text-tile-user-handle-font-color);font-weight:400}.grid-inline tile-content[orientation=vertical] .tile-content-wrapper .user-info,expanded-tiles tile-content[orientation=vertical] .tile-content-wrapper .user-info{flex-direction:column;gap:10px}.grid-inline tile-content[orientation=vertical] .tile-content-wrapper .user-info .avatar-wrapper,expanded-tiles tile-content[orientation=vertical] .tile-content-wrapper .user-info .avatar-wrapper,.grid-inline tile-content[orientation=vertical] .tile-content-wrapper .user-info .user-link,expanded-tiles tile-content[orientation=vertical] .tile-content-wrapper .user-info .user-link{margin-right:unset}.grid-inline shopspot-icon[mode=expanded] .fs-tag,expanded-tiles shopspot-icon[mode=expanded] .fs-tag{display:var(--shopspots-display-expanded)}.grid-inline shopspot-icon .stacklapopup-shopspot-badge,expanded-tiles shopspot-icon .stacklapopup-shopspot-badge{background:#fff;border-radius:50%;cursor:pointer;left:-12px;position:absolute;top:-12px;padding:5px 8px;text-align:center}.grid-inline shopspot-icon .fs-tag,expanded-tiles shopspot-icon .fs-tag{position:absolute;width:13px;height:13px;background:var(--shopspot-icon);color:#fff;display:var(--shopspots-display-inline);background-size:70%;border:solid #fff 1px;border-radius:50%;text-align:center;font-size:10px;font-style:normal;font-weight:500;z-index:10}.grid-inline shopspot-icon .fs-tag>.tooltip,expanded-tiles shopspot-icon .fs-tag>.tooltip{display:none;width:auto;background-color:#000;color:#fff;text-align:center;padding:3px;border-radius:.5px;position:absolute;z-index:1;top:25px;left:50%;transform:translateX(-50%);transition:opacity .3s;white-space:nowrap;line-height:12px;font-size:12px}.grid-inline shopspot-icon .fs-tag>.tooltip::after,expanded-tiles shopspot-icon .fs-tag>.tooltip::after{content:"";position:absolute;bottom:100%;left:50%;margin-left:-5px;border-width:5px;border-style:solid;border-color:rgba(0,0,0,0) rgba(0,0,0,0) #000}.grid-inline time-phrase,expanded-tiles time-phrase{display:flex}.grid-inline time-phrase .tile-timephrase,expanded-tiles time-phrase .tile-timephrase{color:#b9bab9;border-radius:2px;font-weight:400;display:inline-block;font-size:10px}.grid-inline share-menu .share-button,expanded-tiles share-menu .share-button{display:flex;cursor:pointer;margin-left:auto}.grid-inline share-menu .share-socials-popup-wrapper,expanded-tiles share-menu .share-socials-popup-wrapper{display:none;position:absolute;place-items:center;z-index:20}.grid-inline share-menu .share-socials-popup-wrapper::backdrop,expanded-tiles share-menu .share-socials-popup-wrapper::backdrop{background:rgba(0,0,0,.8);backdrop-filter:blur(5px)}.grid-inline share-menu .share-socials-popup-wrapper .panel-overlay,expanded-tiles share-menu .share-socials-popup-wrapper .panel-overlay{display:none}.grid-inline share-menu .share-socials-popup-wrapper .panel-overlay.active,expanded-tiles share-menu .share-socials-popup-wrapper .panel-overlay.active{display:block;background:#000;cursor:pointer;height:100vh;opacity:.8;position:absolute;width:100vw;z-index:6;top:0;left:50%;transform:translate(-50%, -50%)}.grid-inline share-menu .share-socials-popup-wrapper .share-socials-popup,expanded-tiles share-menu .share-socials-popup-wrapper .share-socials-popup{display:flex;flex-direction:column;width:351px;border-radius:5px;background:#fff;position:relative;z-index:7}.grid-inline share-menu .share-socials-popup-wrapper .share-socials-popup .share-modal-exit,expanded-tiles share-menu .share-socials-popup-wrapper .share-socials-popup .share-modal-exit{position:absolute;top:-12px;right:-12px;z-index:8}.grid-inline share-menu .share-socials-popup-wrapper .ugc-inline-share-buttons,expanded-tiles share-menu .share-socials-popup-wrapper .ugc-inline-share-buttons{align-items:baseline;display:flex;justify-content:space-between;padding:20px 19px}.grid-inline share-menu .share-socials-popup-wrapper .popup-text,expanded-tiles share-menu .share-socials-popup-wrapper .popup-text{text-align:center;font-size:14px;font-style:normal;font-weight:400;line-height:50%;border-bottom:solid #d6d4d5 1px;padding:20px 0 12px;color:#000}.grid-inline share-menu .share-socials-popup-wrapper .url-copy,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy{margin:0 19px 20px}.grid-inline share-menu .share-socials-popup-wrapper .url-copy .url-controls,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy .url-controls{display:flex;height:32px;align-items:flex-start;border-radius:3px;border:1px solid #d6d4d5;background:#fff}.grid-inline share-menu .share-socials-popup-wrapper .url-copy .share-url,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy .share-url{border:none;outline:none;background:#fff;margin:0;box-shadow:none;padding:8px 12px;width:100%;color:#4d4a4c;font-size:12px;font-weight:400}.grid-inline share-menu .share-socials-popup-wrapper .url-copy .copy-button,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy .copy-button{border:none;border-left:1px solid #d6d4d5;outline:none;background:#fff;box-shadow:none;cursor:pointer;height:100%;padding:9px 12px;color:#1f63ca;font-size:12px}.grid-inline share-menu .share-socials-popup-wrapper .url-copy .copy-button:hover,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy .copy-button:hover{background-color:#1f63ca;color:#fff}.grid-inline share-menu .share-socials-popup-wrapper .url-copy .copy-status,expanded-tiles share-menu .share-socials-popup-wrapper .url-copy .copy-status{display:none;color:green;font-weight:500;position:absolute;left:20px}@media(min-width: 577px)and (max-width: 992px){.grid-inline share-menu .share-socials-popup-wrapper,expanded-tiles share-menu .share-socials-popup-wrapper{top:30%}}.grid-inline .icon-section,expanded-tiles .icon-section{display:flex;position:absolute;width:100%;height:100%}.grid-inline .icon-section .top-section,expanded-tiles .icon-section .top-section,.grid-inline .icon-section .bottom-section,expanded-tiles .icon-section .bottom-section{display:flex;position:absolute;justify-content:space-between;z-index:1;width:100%;align-items:flex-end;flex-direction:row}.grid-inline .icon-section .top-section,expanded-tiles .icon-section .top-section{z-index:2;top:8px}.grid-inline .icon-section .top-section .content-icon,expanded-tiles .icon-section .top-section .content-icon{margin-left:8.67px}.grid-inline .icon-section .top-section .shopping-icon,expanded-tiles .icon-section .top-section .shopping-icon{margin-left:auto;margin-right:8.67px}.grid-inline .icon-section .bottom-section,expanded-tiles .icon-section .bottom-section{z-index:2;bottom:8px}.grid-inline .icon-section .bottom-section .network-icon,expanded-tiles .icon-section .bottom-section .network-icon{margin-left:auto;margin-right:8.67px}.grid-inline .icon-section .center-section,expanded-tiles .icon-section .center-section{position:absolute;display:flex;z-index:1;justify-content:center;width:100%;align-items:center;height:100%}:host{transition:ease all .5s}.grid{display:grid;justify-content:center;gap:var(--margin);grid-template-columns:repeat(auto-fit, minmax(var(--tile-size), 1fr));grid-auto-flow:row;margin-bottom:20px}.grid .ugc-tile{color:#fff;text-align:center;border-radius:var(--inline-tile-border-radius, 5px);position:relative;background-size:cover;background-position:top;background-repeat:no-repeat;height:100%;box-sizing:border-box;object-fit:cover;object-position:var(--image-position);overflow:hidden;width:100%;aspect-ratio:1/1}@media(max-width: 576px){.grid{grid-template-columns:repeat(auto-fit, minmax(min-content, 100%))}.grid .ugc-tile{height:auto}.grid .tile{aspect-ratio:1/1}}#nosto-ugc-container{background-color:var(--widget-background);padding:var(--margin)}#nosto-ugc-container .tile{background-size:cover;background-position:top;height:100%;width:100%;position:relative}#nosto-ugc-container .tile .icon-play{top:43%;left:0;right:0;margin:auto}
```


# Creating hover cards using blank canvas

* [Overview](#overview)
* [Sample #1](#card1)

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

## Overview

Nosto's UGC Blank Canvas Widget allows users to very quickly and easily design their own Widget templates to suit their organisation's requirements. Included on this page is several simple templates which can be used by customers who want a different way to render image and text content Tiles via Stackla.

Each of the designs have been inspired from [LittleSnippets.net](http://littlesnippets.net), an online blog which contains hundreds of free CSS3/HTML code snippets.

The code and samples below have been optimised for predominately Instagram content, however it can be easily adjusted based upon your requirements.

[Back to Top](#top)

## Sample #1

In this first sample we are introducing an Angle Line treatment which is present both on the Tile rest state and expands once the user hovers over the respective tile. In this design we introduce the Content Creator and the Tile text on mouse-over.

**Output**

```javascript
<script type="text/javascript">
    (function (d, id) { 
    var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; 
    if (el) el.dataset.initTimestamp = (new Date()).getTime(); 
    if (d.getElementById(id)) 
    return; t = d.createElement('script'); 
    t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; 
    t.id = id; (d.getElementsByTagName('head')[0] || 
    d.getElementsByTagName('body')[0]).appendChild(t); 
    }(document, 'stackla-widget-js'));
</script>

```

**Tile Markup**

```
<figure class="tile">
  <img src="{{image}}" alt="{{message}}" />
  <figcaption>
    <h3>@{{user}}</h3>
    <p>{{message}}</p>
  </figcaption>
  <a href="{{original_url}}" target="_blank"></a>
</figure>
```

**CSS**

```
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro);
@import url(https://fonts.googleapis.com/css?family=Teko:700);
.tile {
  background-color: #fff;
  color: #ffffff;
  display: inline-block;
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 16px;
  margin: 10px 5px;
  max-width: 400px;
  min-width: 400px;
  max-height: 400px;
  min-height: 400px;
  overflow: hidden;
  position: relative;
  text-align: right;
  width: 100%;
}
.tile *,
.tile *:before,
.tile *:after {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-transition: all 0.45s ease;
  transition: all 0.45s ease;
}
.tile img {
  backface-visibility: hidden;
  height: 400px;
  vertical-align: top;
}
.tile:before,
.tile:after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  content: '';
  background-color: #b81212;
  opacity: 0.5;
  -webkit-transition: all 0.45s ease;
  transition: all 0.45s ease;
}
.tile:before {
  -webkit-transform: skew(30deg) translateX(80%);
  transform: skew(30deg) translateX(80%);
}
.tile:after {
  -webkit-transform: skew(-30deg) translateX(70%);
  transform: skew(-30deg) translateX(70%);
}
.tile figcaption {
  position: absolute;
  top: 0px;
  bottom: 0px;
  left: 0px;
  right: 0px;
  z-index: 1;
  bottom: 0;
  padding: 20px 20px 20px 40%;
}
.tile figcaption:before,
.tile figcaption:after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #b81212;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.7);
  content: '';
  opacity: 0.5;
  z-index: -1;
}
.tile figcaption:before {
  -webkit-transform: skew(30deg) translateX(100%);
  transform: skew(30deg) translateX(100%);
}
.tile figcaption:after {
  -webkit-transform: skew(-30deg) translateX(90%);
  transform: skew(-30deg) translateX(90%);
}
.tile h3,
.tile p {
  margin: 0;
  opacity: 0;
  letter-spacing: 1px;
}
.tile h3 {
  font-family: 'Teko', sans-serif;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2em;
  text-transform: uppercase;
}
.tile p {
  font-size: 1.0em;
}
.tile a {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1;
}
.tile:hover h3,
.tile.hover h3,
.tile:hover p,
.tile.hover p {
  -webkit-transform: translateY(0);
  transform: translateY(0);
  opacity: 0.9;
}
.tile:hover:before,
.tile.hover:before {
  -webkit-transform: skew(30deg) translateX(30%);
  transform: skew(30deg) translateX(30%);
  -webkit-transition-delay: 0.05s;
  transition-delay: 0.05s;
}
.tile:hover:after,
.tile.hover:after {
  -webkit-transform: skew(-30deg) translateX(20%);
  transform: skew(-30deg) translateX(20%);
}
.tile:hover figcaption:before,
.tile.hover figcaption:before {
  -webkit-transform: skew(30deg) translateX(50%);
  transform: skew(30deg) translateX(50%);
  -webkit-transition-delay: 0.15s;
  transition-delay: 0.15s;
}
.tile:hover figcaption:after,
.tile.hover figcaption:after {
  -webkit-transform: skew(-30deg) translateX(40%);
  transform: skew(-30deg) translateX(40%);
  -webkit-transition-delay: 0.1s;
  transition-delay: 0.1s;
}
```

[Back to Top](#top)


# Dynamically specify products to display in widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/dynamically-specify-products-to-display-in-widget) here.
{% endhint %}

## Overview

Nosto's UGC Social Commerce product is a very powerful resource to an eCommerce site's developer's toolkit. Not only can Nosto's UGC provide direct linking between social media content and a product (or even SKU), those same relationships can be leveraged by the eCommerce CMS or page to specify the content that should be surfaced. By modifying the embed code to specify the products, categories or brands, it is a great complement to the Filter associated with a Widget.

This guide will explore how to filter the widget to be displayed in a Product, Category or Brand pages by only showing content related to those pages.

## Key concepts

### Dynamic Filtering

Dynamic Filtering unlocks special features of Nosto's UGC, outlined in this guide. You will need to have this feature enabled to complete this guide.

### Social Commerce

The Social Commerce product unlocks special features of Nosto's UGC to create Product Tags and other behaviour outline in this guide.

### Product Tags

Like other Tag types in Nosto's UGC, they can be attached to a Tile, in either a 1-to-1 or 1-to-many fashion.

### Widget

Widgets that are embedded for Stacks with Dynamic Filtering enabled have the ability to have their filters augmented with specific Tags.

### Filter

Nosto's UGC Filters drive the content being displayed in the Widget. Dynamic Filtering features allow Tags to be specified to overwrite the Tags chosen for the Filter.

### Example Application

In this guide we will not be creating an application, but really just an example snippet being modified by an imaginary eCommerce system. This translates to following the pattern of a view being passed a variable containing data about the products or product groups we are interested in and the rendering of the widget.

We are assuming that the Stack already has content in it that has been tagged with the relevant Product Tags and that each Product Tag has a Category and/or Brand associated to it.

## The Fun Part

### Getting Started

In this example we will need to do the following:

1. Create a Filter
2. Create a Widget
3. Review relevant tags
4. Prepare embed code for Product page
5. Prepare embed code for Category page
6. Prepare embed code for Brand page

### Create a Filter

Creating a Filter in Nosto's UGC is super easy and outlined in [this Knowledgebase article](http://help.nosto.com/en/articles/5793303-how-to-create-a-filter). Assuming that we would like to only showcase product images, select the following options:

* Name it "Products"
* Set it to sort by "Latest"
* We only want pictures, so we set the Image Type to "Image"

The rest of the options can be left to default.

### Create a Widget

We will be using one Widget that will be customised via the eCommerce system's view. For this reason we just need one Widget and will augment its embed code to specify the product, category or brand to be used.

Create a Waterfall Widget and set its filter to "Products". Save and observe the preview with your content.

### Review relevant Tags

To demonstrate the use of Product Tags, we assume that there are already 2 product tags that we will use for tagging with the following details:

1. **Tag of type:** Product, **ID:** 101, **Name:** "Blue T-Shirt", **URL:** "<http://example.com/product/blue-t-shirt>", **External Product ID:** "13456743", **Category:** \["T-Shirt"], **Brand:** Nike
2. **Tag of type:** Product, **ID:** 102, **Name:** "Black T-Shirt", **URL:** "<http://example.com/product/black-t-shirt>", **External Product ID:** "17456941", **Category:** \["T-Shirt"], **Brand:** Patagonia

To view these details, enter the Admin Portal > Engage > Commerce.

We are also assuming that content has been previously tagged with them.

### Prepare embed code for Product Page

There are many different ways in which eCommerce systems generate product pages and product listings. We are assuming a very simple CMS pattern that has one generic view for a product page in this case for the product "Blue T-Shirt"

When deploying the embed code for a product page, products can be specified in two ways:

1. To use their Visual UGC IDs: 101, 102 and 103; or
2. To use their internal (external to Visual UGC) ID, prefixed with "ext:" which are: "ext:13456743", "ext:17456941"

In the embed code the products are specified by adding a `data-tags` parameter to the code and outputting the products as a comma separated list.

```html
<div id="stackla-widget">
    <!-- BEGIN: Your widget code -->
    <div class='stacklafw' data-tags='ext:13456743' data-id='1234' data-hash='aabbccddeeff' data-ct='' data-ttl="30" ></div>
    <script type='text/javascript'>
        (async () => {
            const widget = await import('https://widget-ui.stackla.com/core.esm.js');
            widget.init();
        })();
    </script>
    <!-- END: Your widget code -->
</div>
```

Specifying the data string above as `ext:13456743` would result in the same output if the products were specified using the Visual UGC product tag id `101.`Visual UGC will interpret the `ext:` protocol as specifying the External Product ID and anything else as a Visual UGC Tag ID.

**Note about specifying Tags:**

You may specify up to 10 Tags and the Tags that you specify will overwrite any specified for the Filter. Further you can specify a combination of Product and Content Tags.

### Prepare embed code for Category Page

There are many different ways in which eCommerce systems generate category pages. We are assuming a very simple CMS pattern that has one generic view for a category page in this case for the category "T-Shirt"

When deploying the embed code for a category page, you will need to specify the category as saved in the product tag

In the embed code the categories are specified by adding a `data-categories` parameter to the code and outputting the categories as a comma separated list.

```html
<div id="stackla-widget">
<!-- BEGIN: Your widget code -->
<div class="stackla-widget" **_data-categories='T-Shirt'_** data-id='1234' data-hash='aabbccddeeff' data-ct='' data-ttl="30" ></div>
    <script type="module">
        (async () => {
            const widget = await import('https://widget-ui.stackla.com/core.esm.js');
            widget.init();
        })();
    </script>
<!-- END: Your widget code -->
</div>
```

Using this dynamic filter will result in the widget only showing content that was tagged with products that match the "T-Shirt" category.

**Note about specifying Categories:**

You may specify up to 10 Categories.

### Prepare embed code for Brand Page

There are many different ways in which eCommerce systems generate brand pages. We are assuming a very simple CMS pattern that has one generic view for a category page in this case for the category "Nike"

When deploying the embed code for a brand page, you will need to specify the brand as saved in the product tag

In the embed code the categories are specified by adding a `data-brand` parameter to the code and outputting the categories as a comma separated list.

```html
<div id="stackla-widget">
    <div class="stackla-widget"  data-brand='Nike' data-id='1234' data-hash='aabbccddeeff' data-ct='' data-ttl="30" ></div>
    <script type="module">
        (async () => {
            const widget = await import('https://widget-ui.stackla.com/core.esm.js');
            widget.init();
        })();
    </script>
</div>
```

Using this dynamic filter will result in the widget only showing content that was tagged with products that match the "Nike" brand.

**Note about specifying Brands:**

You may specify up to 10 Brands.


# Dynamically specify tile to display in widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/dynamically-specify-tile-to-display-in-widget) here.
{% endhint %}

* [Introduction](#introduction)
* [Key Concepts](#key-concepts)
* [The Fun Part](#fun-part)
* [Creating a Widget](#widget)
* [Find a Tile ID](#tile-id)
* [Widget code for reference](#embed-code)
* [End Result](#end-result)

## Introduction

Through Nosto's UGC Dynamic Filtering functionality, customers have the ability to customise the outputs of their Widgets based upon their relevant use-case by overwriting the Filter and/or the Tags associated with that Filter to generate a more relevant output.

Now whilst the ability to change what content is shown at a Filter or Tag level is incredibly useful for clients attempting to generate relevant outputs for their eCommerce site or generate a personalised display via their DMP, there will of course be times where a client needs to get more granular and specific around what content they wish to display on their outputs.

This is where Nosto's UGC ability to specify exactly which Content Tiles are displayed in a Widget can become extremely useful.

This guide will explore a simple use-case of where a client wishes to display a single Tile in their Widget.

[Back to Top](#top)

## Key Concepts

### Dynamic Filtering

Dynamic Filtering unlocks special features of Nosto'sNosto's UGC, outlined in this guide. You will need to have this feature enabled to complete this guide.

### Widgets

Widgets that are embedded for Stacks with Dynamic Filtering enabled have the ability to overwrite what Tiles are rendered in their Widgets.

### Tile ID

The Tile ID is a unique 24 character alphanumeric unique ID generated by Nosto's UGC for every pieces of content aggregated onto the Platform.

[Back to Top](#top)

## The Fun Part

In this example we will need to do the following:

1. Creating a Widget
2. Finding a Tile ID
3. Updating the Widget Embed Code

[Back to Top](#top)

## Creating the Widget

Similar to our guide on [Dynamically Specifying Products to Display in a Widget](/ugc/guides/widgets-nextgen/widget-creation-guides/dynamically-specify-products-to-display-in-widget) we will only need to create one Widget for this scenario, and will simply augment its embed code to specify which Tile(s) to display.

As such, simply create a Waterfall Widget, leave its filters to ‘Latest’ and Save. The preview should show you a Widget with a series of Tiles in it.

[Back to Top](#top)

## Find a Tile ID

There are a number of ways in which you can find a Tile’s Tile ID. Ranging from using the REST API, to Webhooks to simply viewing the Tile data in the Curate Content section of Stackla.

For this guide, we are just going to collect it manually via the Curate Content section of Stackla.

To source, simply find the tile you wish to get the ID for, click on the overflow menu and select ‘Data’.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/IGxOZcHNnoWdRGABnMLf/screen1-1.png)

This will load up the Tile Data and we can grab the ID from the Data modal. In the below example, the ID of the tile is `599e0c3de09ca7e44bf290f1`

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/0H5px9uBGbLW76NHJiBG/screen2-1.png)

[Back to Top](#top)

## Update the Widget Embed Code

As we mentioned earlier, we will be using a default Waterfall Widget, leveraging the latest filter for this example. As such, we will be presented with embed code similar to the below:

```html
<div class="stackla-widget" data-hash="<hash>" data-id="<id>" data-ttl="30" style="width: 100%; overflow: hidden;"></div>
<script type="module">
    (async () => {
        const widget = await import('https://widget-ui.teaser.stackla.com/core.esm.js');
  		widget.init();
    })();
</script>
```

Now to change what content is displayed in the Widget, all we need to do is add the parameter data-tile-id to the DIV section of the embed code and then include the Tile ID(s) that we wish to render.

For example, to showcase the one tile which we collected earlier, we would add `data-tile-id=”599e0c3de09ca7e44bf290f1”`, and of course if we wanted to showcase two Tiles we would simply add a comma between the Tile IDs.

[Back to Top](#top)

## End Result

Rendered below is the Nosto's UGC Waterfall Widget rendering the single Tile as listed in this guide:

```html
<script type="text/javascript">(function (d, id) { var t, el = d.scripts[d.scripts.length - 1].previousElementSibling; if (el) el.dataset.initTimestamp = (new Date()).getTime(); if (d.getElementById(id)) return; t = d.createElement('script'); t.src = '//assetscdn.stackla.com/media/js/widget/fluid-embed.js'; t.id = id; (d.getElementsByTagName('head')[0] || d.getElementsByTagName('body')[0]).appendChild(t); }(document, 'stackla-widget-js'));</script>
```


# Creating your first widget

{% hint style="warning" %}
You are reading the NextGen Documentation

You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/blank-canvas/creating-your-own-widget-type-by-using-the-blank-canvas-widget) here.
{% endhint %}

**When creating a UGC widget, there are two main options to consider.**

[Utilise a premade template, by navigating to the widget template gallery](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-the-gallery)

In this option, you are able to select a widget which our team is constantly maintaining.

We've tried and tested these widgets, and are continually working to keep them bug-free for your enjoyment.&#x20;

By selecting a widget from the widget gallery, you are automatically receiving the latest updates to the template you have selected.

[Utilise our widget engine to build your own widget, via the **Blank Canvas** widget offering. (Advanced)](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide)

This is the more advanced route, as it requires prior experience as a developer to build out your dream template.

Our advanced widget SDK will ensure that development is versatile, and following the latest trends in software engineering.

By utilising blank canvas, you choose to opt out of the latest updates to given templates such as waterfall, and instead choose to build and maintain your own template, while relying on our core widget engine to allow swift building of templates.


# Creating a widget from the gallery

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

**Creating a premade template**

1\) Click the Touchpoints > Widgets link to access the Widgets screen.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FlRrC1xOtij9NP3EEkP9Z%2Fimage.png?alt=media&amp;token=7b0b274f-b1f7-4b3e-9f70-5695588c036a" alt="" width="127"><figcaption></figcaption></figure>

2\) Press the *Create New Widget* button

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FVRbewn7si3R9LNOh1WOQ%2Fimage.png?alt=media&amp;token=116ff013-02eb-4847-a43c-f29394749b3e" alt=""><figcaption></figcaption></figure>

3\) Choose your widget of choice from the gallery<br>

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Frls8GLhoMb1uuDSBhEUQ%2Fimage.png?alt=media&amp;token=318e63ab-861c-4f00-a2c5-6a9314867c52" alt=""><figcaption></figcaption></figure>

4\) Enjoy your new widget!

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FkuT7QRzjuRQYIx8ViK7x%2Fimage.png?alt=media&amp;token=6623a80e-a2fc-44e0-88b7-e3ba2fe5bf6c" alt=""><figcaption></figcaption></figure>


# Creating a widget from Blank Canvas (Developer Guide)

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

Creating a widget from blank canvas can be quite technical in nature.

It is recommended that you utilise a developer, or a team of developers to create a blank canvas widget.

You can utilise the tabs on the left hand side, in order - to setup a development environment to begin your UGC Blank Canvas widget. The guides should run you through how to create a basic widget start to finish.


# Creating a development space for your team

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

While development of widgets in UGC Widgets 2.0 primarily took place in the browser, NextGen Widget development will primarily take place in the IDE.

As mentioned in the [Architecture of widgets](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-new-widget/architecture-of-widgets), there are two repositories responsible for widget development.

While we encourage contribution to the widget-utils repository, the stackla-widget-templates repository is created for teams to [**Fork**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo)**.**&#x20;

Forking allows engineers to utilise our widget templates repository in their own team setting, and allow widgets to be created without sharing them in the UGC repository.

This is the ideal way to encourage your developers to contribute in a [linted](https://eslint.org/), [typechecked](https://www.typescriptlang.org/) & [tested](https://www.cypress.io/) environment.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2F0xQPZC9DWTVs6VKxsODp%2Fimage.png?alt=media&amp;token=9506f4a9-fbb3-4f0b-9bf1-cfaaa6b2e37f" alt=""><figcaption></figcaption></figure>

So what are you waiting for? Click that Fork button and build some amazing widget templates!


# Setting up the IDE

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

Once you have read our guide on [Creating a development space for your team](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-development-space-for-your-team), you can begin setting up your IDE for widget template development.

To get started, follow the instructions below

1\) Clone your Forked repo of stackla/stackla-widget-templates. You can do this by executing&#x20;

`git clone https://your-github-repo-here`

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FIUE1CTjDhn9ClIp7Isar%2Fimage.png?alt=media&amp;token=4aafe1cb-d284-4f42-8297-2f457afaf844" alt=""><figcaption></figcaption></figure>

2\) Utilise a code editor such as [VSCode](https://code.visualstudio.com/) to prepare your IDE for development

3\) Inside the folder, fetch the latest widget-utils repository submodule:&#x20;

**NOTE:** If you would like to use your own widget utilities fork, you can also update the **.gitmodules** file and place the URL to your git repository there.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FJnNjCSL4KOcZukto2T82%2Fimage.png?alt=media&amp;token=4f440593-a2bf-4804-96d1-f16977c39e10" alt=""><figcaption></figcaption></figure>

**Execute the following;**

`git submodule init && git submodule update`

4\) Install all packages by executing: `npm install`

5\) Execute `npm run start` to see our gallery of widgets in action.

6\) Access the following URL, substituting the widgetType with any widget you wish to preview.

{% embed url="<https://localhost:4003/preview?widgetType=carousel>" %}

Congratulations! You are now ready to start developing your first widget.&#x20;

Lets get started.

[Creating a starter project](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-starter-project)


# Creating a starter project

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

## Starter project activity

This project has been created with the sole purpose of:

1. Allowing you to see what is possible with widgets
2. A space for you to follow the activity provided to learn about how you can customise widgets with ease

### What you will learn

1. How to adjust styles for inline tiles & expanded tiles
2. How to adjust templates for inline tiles & expanded tiles.
3. How to hook into emitted events
4. How to apply dynamic css variables

### Getting started with the activity

* You will notice this widget is bouncing. Please stop the circle from bouncing.
* Why is the widget in a circle? Please remove the circle and ensure it is in a grid format, covering the entire screen.
* The border of the circle is based on the mood given to the method. Please update it so that it is using 'nosto' mood with a pink border to represent Nosto.
* Every time I expand a tile, a dog appears. Please change this to the actual tile image.
* Every time an event is emitted, a toast message appears, please remove this.

A successful attempt at this activity will be something like the following.&#x20;

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FkZwpOEJrFyEnkNjzCgYt%2Fimage.png?alt=media&amp;token=5c88203d-106f-4be5-8884-3bf1ee3925d6" alt=""><figcaption></figcaption></figure>

**Answers:**

* To stop the circle from bouncing, remove `@keyframes bounce { }` in extras.scss
* To remove the circle, remove the `border-radius` property in `extras.scss`
* To update the border color to the Nosto mood, update the `"--my-mood-border"` variable in the widget.tsx file.
* To change the dog image to the actual tile image, update the `tile.template` file to instead use the `tile.image` property.
* To remove the toast message, remove the eventListener in widget.tsx


# Repository Structure

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

**Stackla Widget Templates repo**

The Stackla Widget Templates repository is organised with the following structure

**/widgets**

The widgets folder is where all widgets, and widget related code is contained. This is where you will be doing a majority of your work.

Each subfolder in the widgets folder represents a widget. Except for the styles folder, which is where all shared widget styles are contained.

When developing a widget, you can copy a simple widget to begin with, i.e. grid folder, and make tweaks as you go to develop your dream widget.

The basic composition of a widget looks like this:

\
![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FgMptns2MOZmNDkzRKHj8%2Fimage.png?alt=media\&token=14ea8951-b3de-4792-8f2e-97d771aac8a7)

* widget.scss - This is the root styles file, where all necessary style changes will be made. You can follow SCSS conventions to import other files as required.
* layout.hbs, tile.hbs are utilised for the server side rendered HTML associated with the tiles.
* widget.tsx - This is the root TS file, where all necessary widget code is integrated.

**/packages/widget-utils**

This utilities package is where we handle most of the complexities for the developers, so they don't have to.

In the case that you have changes that you'd like to recommend, please do open a pull request to help us improve our product and the experience for other engineers.

<div align="left"><figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FowY4MTv5kyOYWfC2fVHQ%2Fimage.png?alt=media&amp;token=4976bc8f-1b60-4c22-a54b-b185cdb84581" alt="" width="254"><figcaption></figcaption></figure></div>


# Understanding Handlebars

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

Handlebars is utilised as our server side rendering engine to ensure that HTML is rendered before it reaches the user's screen.

You will notice two core files in your stackla-widget-templates repository

**1) widgets/{widget-name}/layout.hbs**

**2) widgets/{widget-name}/tile.hbs**

The `tile.hbs` file serves as a Handlebars template for rendering individual tiles. It allows you to modify how **one** tile looks in the layout

```handlebars
{{#tile class="grid-item"}}
<div class="tile" data-background-image="{{image_thumbnail_url}}">
  <div class="icon-section">
    <div class="top-section">
      {{#each attrs}}
      {{#ifequals this "instagram.reel"}}
      <div class="content-icon icon-reel"></div>
      {{/ifequals}}
      {{/each}}
      {{#if tags_extended.length}}
      <div class="shopping-icon icon-products"></div>
      {{/if}}
    </div>
    <div class="center-section">
      {{#ifequals media "video"}}
      <div class="icon-play"></div>
      {{/ifequals}}
    </div>
    <div class="bottom-section">
      <tile-tags tile-id="{{id}}" variant="dark" mode="swiper" context="grid-inline"></tile-tags>
      <div class="network-icon icon-{{source}}"></div>
    </div>

    <shopspot-icon tile-id={{id}} />
  </div>
</div>
{{/tile}}
```

The `layout.hbs` file serves as the main template for rendering the overall layout of your widget. It extends from a parent template and provides additional functionality to customize the layout.

Here's an excerpt from the `layout.hbs` file:

```handlebars
<div class="ugc-tiles">
    {{#each tiles}}
    <div class="tile" data-id="{{id}}">
      {{parent}}
    </div>
    {{/each}}
  </div>
```

There are a few helpers you can utilise in your templating, they are the following:

* {{#Ifequals}} - Checks if two values are equal to one another
* {{#tile}} - Creates a tile with the recommended classes to operate with a widget&#x20;
* {{#if}} - Check if a value is true.
* {{#tiles}} - Loop through each tile

To understand how to best utilise the attributes available to you, please have a look at the [Composition of tiles](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-new-widget/architecture-of-widgets/composition-of-tiles) information sheet.


# Creating a new widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

The `loadWidget` function is used to initialise and configure a widget in your application. It takes an optional `settings` object as a parameter, which allows you to customise the widget's behavior.

Please utilise the guides below to understand what customisations are supported more specifically

{% content-ref url="/pages/pYodlWPbPNWLkZv0LoeC" %}
[Features](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-new-widget/features)
{% endcontent-ref %}

{% content-ref url="/pages/hI915vs63FSIDl7xoUMN" %}
[Event Listeners](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-new-widget/event-listeners)
{% endcontent-ref %}

{% content-ref url="/pages/B2GtEKVjYu7IyE8tRogt" %}
[Templates](/ugc/guides/widgets-nextgen/widget-creation-guides/creating-your-first-widget/creating-a-widget-from-blank-canvas-developer-guide/creating-a-new-widget/templates)
{% endcontent-ref %}


# Architecture of widgets

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

**Repositories**

The repositories being utilised across the widget development pipeline include:<br>

**Templates Repository**

The purpose of this repository is to allow the development of widget templates.

This repository contains the SCSS, TSX and Handlebars templates utilised to create the templates.

The logic here is intentionally separated from framework level code to keep development simple. To manage your widget, we would recommend copying the blankcanvas widget folder and rename it to a folder name suitable for your new widget.

* <https://github.com/stackla/stackla-widget-templates>

**Widget Utilities Repository**

The purpose of this repository is to create utilities that engineers can maintain and share across their projects. We welcome any contributions to this repository for tools that may be helpful in the development of widgets.

There is also some widget configuration based logic utilised here to allow the framework to hook into the widget templates and respect given properties, for instance - when colors are defined in the widget settings screen of my.stackla.com, they should be managed in the widget-utils repository.

* <https://github.com/Stackla/widget-utils>

**Shadow Root**

A shadow root is defined for the widget, which enables the widget to remain isolated from the rest of the DOM.&#x20;

The Shadow Root is accessible for mutation from external JS manipulation if required, as its an **'open'** Shadow Root.

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FiFr3LDeismqTB80yHEXD%2Fimage.png?alt=media\&token=ad71af31-0865-4e1c-a7f3-4976e5524fef)

To mutate the shadow root, users can either utilise the prebuilt **SDK** or manually target the ugc-widget div.&#x20;

Best practice would ensure mutation logic is isolated in the widget code.

**Server Side Rendering**

The initial load of the widget is server side rendered, along with future tile retrieval.

An initial request is made to the widget server which retrieves the configuration associated with the widget, the HTML rendered from the Handlebars templates, and all of the JS & CSS.

**Tiles Rendering**

Depending on the widget you utilise, tiles are automatically rendered on the screen based on a predetermined algorithm.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FWs5EXoXGE9bPOB9DUmys%2Fimage.png?alt=media&amp;token=226af002-5561-4762-9a85-fdf1524860c8" alt=""><figcaption></figcaption></figure>

Each tile is rendered using a **.ugc-tile** div. These elements can be mutated as necessary.

Tiles are rendered by utilising the [tiles service](https://github.com/Stackla/widget-utils/blob/master/src/types/services/tiles.service.ts), and users can access the tiles service by utilising sdk.tiles.

**Advanced Usage**

**Globals**

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FE4JxPQfZywyGRdUP53aK%2Fimage.png?alt=media&amp;token=7c6a9cae-ee7c-431c-8a82-480f18a08020" alt=""><figcaption></figcaption></figure>

Globals are located in the window\.ugc namespace - this is where all widgets can be accessed if required.

To access a widget's SDK outside of the widget tsx, a user can utilise **window\.ugc.getWidgetBySelector().sdk.** This is only recommended when testing the widget within the browser, or checking the values of properties in the global object.

**Placement**

What you see on the screen is the placement. The placement contains the widget, and any associated configuration responsible for the display of the widget.

The [placement](https://github.com/Stackla/widget-utils/blob/master/src/types/core/placement.ts) can be accessed with **sdk.getPlacement().**

**Events**

Events are utilised to enable the interaction between different decoupled components. For instance, when the Load More button is clicked, a moreLoad event is invoked, which can be listened to by multiple listeners to trigger side effects.

The [events service ](https://github.com/Stackla/widget-utils/blob/master/src/types/services/event.service.ts)can be accessed with **sdk.getEvents()**


# Composition of tiles

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

**Tile**

A **Tile** is the central entity in Visual UGC, representing a curated piece of user-generated content. Tiles are enhanced with metadata, media, tags, and interactivity, enabling seamless integration into marketing campaigns or other content strategies.

* **Core Attributes**:
  * `preloaded` (boolean, optional): Indicates if the Tile is preloaded in Visual UGC.
  * `_id` (object, optional): MongoDB-style object containing `$id` (string).
  * `carousel` (number): Position of the Tile in a Stackla carousel.
  * `created_at` (number): Timestamp when the Tile was added to Stackla.
  * `disabled` (boolean): Indicates if the Tile is disabled from public display.
  * `hotspots` (array of Hotspots, optional): Interactive regions within the Tile for tagging products or adding links.
  * `image` (string): URL of the primary image in the Tile.
  * `avatar` (string): URL of the avatar image associated with the original user/content creator.
  * `image_expire_at` (number): Expiration timestamp for the Tile's primary image.
  * `lang_detection_type` (string, optional): Detected language of the content within the Tile.
  * `media` (string): Media type for the Tile (e.g., image, video).
  * `message` (string): Caption or text description associated with the Tile.
  * `name` (string | null): Name or title of the Tile (often based on the source).
  * `original_url` (string): Original media source URL (e.g., link to an Instagram or Twitter post).
  * `original_link` (string): Link back to the original content on the platform it was pulled from.
  * `page_post` (boolean): Indicates if the Tile originated from a page post.
  * `reviewed` (boolean): Marks whether the Tile has been reviewed for moderation or tagging.
  * `score` (number): Sentiment or engagement score of the Tile.
  * `source` (string): The content's originating platform (e.g., Instagram, Twitter).
  * `tags` (array of strings): Tags applied to the Tile for categorization or marketing.
  * `tags_extended` (array of TagExtended, optional): Enhanced metadata for each tag.
  * `terms` (array of strings): Terms or keywords associated with the Tile.
  * `video` (Video, optional): Metadata about video content for the Tile.
  * `video_files` (array of VideoFile, optional): Video file details for playback or export.
  * `original_image_url` (string): URL of the unaltered original image from the source platform.
  * `embed_url` (string): Embed URL for the Tile, allowing integration with external sites.
  * `youtube_id` (string): YouTube video ID, if the Tile references a YouTube video.
  * `title` (string): Title or heading for the Tile.
  * `full_embed_html` (string): Full HTML snippet for embedding the Tile into external content.
  * `attrs` (array of strings): Additional attributes or metadata for the Tile.

***

**Hotspot**

A **Hotspot** is an interactive region within a Tile, designed for tagging products or adding links. These are often used to highlight shoppable elements within UGC.

* **Attributes**:
  * `id` (number): Unique identifier for the Hotspot.
  * `x` (number): X-coordinate for the Hotspot's position within the Tile.
  * `y` (number): Y-coordinate for the Hotspot's position within the Tile.
  * `width` (number): Width of the interactive area.
  * `height` (number): Height of the interactive area.
  * `type` (string): Type of Hotspot (e.g., product link, custom link).
  * `custom_url` (string): Custom URL assigned to the Hotspot for redirection.
  * `product_link_attribute` (string): Product-specific attributes for shoppable Hotspots.
  * `tag` (TagExtended): Associated metadata tag for the Hotspot.
  * `coords` (array of numbers): Coordinates defining the Hotspot's position and size.

***

**TagExtended**

The **TagExtended** type represents a comprehensive tagging system that combines basic tagging functionality with additional metadata for products, categories, and campaigns. These tags are often used to link user-generated content with specific marketing initiatives.

* **Attributes**:
  * `id` (string): Unique identifier for the tag.
  * `active` (boolean, optional): Indicates whether the tag is currently active.
  * `auto_apply` (boolean, optional): Indicates if the tag is automatically applied based on certain criteria.
  * `availability` (number): Availability status of the tagged item.
  * `availability_status` (string, optional): Additional information about item availability.
  * `brand` (string, optional): Brand associated with the tag.
  * `categories` (string, optional): Categories linked to the tag.
  * `created_at` (string, optional): Timestamp when the tag was created.
  * `cta_text` (string, optional): Call-to-action text linked to the tag.
  * `currency` (string, optional): Currency format for the price.
  * `custom_fields` (string, optional): Custom metadata fields for the tag.
  * `custom_slug` (string, optional): Custom slug for the tag.
  * `custom_url` (string): URL linked to the tag.
  * `description` (string, optional): Description of the tagged item.
  * `ext_product_id` (string, optional): External product ID associated with the tag.
  * `image` (string, optional): URL of the image representing the tag.
  * `is_cross_seller` (boolean): Indicates if the tag supports cross-selling.
  * `price` (string): Price associated with the tag.
  * `type` (string): Type of the tag (e.g., product, campaign).
  * `variant` (array of Variant, optional): List of product variants linked to the tag.

***

**Variant**

A **Variant** represents a specific version of a product, allowing multiple configurations to be associated with a single tag.

* **Attributes**:
  * `id` (string): Unique identifier for the variant.
  * `availability` (string): Availability status of the product variant.
  * `brand` (string): Brand name associated with the variant.
  * `condition` (string): Product condition (e.g., new, used).
  * `description` (string): Description of the product variant.
  * `google_product_category` (string): Google-defined product category.
  * `group` (string): Identifier for product grouping.
  * `image_link` (string): URL of the product variant's image.
  * `item_group_id` (string): Group ID for related variants.
  * `link` (string): Product page URL for the variant.
  * `price` (string): Price of the product variant.
  * `title` (string): Title of the product variant.
  * `via_feed` (number | null): Indicates whether the variant was imported via a product feed.

***

**Video**

A **Video** object contains details about standard-resolution video content for a Tile.

* **Attributes**:
  * `standard_resolution` (object):
    * `url` (string): URL of the video in standard resolution.

***

**VideoFile**

The **VideoFile** type provides metadata for individual video files, enabling playback or download.

* **Attributes**:
  * `url` (string): URL of the video file.
  * `mime` (string): MIME type of the video file (e.g., `video/mp4`).
  * `size` (number): Size of the file in bytes.
  * `width` (number): Video width in pixels.
  * `height` (number): Video height in pixels.
  * `duration` (number): Duration of the video in seconds.


# Features

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

#### Features

***

Widgets can have various features that enhance their functionality. Some common features include:

* **tileSizeSettings** : Allows you to customize the size of each tile.
* **cssVariables** : Enables custom CSS variables for your widget.
* **handleLoadMore** : Determines whether more tiles should be loaded when the user scrolls to the end of the list.
* **limitTilesPerPage** : Limits the number of tiles displayed per page.

By default, many features are enabled, and they can be toggled off as required.

More features can be found here:

[**https://github.com/Stackla/widget-utils/blob/faa49c50d77a5b4554e80c5cef012c6e4e5ed3fc/src/widget-loader.ts#L22**](https://github.com/Stackla/widget-utils/blob/faa49c50d77a5b4554e80c5cef012c6e4e5ed3fc/src/widget-loader.ts#L22)

You can add these features/toggles by setting their corresponding properties in your widget settings. For example:

```
const myWidgetSettings = {
  features: {
    tileSizeSettings: {
      small: "173px",
      medium: "265.5px",
      large: "400px"
    },
    cssVariables: {
      "--my-mood-border": getMyMoodBorder()
    }
  }
};

loadWidget(myWidgetSettings);
```


# Event Listeners

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

#### Events

***

Widgets support various events that can be triggered during different stages of their lifecycle. Here are some common events:

* **onLoad** : Triggered when the widget is fully loaded.
* **onTilesUpdated** : Triggered when the tile data changes.
* **onResize** : Triggered when the user resizes the widget.
* **onLoadMore** : Triggered when more tiles need to be loaded.
* **onTileExpand** : Triggered when a tile is expanded or collapsed.
* **onTileClose** : Triggered when a tile is closed.

Events are frequently updated, and can be found here:\
<https://github.com/Stackla/widget-utils/blob/3a7e9585700ea8b556ff266c3d69fa1aaff4eb84/src/events/index.ts>

You can attach custom callback functions to these events using the `callbacks` property in your widget settings. For example:

```
const myWidgetSettings = {
  callbacks: {
    onLoad: () => console.log('Widget loaded!'),
    onTilesUpdated: () => console.log('Tile data updated!')
  }
};

loadWidget(myWidgetSettings);
```


# Templates

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

#### Templates

***

Template customisations can be made to individual web components, to ensure they generate UI according to your needs.

By default, each web component has its own layout, which renders when that web component is loaded into the screen.

Currently, we have a few web components which can be customised.

```json
  "expanded-tile",
  "products",
  "shopspots",
  "add-to-cart",
  "load-more",
  "tile-content",
  "timephrase",
  "share-menu",
  "tags"
```

These components can be located in the DOM.

In order to add customisations, one can copy of one of the sample templates and begin making changes to the JSX as required.

For example, to modify the load-more web component, we may do something like the following:

**1) Create a template**

```
import { createElement } from "@stackla/widget-utils"

export default function LoadMoreTemplate() {
  return (
    <div id="buttons">
      <a id="load-more">I have changed the load more button to look like this!</a>
    </div>
  )
}
```

**2) Utilise load widget with the customisation in place**

```
import { loadWidget } from "@stackla/widget-utils"
import { LoadMoreTemplate } from "./load-more.template"

loadWidget({
  templates: {
    "load-more": {
      template: LoadMoreTemplate
    }
  }
})
```

**3) Enjoy your new load more button!**


# Configuring your widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

When using the blank canvas widget, we do not have configuration on the backend available for your use.

All configuration is now managed in the frontend by the developer.

You will find a config.ts available with the following data & documentation.

````typescript
```typescript
import { ExpandedTileOptions, InlineTileOptions, Style } from "packages/widget-utils/dist/esm"

export const config: {
  style: Style
  inlineTile: InlineTileOptions
  expandedTile: ExpandedTileOptions
} = {
  style: {
    // The background color of the widget, uses var(--widget-background) in css-variables.ts
    widget_background: "#ffffff",
    // The background color of the tile, uses var(--text-tile-background) in css-variables.ts
    text_tile_background: "#ffffff",
    // The font color of the tiles, uses var(--text-tile-font-color) in css-variables.ts
    text_tile_font_color: "#000000",
    // The font color of the user handle, uses var(--text-tile-user-handle-font-color) in css-variables.ts
    text_tile_user_handle_font_color: "#636062",
    // The font color of the user name, uses var(--text-tile-user-name-font-color) in css-variables.ts
    text_tile_user_name_font_color: "#ffffff",
    // The font size of the tile contents, uses var(--text-tile-font-size) in css-variables.ts
    text_tile_font_size: "10",
    // The font size of the user handle, uses var(--text-tile-user-handle-font-size) in css-variables.ts
    text_tile_user_handle_font_size: "14",
    // The font size of the user name, uses var(--text-tile-user-name-font-size) in css-variables.ts
    text_tile_user_name_font_size: "14",
    // The margin of the widget, and gap between tiles, uses var(--margin) in css-variables.ts
    margin: "10",
    // The mode of what happens when a tile is clicked, options: [EXPAND], [ORIGINAL_URL] or [CUSTOM]
    // [EXPAND] will expand the tile, [ORIGINAL_URL] will open the original URL associated with a social media image, [CUSTOM] will open a custom URL (not implemented)
    click_through_url: "[EXPAND]",
    // The background image of the icon, uses var(--shopspot-icon) in css-variables.ts, defaults to #000
    shopspot_icon: "",
    // Whether the tile should automatically pull new tiles or not
    auto_refresh: "true",
    // Whether the widget should only load x amount of tiles per page
    tiles_per_page: "",
    enable_custom_tiles_per_page: true,
    // Whether the widget should load more tiles on scroll, button or static
    load_more_type: "button",
    // The name of the widget
    name: "Blank Canvas",
    // The link color of the tile, uses var(--text-tile-link-color) in css-variables.ts
    text_tile_link_color: "",
    // The minimum amount of tiles required to show the widget
    minimal_tiles: "6",
    // Tile size: small, medium, large
    inline_tile_size: "medium",
    // The border radius of the inline tile, uses var(--inline-tile-border-radius) in css-variables.ts
    inline_tile_border_radius: "5",
    // The border radius of the expanded tile, uses var(--expanded-tile-border-radius) in css-variables.ts
    expanded_tile_border_radius: "5"
  },
  expandedTile: {
    // Whether to show the caption of the tile
    show_caption: true,
    // Whether to show the timestamp of the tile
    show_timestamp: true,
    // Whether to show the navigation options of the tile
    show_nav: true,
    // Whether to show the sharing options of the tile
    show_sharing: true,
    // Whether to show the shopspots of the tile
    show_shopspots: true,
    // Whether to show the products of the tile
    show_products: true,
    // Whether to show the tags of the tile
    show_tags: true,
    // Whether to show the votes of the tile
    show_votes: true,
    // Whether to show the cross sellers of the tile
    show_cross_sellers: true,
    // Whether to show the add to cart
    show_add_to_cart: true
  },
  inlineTile: {
    // Whether to show the navigation options of the tile
    show_nav: true,
    // Whether to show the sharing options of the tile
    show_sharing: true,
    // Whether to show the shopspots
    show_shopspots: true,
    // Whether to show the tags of the tile
    show_tags: true,
    // Whether to show the timestamp of the tile
    show_timestamp: true,
    // Whether to show the caption of the tile
    show_caption: true,
    // Whether to show the products of the tile
    show_products: true,
    // Whether to show the add to cart functionality
    show_add_to_cart: true,
    // Whether to auto play the video
    auto_play_video: false,
    // Whether to show the inline tiles
    show_inline_tiles: true,
    // Whether to show the carousel
    show_carousel: false
  }
}

```

````


# Building your widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

**Development**

When building your widget, it is recommended to utilise the `npm run start` command to automatically rebuild any new changes you introduce to the code base.

This will automatically build the utilities packages.

**Preparing for production**

When building for production, you can utilise `npm run build`  which will automatically build the assets for the production environment.

All changes to assets will be available in the dist folder

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FZcOs7bop3gPccpdr3fgx%2Fimage.png?alt=media&amp;token=7b3171d3-ae34-466f-97f8-cf3014e2bf16" alt=""><figcaption></figcaption></figure>


# Shipping to production

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is unique to NextGen widgets**
{% endhint %}

In-order to ship your widget to production, you can utilise the assets built in the dist folder.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FCACqdGE7fJ82pU1EfU6e%2Fimage.png?alt=media&amp;token=6bdb4a59-4936-4706-92e7-7c8adea3d5e9" alt=""><figcaption></figcaption></figure>

There are a few ways you can utilise these to ensure your widget is production ready:

**1) Self host your CSS/JS assets, and update the templates in the widget editor**

You can utilise a CDN to host your CSS and JS files for the widget, and you can load them via the sdk by injecting the scripts into the placement:&#x20;

Before injecting the scripts, dont forget to add the layout and tile handlebars data to the widget editor.

**CSS**

`@import url('https://example.com/styles.css')`

**JS**

`sdk.addLoadedComponents(['https://example.com/widget.js']);`

This should be performed inside the Javascript box and css box of the widget editor

**2) Update the layouts, and templates in the widget editor**

Place the dist file contents into the corresponding boxes on the widget editor and click Preview Changes.

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FWdSTjD3J8Hqr990LoERC%2Fimage.png?alt=media&amp;token=15b34ccf-3723-44b9-be9b-e320d1bae8dd" alt=""><figcaption></figcaption></figure>


# Filtering and Organizing Tiles in Your Widget

This guide will help you set up your widget so users can **filter content** (tiles) and **organize how it’s displayed**. It assumes you’re using a **widget editor** with Layout, Tile, CSS, and JS boxes.

**Prerequisites:**

Any widget template, we recommend Grid widget for this one but please create a widget with a default filter already selected.

***

### **1. Layout Box – Adding Filter Buttons**

The **Layout Box** is where you define the buttons or controls users click to filter or organize content.

**Example:**

```html
<div class="stackla-links" style="display: flex; gap: 10px;">
  <a href="#!" id="154374">For Her</a> |
  <a href="#!" id="154375">For Him</a> |
  <a href="#!" id="154376">For Teens</a> |
  <a href="#!" id="154377">For Kids</a> |
  <a href="#!" id="154378">For Pets</a>
</div>

<div class="ugc-tiles grid grid-inline" aria-label="Content grid">
    {{#tiles}}
    {{>tpl-tile options=../options}}
    {{/tiles}}
</div>

<load-more widgetId="{{options.wid}}" aria-label="Load more content" />
```

**What this does:**

* `.stackla-links` → Buttons users click to filter tiles.
* `.ugc-tiles` → Grid where tiles will appear.
* `<load-more>` → Button to load more content if needed.

**Tip:** You can add as many buttons as you like for different filters, brands, or categories.

***

### **2. Tile Box – How Each Tile Appears**

The **Tile Box** controls the **look of each tile**. Tiles can be images or videos, and you can show icons for shoppable content, Instagram Reels, or other indicators.

**Example:**

```html
{{#tile class="grid-item"}}
  <div class="tile" data-background-image="{{image}}">
    <img style="display: none;" src="{{image}}" alt="{{name}}" />
    <div class="icon-section">
      {{#ifHasProductTags this}}
        <div class="shopping-icon icon-products" aria-label="Shoppable content"></div>
      {{/ifHasProductTags}}
      <div class="network-icon icon-{{source}}" aria-label="Content from {{source}}"></div>
    </div>
    <tile-tags widgetId="{{options.wid}}" tile-id="{{id}}" variant="dark" mode="swiper" context="grid-inline" aria-label="Content tags"></tile-tags>
    <shopspot-icon widgetId="{{options.wid}}" tile-id={{id}} aria-label="Shopping options" />
  </div>
{{/tile}}
```

**What this does:**

* Displays images or video tiles.
* Shows icons for shoppable products or Instagram Reels.
* Displays content tags and shopspots.

**Tip:** Customize icons or tags to fit your style.

***

### **3. CSS Box – Organizing Tiles Visually**

The **CSS Box** controls **how your tiles are laid out** and how the filter buttons look.

**Example CSS:**

```css
.stackla-links a {
  text-decoration: none;
  color: #333;
  font-weight: bold;
  cursor: pointer;
  margin-right: 8px;
}

.stackla-links a:hover {
  color: #007bff;
}

.ugc-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 15px;
}

.tile {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
}
```

**Tip:** Use CSS to create **different grid layouts** (e.g., single column, multi-column, masonry style) so users can organize content visually.

***

### **4. JS Box – Making Filters Work**

The **JS Box** makes your filters interactive and allows users to **organize tiles in different ways**. You can filter by:

* **Filter ID** – Predefined filters (like “For Her” or “For Him”)
* **Tags** – Content tags like “summer” or “holiday”
* **Brand** – Tiles from a specific brand
* **Category** – E.g., sports, fashion
* **Text Search** – Search by text or keyword
* **Tag Group** – Regional or group-based filtering

**Example JS for Filter Buttons:**

```js
// Load the grid template
sdk.loadTemplate('grid');

// Make filter buttons clickable
sdk.querySelectorAll(".stackla-links a").forEach(item => {
  item.onclick = () => {
    const filterId = item.getAttribute('id');
    sdk.searchTilesByFilterId(filterId, true); // true = replace current tiles
  };
});
```

**Example: Other Ways to Filter**

```js
// Search by text
sdk.searchTiles('summer');

// Search by tags
sdk.searchTilesByTags('holiday,vacation');

// Search by brand
sdk.searchTilesByBrand('Nike');

// Search by categories
sdk.searchTilesByCategories('sports,fashion');

// Search by tag group
sdk.searchTilesByTagGroup('au'); // e.g., Australia
```

**Tip:** You can create buttons or dropdowns for **different ways to organize tiles**, not just filter IDs.

***

### **5. Organizing Tiles Dynamically**

You can let users **choose how to organize content**:

* **Filter by tags** → Let users pick multiple tags.
* **Mix filters** → Users can combine brand + category + tag.

**Example: Combining Filters**

```js
// Show Nike products in sports category
sdk.searchTilesByBrand('Nike');
sdk.searchTilesByCategories('sports', false); // false = append to current results
```

***

### **6. Extra Features**

* **Highlight active filter**:

```js
sdk.querySelectorAll(".stackla-links a").forEach(item => {
  item.onclick = () => {
    sdk.searchTilesByFilterId(item.getAttribute('id'), true);
    // highlight active button
    sdk.querySelectorAll(".stackla-links a").forEach(i => i.style.fontWeight = 'normal');
    item.style.fontWeight = 'bold';
  };
});
```

***

### **Summary**

1. **Layout Box:** Create filter buttons and grid container.
2. **Tile Box:** Decide how each tile looks (image/video, icons, tags).
3. **CSS Box:** Style buttons, tiles, and grid layout.
4. **JS Box:** Make filters work and allow users to organize tiles in different ways.

**Result:** Users can interact with your widget to **filter content, view by category/brand/tag, and organize tiles dynamically**, all without needing to write complex code.

### **Result**

<figure><img src="https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2FSUxgbWbx9TRZy3kVDktN%2Fimage.png?alt=media&amp;token=27699854-2dac-4a56-a1b7-e77de599bcd1" alt=""><figcaption></figcaption></figure>


# Styling Guides

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

{% endhint %}


# Using Webfonts in Widgets

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/using-webfonts-in-widgets) here.

{% endhint %}

## Overview

This article will instruct you on how to use web fonts from different providers such as [Google](https://www.google.com/fonts), [Typekit](https://typekit.com), [Fonts.com](http://www.fonts.com) or [Fontdeck](http://fontdeck.com).

## Step 1 - Load Fonts

You will need to download the font files before using them. Let's take a look at some different approaches to load fonts onto our web page.

### IDE Users

If you are an IDE user, you can upload your font files to any given server and use the following code to load them:

````ts
// widget.ts
declare const sdk: ISdk
import { fonts } from "./fonts"

loadWidget({
  config: {
    fonts: fonts
  }
})
```;

```fonts.ts
export const fonts = [
  {
    fontFamily: "HelveticaNeueLtPro",
    src: [
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Roman.ttf", format: "truetype" }
    ],
    fontWeight: "normal",
    fontStyle: "normal"
  },
  {
    fontFamily: "HelveticaNeueLtPro",
    src: [
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Md.woff2", format: "woff2" },
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Md.woff", format: "woff" }
    ],
    fontWeight: 500,
    fontStyle: "normal",
    fontDisplay: "swap"
  },
  {
    fontFamily: "HelveticaNeueLtPro",
    src: [
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Lt.woff2", format: "woff2" },
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Lt.woff", format: "woff" }
    ],
    fontWeight: 400,
    fontStyle: "normal",
    fontDisplay: "swap"
  },
  {
    fontFamily: "HelveticaNeueLtPro",
    src: [
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Bd.ttf", format: "truetype" }
    ],
    fontWeight: "700",
    fontStyle: "normal"
  },
  {
    fontFamily: "HelveticaNeueLtPro",
    src: [
      { url: "https://templates.stackla.com/assets/fonts/helvetica/HelveticaNeueLTPro-Bd.ttf", format: "truetype" }
    ],
    fontWeight: "bold",
    fontStyle: "normal"
  }
]
````

### Non-IDE Users

If you are a non-IDE user, you can do the following:

1. Import the font in your head section:

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap">
```

2. Add the font to the CSS editor:

```css
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxP.woff2) format('woff2');
}
```

3. Apply the font to your widget:

```css
.ugc-widget {
  font-family: 'Roboto', sans-serif;
}
```

4. Save your changes and publish the widget.

Congratulations! You have successfully added a web font to your widget. You can now use this font in your CSS styles to customize the appearance of your widget.


# Styling Widget Shopspots

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/styling-widget-shopspots) here.
{% endhint %}

## Overview

This article will instruct you on customizing Shopspot style in both Tiles and Expanded Tiles.

## Shopspots in Tiles & Expanded Tiles

You can style Shopspots in tiles via the Custom CSS tab in Nosto Admin Portal or in the IDE by utilising the SDK.

### HTML Structure

The structure is pretty simple. It uses `.shopspot-icon>.fs-tag` as namespace.

```
<div class="fs-tag" data-tag-id="43" style="left: 15.63%; top: 15.63%; cursor: pointer;">
    <div class="tooltip tag-43">Tag</div>
</div>
```

### Sample CSS

Lets make our shopspots rainbow colored!

Place the following code into the Custom CSS in Nosto's UGC Widget settings or in the IDE's widget.scss file.

```
shopspot-icon .fs-tag {
    background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet) !important;
    border-radius: 50%;
    width: 30px;
    height: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
}
shopspot-icon .fs-tag:hover {
    background: linear-gradient(to right, violet, indigo, blue, green, yellow, orange, red) !important;
    transform: scale(1.2);
    transition: all 0.3s ease;
}
```

### Result

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-220d7183b95b64d3bf020a2f16a6a2e301861ee5%2Frainbow-shopspots.png?alt=media)

Congratulations! You have successfully styled your shopspots in tiles.


# How to make expanded tiles vertical

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

## How to make the expanded tile vertical <a href="#how-to-make-the-expanded-tile-vertical" id="how-to-make-the-expanded-tile-vertical"></a>

1. Remove Unnecessary Classes In the ExpandedTiles template `expanded-tile.template.tsx`, remove the following class names to prevent horizontal swiper behavior:

* `swiper-wrapper`
* `swiper`
* `swiper-expanded`
* `swiper-slide`

2. Add this click listener to close Tile when clicking outside of Expanded Tile in vertical list in `widget.tsx`. To close the Expanded Tile when clicking outside of it, add this JavaScript event listener:

```
const dialog = sdk.querySelector("#overlay-expanded-tiles")
dialog.addEventListener("click", event => {
  if (dialog === event.target) {
    sdk.closeExpandedTiles()
  }
})
```

3. Hide navigation arrows on Expanded Tile: Since the tile is now vertical, hide the left/right arrows using CSS.

```
#nosto-ugc-extensions {
    #overlay-expanded-tiles {
        overflow-x: hidden;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }

    .swiper-expanded-button-prev,
    .swiper-expanded-button-next {
        display: none !important;
    }
}

@media only screen and (max-width: 992px) {
    .ugc-tile {
        width: 100vw;
    }
    .panel,
    .panel-left,
    .panel-right {
        width: 100vw;
        box-sizing: border-box;
    }
}
Copy
```

## Summary <a href="#summary" id="summary"></a>

✔ Expanded tile will be displayed vertically.&#x20;

✔ Clicking outside the tile will close it.&#x20;

✔ Navigation arrows are hidden.&#x20;

✔ Vertical scrolling is enabled for long content.


# How to add a title / subtitle to a widget

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.
{% endhint %}

## Overview

Nosto's UGC offers the ability to create advanced customizations to your widgets to match your brand needs.

In this guide, we are going explain how you can add a Title & Sub-Title directly to your widgets to ensure if a widget hides in your website because it doesn't have enough content to be displayed the entire header hides with it.

Please note that this customization is currently not supported for Story widgets.

## Getting Started

Both IDE and Custom Code Editor require one simple change to add a Title & Sub-Title to your widgets.

1. Go to your layout.hbs file (or Widget Layout in the Custom Code Editor) and add the following code to the top of your file:

```hbs
<div class="ugc-headline">
  <span class="ugc-title">Get Inspired!</span>
  <div class="ugc-widget-subtitle">See what others have to share about #mybrand</div>
</div>
```

2. Save your changes and publish the widget.
3. Go to your CSS file (or Widget CSS in the Custom Code Editor) and add the following code to style your Title & Sub-Title:

```css
.ugc-headline {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 20px;
}
.ugc-title {
  font-size: 24px;
  font-weight: bold;
  color: #000;
  margin-bottom: 10px;
}
.ugc-widget-subtitle {
  font-size: 16px;
  color: #666;
}
```

4. Save your changes and publish the widget.
5. Your widget should now display a Title & Sub-Title at the top of the widget.

### Result

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-c027e2c1d1f4fd00d2725543f46f78a2f5141c78%2Fhow-to-add-a-title.png?alt=media)

```




```


# Typescript API Guides


# How to use the SDK

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

**Note: This feature is exclusive to NextGen widgets.**
{% endhint %}

The SDK can be utilised to power your widget with advanced functionality.

The SDK gives developers access to:

1. Their widget placement, along with all the parameters associated with it
2. The tiles service, allowing the manipulation of the tiles visible, and hidden.
3. The events service, allowing particular events to be hooked into, or emitted.

While the SDK is documented in the Understanding the SDK page, here is an example of how it has been utilised to build search functionality in the blank canvas widget template.

```
import { ISdk, loadWidget } from "@stackla/widget-utils"
import { config } from "./config"

declare const sdk: ISdk

loadWidget(sdk, {
  config: {
    ...config
  }
})

sdk.querySelector("#search-input")?.addEventListener("input", async e => {
  const eventTarget = e.target
  if (eventTarget instanceof HTMLInputElement) {
    const searchValue = eventTarget.value
    await sdk.tiles.searchTiles(searchValue)
  }
})
```

As soon as the widget is loaded, I perform a `querySelection` on an item in the placement using the SDK.

I attach an event, so that every time the input is modified, it performs a live search of the selection of tiles.

Note: Here I am accessing the Tiles Service via `sdk.tiles` and there is a plethora of methods that can be utilised to manipulate the tile view.

Now, every time a user searches, the tiles update to their search results.

Under the hood, this is how the tile search method is implemented

```
  public async searchTiles(query: string, clearExistingTiles: boolean = true) {
    this.widgetRequest.search = query

    if (clearExistingTiles) {
      const tiles = this.getPlacement().querySelector(".ugc-tiles")
      if (!tiles) {
        return
      }

      this.tiles = {}
      tiles.innerHTML = ""
    }

    return this.fetchTiles()
  }
```

This same functionality can take place in your code, without the use of the `searchTiles` method here, by utilising the suite of methods in the Tile Service.


# Bind your own events

* [Introduction](#introduction)
* [Creating your Custom Event](#custom-events)

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/bind-your-own-events) here.
{% endhint %}

## Introduction

Nosto's UGC Widget SDK empowers users to feed their favorite Analytics tools with useful interaction information from their UGC Widgets based on common user behavior.

These common events relate to items such as Expanding a Tile, Loading more Content, or Clicking on a Shopspot link, and are based on the areas our users have wanted to track the most.

Now whilst these ‘Events’ cover the most common areas a client may wish to track, there are always scenarios where you need to track the less common areas and thus need to be able to create your events.

This guide provides a few examples as to how a user can create their custom events.

[Back to Top](#top)

## Creating your Custom Event

Now for this guide, we are going to focus on tracking the Left / Right arrows that are available on the Carousel Widget on the In-Line tile.

![](https://3731068598-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F903dsFKGBuxQbjtVNMIV%2Fuploads%2Fgit-blob-e6c4054bf5ff4d99d798fcf0ce2d92cdc72d1ede%2Fcarousel-widget.png?alt=media)

In this use case, we want to understand whether or not people are clicking on these buttons to see either new content or previous content.

Now as these buttons aren’t offered as part of Nosto's UGC standard \[Widgets Event API], we are going to create our Events by binding a click function to the CSS classes associated with these buttons (`.swiper-inline-carousel-button-prev` & `.swiper-inline-carousel-button-next`)

To do this, we can take two routes.

Either :

1. We append Custom JS to the code editor of the widget itself with the following contents:

```
sdk.querySelector('.swiper-inline-carousel-button-prev').addEventListener('click', function () {
	console.log('Click Previous');
});

sdk.querySelector('.swiper-inline-carousel-button-next').addEventListener('click', function () {
	console.log('Click Next');
});

```

2. We update the widget.tsx file in the IDE with the following contents:

```
import { ISdk } from '@stackla/widget-utils':

declare const sdk: ISdk;

sdk.querySelector('.swiper-inline-carousel-button-prev').addEventListener('click', function () {
	alert('inlinePrevClicked');
});
sdk.querySelector('.swiper-inline-carousel-button-next').addEventListener('click', function () {
	alert('inlineNextClicked');
});
```

Once added, we can then simply save and test.

We also offer other event bindings, which can be found in the Event Listeners section of the NextGen widgets documentation.

We suggest that you familiarise yourself with these events, as they can be useful for tracking other interactions within your widgets.

[Back to Top](#top)


# How to change the contents of an expanded tile

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is exclusive to NextGen widgets.**
{% endhint %}

This guide will explain how to customize the content displayed within an expanded tile in Nosto UGC NextGen widgets.

## For IDE Users:

As an IDE user, you will directly interact with the widget's codebase, primarily within the widgets/blankcanvas folder. Your main focus will be on widget.tsx for logic, layout.hbs or tile.hbs for HTML structure, and widget.scss for styling.

1. Understanding the Expanded Tile Structure

The expanded tile's content is rendered through the expanded-tiles web component. A sample template is located in samples/expanded-tile.template.tsx. This template dictates what information from the tile is displayed when it expands. When a tile is expanded, the SDK triggers the EVENT\_TILE\_EXPAND event. The expanded-tiles component then renders the content based on its internal logic and the provided template.

2. Modifying the Expanded Tile Template

To change the content, you'll need to override the default expanded-tiles template.

```tsx
// File: widget.tsx

import { ISdk, loadWidget } from "@stackla/widget-utils";
import { config } from "./config";
import { ExpandedTiles } from "./expanded-tile.template"; // Import your custom template

declare const sdk: ISdk;

loadWidget(sdk, {
  config: {
    ...config,
  },
  templates: {
    "expanded-tiles": { // Specify the component you want to override
      template: ExpandedTiles, // Provide your custom template function
    },
  },
});
```

(Create this file if it doesn't exist, and copy the content from samples/expanded-tile.template.tsx as a starting point.)

Within this file, you have full control over the HTML structure of the expanded tile. You can add, remove, or reorder elements to suit your needs.

Here's an example of how you might modify the ExpandedTile function to add a custom header and reorder some elements:

````tsx
/// File: blankcanvas/expanded-tile.template.tsx

import { ISdk, Tile } from "@stackla/widget-utils/types";
import { createElement, createFragment } from "@stackla/widget-utils/jsx";
import {
  VideoContainer,
  VideoErrorFallbackTemplate,
  ExpandedTileProps,
  ShopspotProps
} from "@stackla/widget-utils/components";

declare const sdk: ISdk;

export function ExpandedTile({ tile }: ExpandedTileProps) {
  const { show_shopspots, show_products, show_tags, show_sharing, show_caption, show_timestamp } =
    sdk.getExpandedTileConfig();

  const shopspotEnabled = sdk.isComponentLoaded("shopspots") && show_shopspots && !!tile.hotspots?.length;
  const productsEnabled = sdk.isComponentLoaded("products") && show_products && !!tile.tags_extended?.length;
  const tagsEnabled = show_tags;
  const sharingToolsEnabled = show_sharing;

  const parent = sdk.getNodeId();

  return (
    <>
      <div class="expanded-tile-wrapper">
        <a class="exit" href="#">
          <span class="widget-icon close-white"></span>
        </a>
        <BackArrowIcon /> {/* Assuming you've included BackArrowIcon from the sample */}
        <div class="swiper swiper-expanded">
          <div class="swiper-wrapper ugc-tiles">
            {/* The main tile content structure starts here */}
            <div class="ugc-tile swiper-slide" data-id={tile.id}>
              <div class="panel">
                <div class="panel-left">
                  <IconSection tile={tile} productsEnabled={productsEnabled} />
                  <div class="image-wrapper">
                    <div class="image-wrapper-inner">
                      {tile.media === "video" ? (
                        <>
                          <VideoContainer shopspotEnabled={shopspotEnabled} tile={tile} parent={parent} />
                          <VideoErrorFallbackTemplate tile={tile} />
                        </>
                      ) : tile.media === "image" ? (
                        <ImageTemplate tile={tile} image={tile.image} shopspotEnabled={shopspotEnabled} parent={parent} />
                      ) : tile.media === "text" ? (
                        <span class="content-text">{tile.message}</span>
                      ) : tile.media === "html" ? (
                        <span class="content-html">{tile.html}</span>
                      ) : (
                        <></>
                      )}
                    </div>
                  </div>
                </div>
                <div class="panel-right">
                  <div class="panel-right-wrapper">
                    <div class="content-wrapper">
                      <div class="content-inner-wrapper">
                        {/* --- Custom Header Section --- */}
                        <div class="custom-expanded-header">
                          <h3>Expanded Tile Details</h3>
                          {tile.name && <p>User: {tile.name}</p>}
                          {tile.source_created_at && (
                            <p>
                              Date: <time-phrase source-created-at={tile.source_created_at}></time-phrase>
                            </p>
                          )}
                        </div>
                        {/* --- End Custom Header Section --- */}

                        <tile-content
                          tileId={tile.id}
                          render-share-menu={sharingToolsEnabled}
                          render-caption={show_caption}
                          render-timephrase={show_timestamp}
                        />
                        {tagsEnabled && (
                          <tile-tags tile-id={tile.id} mode="swiper" context="expanded" navigation-arrows="true" />
                        )}
                        {productsEnabled && (
                          <>
                            <ugc-products parent={parent} tile-id={tile.id} />
                          </>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            {/* ... other swiper slides if needed */}
          </div>
          {/* Swiper navigation buttons (if enabled) */}
          <div class="swiper-expanded-button-prev swiper-button-prev btn-lg" style={{ display: sdk.getExpandedTileConfig().show_nav ? "flex" : "none" }}>
            <span class="chevron-left" alt="Previous arrow" />
          </div>
          <div class="swiper-expanded-button-next swiper-button-next btn-lg" style={{ display: sdk.getExpandedTileConfig().show_nav ? "flex" : "none" }}>
            <span class="chevron-right" alt="Next arrow" />
          </div>
        </div>
      </div>
    </>
  );
}

// Ensure you also include these helper functions from the original expanded-tile.template.tsx
function BackArrowIcon() {
  return (
    <a class="back" href="#">
      <span class="widget-icon back-arrow"></span>
    </a>
  );
}

export function IconSection({ tile, productsEnabled }: { tile: Tile; productsEnabled: boolean }) {
  const topSectionIconContent = [];
  const bottomSectionIconContent = [];

  if (tile.attrs?.includes("instagram.reel")) {
    topSectionIconContent.push(<div class="content-icon icon-reel"></div>);
  } else if (tile.attrs?.includes("youtube.short")) {
    topSectionIconContent.push(<div class="content-icon icon-youtube-short"></div>);
  }
  if (productsEnabled) {
    topSectionIconContent.push(<div class="shopping-icon icon-products"></div>);
  }

  bottomSectionIconContent.push(<div class={`network-icon icon-${tile.source}`}></div>);

  return (
    <div class="icon-section">
      <div class="top-section">{...topSectionIconContent}</div>
      <div class="bottom-section">{...bottomSectionIconContent}</div>
    </div>
  );
}

export function ShopSpotTemplate({ shopspotEnabled, parent, tileId }: ShopspotProps) {
  return shopspotEnabled ? (
    <>
      <shopspot-icon parent={parent} mode="expanded" tile-id={tileId} />
    </>
  ) : (
    <></>
  );
}

export function ImageTemplate({
  tile,
  image,
  shopspotEnabled = false,
  parent
}: {
  tile: Tile;
  image: string;
  shopspotEnabled?: boolean;
  parent?: string;
}) {
  return image ? (
    <>
      <div class="image-filler blurred" style={{ "background-image": `url('${image}')` }}></div>
      <div class="image">
        {shopspotEnabled ? (
          <ShopSpotTemplate shopspotEnabled={shopspotEnabled} parent={parent} tileId={tile.id} />
        ) : (
          <></>
        )}
        <img class="image-element" src={image} loading="lazy" alt={tile.description || "Image"} />
      </div>
    </>
  ) : (
    <></>
  );
} ```

3. Styling the Expanded Tile
You can apply custom styles to your expanded tile within widget.scss. Remember that all styles are within the shadow DOM.

File: blankcanvas/widget.scss

SCSS
```scss
@use "styles" as gridStyles;
@use "@styles/partials/media-queries";
@use "@styles/partials/inline-dimens" as dimens;

:host {
  transition: ease all 0.5s;
}

expanded-tiles {
  // Styles for the expanded tile container
  .panel {
    background-color: #f0f0f0; // Example: change background
    border-radius: var(--expanded-tile-border-radius);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  }

  .custom-expanded-header {
    padding: 15px;
    background-color: #333;
    color: white;
    h3 {
      margin-top: 0;
      margin-bottom: 5px;
    }
    p {
      margin-bottom: 0;
      font-size: 0.9em;
    }
  }

  // Styles for the content sections
  .panel-left, .panel-right {
    /* Add your specific styles here */
  }

  // Styles for the image/video wrapper
  .image-wrapper {
    /* Add your specific styles here */
  }

  // Styles for the right panel content
  .content-inner-wrapper {
    /* Add your specific styles here */
  }
}
````

## For Web Editor Users:

As a web editor user, you'll typically interact with the widget through the Nosto Admin Portal's "Custom Code" section. This means you'll be working with compiled HTML, CSS, and JavaScript.

1. Understanding the Expanded Tile When a user clicks on a tile, it can expand to show more details. This "expanded tile" is a special overlay that appears. To change what's inside it, you'll need to modify the underlying code that controls its appearance.
2. How to Modify the Expanded Tile Content You'll make your changes in four key boxes within the Custom Code editor:

Box 1: Custom JS

```javascript
// This script overrides the default expanded tile template
// It tells the widget to look for a custom template you define.

// Basic check if the sdk is available to prevent errors
sdk.addTemplateToComponent(function(sdk, component) {
    // This is where your custom HTML for the expanded tile will go.
    // We'll put the actual HTML in "Box 3: Widget Layout" for easier management.
    // For now, we'll return a basic structure.
    const tile = sdk.getTile(); // Get the data of the tile that was clicked [cite: 2028]

    if (!tile) {
        return ''; // If no tile data, return empty
    }

    // IMPORTANT: Replace 'ugc-widget-YOUR_WIDGET_ID_HERE' with your actual widget ID from the embed code.
    // You can find your widget ID in the widget embed code (data-id attribute).
    const widgetId = 'ugc-widget-122074'; // Example ID, replace with your actual widget ID [cite: 1399]

    return `
        <div class="expanded-tile-wrapper">
            <a class="exit" href="#">
                <span class="widget-icon close-white"></span>
            </a>
            <div class="panel">
                <div class="panel-left">
                    <img src="${tile.image}" alt="${tile.description || 'Tile Image'}" style="width: 100%; height: 100%; object-fit: cover;">
                </div>
                <div class="panel-right">
                    <div class="custom-expanded-header">
                        <h3>Details for: ${tile.name || 'Untitled Content'}</h3>
                        <p>Source: ${tile.source}</p>
                        <p>Date: <time-phrase source-created-at="${tile.source_created_at}"></time-phrase></p>
                    </div>
                    <div class="content-wrapper">
                        <div class="content-inner-wrapper">
                            <p>${tile.message || 'No description available.'}</p>
                            ${tile.tags_extended && tile.tags_extended.length > 0 ? `<tile-tags tile-id="${tile.id}" mode="swiper" context="expanded" navigation-arrows="true"></tile-tags>` : ''}
                            ${tile.tags_extended && sdk.getProductTagsFromTile(tile).length > 0 ? `<ugc-products parent="${widgetId}" tile-id="${tile.id}"></ugc-products>` : ''}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `;
}, 'expanded-tiles');
```

Box 2: Custom CSS

```css
/* Styling for the expanded tile container */
.expanded-tile-wrapper {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9); /* Dark overlay */
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000; /* Ensure it's on top of other content */
}

/* Styles for the close button */
.expanded-tile-wrapper .exit {
  position: absolute;
  top: 20px;
  right: 20px;
  cursor: pointer;
  z-index: 1001;
}

.expanded-tile-wrapper .close-white {
  width: 30px;
  height: 30px;
  background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>');
  background-repeat: no-repeat;
  background-size: contain;
}

/* Styles for the main panel containing the tile content */
.expanded-tile-wrapper .panel {
  display: flex;
  background-color: #ffffff; /* White background for the content area */
  border-radius: 8px;
  overflow: hidden;
  max-width: 90%; /* Adjust as needed */
  max-height: 90vh; /* Adjust as needed */
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}

/* Styles for the left section (image/video) */
.expanded-tile-wrapper .panel-left {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 10px;
}

/* Styles for the right section (text content) */
.expanded-tile-wrapper .panel-right {
  flex: 1;
  padding: 20px;
  overflow-y: auto; /* Enable scrolling for long content */
}

/* Custom Header Styling */
.expanded-tile-wrapper .custom-expanded-header {
    background-color: #f8f8f8;
    padding: 15px;
    border-bottom: 1px solid #eee;
    margin-bottom: 15px;
}

.expanded-tile-wrapper .custom-expanded-header h3 {
    margin: 0;
    color: #333;
    font-size: 1.2em;
}

.expanded-tile-wrapper .custom-expanded-header p {
    margin: 5px 0 0;
    color: #666;
    font-size: 0.9em;
}

/* Basic styling for other elements in the right panel if they exist */
.expanded-tile-wrapper .content-wrapper p {
  color: #333;
  line-height: 1.5;
}

/* Adjustments for smaller screens */
@media (max-width: 768px) {
  .expanded-tile-wrapper .panel {
    flex-direction: column; /* Stack sections vertically on small screens */
    max-width: 95%;
    max-height: 95vh;
  }
}
```

Congratulations! You have successfully modified the contents of an expanded tile in Nosto UGC NextGen widgets. You can further customize the HTML and CSS to fit your brand's needs.


# Open and close expanded tiles

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets) here.

**Note: This feature is exclusive to NextGen widgets.**
{% endhint %}

## How to open and close expanded tiles <a href="#how-to-open-and-close-expanded-tiles" id="how-to-open-and-close-expanded-tiles"></a>

Expanded tiles are dialog overlays contained within the core widget. They are used to display additional information about a tile, such as the tile's image, title, and description. This guide will show you how to open and close expanded tiles using the SDK.

### Opening an expanded tile <a href="#opening-an-expanded-tile" id="opening-an-expanded-tile"></a>

You can utilise sdk.openExpandedTile() to open an expanded tile. This method accepts a tileId as a parameter, which is the unique identifier of the tile you wish to expand.

```
sdk.openExpandedTile(tileId)
```

### Closing an expanded tile <a href="#closing-an-expanded-tile" id="closing-an-expanded-tile"></a>

You can utilise sdk.closeExpandedTile() to close an expanded tile. This method does not require any parameters.

```
sdk.closeExpandedTile()
```

### Example <a href="#example" id="example"></a>

Here is an example of how you can open and close an expanded tile using the SDK.

```
import { ISdk } from "@stackla/widget-utils"

declare const sdk: ISdk

sdk.querySelectorAll(".tile").forEach(tile => {
  tile.addEventListener("click", () => {
    const tileId = tile.getAttribute("data-tile-id")
    sdk.openExpandedTile(tileId)
  })
})

setTimeout(() => {
  sdk.closeExpandedTile()
}, 5000)
```


# How to localise the load more button on widgets

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/how-to-localise-the-load-more-button-on-widgets) here.
{% endhint %}

## Pre-Requisites

## Overview

Nosto's UGC offers the ability to create advanced customizations to your widgets to match your brand needs.

In this guide, we are going explain how you can localize the Load More button when you use the same widgets in different websites/regions.

Please note that this customization is currently not supported for Story widgets.

## Getting Started

#### Create a Widget

Create a new Widget that supports the Load More button and leave all the settings as per default.

#### Localize the Load More button

* Open the Custom Code editor and click Inline Tile. Under the Javascript section add the following code:

```
var tagGroup = sdk.getTagGroup();

if (tagGroup && tagGroup === 'AU') {
    sdk.querySelector('load-more #buttons #load-more').innerText = 'Load More';
}
if (tagGroup && tagGroup === 'FR') {
    sdk.querySelector('load-more #buttons #load-more').innerText = 'Charger plus';
}
if (tagGroup && tagGroup === 'PT') {
    sdk.querySelector('load-more #buttons #load-more').innerText = 'Carregar mais';
}
```

* In the code above, update the tagGroup to match the different Product Feed Group Names for your account. You can find those by going to **UGC Settings > Product Feeds**.
* Then click **Save**.

####


# How to load external JS and external CSS

{% hint style="warning" %}
You are reading the **NextGen Documentation**

**NextGen widgets** are a new and improved way to display UGC content onsite.&#x20;

From September 23rd 2025, all new widgets created will be NextGen.

Please check your widget version on the **Widget List page** to see if it is a **Classic** or **NextGen** widget.

You can read the [Classic Widget Documentation](/ugc/guides/onsite-widgets/loading-external-js-and-css-libraries-to-widgets) here.
{% endhint %}

## Overview

This article will demonstrate how to load external JavaScript and CSS into your widgets using our custom code editors.

## Loading the External CSS

Loading the external CSS is very straight-forward. You just need to use the CSS `@import`.

### Example

You can write the following code in your Custom CSS code editor to load [Bootstrap](http://getbootstrap.com) and [Font Awesome](http://fontawesome.io) libraries.

```
@import url(https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.min.css);
@import url(https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.2/css/font-awesome.min.css);
```

[Back to Top](#top)

## Loading the External JavaScript

In order to load external Javascript files, you can utilise

```js
sdk.addLoadedComponents(["https://google.com/bananas.js"])
```

Congratulations! You have successfully loaded external JS and CSS into your widgets.


# Digital Screens


# Customizing Carousel Event Screen

* [Overview](#overview)
* [How It Works](#how-it-works)
  * [Queue](#queue)
  * [Themes](#themes)
  * [Modes](#modes)
  * [Queue and Slides](#queue-and-slides)
* [Customizing CSS](#customizing-css)
  * [Tile Structure](#tile-structure)
* [Customizing JavaScript](#customizing-js)
  * [Available Libraries](#available-libraries)
* [Sample](#sample)

## Overview

This section demonstrates how the Carousel Event Screen works. It will provide some assistance and clarity whilst troubleshooting specific tile display or missing issues.

[Back to Top](#top)

## How It Works

### Queue

The queue is where our system holds tiles that will be displayed on the event screen. With the exception of pinned tiles, it's the amount of tiles that will be appended into the Carousel Event viewport.

![Queue](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/qPuP8QmPv401Bpdkr2Tf/queue%20\(5\).png)

* By default the queue has a capacity of 30 tiles. You can change the capacity by using Custom Javascript. A smaller queue causes a higher frequency of seeing the same tiles.
* Whenever a new tile is received, the oldest tile will be removed from the queue.
* If you use a high velocity term for your filter in the Event Screen, it's possible that all of the current tiles could be replaced with new ones after each queue check.
* You can change the queue capacity by updating the **Amount of Tiles in Loop** option in **Display Options**.\
  ![Display Options](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Rzd2xZR0ufVCvX5R3S8N/queue-option%20\(5\).png)

[Back to Top](#top)

### Themes

Currently we have two theme options. One is **Less is More** and the other one is **Blocks**.

| Blocks (Default)                                                                                          | Less is More                                                                                                  |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/6AgkHpkqhU7dHc0WlI0R/theme-blocks.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SvbRPKdi44yrKI8DrU74/theme-lessismore.png) |

[Back to Top](#top)

### Modes

Currently we have the following different modes. There is also a **Random** option which applies different modes for different tiles randomly.

| Text over image (Default)                                                                                       | Side-by-side                                                                                                 | Image only                                                                                                  |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dL7vSwpT1VVcB3lz3Nlp/mode-textoverimage.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/JwJtgzOks5i6FXonR7wH/mode-sidebyside.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/WFJhJDXPA8CzTfcU7yfy/mode-imageonly.png) |

You can still configure the **Themes** option to have different visual presentation.

| Text over image with Less is Simple                                                                           | Side-by-side with Less is Simple                                                                                        | Image only with Less is Simple                                                                                         |
| ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SvbRPKdi44yrKI8DrU74/theme-lessismore.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/wHD8y503G2WOwrqCjwqv/mode-sidebyside-lessismore.png) | ![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/SMW5Y80pczhteWnqqZ1L/mode-imageonly-lessismore.png) |

[Back to Top](#top)

### Queue and Slides

The tiles in queue will be converted to slides in the Carousel Event.

![Queue and Slides](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/2bVjTznVf2NdRvufD0qI/queue-and-slides.png)

As illustrated, we have 30 items in the queue, and will get the exact same amount of slides in the carousel. Note that the pinned tile are not counted in the amount - this is something that you will need to consider. For example, if you set the **Amount of Tiles in Loop** to 30 tiles and you also have 2 pinned tiles. There will be 32 slides in the Carousel event.

[Back to Top](#top)

## Customizing CSS

### Tile Structure

The Carousel Event Screen is mostly composed by tiles (slides), as such it's much easier to customize it when you are more familiar with its structure.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/o6wW4510OHVnb2wo8EH1/tile-structure.png" alt="Tile Structure" data-size="original"></p> | <p><strong>Sample</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/5LeZxDsJQGhdhGc5Fkwg/tile-sample.png" alt="Tile Sample" data-size="original"></p> |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Code

The diagram above only shows until the 3rd level. Check the complete Tile structure by referencing the following code.

```
<div class="tile">
    <div class="tile-blur" style="background-color: rgba(0, 0, 0, 0.75)"></div>
    <div class="tile-background" style="background-color: rgba(0, 0, 0, 0.75)"></div>
    <div class="tile-image" style="background-image:url(http://xxx.png)"></div>
    <div class="tile-content">
        <div class="tile-caption">
            <p>...</p>
        </div>
    </div>
    <div class="tile-user-info">
        <div class="tile-avatar">
            <img class="tile-avatar-img" src="...">
        </div>
        <div class="tile-user">
            <div class="tile-user-top">
                <span class="tile-user-name">...</span>
            </div>
            <div class="tile-user-bottom">
                <span class="tile-user-handle">...</span>
            </div>
        </div>
        <div class="tile-source">
            <div class="tile-source-icon social-source "></div>
            ...
        </div>
    </div>
</div>
```

[Back to Top](#top)

## Customizing JavaScript

If you're looking to customize the Carousel Event Screen using our Javascript API - you can find the documentation [here](broken://pages/dLRvxvNIwoYdDagnCeBL).

### Available Libraries

The Carousel Event Screen currently has the following JavaScript libraries installed.

* **jQuery**: You can access it by using `$` global variable. The current version is **2.1.4**.
* **lodash**: You can access it by using `_` global variable. The current version is **3.10.1**.
* **Mustache.js**: You can access it by using `Mustache` global variable. The current version is **0.8.1**.
* **dotdotdot**: You can access it as a jQuery Plugin (`$.fn.dotdotdot`). The current version is **1.6.7**.
* **slick**: You can access it as a jQuery Plugin (`$.fn.slick`).

[Back to Top](#top)

## Sample

The following is an example of the customized Carousel Event Screen. Click the following image to see the Event in your browser.

[![Carousel Sample](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Z0pAXS5SwXf15YhRnINI/carousel-sample.png)](http://stacklapreview.stackla.com/eventscreen/show/2538)

### Custom Header

The default header which Nosto's UGC provides is very basic and only shows the name and the hashtag. In this example we've demonstrated a different HTML structure for the header. You can use the following code to achieve this:

```
<div class="logo">
    <img src="https://p5.zdassets.com/hc/settings_assets/935218/200224168/xTPeIfkNOxE8uqgHbtxK9Q-stackla-logo.png"/>
</div>
<h2 class="title">Image Gallery</h2>
```

### Custom CSS

The above code will be wrapped by a

with the ID of **custom-header**. You have to apply the style to make it pretty.

```
@import url(https://fonts.googleapis.com/css?family=Bungee+Inline|Bungee+Hairline|Bree+Serif);

body {
    font-family: 'Bree Serif', serif;
}

/* Custom Header */
#custom-header {
    box-sizing: border-box;
    height: 100px;
    padding: 10px 40px;
    &:after {
        clear: both;
        content: '';
        display: block;
    }
    .logo {
        float: left;
    }
    .title {
        float: right;
        font-family: 'Bungee Hairline', cursive;
        font-size: 42px;
    }
}

/* Content */
#content {
    top: 100px !important; /* Fix for the custom header */
}

/* User Info */
.tile-user, .tile-source {
    font-family: 'Bungee Hairline', cursive;
}
```

[Back to Top](#top)


# Customizing Mosaic Event Screen

* [Overview](#overview)
* [How It Works](#how-it-works)
  * [Queue](#queue)
  * [Grids](#grids)
  * [Queue and Grids](#queue-and-grids)
* [Customizing CSS](#customizing-css)
  * [Grid Layout](#grid-layout)
  * [Transition Animation](#transition-animation)
  * [Tile Structure](#tile-structure)
* [Customizing JavaScript](#customizing-js)
  * [Available Libraries](#available-libraries)
* [Sample](#sample)

## How It Works

This section demonstrates how the Mosaic Event Screen works. It should help to provide some clarity when troubleshooting specific tile display or missing issues.

### Queue

The queue is where our system holds tiles that will be displayed on the event screen. It's invisible and operates in the background.

![Queue](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/FrJo0BAo5uGhkTObPtrF/queue.png)

* By default the queue has a capacity of 30 tiles. You can change the capacity by using Custom Javascript. A smaller queue causes a higher frequency of seeing the same tiles.
* Whenever a new tile is received, the oldest tile will be removed from the queue.
* If you use a high velocity term for your filter in the Event Screen, it's possible that all of the current tiles could be replaced with new ones after each queue check.
* You can change the queue capacity by updating the **Amount of Tiles in Loop** option in **Display Options**.\
  ![Display Options](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/TXS1wKXoXDH9KfF35DGh/queue-option.png)

[Back to Top](#top)

### Grids

Currently we have 2 different layouts. Each layout has 10 tiles.

![Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ED719KS0xCUi4uMPdEBF/grid-and-cell.png)

The 2 grids take turns to display for 10 second each by default.

[Back to Top](#top)

### Queue and Grids

The tiles in queue will be cloned one by one to fill out all of the cells in each grid.

![Queue and Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/YD0rr8pab23thjMDyJXw/queue-and-grids.png)

As illustrated, we have 30 tiles in the queue but only have 20 cell spaces. The left 10 tiles will be used in the next round. It's possible that user doesn't see these tiles if it is a high velocity event which causes tiles in queue being replaced very soon.

[Back to Top](#top)

## Customizing CSS

### Grid Layout

The Grid Layout is actually hard-coded, which makes it a little more difficult to customize. Each cell has a very specific position and dimension that should be considered.

![Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ED719KS0xCUi4uMPdEBF/grid-and-cell.png)

|

```
/* Grid 1 /
.mosaic-grid-type-1 {
.mosaic-grid-cell-1 {
bottom: 0;
left: 0;
width: 26.75%;
height: 62.6%;
}
.mosaic-grid-cell-2 {
right: 0%;
top: 0;
width: 20.83%;
height: 66.69%;
}
.mosaic-grid-cell-3 {
right: 18.3125%;
bottom: 0%;
width: 18.3125%;
height: 33.31%;
}
.mosaic-grid-cell-4 {
top: 0%;
left: 20.83%;
width: 20.83%;
height: 37.4%;
}
.mosaic-grid-cell-5 {
left: 26.75%;
top: 37.4%;
width: 14.91%;
height: 29.29%;
}
.mosaic-grid-cell-6 {
left: 26.75%;
bottom: 0%;
width: 18.3125%;
height: 33.31%;
}
.mosaic-grid-cell-7 {
left: 41.66%;
top: 0;
width: 37.51%;
height: 66.69%;
}
.mosaic-grid-cell-8 {
left: 45.0625%;
bottom: 0;
width: 18.3125%;
height: 33.31%;
}
.mosaic-grid-cell-9 {
left: 0%;
top: 0;
width: 20.83%;
height: 37.4%;
}
.mosaic-grid-cell-10 {
right: 0;
bottom: 0;
width: 18.3125%;
height: 33.31%;
}
}
```

*|*

```
/ Grid 2 */
.mosaic-grid-type-2 {
.mosaic-grid-cell-1 {
top: 0;
left: 0;
width: 22.25%;
height: 66.666667%;
}
.mosaic-grid-cell-2 {
left: 40.5%;
bottom: 0;
width: 40.75%;
height: 60%;
}
.mosaic-grid-cell-3 {
left: 0%;
bottom: 0;
width: 20.25%;
height: 33.34%;
}
.mosaic-grid-cell-4 {
right: 0;
top: 33.33333%;
width: 18.75%;
height: 33.34%;
}
.mosaic-grid-cell-5 {
left: 22.25%;
top: 40%;
width: 18.25%;
height: 26.67%;
}
.mosaic-grid-cell-6 {
left: 20.25%;
bottom: 0%;
width: 20.25%;
height: 33.33%;
}
.mosaic-grid-cell-7 {
left: 55.35%;
top: 0;
width: 25.9%;
height: 40%;
}
.mosaic-grid-cell-8 {
right: 0;
top: 0;
width: 18.75%;
height: 33.33%;
}
.mosaic-grid-cell-9 {
top: 0%;
left: 22.25%;
width: 33.1%;
height: 40%;
}
.mosaic-grid-cell-10 {
right: 0;
bottom: 0;
width: 18.75%;
height: 33.33%;
}
}
```

\| | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

To have your own customized grid layout, you can clone and modify the above code by pasting it into our Custom CSS Code Editor

[Back to Top](#top)

### Transition Animation

You can make use of the following CSS class names to have different transition animation.

* `mosaic-grid`: A grid always has this class name.
* `mosaic-grid-active`: The current showing grid.
* `mosaic-grid-switch`: The intermediate class before the current grid changes to active.

The following is our default CSS for transition animation. We make use of the `scale` method in CSS3 2D Transform.

```
.mosaic-grid {
    visibility: hidden; // All grids are hidden by default
}
.mosaic-grid-switch,
.mosaic-grid-active { // 2 states just for tile animation
    visibility: visible;
}
.mosaic-grid-cell > div { // Centerise tile with 0x0 in size
    height: 100%;
    left: 0;
    overflow: hidden;
    position: absolute;
    top: 0;
    transform: scale(0);
    transition: transform 0.5s;
    width: 100%;
}
.mosaic-grid-active .mosaic-grid-cell > div { // Animatin applies
    transform: scale(1);
}
```

[Back to Top](#top)

### Tile Structure

The Mosaic Event Screenis mostly composed by tiles - as such it is much easier to customize the Mosaic Event Screen when you feel familiar with it's structure.

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/dkrfdfaaFkdz7C2vlLlU/tile-structure-1%20(3).png" alt="Tile Structure" data-size="original"></p> | <p><strong>Sample</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/DvPgGm1oHH6cIZBH44ZR/tile-sample-1.png" alt="Tile Sample" data-size="original"></p> |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Code

The diagram above only shows until the 4th level. Check the complete Tile structure by referencing the following code.

```
<div class="tile">
    <div class="tile-content">
        <div class="tile-user-info">
            <div class="tile-avatar">
                <img class="tile-avatar-img" src="...">
            </div>
            <div class="tile-user">
                <div class="tile-user-top">
                    <span class="tile-user-name">...</span>
                </div>
                <div class="tile-user-bottom">
                    <span class="tile-user-handle">...</span>
                </div>
            </div>
        </div>
        <div class="tile-caption">
            <p>...</p>
        </div>
        <div class="tile-source">
            <div class="tile-source-icon social-source "></div>
            ...
        </div>
    </div>
    <div class="tile-image-wrapper">
        <div class="tile-image"></div>
    </div>
</div>
```

[Back to Top](#top)

## Customizing JavaScript

If you're looking to customize the Mosaic Event Screen using our Javascript API - you can find the documentation [here](broken://pages/dLRvxvNIwoYdDagnCeBL).

### Available Libraries

The Mosaic Event Screen currently has the following JavaScript libraries installed.

* **jQuery**: You can access it by using `$` global variable. The current version is **2.1.4**.
* **lodash**: You can access it by using `_` global variable. The current version is **3.10.1**.
* **Mustache.js**: You can access it by using `Mustache` global variable. The current version is **0.8.1**.
* **slabText**: You can access it as a jQuery Plugin (`$.fn.slabText`). The current version is **2.3**.
* **imagesLoaded**: You can use it as a jQuery Plugin (`$.fn.imagesLoaded`). The current version is **3.1.4**.
* **dotdotdot**: You can access it as a jQuery Plugin (`$.fn.dotdotdot`). The current version is **1.6.7**.

[Back to Top](#top)

## Sample

The following is an example of the customized Mosaic Event Screen. Click the following image to see the Event in your browser.

[![Mosaic Sample](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/EeswaU3TlwMJ9umSuVMW/mosaic-sample.png)](http://stacklapreview.stackla.com/eventscreen/show/2014)

### Custom CSS

We want to add a background image and change the transition animation by the Custom CSS.

```
body {
    background: url(https://stackla-ui-kit.s3.amazonaws.com/bg_concert.jpg) center center no-repeat;
    background-size: cover;
}
//============
// Layout
//============
.mosaic-grid-cell {
    border: 5px solid transparent;
}
//============
// Transition
//============
.mosaic-grid-cell > div {
    opacity: 0;
    transition: transform 2s,
                opacity 1.5s;
}
.mosaic-grid-active .mosaic-grid-cell > div { // Animation applies
    opacity: 1;
    transform: scale(1);
}
//============
// Tile
//============
.tile-image {
    opacity: 0.8;
}
.tile-caption {
    text-shadow: 1px 1px 5px rgba(0, 0, 0, 0.8);
}
.tile-user {
    font-family: 'Open Sans Condensed', sans-serif;
}
.tile-caption {
    font-family: 'industry', sans-serif;
}
```

### Custom JavaScript

We want to change the queue capacity to 10. However, the option is not available in the **Amount of Tiles in Loop** of Display Options. We can use JavaScript to achieve that.

Another goal is use both the Google Web Font and Typekit. It's easy to achieve with the Web Font Loader library.

```
window.StacklaEventscreens.options = {
    debug: true, // Enable debug mode
    queue_length: 10 // Change the queue from 30 to 10 which is not available in Display Options
};

//============
// Web Font
//============
WebFontConfig = {
    google: {
        families: [
            'Open+Sans+Condensed:300:latin'
        ]
    },
    typekit: {
        id: 'pjj3gpq'
    },
    active: function() {
        sessionStorage.font = true;
    }
};
(function() {
    var wf = document.createElement('script');
    wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
})();
```

[Back to Top](#top)


# Customizing Scrollwall Event Screen

* [Overview](#overview)
* [How It Works](#how-it-works)
  * [Queue](#queue)
  * [Queue and Grids](#queue-and-grids)
  * [Animation](#animation)
* [Customizing the CSS](#customizing-css)
  * [Layout](#grid-layout)
  * [Animation](#animation-css)
  * [Tile Structure](#tile-structure)
* [Customizing the JavaScript](#customizing-js)
  * [Available Libraries](#available-libraries)
* [Sample](#sample)

## Overview

This article will demonstrate to you how our Scrollwall Event Screens work and how you can customize them.

## How It Works

This section will demonstrate how the Scrollwall Event Screen works. It may help you understand how issues around specific tile displays or missing tiles could arise.

### Queue

The queue is where our system holds tiles that will be displayed on the event screen. It's invisible and operates in the background.

![Queue](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/TmFxoPKGJn3yTV9I530e/queue.png)

* By default, the queue has a capacity of 30 tiles. You can change the capacity by using either the **Custom JavaScript** or the **Amount of Tiles in Loop** option in the **Display Options**.\
  ![Display Options](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/gyzb79YqY25Vw4kpF8fc/queue-option.png)
  * A smaller queue causes a higher frequency of seeing the same tiles.
* Whenever a new tile is received, the oldest tile will be removed from the queue.
* If you use a high-velocity term for your filter in the Event Screen, it's possible that all of the current tiles could be replaced with new ones after each queue check.

[Back to Top](#top)

### Queue and Grids

The tiles in the queue will be cloned one by one to fill out all of the cells in different rows.

![Queue and Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ujhVN0hbl9azuh8rpEjc/queue-and-layout.png)

You might be wondering why we need to create 2 groups in one row. That's all because of the scrolling animation. We will explain this in the following section.

[Back to Top](#top)

### Animation

The following diagram illustrates how the Scrollwall animates.

![Animation and Swapping](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/3H7IvFH9BC7mdEDoxfIX/animation-and-swapping.png)

* Each **tile** could have **different width** according to its content.
* Each **row** could have **different width** according to the total width of its belonging tiles.
* Each **row** is animated by the CSS Transform, which **loops to move it to -50% of the x-axis in 90 seconds**.
* Each **loop** must arrive the destination at the **same time**. Thus, **the different width of rows** results in the **different speed**.
* When a loop finishes, we will **swap the position of the 2 groups** and also **replace the new tiles** into the **previous showing group**.
  * The 2 groups swapping technique is required for showing an endless row.
  * It's difficult to replace new tiles anytime they arrive since it might affect the row width and cause a **jumpy animation**.
  * To avoid the above issue, we create **2 groups for swapping**. When the former group arrives the destination (x:-50%), it can be swapped and updated without the issue because it has been **invisible from the viewport**. All you can see right now is the later group.
  * At the swapping point, the later group won't be updated with new tiles since it's visible. Any visible content replacement will appear strange on the screen for the user. Thus, **only the invisible group will be updated**.
* Assuming that 30 new tiles arrive and are placed in the queue during the 1st and 2nd loops (The first 180 seconds). You can view the 15 new tiles from the 3rd loop.
  * Will you see another 15 new tiles in the 4th loop? Well, it depends on how many new tiles are being received during the 3rd loop.
* Note that there are **no new tiles being replaced in the first 180 seconds**. However, you can see the new tiles **every other 90 seconds from then on**.

[Back to Top](#top)

## Customizing CSS

### Layout

Each layout class name has a **scrollWall-** prefix. (We use the [SUIT CSS](https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md) naming convention for the Scrollwall Event Screen implementation.)

![Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/mH4jcNrAmLKcThCh3ZWX/layout.png)

[Back to Top](#top)

### Animation CSS

The following code snippet is our default animation CSS.

```
@keyframes scroll {
    0% {
        transform: translate3d(0, 0, 0);
    }
    100% {
        transform: translate3d(-50%, 0, 0);
    }
}
// Animation
.scrollWall-row {
    animation-name: none;
}
.scrollWall-row.is-animating {
    animation-name: scroll;
    animation-duration: 90s; // Will be overwritten by JS
    animation-iteration-count: infinite;
    animation-timing-function: linear;
}
```

You can define your own animation by Custom CSS. Note that the `animation-duration` will be overwritten by the JavaScript.

[Back to Top](#top)

### Tile Structure

The Scrollwall Event Screens are mostly composed of tiles - as such it is much easier to customize the Scrollwall Event Screen when you feel familiar with its structure.

| <p><strong>Sample</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/1OBCqMChLoaTokyr3wej/tile-sample-2.png" alt="Tile Sample" data-size="original"></p>        |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/NEkOA4wgPnijRwm5DKn5/tile-structure-2.png" alt="Tile Structure" data-size="original"></p> |

### Code

The diagram above only shows until the 4th level. Check the complete Tile structure by referencing the following code.

```
<div class="tile">
    <div class="tile-content">
        <div class="tile-user-info clearfix">
            <div class="tile-avatar">
                <img class="tile-avatar-img img-circle"/>
            </div>
            <div class="tile-user">
                <div class="tile-user-top">
                    <span class="tile-user-name"></span>
                </div>
                <div class="tile-user-bottom">
                    <span class="tile-user-handle"></span>
                </div>
            </div>
        </div>
        <div class="tile-caption">
            <p></p>
        </div>
        <div class="tile-source">
            <div class="tile-source-icon social-source instagram"></div>
            <span>
                <span class="tile-timestamp"></span>
           </span>
        </div>
    </div>
    <div class="tile-image-wrapper">
        <div class="tile-image" style="background-image: url(...)"></div>
    </div>
</div>
```

[Back to Top](#top)

## Customizing the JavaScript

If you're looking to customize the Scrollwall Event Screen using our Javascript API - you can find the documentation [here](broken://pages/dLRvxvNIwoYdDagnCeBL).

### Available Libraries

The Scrollwall Event Screen currently has the following JavaScript libraries installed.

* **jQuery**: You can access it by using the `$` global variable. The current version is **2.1.4**.
* **lodash**: You can access it by using the `_` global variable. The current version is **3.10.1**.
* **Mustache.js**: You can access it by using the `Mustache` global variable. The current version is **0.8.1**.
* **dotdotdot**: You can access it as a jQuery Plugin (`$.fn.dotdotdot`). The current version is **1.6.7**.

[Back to Top](#top)

## Sample

The following is an example of the customized Scrollwall Event Screen. Click the following image to see the Event in your browser.

[![Scrollwall Sample](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/Niug2HRh55uxDlKRDHKv/scrollwall-sample.png)](http://stacklapreview.stackla.com/eventscreen/show/2070)

### Custom CSS

We want to add a background image and change the transition animation using Custom CSS.

```
body {
    background: #000 url(https://stackla-ui-kit.s3.amazonaws.com/bg_concert.jpg) center center no-repeat;
    background-size: cover;
}

//============
// Layout
//============
.scrollWall-group.is-debug:first-child {
    background: transparent;
}

//============
// Tile
//============
.tile {
    background-color: rgba(255, 255, 255, 0.7);
    &-image-wrapper,
    &-video-wrapper {
        &:before, &:after {
            border: none;
            display: none;
        }
    }
    &-image {
        border-left: none;
        opacity: 1 !important;
        min-width: 400px;
    }
    &-serial-number {
        background: rgba(0, 0, 0, 0.5);
        border-radius: 50%;
        width: 40px;
    }
    &-caption p {
        font-family: 'industry', sans-serif;
        text-shadow: 0 1px 0 rgba(255, 255, 255, 1);
    }
    &-source,
    &-user {
        color: #333;
        font-family: 'Open Sans Condensed', sans-serif;
    }
}
```

### Custom JavaScript

We want to change the queue capacity to 10. However, the option is not available in the **Amount of Tiles in Loop** of Display Options. We can use JavaScript to achieve that.

Another goal is to use both the Google Web Font and Typekit. It's easy to achieve with the Web Font Loader library.

```
window.StacklaEventscreens.options = {
    debug: true, // Enable debug mode
    queue_length: 10 // Change the queue from 30 to 10 which is not available in Display Options
};

//============
// Web Font
//============
WebFontConfig = {
    google: {
        families: [
            'Open+Sans+Condensed:300:latin'
        ]
    },
    typekit: {
        id: 'pjj3gpq'
    },
    active: function() {
        sessionStorage.font = true;
    }
};
(function() {
    var wf = document.createElement('script');
    wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
})();
```

[Back to Top](#top)


# Customizing the Mosaic Event Screen to Have 9 Even Tiles

* [Previously](#previously)
* [Overview](#overview)
* [How - Changing the Grid Layout](#how)
  * [Previous Layout](#prev-layout)
  * [New Layout](#new-layout)
  * [Limitation](#limitation)
* [Code](#code)
  * [Basic Style](#basic-style)
  * [Gap Style](#gap-style)
* [Demo](#demo)

## Previously

This is an extended guide for the [Customizing the Mosaic Event Screen](https://github.com/Stackla/docs/blob/master/docs/guides/customizing-the-mosaic-eventscreen/README.md) tutorial. If you are not familiar with our Mosaic Event Screens, we suggest you start there first as some of those details will come in handy.

[Back to Top](#top)

## Overview

This section demonstrates how to create a layout of 9 even tiles for the Mosaic Event Screen. The following image is a snapshot of the result:\
![Snapshot](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/gzlVNeZttkcDjxjJokx3/snapshot.png)

[Back to Top](#top)

## How - Changing the Grid Layout

In the previous tutorial, we mentioned the [Grid Layout](/ugc/guides/digital-screens/customizing-mosaic-eventscreen#grids), which details how an Event Screen can have a different visual effect after each transition.

[Back to Top](#top)

### Previous Layout

The following is the default Grid Layout.

![Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/oTn0uG6NEXvmLC5ufBfX/grid-and-cell.png)

[Back to Top](#top)

### New Layout

To update the Grid Layout structure, we need to update the stylesheet. As you can see, all the cells should have 33.33% for both width and height.

![Grids](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/W86woBZmuWJNylZJxMcv/grid-layout.png)

[Back to Top](#top)

### Limitation

Previously there are 10 cells. However, in the new layout, you can only have 9. The only way to achieve this is by hiding the 10th tile with `display: none;`. That's probably not so good if you want to display all tiles.

[Back to Top](#top)

## Code

Let's implement the new layout!

### Basic Style

You need to overwrite the stylesheet for both the **cell dimension** and the **cell position**. Place the following stylesheet into your Custom CSS editor to display the new layout.

```
/* Width & Height */
.mosaic-grid-type-1,
.mosaic-grid-type-2 {
    .mosaic-grid-cell-1,
    .mosaic-grid-cell-2,
    .mosaic-grid-cell-3,
    .mosaic-grid-cell-4,
    .mosaic-grid-cell-5,
    .mosaic-grid-cell-6,
    .mosaic-grid-cell-7,
    .mosaic-grid-cell-8,
    .mosaic-grid-cell-9 {
        width: 33.33%;
        height: 33.33%;
    }
    .mosaic-grid-cell-2,
    .mosaic-grid-cell-5,
    .mosaic-grid-cell-8 {
        width: 33.34%;
        height: 33.33%;
    }
    .mosaic-grid-cell-4,
    .mosaic-grid-cell-5,
    .mosaic-grid-cell-6 {
        height: 33.34%;
    }
    .mosaic-grid-cell-10 {
        display: none;
    }
}
/* Position */
.mosaic-grid-type-1,
.mosaic-grid-type-2 {
    .mosaic-grid-cell-1 {
        top: 0;
        left: 0;
        right: auto;
    }
    .mosaic-grid-cell-2 {
        top: 0;
        left: 33.33%;
        right: auto;
    }
    .mosaic-grid-cell-3 {
        top: 0;
        left: auto;
        right: 0;
    }
    .mosaic-grid-cell-4 {
        top: 33.33%;
        left: 0;
    }
    .mosaic-grid-cell-5 {
        top: 33.33%;
        left: 33.33%;
        right: auto;
    }
    .mosaic-grid-cell-6 {
        top: 33.33%;
        left: auto;
        right: 0;
    }
    .mosaic-grid-cell-7 {
        top: 66.64%;
        left: 0;
        right: auto;
    }
    .mosaic-grid-cell-8 {
        top: 66.67%;
        left: 33.33%;
        right: auto;
    }
    .mosaic-grid-cell-9 {
        top: 66.67%;
        right: 0;
        left: auto;
    }
}
```

[Back to Top](#top)

### Gap Style

To create gaps between tiles you can use the following CSS example.

```
@gap: 10px;
.tile {
    background: #000;
    // Gap Setting
    &-content {
        background: #333;
        box-sizing: content-box;
        margin: @gap 0 0 @gap;
        .mosaic-grid-cell-3 &,
        .mosaic-grid-cell-6 &,
        .mosaic-grid-cell-9 & {
            margin-right: @gap;
        }
        .mosaic-grid-cell-7 &,
        .mosaic-grid-cell-8 &,
        .mosaic-grid-cell-9 & {
            margin-bottom: @gap;
        }
    }
}
```

[Back to Top](#top)

## Demo

Click the following image to see the Event in your browser.

[![Snapshot](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/gzlVNeZttkcDjxjJokx3/snapshot.png)](https://stacklapreview.stackla.com/eventscreen/show/2724)

[Back to Top](#top)


# Email


# Adding Location to an Email Tile

* [Overview](#overview)
* [Getting Started](#getting-started)
* [Google Maps](#google)

## Overview

This article will instruct how to add a map visual to the base of an Email Tile to visualize the location where a UGC Tile was created using Google Static Maps API.

To achieve this we will adjust the Tile Layout. Nosto's UGC Layout editors are built using the [Mustache](https://mustache.github.io) template framework, which is what we will be editing to add the Map visuals to the Tile.

We will not be adjusting the CSS or any other component of the Email template. For information on how to style your Email Tile, [click here](/ugc/guides/email/styling-the-email-plugin).

[Back to Top](#top)

## Getting Started

Assuming that you have already created your Email Campaign and have configured your Campaign Settings and Email Tile Appearance, we can now look to add our Map visual.

For this guide, we are going to explore using Google Maps to provide the render that we will append to the base of the Tile. We have chosen OSM and Google as Map providers that offer a Static Map API.

### Google Maps

Google offers its own Static Map API, called [Maps Static API](https://developers.google.com/maps/documentation/maps-static/intro). Much like other vendors, the Maps Static API lets you embed a Google Maps image on your web page without requiring JavaScript or any dynamic page loading, however, the Map Static API requires you to generate a Google API Key before you can use it.

To find out how to generate your API Key, [click here](https://developers.google.com/maps/documentation/maps-static/get-api-key).

The structure of the Static Map API endpoint is as follows:

```
/staticmap?center={{Latitude}},{{Longitude}}&zoom={{Zoom}}&size={{Width}}x{{Height}}&maptype={{MapType}}&key={{APIKey}}
```

You can also add Markers to your output by adding the following parameters

```
&markers=color:{{Colour}}%7Clabel:{{Label}}%7C{{Latitude}},{{Longitude}}
```

Following a similar process to what we did with OSM, we will look to add the following code to the Tile Partial in our Code editor:

```
<div id="{{_id.$id}}" class="tile {{source}} {{media}}"
    style="background-color:#{{style.text_tile_background}}">
    <div class="tile-content">
        {{>tpl-image}}
        {{>tpl-profile}}
        {{>tpl-message}}
		{{#location}}
		<img src="https://maps.googleapis.com/maps/api/staticmap?center={{location.latitude}},{{location.longitude}}&zoom=13&size=280x200&maptype=roadmap&markers=color:blue%7Clabel:
{{location.name}}%7C{{location.latitude}},{{location.longitude}}&key=API_KEY_HERE">
		{{/location}}
    </div>
</div>
```

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/cbx9t7Tz6ICxlwBDg6ot/5b0be268688da-02.jpg)

[Back to Top](#top)


# Styling the Email Widget

* [Overview](#overview)
* [Using the Code Editor](#code-editor)
* [Getting Started](#getting-started)
* [Create the Square Tiles](#create-the-square-tiles)
* [End Result](#end-result)

## Overview

This article will instruct you on how to custom style our Email Plugin Tile Snaps using Custom CSS.

[Back to Top](#top)

## Using the Code Editor

You can use the Code Editor to modify the CSS, JavaScript and the Layout for our Tiles. For the purpose of this guide, we will be focusing on manipulating the CSS and Layout for our Tiles.

**Note:** Nosto's UGC CSS editors support the [LESS CSS pre-processor](http://lesscss.org). This means you are free to use either the traditional CSS syntax or the LESS syntax when designing your Tile Snap. Nosto's UGC Layout editors are built using the [Mustache](https://mustache.github.io) template framework.

### Template Structure

Whilst the Tiles generated from Nosto's UGC Email widget are static, the design of the Tiles are based upon our Waterfall Widget.

This means the class selectors and ID selectors used to style our Waterfall Widget are exactly the same, by default, in our Email Plugin. It also means that there a selectors referenced within the Email Plugin, which will not render within a static image (ie. Hover / Mouse-Over Effects).

The basic structure of the Tiles are as follows:

| <p><strong>Diagram</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/ce9FzD31VCsX33Rf6tKc/waterfall-tile-structure.png" alt="" data-size="original"></p> | <p><strong>Snapshot</strong></p><p><img src="https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/x6V0GIgcX8UtXReG9OlD/waterfall-tile-example.png" alt="" data-size="original"></p> |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### Fork Process:

The best place to start when it comes to manipulating your Email Tile Snap is to leverage the fork link at the top-right hand corner of each editor pane. This exposes the Mustache and CSS code from the Nosto's UGC codebase for you to edit.

This approach is different from our Nosto's UGC Widgets, with exception to the Blank Canvas, which will only present the boilerplate when your click fork within each of these panes. As such, Nosto's UGC treats the code within the Code Editor as the only code for this output. It means if you remove any reference to a Mustache partial or a CSS selector, then this will not be loaded within the Tile Snap.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/fiip7mvoBFmKKMB26jX6/code-editors-1.png)

## Getting Started

### Create your Email Campaign

First step in the process is to create an Email campaign. To start, please ensure that the Email plugin has been installed on your Stack, and then go to **Display > Email** and select **New Campaign**.

From here you should be presented with the default, Waterfall inspired, Email campaign.

### Configure your Campaign’s Settings

Before you apply your imagination and innovative ideas to your Email Campaign’s Tile Snaps, it's suggested to set up it correctly.

In Email Campaign setup, we only provide very basic setting options. These include Name, Filter, Slug, Show Tags and Tile Creation under Settings, and some basic Tile Configurations under Appearance.

Arguably Selected Filter is the most important setting among them.

### Custom Code Editors

Unlike other widgets which have the CSS and JavaScript editors, the Blank Canvas widget provides the other Layout and Tile editors so that you can write code in [Mustache](https://mustache.github.io).

### Coding for Layouts:

If you are familiar with the [Mustache](https://mustache.github.io) template engine, you probably know the [Partials](https://mustache.github.io/mustache.5.html#Partials). The Partials feature just makes it easier to modulize your templates.

Nosto's UGC introduces the Mustache Partials feature. You can add, remove, or rename any templates. By default we have included the following:

* Layout
  * Tile
    * Image
    * Profile
    * Message
    * Tags

Image, Profile, Message and Tags are simply partials of the Tile template and can be rolled up into the Tile template.

### Coding for CSS

As Tile Snaps are generated as JPEG images, any CSS rules you apply won’t impact any Email campaigns or anywhere else you might add these outputs.

The CSS is simply used to help inform Nosto's UGC how the Tiles should render.

As mentioned above, to access the default CSS which is applied to a Tile Snap, simply press on the Fork button.

[Back to Top](#top)

## Creating the Square Tile

A possible use-case for clients who are using our Email Plugin to create Tile Snaps, is that they want to ensure all Snaps have a consistent Height and Width.

Naturally, as Images files aggregated by Social Networks may have varying sizes, we are going to crop our Images to be not only square, but for the Image to be centred.

Using the standard, Waterfall inspired template, we are going to show how you can achieve this quickly and easily in Nosto's UGC to render something similar to the below:

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/eMOa1DTfyunvGgbgFTd1/screen2-250x250.jpg)

### Tile

To begin with, as we have no need for the message associated with our tiles, we are going to load up the Tile partial within the Custom Code editor and select Fork.

From here we are going to delete references to `{{>tpl-message}}`, `{{>tpl-tags}}` and `{{>tpl-actions}}` to leave us with the following Mustache markup.

```

<div id="{{_id.$id}}" class="tile {{source}} {{media}}"
    style="background-color:#{{style.text_tile_background}}">
    <div class="tile-content">
        {{>tpl-image}}
        {{>tpl-profile}}
    </div> 
</div>
```

### Image

Next we are going to manipulate the Image partial. Now for this partial, when we hit fork, you will notice references to Shopspots. As we don’t need these for this example, we are going to remove all that code.

Next we are going to make the Image for the Tile a background image so we can easily re-size, crop and position.

```

{{#image}}
    <div class="tile-image-wrapper" style="background-image:url({{image}})">
    </div>
{{/image}}
```

### CSS

Last step now is to change the CSS. Again, much like the two partials, we are going to hit fork to load the basic CSS for the Nosto's UGC output.

From here we wish to do the following, we want to assign a fixed Width and Height to our `.tile` class of 400px each.

We then want to change `.tile-source`, `.tile-user-info` and `.tile-avatar-wrapper` to be `position:absolute` and then to apply fixed positions based upon how far from the top, bottom, left and/or right we would like them to be.

We will then define our `.tile-image-wrapper` with the following:

```

.tile-image-wrapper {
    width: 400px;
    height: 400px;
    overflow: hidden;
    margin: 0px;
    position: relative;

	background-size: cover;
	background-position: center center;
}
```

And finally we remove any CSS which we feel isn’t relevant for our output. Leaving us with something like the below:

```

@import url(https://fonts.googleapis.com/css?family=Lato:400,300,100,700,900);
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline; }

/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
    display: block; }

ol, ul {
    list-style: none; }

blockquote, q {
    quotes: none; }

blockquote:before, blockquote:after, q:before, q:after {
    content: '';
    content: none; }

table {
    border-collapse: collapse;
    border-spacing: 0; }

@font-face {
    font-family: 'stackla-icons';
    src: url('/media/fonts/stackla-icons/stackla-icons20140417.eot');
    src: url('/media/fonts/stackla-icons/stackla-icons20140417.eot?#iefix') format('embedded-opentype'), url('/media/fonts/stackla-icons/stackla-icons20140417.woff') format('woff'), url('/media/fonts/stackla-icons/stackla-icons20140417.ttf') format('truetype'), url('/media/fonts/stackla-icons/stackla-icons20140417.svg#stackla-icons') format('svg');
    font-weight: normal;
    font-style: normal; }

[class^="stackla-icon-"], [class*=" stackla-icon-"] {
    font-family: 'stackla-icons';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    /* Better Font Rendering =========== */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale; }

body {
    color: #666;
    font-family: 'Lato', sans-serif;
    font-size: 12px;
    font-weight: 300;
    line-height: 1.0;
	color: #fff;
    margin: 0;
    padding: 0;
    width: 100%; }

a:link, a:visited {
    color: #fff;
    text-decoration: none; }

strong {
    font-weight: bold; }

.clearfix:after, .tile-sharing-expanded:after, .tile-belt:after, .tile-interactions:after {
    clear: both; }

.clearfix, .tile-sharing-expanded, .tile-belt, .tile-interactions {
    zoom: 1; }
.clearfix:after, .tile-sharing-expanded:after, .tile-belt:after, .tile-interactions:after {
    clear: both;
    content: '';
    display: block;
    overflow: hidden; }

.hidden {
    display: none; }

.isotope-item.isotope-hidden {
    pointer-events: none; }

.track {
    position: relative;
    overflow: hidden; }

.container {
    margin-top: -1px;
    margin-right: -1px;
    position: relative; }


.tile-source {
	position: absolute;
    font-size: 40px;
    line-height: 40px;
    margin-right: 8px;
	bottom: 40px;
	left: 10px;
    width: 40px;
	text-shadow: 1px 1px 3px #000000; }
.tile-source-twitter {
    color: #fff; }
.tile-source-flickr {
    color: #fff; }
.tile-source-instagram {
    color: #fff; }
.tile-source-sta_feed {
    color: #fff; }
.tile-source-gplus {
    color: #fff; }
.tile-source-rss {
    color: #fff; }
.tile-source-youtube {
    color: #fff; }
.tile-source-facebook {
    color: #fff; }

.tile-user-info {
	position: absolute;
    display: table;
	bottom: 40px;
	left: 52px;}
.tile-user-info .tile-user {
    width: 100%; }
.tile-user-info a {
    text-decoration: none; }
.tile-user-info a:link, .tile-user-info a:visited {
    color: #fff; }
.tile-user-info a:hover {
    text-decoration: underline; }
.tile-user-top, .tile-user-bottom {
    display: block;
    line-height: 1.0;
    width: 100%; }
.tile-user-name, .tile-user-handle {
    display: inline-block;
	color: #fff !important;
    font-weight: 700;
	text-shadow: 1px 1px 3px #000000;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    width: 100%; }

/* Tile */
.tile {
    cursor: pointer;
    font-weight: 300;
    position: relative;
	height: 400px !important;
	width: 400px !important;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale; }
.tile-content {
    position: relative;
    overflow: hidden;
    margin-bottom: 0px; }
.tile-content > div:last-child {
    margin-bottom: 0; }
.tile-image-wrapper, .tile-video-wrapper {
    position: relative; }
.tile-video-wrapper, .tile-image-wrapper {
    margin-bottom: -36px;
    position: relative; }
.tile-video-wrapper iframe, .tile-image {
    display: block;
    margin: 0 auto;
    z-index: 1; }
.tile-image {
    height: auto;
    width: 100%; }
.tile-video-icon {
    background: url(http://sdp-media.dev/images/video-icon.png) no-repeat 0 0; }
.tile-avatar-wrapper {
    display: block;
    margin: 0 auto;
    position: absolute;
	bottom: 32px;
	right: 20px;
    text-align: right;
    width: 100%; }
.tile-avatar-link {
    display: inline-block;
    height: 56px;
    width: 56px; }
.tile-avatar-placeholder, .tile-avatar-image {
    background-color: rgba(255, 255, 255, 0.5);
    border: solid 2px #fff;
    border-radius: 50%;
    display: inline-block;
    height: 52px;
    vertical-align: middle;
    width: 52px; }
.tile-avatar-placeholder {
    display: inline-block;
    position: absolute; }
.tile-avatar-placeholder img {
    border-radius: 50%;
    border: none;
    box-sizing: border-box;
    height: 100%;
    width: 100%; }
.facebook .tile-avatar-placeholder {
    background: #39579a; }
.twitter .tile-avatar-placeholder {
    background: #00abf0; }
.gplus .tile-avatar-placeholder {
    background: #dd4b39; }
.instagram .tile-avatar-placeholder {
    background: #80413c; }
.stackla .tile-avatar-placeholder, .sta_feed .tile-avatar-placeholder {
    background: #75c237; }
.rss .tile-avatar-placeholder {
    background: #ff6600; }
.weibo .tile-avatar-placeholder {
    background: #e6162d; }
.youtube .tile-avatar-placeholder {
    background: #d32325; }
.pinterest .tile-avatar-placeholder {
    background: #c3090c; }
.ecal .tile-avatar-placeholder {
    background: #1e8fcf; }
.tumblr .tile-avatar-placeholder {
    background: #45556b; }
.flickr .tile-avatar-placeholder {
    background: #ed1e83; }
.wordpress .tile-avatar-placeholder {
    background: #21759B; }
.tile-belt {
    margin-bottom: 0px; }
.tile-title {
    margin-bottom: 0px; }
.tile-caption, .tile-html {
    color: #333;
    font-weight: 400;
    margin-bottom: 0px;
    overflow: hidden;
    word-wrap: break-word; }
.tile-caption .emoji, .tile-html .emoji {
    vertical-align: middle; }
.tile-emoji {
    margin-right: 2px;
    vertical-align: middle;
    width: 13px; }

.tile.html {
    padding: 0; }

.tile iframe {
    display: block; }

.tile-custom-clickthru {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 1; }

.tile-official-star {
    position: absolute;
    top: 0;
    right: 0;
    color: #ffcc00 !important;
    font-size: 18px; }

.tile-title {
    font-weight: bold;
    font-size: 20px;
    color: #333;
    max-height: 80px;
    overflow: hidden; }

.anonymous .tile-image-wrapper, .anonymous .tile-video-wrapper, .content-only .tile-image-wrapper, .content-only .tile-video-wrapper {
    margin-top: 0; }


.icon {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -15px;
    margin-top: -15px;
    width: 30px;
    height: 30px; }

.stackla .tile-html {
    max-height: none; }


.tile-html {
    background: #eee;
    padding: 0px; }

.stackla .tile-html {
    margin: 0;
    padding: 0;
    background: none; }

.tile {
  	border: solid 1px #e5e5e5;
    box-sizing: border-box;
    width: 320px; }
.tile-content {
    box-sizing: border-box;
    position: relative;
    padding: 0px; }
.tile-video-wrapper {
    margin: 0; }
.tile-source {
    float: left; }
.tile-timestamp {
    float: right; }

.tile-image-wrapper {
    width: 400px;
    height: 400px;
    overflow: hidden;
    margin: 0px;
    position: relative;

	background-size: cover;
	background-position: center center;
}
```

From here we hit Save and away we go.

[Back to Top](#top)

## End Result

Assuming you've copied the code from above, your Email Tile should look very similar to the live version below

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/RUCXzaD83nzHlvs0eTBn/run-club-01.jpg)

[Back to Top](#top)


# Integrations


# DoubleClick


# UGC Ads with Nosto and Google DoubleClick

This guide provides a high level overview around how Nosto's UGC can be utilised to empower Banner Ads generated across the Google DoubleClick network.

* [Overview](#overview)
* [Building a Banner Ad](#building-a-banner-ad)
  * [Generate the Feed](#generate-the-feed)
  * [Add the Feed to DoubleClick Studio](#add-the-feed-to-doubleclick-studio)
  * [Build the Ad Template](#build-the-ad-template)
  * [Publish the Template](#publish-the-template)

## Overview

One of the strengths of the Nosto's UGC platform is the ability to serve aggregated and curated User Generated Content (UGC) through display outputs beyond those offered within the package.

From Digital Out Of Home (DOOH) displays to Banner Advertising, Nosto's UGC can provide a XML or JSON content feed which can be translated by many of the industry's leading AdTech or Digital Display providers to generate dynamic, authentic displays.

Google DoubleClick is one such provider which supports this approach, by allowing clients to provide a feed of content to generate a Banner Ad seamlessly.

This guide outlines at a high level one of the approaches a client can take to creating Ads in Google DoubleClick using Nosto's UGC as the source.

## Building a Banner Ad

There are a number of ways in which Google DoubleClick can accept a content feed from a third party source like Nosto's UGC to help generate a Banner Ad.

This guide focuses on using DoubleClick Studio and Google Web Designer, however customers can also use DoubleClick Creative Solutions or the DoubleClick Rich Media editor to also create dynamic ads powered by Stackla.

### Generate the Feed

Step one to integrating Nosto's UGC with your Google DoubleClick account is to generate the feed that will power your Ad Units.

*Note: It is assumed prior to this step that the client has already setup their Stack and defined a Filter where approved content for the Ad Units is available.*

To start the process, the client should install the Nosto's UGC REST API Plugin and generate an Authentication Token for their account. To do this the client should:

* Go to the Nosto Admin Portal > UGC and click **‘Plugins’**
* Find the Plugin titled **‘REST API’** and hit install
* Once installed, they can now generate an Authentication Token by hitting **‘Generate’**
* The user will be asked to confirm their password
* The Authentication Token will now be generated

Once generated, the user can now visit the REST API section of the Admin Interface to find the feed endpoint. To do this, the user should click on **‘Developer’** and then **‘REST API Console’**.

The REST API endpoint we will be using in DoubleClick Studio is called **GET/Filter/{FilterID}/Content**. It is located under the **‘Filters’** section within the REST API Console.

The minimum parameters the user will need to define when generating the endpoint are:

* **filterID** - ID of the Filter of the content the client wishes to use
* **limit** - Number of pieces of content they want to make available in the feed
* **status** - Should be set to published

The **‘Response Content’** type should also be set to application/xml.

Once these values have been set, the user can hit **‘Try it Out’** to generate the endpoint URL they’ll need for Google DoubleClick Studio.

### Add the Feed to DoubleClick Studio

Once you have generated your Feed it is now possible to add it to DoubleClick Studio.

To do this, the user should log into their DoubleClick Studio account and click on the tab **‘Dynamic Content’**.

From here, the user can setup their respective **‘Profiles’** and **‘Content Sources’** for their display ads.

The Nosto's UGC Feed endpoint should be added as a **‘Content Source’** within DoubleClick Studio and associated with a Profile(s).

### Build the Ad Template

Once the feed has been added as a ‘Content Source’ in DoubleClick Studio, you can now load Google Web Designer to create your respective banner ads.

Google Web Designer provides the opportunity for clients to create their own Banner Ads from scratch or use a pre-built ad template. For the purposes of this guide we will create a blank MREC (300x250) banner ad.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/BKBhV1ElbXhe72akdkzO/image03.png)

Once we have specified the Dimensions, Environment, Name and Location we can hit OK to proceed to the canvas editor.

Once in the Canvas Editor you can now start building our your Banner Ad design. For the purposes of this example, we are going to leverage an out of the box component titled **‘Swipeable Gallery’**.

To add a **‘Swipeable Gallery’** simply click on the ‘Components’ section on the Right Hand Size and select the option from the list provided. Once selected you can now drag this onto the canvas into the position you would like the images to render.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/D8zIhGOfyT7U0XamOfmo/image01.png)

Once added to the Canvas, Google will apply an automatic ID to the component. In this example the **‘Swipeable Gallery’** we have just added is titled **gwd-swipegallery #gwd-swipegallery\_1**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/z3JpQOtpeBjScsZDkhLb/image00.png)

This title will be required once we start binding these components to our Dynamic feeds.

Next step is again on the right hand side to find the section titled **‘Dynamic’** and press the + button to create some Dynamic Properties.

From here you will be presented with a Prompt allowing you to pick a **Data Schema Type**. We will want to select **‘Custom Schema’** as we will be using the Content Feed we specified earlier.

Google will prompt you to log into your DoubleClick account, before then presenting you the option to specify your **Account**, **Advertiser** and **Profile**.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/N9DYCIWhYQFxKFxVISd3/image02.png)

**Profile** relates to the Custom Profile we created in DoubleClick Studio earlier in this guide. Make sure to select the Profile which is associated with your Nosto's UGC feed.

![](https://content.gitbook.com/content/903dsFKGBuxQbjtVNMIV/blobs/72SX8HgUxpJ2QSoqkurc/image04.png)

Once done, you may now click on the **‘Bindings’** Tab and select to add a binding.

When creating a Binding you must specify three values:

* Element
* Element Attribute
* Data Schema Object

**Element** refers to both components and any other items added to the Banner display. For the purpose of this guide, we will specify the component **gwd-swipegallery #gwd-swipegallery\_1**.

**Element Attribute** refers to all the configurable attributes that relate to that item / component. For a **‘Swipeable Gallery’** this includes image, text, HTML, sizing and transitional attributes.

Each attribute selected needs to be associated with an object within the Nosto's UGC Feed. As such if you pick the **Element Attribute** ‘Images’ you will want to find the relevant ‘Image’ in the Nosto's UGC feed.

From here you can elect to join as many Element Attributes to as many Data Schema Objects as you like. You can also choose to apply additional Filters in this view as well if desired.

### Publish the Template

Once you have created your template and are happy with it, you may now choose to Publish and add this to your DoubleClick account for use.


# Zapier




---

[Next Page](https://docs.nosto.com/ugc/llms-full.txt/1)

