# Работа с рекомендательными виджетами

Вы можете использовать JS SDK API для получения рекомендаций в рамках любого типа веб-кампаний непосредственно в коде своего проекта. Результат возвращается в JSON-формате. Вот некоторые преимущества, которые дает использование данного API:

- полный контроль над тем, как отображать рекомендации;
- возможность добавлять кастомный прелоадер-скелетон;
- дополнительная или пост-обработка данных из фида перед тем как отобразить виджет;
- контроль над fallback - вы можете не отображать виджет, если стратегия ушла в fallback или сделать запрос к другой стратегии в этом случае;
- использование динамических фильтров;
- подмена контекста в запросе.

---

## Получение рекомендаций

Функция используется для получения рекомендаций на основе заданной стратегии или нескольких стратегий. Она позволяет запрашивать рекомендации с индивидуальными настройками и исключать дубликаты между разными стратегиями.

**Синтаксис**

```jsx
GF.Recommendations.get(strategy, options, callback)
```

**Параметры:**

{.compact}
| Параметр       | Тип    | Описание |
| --- | --- | --- |
| `strategy`  [!badge variant="danger" text="обязательно"]  | `string / Array<string> / Array<Object>` | Идентификатор стратегии или массив стратегий. В массиве можно передавать строки или объекты с полем `id`. |
| `options`  [!badge variant="danger" text="обязательно"]  | `Object` | Может быть пустым объектом. Если `context` не передан, используется текущий `GF.pageContext`. |
| Объект **options**: | | |
| `context` | `Object` | Контекст для стратегии. По умолчанию используется контекст страницы, с которой происходит вызов. |
| `maxProducts` | `number` | Количество запрашиваемых товаров. По умолчанию 20. Максимально 50. |
| `identifier` | `string` | Дополнительный идентификатор для сценариев, где выдача должна учитывать внешний контекст. |
| `realtimeRules` | `Array<Object>` | Массив динамических фильтров. Подробнее о них ниже. |
| `callback`  [!badge variant="danger" text="обязательно"]  | `function` | Коллбэк-функция, выполняемая после получения результата работы стратегии.  |

=== Пример использования:

```jsx
GF.Recommendations.get('1364138cfe9b4f979a044a32', {
  maxProducts: 10,
}, function (err, data) {
  if (err) {
    // error handling
    return;
  }

  const recData = data;
});
```
=== Пример ответа:

Набор свойств товара в ответе зависит от настроек продуктового фида в секции. 

!!!
Если вам не хватает каких-либо данных, обратитесь в службу поддержки или к команде, работающей на вашем проекте.
!!!

```json
{
  "wId": "63a98ddd11fad4619a0c05b0",
  "name": "Sample strategy name",
  "fId": "6336f2f22b77b4c6da026ad4",
  "fallback": false,
  "slots": [
    {
      "item": {
        "sku": "1000001",
        "group_id": "1000001",
        "categories": [
          "Женское",
          "Платья"
        ],
        "in_stock": true,
        "name": "Платье из вискозы и шерсти",
        "price": "29900",
        "url": "...html",
        "image_url": "https://...image.png"
      },
      "fallback": false,
      "strId": 3
    }
  ]
}
```
==- Расширенные варианты использования

```jsx
GF.Recommendations.get(
  ['6406d885d9ea38c202022a73', '1364138cfe9b4f979a044a32'],
  {
    maxProducts: 5,
    realtimeRules: [],
  },
  function (err, data) {
    console.log(data);
  },
);

GF.Recommendations.get(
  [
    { id: '1364138cfe9b4f979a044a32' },
    { id: '6406d885d9ea38c202022a73', maxProducts: 2 },
  ],
  {
    maxProducts: 5,
    realtimeRules: [],
    context: { type: 'PRODUCT', data: ['123123'], lng: 'spb' },
  },
  function (err, data) {
    console.log(data);
  },
);
```
===

---

## Трекинг рекомендаций

Чтобы отслеживать аналитику рекомендательного виджета, который вы вручную добавили на свой сайт с помощью Client-Side API, необходимо определенным образом разметить виджет и вызвать дополнительную функцию трекинга. Для настройки трекинга используются данные, полученные в ответе API.

Блок, содержащий товары виджета, должен иметь дата-атрибуты:

- `data-sl-widget-id` - `wId` из JSON-ответа, идентификатор стратегии;
- `data-sl-feed-id` - `fId` из JSON-ответа, идентификатор фида;
- `data-sl-fallback` - `fallback` из JSON-ответа, статус fallback стратегии;
- `data-sl-external` - опциональный признак внешнего виджета. Используйте значение `true`, если виджет рендерится вне кампании Gravity Field.

Блок, содержащий товар, должен иметь дата-атрибуты:

- `data-sl-product-id` - `sku` из JSON-ответа, артикул товара;
- `data-sl-strategy-id` - `strId` из JSON-ответа, идентификатор типа стратегии, может отличаться у разных слотов;
- `data-sl-product-fallback` - `fallback` из JSON-ответа товара, статус fallback конкретного товара;
- `data-sl-product-slot` - порядковый номер товара в виджете, начинается с 0.

Допускается произвольный уровень вложенности между блоком-контейнером и товаров.

=== Пример

```html
<div 
   data-sl-widget-id="6308a8277asdf28f7b055a9d" 
   data-sl-feed-id="6308a8277asdf28f7b055a9d"
   data-sl-fallback="false" 
>
	<div 
     data-sl-product-fallback="false" 
     data-sl-product-id="123456" 
     data-sl-strategy-id="4"
     data-sl-product-slot="0" 
  >
     ...
  </div>
	
</div>
```
===

