Common.js v2

Введение

Возможности фреймворка

Common.js это набор готовых скриптов для упрощения и ускорения разработки тем на платформе InSales.

С помощью фреймворка легко реализовать:

Товар:

  • Селектор опций.
  • Получение информации о товаре.

Корзина:

  • Получение актуальной информации о составе корзины.
  • Добавление/удаление позиций.

Поиск:

  • Живой поиск по сайту.

Сравнение:

  • Получение актуального списка сравнения.
  • Добавление/удаление товаров из сравнения.

Шаблонизация:

  • Работа с динамическими данными через шаблонизатор библиотеки lodash.js.

Шина событий:

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

Подключение фреймворка

Для подключения Common.js необходимо прописать настройку в settings_data.json — "common_js_version": "v2".

Файл settings_data.json не доступен через бэк-офис, поэтому новое свойство нужно добавлять вручную через скачивание темы и последующей установке с новыми параметрами. Так же файл можно поправить если для разработки вы используете — InSales-uploader.

Работу фреймворка можно посмотреть в бесплатном шаблоне — monpasie.

js
        
          

// в settings_data.json должно содержаться данное свойство

{

  "common_js_version": "v2"
}

Важно знать

Библиотеки используемые в Common.js:

Lodash доступен глобально, можно и нужно пользоваться возможностями этой библиотеки при разработке.

Имена переменных которые Common.js присваивает в глобальную область видимости:

  • Cart
  • Shop
  • Products
  • Compare
  • Site
  • AjaxSearch
  • ajaxAPI
  • Template
  • EventBus

В проектах где используется Common.js нельзя переопределять данные переменные, так же при подключении Common.js к работающему сайту нужно проверять переменные на переопределение и особенно обратить внимание на переменные cart и site.

Готовые решения Common.js для компонентов магазина работают по следующей логике:

  1. В разметку компонента проставляются обязательные data атрибуты. В эти атрибуты из liquid передаются данные из магазина, а так же есть data атрибуты которые реализуют действия (кнопки добавить или удалить товар из корзины, переключение модификаций, изменение quantity и т.д.).
  2. Common.js при загрузке страницы пробегает по проставленным data атрибутам и на основе данных инициализирует скрипты для работы компонентов. 
  3. Практически на каждое взаимодействие с компонентами реализованными с помощью фреймворка (обновление корзины, переключение модификации и т.д.) можно повесить обработчик, в callback которого приходит информация о событии, например при обновлении корзины в callback попадает информация о актуальном состоянии корзины, исходя из чего можно сделать динамический виджет корзины или обновление информации на странице корзины без перезагрузки страницы.

Так же стоит ознакомиться с подробным описанием API фреймворка Common.js.

API фреймворка предоставляет удобные и протестированые методы, для разработки своих компонентов.

Но все обращения к API нужно производить после собития DOMContentLoaded оно же $(document).ready(function() {}), $(function() {}).

Products

Разметка

АтрибутНазначениеРасположение
data-product-id Обязательный атрибут для инициализации товара, принимает id товара Тег form который является обёрткой для всех полей товара
action Обязательный атрибут для формы добавления товара в корзину, принимает url корзины. Тег необходим для отправки формы при отключенном JavaScript в браузере Тег form который является обёрткой для всех полей товара
data-product-variants Обязательный атрибут для вывода Option Selectors Тег select в котором выведены все модификации товара
data-quantity Обязательный атрибут для обёртки кнопок изменения колличества и инпута quantity Внутри формы с атрибутом data-product-id
data-quantity-change Атрибут для кнопок +/-, принимает число Внутри обёртки с атрибутом data-quantity
data-item-add Добавление товара в корзину, для данного атрибута следует использовать тег button[type="submit"] Внутри формы с атрибутом data-product-id
name="comment" Комментарий к позиции заказа, для работы поля с данным атрибутом комментарии к заказам должны быть включены в бэк-офисе Input[type="text"] внутри формы с атрибутом data-product-id
Liquid
        
          

