Виджет Смартомато

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

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

Размещение виджета на стороннем сайте

Использование виджета возможно, если:

  1. настроен ресторан в системе Смартомато;
  2. ресторан содержит актуальное опубликованное меню.

На страницах сайта вашего ресторана должен быть размещен код виджета:

<!-- Smartomato Widget -->
<script src="//smartomato.ru/basket/widget/widget.js"></script>
<script type="text/javascript">smartomatoWidget.initialize({
  host: "//_ид_вашей_организации.smartomato.ru/", // Например, "//26.smartomato.ru/",
  assetsBase: "//smartomato.ru/basket/widget",
  organization_id: "9999" // id вашей организации
});</script>
<!-- Smartomato Widget -->

Виджет умеет фиксировать цели в веб-аналитике. Если на сайте вставлен код Яндекс.Метрики или Google Analytics — добавьте информацию об аналитике:

<!-- Smartomato Widget -->
<script src="//smartomato.ru/basket/widget/widget.js"></script>
<script type="text/javascript">smartomatoWidget.initialize({
  host: "//9999.smartomato.ru/",
  assetsBase: "//smartomato.ru/basket/widget",
  organization_id: "9999",
  analytics: {
    yandex_metrika_code:   "99999999", // актуальный id метрики
    google_analytics_code: "UA-99999999-1" // актуальный id аналитики
  }
});</script>
<!-- Smartomato Widget -->

Необходимо предусмотреть в макете сайта кнопку либо иной элемент, нажатие на который будет помещать определенное блюдо в корзину. Для того чтобы виджет понимал, какое именно блюдо необходимо поместить в корзину, нужно добавить следующий атрибут к кнопке data-dish-id="код блюда в системе Смартомато". Например:

<div data-dish-id="1854">Добавить</button>

Могут быть использованы любые html-элементы, принимающие событие onClick. Если в системе управления сложно предусмотреть хранение кодов блюда Cмартомато, можно использовать id, используемые в вашей системе, тогда в интерфейсе управления Смартомато необходимо задать соответствие кодов.

В таком случае в кнопке, вместо data-dish-id необходимо добавлять атрибут data-dish-external-id. Например так:

<div data-dish-external-id="1854">Добавить</button>

Настройка feature-флагов

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

smartomatoWidget.initialize({
  host: 'setters.smartomato.ru',
  features: {
    // Данный флаг позволяет автоматически расширить высоту страницы сайта
    // c учетом нижней панели виджета
    extendPageHeight: true
  }
});

Отображение состояния стоп-листов на сайте

Смена условий доставки заказа в виджете, таких как адрес и время, может приводить к измению доступности блюд в контексте выбранных ресторанов. Интеграция виджета на стороннем сайте позволяет определить особое поведение при изменении этого состояния.

При инициализации возможна передача параметра-функции dishAvailabilityHandler, которую виджет будет вызывать при смене стоп-листов. Единственным параметров функции является массив availability, который имеет следующий формат:

var availability = [
  {
    restaurant_id: 332,     // Выбранный ресторан
    dish_id:       148,     // Идентификатор блюда
    external_id:  "dish-3", // Внешний идентификатор

    // Статус доступности:
    //   - available     блюдо доступно
    //   - not_available блюдо недоступно в ресторане
    //   - call          уточнить наличие блюда по телефону
    status: "available"
  },
  ...
]

Приведем пример инициализации с параметром dishAvailabilityHandler:

smartomatoWidget.initialize({
  host: '...',

  dishAvailabilityHandler: function(availabilityData) {
    // Идея следующая: находим соответствующую карточку блюда и
    // выставляем класс в зависимости от доступности блюда
    // Карточка блюда имеет класс .dish-card
    var dishCards = {};

    $('[data-dish-id]').each(function(i, el) {
      // От кнопки "купить" идем вверх до карточки блюда
      var $card  = $(el).closest('.dish-card');
      var dishId =  $(el).data('dishId')

      // Сбрасываем классы доступности
      $card
        .toggleClass("status-available",     true)
        .toggleClass("status-not-available", false)
        .toggleClass("status-call",          false);

      dishCards[ dishId ] = $card;
    });

    if (availabilityData == null) return;

    // Выставляем классы в соответствии с availabilities
    for (var j = 0; j < availabilityData.length; ++j) {
      var entry = availabilityData[j];
      var $card = dishCards[ entry.dish_id ];

      $card
        .toggleClass("status-available",     entry.status === "available")
        .toggleClass("status-not-available", entry.status === "not_available")
        .toggleClass("status-call",          entry.status === "call"));
    }
  }
});