После того как виджет вставлен на страницу, вызовите функцию трекинга:

**Синтаксис**

```jsx
GF.Recommendations.track(element, variationData)
```

**Параметры:**

{.compact}
| Параметр       | Тип    | Описание |
| --- | --- | --- |
| `element`  [!badge variant="danger" text="обязательно"] | `HTMLElement` | DOM-элемент контейнера, размеченный дата-атрибутами `data-sl-widget-id` и другими. |
| `variationData` | `Object` | Данные вариации. Нужны для виджетов внутри кампаний Gravity Field. Если виджет создается вне кампании, можно не передавать. |

Объект **variationData**: 
```
{
  var: '${slVariationId}',
  ver: '${slVersionId}',
  exp: '${slExperienceId}',
  cam: '${slTagId}',
}
```

!!!
`${slVariationId}`, `${slVersionId}`, `${slExperienceId}`, `${slTagId}` - технические переменные редактора вариации Gravity Field. Они будут заменены на соответствующие идентификаторы.
!!!

=== Пример использования:
```jsx
const element = document.querySelector('[data-sl-widget-id="6308a8277asdf28f7b055a9d"]');

GF.Recommendations.track(element, {
  var: '${slVariationId}',
  ver: '${slVersionId}',
  exp: '${slExperienceId}',
  cam: '${slTagId}',
});
```
===

### События виджета

После вызова `GF.Recommendations.track` контейнер виджета может отправлять DOM-события:

{.compact}
| Событие | Когда срабатывает |
| --- | --- |
| `gf_wrimp` | Первый реальный показ виджета. |
| `gf_pimp` | Показ товаров внутри виджета. В `event.detail.slots` передается список показанных товаров. |

```jsx
element.addEventListener('gf_pimp', function (event) {
  console.log(event.detail.slots);
});
```

---

## Динамические правила

В некоторых случаях может появиться потребность в создании правила для стратегии, которое будет зависеть от условий, имеющихся в момент просмотра страницы. Для таких случаев существуют динамические фильтры, которые позволяют создавать правила в реальном времени и получать рекомендации на основании данных, полученных в ходе сеанса. Например, показывать товары по более высокой цене, чем те, которые пользователь просматривает в данный момент, или предлагать товары на основе известных данных о пользователе.

### Распространные варианты использования

- Предложения по бесплатной доставке: рекомендуемые товары, которые попадают в ценовой диапазон, позволяющий пользователю получить бесплатную доставку (выше порогового значения).
- Рекомендовать товары в определенном диапазоне относительно просматриваемого товара, например, от 80% до 150% от текущей цены товара.
- Рекомендовать товары исходя из параметров URL, например, отфильтрованная страница категории по бренду.
- Рекомендовать товары исходя из количества бонусных баллов пользователя в системе лояльности.

Фильтры передаются как массив объектов `realtimeRules` в параметрах запроса рекомендаций.

**Описание параметров правила**

{.compact}
| Параметр       | Тип    | Описание |
| --- | --- | --- |
| `id` | `number` | Уникальный идентификатор правила. Если передавать несколько правил с одним идентификатором - выполнится только первое. |
| `type`* | `string` | Правило может либо включать определенные товары, либо исключать. В зависимости от этого передается `include` или `exclude`. |
| `slots`* | `array of numbers` | Слоты, на которые распространяется правило. Пустой массив означает все слоты. Нумерация слотов начинается с 1. |
| `query`* | `object` |  |
| `query.conditions`* | `array of objects` | Массив условий правила, которые будут работать как логическое `И` |
| `query.conditions[].field`* | `string` | Название колонки фида, к которой относится правило |
| `query.conditions[].arguments` | `array of objects` | Массив условий правила, которые будут работать как логическое `ИЛИ` внутри конкретной колонки фида. |
| `query.conditions[].arguments[].action` | `string` | Способ сравнения. Может принимать следующие значения: `equal`, `not_equal`, `more`, `more_or_equal`, `less`, `less_or_equal`, `contains` |
| `query.conditions[].arguments[].value` | `array` | Массив значений, если передано больше одного - будет работать как логическое `ИЛИ` |

==- Получить товары из определенных категорий в первом слоте

```jsx
GF.Recommendations.get(
  '6634e31af6df45aa130d31d8',
  {
    maxProducts: 20,
    realtimeRules: [
      {
        query: {
          conditions: [
            {
              field: 'categories',
              arguments: [
                {
                  action: 'equal',
                  value: ['Столы'],
                },
                {
                  action: 'equal',
                  value: ['Стулья'],
                },
              ],
            },
          ],
        },
        type: 'include',
        slots: [1],
      },
    ],
  },
  function (err, data) {
    // process data
  },
);
```
==- Получить товары определенного ценового диапазона относительно текущего товара

```jsx
const currentProductPrice = 10000;
const minPrice = currentProductPrice * 0.8;
const maxPrice = currentProductPrice * 1.5;

GF.Recommendations.get(
  '6634e31af6df45aa130d31d8',
  {
    maxProducts: 20,
    realtimeRules: [
      {
        query: {
          conditions: [
            {
              field: 'price',
              arguments: [
                {
                  action: 'more',
                  value: [minPrice],
                },
              ],
            },
            {
              field: 'price',
              arguments: [
                {
                  action: 'less',
                  value: [maxPrice],
                },
              ],
            },
          ],
        },
        type: 'include',
        slots: [],
      },
    ],
  },
  function (err, data) {
    console.log(data.slots.map(function (slot) {
      return slot.item.price;
    }));
  },
);
```
===