<form action="{{ cart_url }}" method="post" data-product-id="{{ product.id }}">
  {% if product.show_variants? %}
    <select name="variant_id" data-product-variants>
      {% for variant in product.variants %}
        <option value="{{ variant.id }}">{{ variant.title | escape }}</option>
      {% endfor %}
    </select>
  {% else %}
    <input type="hidden" name="variant_id" value="{{product.variants.first.id}}" >
  {% endif %}

  <input type="text" name="comment" value="">

  <div data-quantity>
    <input type="text" name="quantity" value="1" />
    <span data-quantity-change="-1">-</span>
    <span data-quantity-change="1">+</span>
  </div>

  <button type="submit" data-item-add>
    Добавить в корзину
  </button>
</form>

Пример

Events

before:insales:product Событие срабатывает перед любым взаимодействием с компонетом продукта
always:insales:product  Событие срабатывает после любого взаимодействия с компонетом продукта
change_quantity:insales:product  Обновление кол-ва в продукте
unchange_quantity:insales:product  Если введено кол-во больше доступного
update_variant:insales:product  Обновление варианта в продукте

Methods

get(id)

Получаем готовый к употреблению товар

getList(idLists)

Получение списка товаров

setConfig(settings)

Обновление настроек продуктов

js
        
          
Products.get(123456)
   .done(function (onDone) { console.log('onDone', onDone) })
   .fail(function (onFail) { console.log('onFail', onFail) });

Products.getList([123456, 123455, 1234454, 123458])
   .done(function (onDone) { console.log('onDone', onDone) })
   .fail(function (onFail) { console.log('onFail', onFail) });

Products.setConfig({
options: {
default: 'option-default'
},
showVariants: true,
hideSelect: true,
initOption: true,
fileUrl: {},
filtered: true,
selectUnavailable: true
})

Option Selectors

Настройка Option Selectors

Чтобы настройки селектора сработали, необходимо удостовериться в правильности разметки — data-product-id.

Настройки задаются через метод setConfig глобального объекта Products, его вызов стоит оставлять в глобальной области видимости. Если запустить метод внутри $(document).ready(function() {}), результат может быть не предсказуем.

Свойства которые можно задать через метод setConfig:

Property Default Назначение
options Пустой объект Через данный объект задаются шаблоны для вывода опций
fileUrl Пустой объект Объект для хранения картинок из раздела «Файлы»
decimal Пустой объект Колличество символов после запятой, для единиц измерения
filtered true Если значение false, то в шаблоне вывода опций доступно свойство disabled
Если значение true, то недоступные опции не выводятся в шаблон.
showVariants true При значении false, рендер опций не производится
initOption true Отмечать активные опции при инициализации?
useMax false Использовать максимальное колличество?
Если значение true, в quantity невозможно указать колличество больше чем доступно на складе.

Пример:

js
        
          

Products.setConfig({
  initOption: true,
  filtered: false,
  showVariants: true,
  fileUrl: fileUrl || {},
  useMax: false,
  decimal: {
    kgm: 1,
    dmt: 1
  },
  options: {
    'Цвет': 'option-image',
    'Размер': 'option-radio',
    'Материал': 'option-select',
    'Жесткий диск': 'option-span',
    'default': 'option-span'
  }
});

settings -> options

Через данный объект задаются шаблоны для вывода опций.

  • Ключ — название опции
  • Значение — data-template-id шаблона

Пример:

template js
        
          

Products.setConfig({
  options: {
    'Цвет': 'option-image',
    'Размер': 'option-radio',
    'Материал': 'option-select',
    'Жесткий диск': 'option-span'
  }
});

        
          

 