Импорт меню

API Смартомато позволяет осуществлять пакетный импорт элементов меню ресторана с учетом связей на основе внешних идентификаторов и поддержкой итеративного импорта (для сохранения статистики).

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

Сбор данных для импорта

В случае отсутствия прямого доступа к базе данных интегрируемого ресторана подготовку данных для импорта можно осуществлять посредством сбора напрямую со страниц сайта ресторана. Рекомендуемыми средствами для этого являются: выполнение скрипта сбора в контексте странице (подразумевается разбор структуры DOM, для этого могут быть использованы вспомогательные библиотеки, например, jQuery) или использование окружений headless-браузеров, например, CasperJS или PhantomJS (этот вариант наиболее удобен, когда сбор подразумевает навигацию между разными страницами сайта).

Приведем пример скрипта CasperJS для сбора данных меню с сайта одного из популярных ресторанов Москвы. Сайт содержит страницу с категориями меню, блюда каждой категории находятся на отдельных страницах, а информация о блюде представлена в виде модального окна, которое открывается при нажатии на карточку блюда:

var casper = require('casper').create({
  remoteScripts: [
    'http://code.jquery.com/jquery-2.1.4.min.js'
  ],
  pageSettings: {
    loadImages:  false,
    userAgent:   'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5)'
  }
  // Опции для отладки:
  // logLevel: "debug",
  // verbose: true
});

// Базовый адрес сайта
var BASE_URL = 'http://*****.ru';

// Страница со списком категорий
var CATEGORIES_URL = BASE_URL + '/ru/menu/';

var result = [];

casper.start(CATEGORIES_URL, function() {

  // ---
  // Получаем список ссылок на категории
  // ---
  var categories = this.evaluate(function() {
    return $('.b-menuList > a.item').map(function() {
      return {
        path: $(this).attr('href'),
        name: $(this).find('.title').text()
      };
    }).get();
  });

  console.log('Получили список категорий:');
  categories.forEach(function(c) {
    console.log('  * ' + c.name + '  ' + BASE_URL + c.path)
  });


  // ---
  // Переходим на страницу каждой категории
  // ---
  this.eachThen( categories, function(response) {
    var link         = BASE_URL + response.data.path;
    var categoryName = response.data.name;

    this.thenOpen(link, function(response) {
      console.log('[!] Открываем страницу "' + categoryName + '"', link);

      // ---
      // Получаем id всех блюд на странице
      // ---
      var dishesIds = this.evaluate(function(categoryName) {
        return $('.products a.item').map(function() {
          return $(this).attr('data-pk');
        }).get();
      });


      console.log('  [+] Найдено ' + dishesIds.length + ' блюд на странице' )

      // ---
      // Нажимаем на карточку каждого блюда и ждем появления модального окна через
      // `waitUntilVisible`, далее получаем атрибуты блюда через элементы
      // модального окна
      // ---
      this.eachThen(dishesIds, function(response) {
        var dishId = response.data;

        this.thenClick('.products a.item[data-pk="' + dishId + '"]', function() {
          console.log('  [+] Кликнули на элемент ' + dishId);

          this.waitUntilVisible('.b-popup', function(argument) {

            var dish = this.evaluate(function() {
              var $root = $('.b-popup');

              var name        = $root.find('.title').text();
              var description = $root.find('.ingridients .text').text();
              var image       = $root.find('.left img').attr('src');
              var weight      = $root.find('.weight .digit').text();
              var price       = parseFloat(
                $root.find('.price .digit').text()
                  .replace(/[^\d\.]/g,'')
              );

              return {
                name:        name,
                weight:      weight,
                description: description,
                remote_photo_url: image,
                price:       price
              };
            });


            dish.external_id      = dishId;
            if(dish.remote_photo_url) {
              dish.remote_photo_url = BASE_URL + dish.remote_photo_url;
            }

            dish.category_name    = categoryName;

            console.log('  [+] Собрали блюдо ' + dish.name);
            result.push(dish);

          });
        });
      });
    });
  });
})

// ---
// Выводим результат после завершения всех навигационных шагов
// ---
casper.run(function() {
  console.log(JSON.stringify(result, null, 2))
  casper.exit();
});

Запуск скрипта производится командой casperjs import.js --ignore-ssl-errors=yes --ssl-protocol=tls, рекомендуемая версия CasperJS >= 1.1.0.