# Custom code

В данном гайде описан процесс создания рекомедательного виджета через кампанию типа Custom code и создания шаблона для последующего его переиспользования.

# Создание кампании

  1. Переходим в раздел кампаний и создаем новую кампанию
  2. Выбирем тип страницы, на котором будет работать кампания
  3. Выбираем тип кампании Custom Code
  4. Выбираем шаблон - No template

Откроется экран настроек кампании, на котором вы можете:

  • Указать название, добавить метки и описание
  • Выбрать триггер (по умолчанию - при загрузке страницы)
  • Изменить тип страницы, на котором будет работать кампания
  • Указать частотность (по умолчанию - на каждый просмотр страницы)
  • Указать запускать ли на каждый SPA-ивент (для SPA-сайтов в большинстве случаев требуется, иначе кампания запустится только при первичной загрузке SPA-приложения)

Экран настроек кампании типа Custom code
Экран настроек кампании типа Custom code

Экран настроек кампании типа Custom code

Переходим к следующему этапу, нажимая кнопку Next.

Нажимаем на Experince, чтобы открыть его настройки.

Экран настроек экспириенса
Экран настроек экспириенса

Экран настроек экспириенса

Переходим в редактор вариации, кликнув в ее название или иконку редактирования справа.

# Редактор вариации

Редактор вариации кампании типа Custom Code отличается от редактора других типов кампаний. Здесь нет внутреннего превью и возможности писать напрямую HTML, так как данный тип кампании представляет собой произвольный Javascript код и добавленный к нему CSS.

image.png
image.png

Переходим в вкладку JS и пишем код.

Для создания рекомендательного виджета нам потребуется как минимум:

  • название виджета;
  • стратегия;
  • количество товаров;
  • место и способ вставки виджета.

Объявим для этого константу и создадим переменные:

const CONFIG = {
    strategyId: '{{ ERROR }}',
    title: '',
    maxProducts: Number('{{ ERROR }}'),
    selector: '',
    insertMethod: '{{ ERROR }}'
};

После того, как в код добавлены переменные, они также появятся во вкладке Variables редактора кампаний.

Можно перейти туда и отредактировать их, поменяв тип и другие параемтры. Также доступно редактирование переменной при клике в нее в рамках открытого таба с кодом.

Пример переменных и их настроек

Устанавливаем типы переменных:

  • Strategy ID → Strategy
  • Title → Text
  • Products count → Number (от 1 до 50)
  • Selector → CSS Selector
  • Insert Method → Dropdown (в нем будем хранить способы вставки After, before, replace, заполним позже)

# Получение товаров

Далее нам потребуется функция, которая будет получать JSON с рекомендованными товарами:

function getRecs() {
    return new Promise((resolve, reject) => {
        GF.Recommendations.get(CONFIG.strategyId, {
            maxProducts: CONFIG.maxProducts
        }, function(err, data) {
            if (err) {
                return reject(false);
            }

            return resolve(data);
        })
    })
}

Вызываем функцию и обрабатываем полученный результат.

Для этого мы ждём определенный элемент на странице, куда планируем вставить виджет и когда он появляется — запрашиваем рекомендации и встраиваем их.

GF.waitForElement(CONFIG.selector, elements => {
    getRecs().then(data => {
        // process data
    });
})

# Рендер виджета

В случае успешного ответа рекомендательной стратегии, в data будет массив slots , представляющий собой набор товаров, которые нужно отрисовать в виджете. Как именно это сделать полностью зависит от вас, но для примера можно использовать следующий код:

function getHtml(data) {
    let html = `
		    <div class="demo-recs__title">${CONFIG.title}</div>
		    <div class="demo-recs">
		`;
    const itemTemplate = getProductItemTemplate();

    for (let i = 0; i < data.slots.length; i++) {
        html += itemTemplate.replace(/{([^{}]+)}/g, function (keyExpr, key) {
            return data.slots[i].item[key] || "";
        });
    }
    html += '</div>';
    
    return html;
}
	

function getProductItemTemplate() {
   
    return `
        <a href="{url}" class="gf-recs__item">
            <img src="{image_url}">
            <div class="gf-recs__price"><span class="gf-recs__price-num">{price}</span></div>
            <div class="gf-recs__name">{name}</div>
        </a>
    `;
}

Функция getHtml генерирует HTML-код для будущего виджета. Для этого используется вспомогательная функция getProductItemTemplate, которая возвращает HTML отдельного товара и в цикле происходит замена товарных переменных на данные, которые пришли из ответа стратегии. При данной реализации важно чтобы товарные переменные, объявленные в одинарных фигурных скобках точно соответствовали названию колонок в товарном фиде.

Теперь мы можем использовать это для нашей функции и попробовать вставить в DOM:

GF.waitForElement(CONFIG.selector, elements => {
    getRecs().then(data => {
		    if (data && data.slots && data.slots.length === 0) {
		      // обработка пустого ответа стратегии, если требуется
		    }
        const html = getHtml(data);
        
        elements[0].insertAdjacentHTML('afterend', html);
        
    });
})

Если все сделано правильно и переменные заполнены — вы увидите вставленный виджет (без CSS он будет выглядеть плохо — это нормально и в рамках данного гайда мы не будем заниматься версткой).

Далее вернемся к переменной CONFIG.insertMethod , которую ранее планировали сделать с типом Dropdown. Давайте зададим ей следующие значения и заменим код вставки:

image.png
image.png

// вместо строчки elements[0].insertAdjacentHTML('afterend', html);

elements[0].insertAdjacentHTML(CONFIG.insertMethod, html);

На данном этапе мы уже можем вставлять виджет на страницу с определенным заголовком и количеством товаров, указав CSS selector в значении соответствующей переменной.

# Сбор аналитических данных

Теперь необходимо сделать так, чтобы можно было собирать аналитические данные по виджету и затем увидеть их в отчете. Для этого требуется добавить определенные дата-атрибуты при генерации HTML и после отрисовки вызвать метод трекинга.

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

Конечный код может выглядеть следующим образом:

const CONFIG = {
    strategyId: '{{ ERROR }}',
    title: '',
    maxProducts: Number('{{ ERROR }}'),
    selector: '',
    insertMethod: '{{ ERROR }}'
};

GF.waitForElement(CONFIG.selector, elements => {
    getRecs().then(data => {
        if (data && data.slots && data.slots.length === 0) {
            // обработка пустого ответа стратегии, если требуется
        }
        const html = getHtml(data);

        elements[0].insertAdjacentHTML(CONFIG.insertMethod, html);
        
        const inertedElement = document.querySelector('.demo-recs');
        GF.Recommendations.track(inertedElement, {
            var: '${slVariationId}',
            ver: '${slVersionId}',
            exp: '${slExperienceId}',
            cam: '${slTagId}',
        })

    });
})

function getRecs() {
    return new Promise((resolve, reject) => {
        GF.Recommendations.get(CONFIG.strategyId, {
            maxProducts: CONFIG.maxProducts
        }, function (err, data) {
            if (err) {
                return reject(false);
            }
            return resolve(data);
        })
    })
}

function getProductItemTemplate() {
    return `
        <a 
            href="{url}" 
            class="gf-recs__item" 
            data-sl-product-fallback="{fallback}" 
            data-sl-product-id="{sku}" 
            data-sl-strategy-id="{strId}"
            data-sl-product-slot="{slot}" 
        >
            <img src="{image_url}">
            <div class="gf-recs__price"><span class="gf-recs__price-num">{price}</span></div>
            <div class="gf-recs__name">{name}</div>
        </a>
    `;
}

function getHtml(data) {
    const mappedProducts = data.slots.map(function (el, i) {
        el.item.strId = el.strId;
        el.item.fallback = el.fallback;
        el.item.slot = i;
        return el.item;
    });

    let html = `
		    <div class="demo-recs__title">${CONFIG.title}</div>
		    <div class="demo-recs" 
                data-sl-widget-id="${data.wId}" 
                data-sl-feed-id="${data.fId}" 
                data-sl-widget-fallback="${data.fallback}"
            >
	`;
    const itemTemplate = getProductItemTemplate();

    for (let i = 0; i < mappedProducts.length; i++) {
        html += itemTemplate.replace(/{([^{}]+)}/g, function (keyExpr, key) {
            return mappedProducts[i][key] || "";
        });
    }
    html += '</div>';

    return html;
}

# Создание шаблона

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

После того, как написан код и созданы необходимые переменные можно нажать кнопку “Save as Template” в правом верхнем углу интерфейса создания вариации. Появится всплывающее окно, в котором можно задать название шаблона и сохранить текущий код и переменные как шаблон.

image.png
image.png

После сохранения шаблона блокируется возможность изменять код непосредственно в вариации. Чтобы изменить код — нужно либо отвязать шаблон от вариации, либо перейти в раздел Assets/Templates и отредактировать код шаблона.

При создании новой вариации можно выбирать из двух типов шаблонов:

image.png
image.png

Custom templates — это шаблоны, которые вы создали в рамках секции. При выборе такого шаблона он связывается с вариацией.

Gravity Templates — это шаблоны, которые созданы в рамках платформы Gravity Field, они не связываются с вариацией и вставляются просто как код. Вы можете адаптировать их под свои цели и затем сохранить как собственный шаблон, который станет доступен в рамках раздела Custom Templates.