<script type="text/template" data-template-id="option-select">
  <div class="<%= classes.option %> is-select">
    <label class="<%= classes.label %>"><%= title %></label>
    <select class="<%= classes.values %>" data-option-bind="<%= option.id %>">
      <% _.forEach(values, function (value){ %>
        <option
          <%= value.controls %>
          <%= value.state %>
        >
          <%= value.title %>
        </option>
      <% }) %>
    </select>
  </div>
</script>

<script type="text/template" data-template-id="option-span">
  <div class="<%= classes.option %> is-span">
    <label class="<%= classes.label %>"><%= title %></label>
    <div class="<%= classes.values %>">
      <% _.forEach(values, function (value){ %>
        <button class="<%= value.classes.all %> is-span"
          <%= value.controls %>
          <%= value.state %>
        >
          <%= value.title %>
        </button>
      <% }) %>
    </div>
  </div>
</script>

<script type="text/template" data-template-id="option-radio">
  <div class="<%= classes.option %> is-radio">
    <label class="<%= classes.label %>"><%= title %></label>

    <div class="<%= classes.values %>">
      <% _.forEach(values, function (value){ %>
        <label class="<%= value.classes.all %> is-radio">
          <input class="<%= value.classes.state %>"

            type="radio"
            name="<%= handle %>"

            <%= value.state %>
            <%= value.controls %>
          >
          <span><%= value.title %></span>
        </label>
      <% }) %>
    </div>
  </div>
</script>

<script type="text/template" data-template-id="option-color">
  <div class="<%= classes.option %> is-color">
    <label class="<%= classes.label %>"><%= title %></label>
    <div class="<%= classes.values %>">
      <% _.forEach(values, function (value){ %>
        <button class="<%= value.classes.all %> is-color"
          <%= value.controls %>
          <%= value.state %>
        >
          <%= value.title %>
        </button>
      <% }) %>
    </div>
  </div>
</script>

settings -> fileUrl

Объект для хранения картинок из раздела «Файлы»

Liquid
        
          

<script>
  {% comment %}
    создание объекта с картинками из файлов для collection
  {% endcomment %}
  if (!fileUrl) {
   var fileUrl = {}
  }
  {% assign option_title  = 'Цвет' %}
  {% assign collection_handle  = 'all' %}
  {% assign image_format  = '.png' %}
  {% for option_name in collections[collection_handle].options %}
    {% if option_name.title == option_title %}
      {% for option_value in option_name.values %}
        {% capture fileName %}{{option_value.title | replace: ' ',  '_' }}{{image_format}}{% endcapture %}
        {% assign fileURL = fileName | file_url  %}
        {% if fileURL %}
          fileUrl['{{ option_value.title | downcase }}'] = '{{ fileURL }}';
        {% endif %}
      {% endfor %}
    {% endif %}
  {% endfor %}
</script>

<script>
  {% comment %}
    создание объекта с картинками из файлов для product
  {% endcomment %}
  if (!fileUrl) {
   var fileUrl = {}
  }
  {% assign option_title  = 'цвет' %}
  {% assign image_format  = '.png' %}
  {% for option in product.options %}
    {% assign option-title = option.title | downcase %}
    {% if option-title == option_title %}
     {% for value in option.values %}
       {% capture fileName %}{{value.title | replace: ' ',  '_'}}{{image_format}}{% endcapture %}
       {% assign fileURL = fileName | downcase | file_url  %}
       {% if fileURL %}
        fileUrl['{{ value.title | downcase }}'] = encodeURI('{{ fileURL }}');
       {% endif %}
     {% endfor %}
    {% endif %}
  {% endfor %}
</script>

<script>
  Products.setConfig({
    fileUrl: fileUrl
  });
</script>

Cart

Разметка

АтрибутНазначениеРасположение
data-cart-form Обязательный атрибут для тега form Тег form для корзины
data-item-id Обязательный атрибут для позиций в корзине. Атрибут принимает id позиции. Обертка для позиции в корзине
data-product-id Обязательный атрибут для инициализации товара. В атрибут передаётся id товара. Обертка для позиции в корзине
data-item-delete Удаление из корзины Обертка для позиции в корзине
data-cart-update Обновление корзины Внутри обёртки с атрибутом data-cart-form
data-cart-update Обновление корзины Внутри обёртки с атрибутом data-cart-form
data-cart-clear Очищение корзины Внутри обёртки с атрибутом data-cart-form
data-coupon-submit Отправка купона Внутри обёртки с атрибутом data-cart-form
data-quantity Обязательный атрибут для обёртки кнопок изменения колличества и инпута quantity Внутри обертки с атрибутом data-product-id и data-item-id
data-quantity-change Атрибут для кнопок +/-, принимает число Внутри обёртки с атрибутом data-quantity
Liquid
        
          

<form action="{{ cart_url }}" method="post" data-cart-form>
  <input type="hidden" name="_method" value="put">
  <input type="hidden" name="make_order" value="">

  {% for item in cart.items %}
    <div data-product-id="{{ item.product.id }}" data-item-id="{{ item.id }}">
      <div data-quantity>
        <input type="text" name="cart[quantity][{{ item.id }}]" value="{{ item.quantity }}">
        <span data-quantity-change="-1">-</span>
        <span data-quantity-change="1">+</span>
      </div>
      <span data-item-delete="{{ item.id }}">X</span>
    </div>
  {% endfor %}

  <div>
    <label> Купон </label>
    <input type="text" name="cart[coupon]" value="{{ cart.coupon }}" />
    <input type="button" value="Применить" data-coupon-submit/>
  </div>

  <input type="submit" value="Оформить" data-cart-submit>
  <button data-cart-clear>Очистить</button>
  <button data-cart-update>Обновить</button>
</form>

Events

before:insales:cart Перед любым взаимдействием с корзиной
add_items:insales:cart Товар добален в корзину
update_items:insales:cart Обновление корзины
always:insales:cart Событие срабатывает после любого взаимодействия с компонетом корзины
delete_items:insales:cart Удаление позиции
remove_items:insales:cart Удаление заданного кол-ва позиций
clear_items:insales:cart После полного очищения корзины
set_coupon:insales:cart После установки купона
set_items:insales:cart После установления кол-ва товаров в корзине для каждой позиции
before:insales:item Событие срабатывает перед любым взаимодействием с компонетом продукта
change_quantity:insales:item Обновление кол-ва в продукте
unchange_quantity:insales:item Если введено кол-во больше доступного
update_variant:insales:item Обновление варианта в продукте
always:insales:item Событие срабатывает после любого взаимодействия с компонетом продукта

Methods

add(task)

Добавить в корзину заданное кол-во товаров

 

clear()

Полностью очистить корзину

 

delete(task)

Удалить позиции из корзины

 

remove(task)

Удадить из корзины заданное кол-во товаров

 

set(task)

Устанавливает кол-во товаров в корзине для каждой позиции

 

setCoupon(task)

Устанавливаем купон

 

js
        
          

Cart.add({
  items: {
    123456: 2,
    123457: 1
  },
  comments: {
    123457: 'Мой комментарий'
  },
  coupon: 'Мой купон'
});

 

Cart.delete({items: [160549240]})

Event Bus

Описание

Event Bus — шина событий

Она предназначена для простого взаимодействия базового функционала с остальными скриптами, не привязываясь при этом к верстке и вспомогательным объектам.

Работает по принципу Pub/Sub (Издатель/Подписчик) и построена на Deferred, что позволяет:

  • привязать к одному событию (Издателю) несколько обработчиков (Подписчиков)
  • обработчики событий сработают, даже если событие произошло раньше того, как мы к нему привязали обработчик.
  • не важен порядок объявления Издателя и Подпсичиков

Работа с шиной производится через объект EventBus.

 

js
        
          

/*Подключаем Подписчика №1*/
EventBus.subscribe('event1', function (data) {
  console.log('event1:', data);
});

 

/*Запускаем Издателя*/
EventBus.publish('event1', 'hello World!');
// -> event1:hello World!

 

/*Подключаем Подписчика №2*/
EventBus.subscribe('event1', function (data) {
  console.log(data, 'Let\'s rock');
})
// -> hello World!Let's rock

 

/*Запускаем Издателя*/
EventBus.publish('event1', 'hello Mars!');
// -> event1:hello Mars!
// -> hello Mars!Let's rock

Подписчик

В работе с API необходимо привязываться подписчикам, так как издатели объявлены внутри API.
Например при добавлении товара в корзину внутри API публикуется событие add_items:insales:cart:

EventBus.publish('add_items:insales:cart', data);

Данные которые переданы через EventBus.publish, доступны в подписчике.

EventBus.subscribe('add_items:insales:cart', function (data) {
  console.log('add_items:insales:cart:', data);
});

В данных подписчика всегда доступен объект action, он содержит свойство method, а также дополнительные сведения, взависимости от события. 
В дополнительных свойствах объекта action могут быть:

  • Ссылка на jQuery объект DOM узла с которым произошло взаимодействие
  • Обновленные данные компонента (Cart, Products и т.д.)
  • Остальное смотреть через console.log или EventBus.logger
js
        
          

EventBus.subscribe('add_items:insales:cart', function (data) {
  console.log('add_items:insales:cart:', data);
});

EventBus.logger

Logger необходим для просмотра срабатываемых событий и данных которые приходят в callback функции подписчиков.

Чтобы воспользоваться логгером, в консоль браузера нужно прописать — EventBus.logger.add('Имя компонента в нижнем регистре'), например:

  • EventBus.logger.add('cart')

Список компонентов:

  • cart
  • product
  • search
  • compares
js
        
          

EventBus.logger.add('cart')

EventBus.logger.add('product')

Methods

add(component)

Добавление логера для компонента

 

publish(eventId, data)

Публикация события с данными

 

subscribe(eventId, callback)

Подписаться на событие

js
        
          

EventBus.logger.add('cart')

EventBus.publish('test_event', {isTest: true, title: 'Test', status: 'ok'});

EventBus.subscribe('test_event', function (data) {
  console.log(data)
});

AjaxSearch

Разметка

Liquid
        
          

<form action="/search" method="get">
  <input type="hidden" name="lang" value="{{ language.locale }}">
  <input type="text" name="q" value="" placeholder="Поиск" data-search-field />
  <button type="submit">Поиск</button>
  <div data-search-result></div>
</form>

Events

before:insales:search Событие срабатывает перед любым взаимодействием с компонетом поиск
update:insales:search Событие срабатывает после обновления результатов поиска
always:insales:search Событие срабатывает после любого взаимодействия с компонетом поиск

Methods

setConfig(settings)

Обновляет настройки

js
        
          

AjaxSearch.setConfig({
  letters: 3,
  template: 'search-default',
  delay: 300
});

Compare

Разметка

Liquid
        
          

<button data-compare-add="{{ product.id }}">
  Добавить товар в сравнение
</button>
<button data-compare-delete="{{ product.id }}">
  Удалить из сравнения
</button>

Methods

add(task)

Добавляем товар в сравнение

getCompare()

Получение текущего состояния Сравнения

remove(task)

Удаляем товар из сравнения

update()

Обновляем состояние сравнения

js
        
          
Compare.add({ item: 123456 });

Template

Описание

Компонет «Template» отвечает за хранение и получение шаблонов написанных на шаблонизаторе библиотеки lodash.

 

Methods

load(template_body, template_id)

Загрузка нового шаблона в список

 

render(data, template_id)

Генерация фрагмента нужного шаблона с данными

 

js
        
          
Template.load('<button class="button button--click_me"><%= title %></button>', 'test-button');

$(targetNode).html(Template.render({ title: 'Click me!' }, 'test-button' ));