Карусель — широко используемый компонент в веб-приложениях. Он представляет собой список элементов (данных). Одновременно представлен только один элемент (называемый «текущий»), пользователь может просматривать предыдущий или следующий элемент в пользовательском интерфейсе.

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

Примечание: код можно найти в репозитории github

Или вы можете проверить пример карусели здесь

Давайте сначала подумаем, как мы будем использовать карусель:

// create the carousel with an element id
var myCarousel = carousel();  
myCarousel.init('my-carousel');  
// set the items of the carousel
myCarousel.setItems([  
  { img: 'https://unsplash.it/600/?random&abc=1',
    title: 'Example Item Title 1',
    subtitle: 'subtitle for item 1' },
  { img: 'https://unsplash.it/600/?random&abc=2',
    title: 'Example Item Title 2',
    subtitle: 'subtitle for item 2' },
  { img: 'https://unsplash.it/600/?random&abc=3',
    title: 'Example Item Title 3',
    subtitle: 'subtitle for item 3'}
]);
// switch to the next item
myCarousel.showNext();  
// switch to the previous item
myCarousel.showPrev();

Поскольку Collarjs управляется сообщениями, эти API могут быть представлены в виде сообщений:

// 'init carousel' message
{ msg: "init carousel",
  id: "element id" }
// 'set items' message
{ msg: "set items",
  items: [] // list of item objects }
// switch to previous item
{ msg: "prev item" }
// switch to next item
{ msg: "next item" }

Затем давайте создадим конвейер для обработки каждого из этих сообщений. Прежде чем мы начнем обрабатывать сообщения, нам нужно создать входной узел для приема сообщений и выходной узел для возврата результатов.

// create a namespace
var ns = window.collar.ns('com.collartechs.example.carousel');  
var input = ns.input('carousel input');  
var output = ns.output('carousel output');

Обработка сообщения «init carousel»

Во время запуска карусели мы просто создаем контейнер для размещения элементов карусели и инициируем датчик для наблюдения за пользовательским вводом в пользовательском интерфейсе. Фильтр (оператор when) используется, чтобы убедиться, что здесь обрабатывается только сообщение «init carousel». Идентификатор элемента, полученный из сообщения «init carousel», сохраняется в переменной id. Контейнер карусели определяется в строке шаблона. См. следующий код:

var id; // element id for container  
var carouselTemplate = `  
<div class="carousel">  
  <ol class="carousel-content"></ol>
  <div class="carousel-prev">prev</div>
  <div class="carousel-next">next</div>
</div>  
`;
var uiSensor = ns.sensor('carousel UI sensor', function (options) {  
    var sensor = this;
    if (options === 'init carousel') {
      // watch clicking prev event, and send 'prev item' message
      document.querySelector('#' + id + ' .carousel-prev')
        .addEventListener('click', function () {
          sensor.send({ msg: 'prev item' });
        });
      // watch clicking next event, and send 'next item' message
      document.querySelector('#' + id + ' .carousel-prev')
        .addEventListener('click', function () {
          sensor.send({ msg: 'next item' });
        });
    }
  });
input  
  .when('init carousel', function (signal) {
    return signal.get('msg') === 'init carousel';
  })
  .do('init carousel container', function (signal) {
    id = signal.get('id');
    var container = document.querySelector('#' + id);
    container.innerHTML = carouselTemplate;
  })
  .do('init UI sensor', function (signal) {
    uiSensor.watch('init carousel');
  })
  .to(output); // just output the same message

В приведенном выше коде мы напрямую передаем сообщение на вывод, если вы хотите сообщить внешнему миру, что карусель инициирована, вы можете использовать оператор map для создания сообщения «карусель инициирована» и поместить дополнительный данные в сообщении, например, id карусели:

.map('generate "carousel initatiated" message', function (signal) {
    return signal.new({
      msg: 'carousel initiated',
      id: signal.get('id')    
    });
  })
  .to(output);

Обработка сообщения «установить элементы»

После того, как компонент карусели создан, нам нужно настроить его, передав ему список элементов. Это обрабатывается сообщением «установить элементы»:

input  
  .when('set items')
  .do('store items in memory')
  .do('create items elements')
  .do('change current index to 0')
  .do('show current item')
  .to(output);

Для представления карусели мы используем следующую структуру данных:

var data = {  
  items: [], // the list of items
  current: 0 // the index of current item
}

Реализация проста:

const carouselItemTemplate = '<li class="carousel-item {CURRENT}">' +  
  '<img src="{IMG}"/>' +
  '<div class="carousel-desc">' +
     '<div class="carousel-title">' +
        '<h1>{TITLE}</h1>' +
     '</div>' +
     '<div class="carousel-calories">' +
        'Subtitle: <span>{SUBTITLE}</span>' +
     '</div>' +
  '</div>' +
'</li>';

input  
  .when('set items', function (signal) {
    return signal.get('msg') === 'set items';
  })
  .do('store items in memory', function (signal) {
    data.items = signal.get('items');
  })
  .do('create items elements', function (signal) {
    var carouselElemItemStr = '';
    for (var i = 0; i < data.items.length; i++) {
      carouselElemItemStr += carouselItemTemplate
        .replace('{CURRENT}', '')
        .replace('{IMG}', data.items[i].img)
        .replace('{TITLE}', data.items[i].title)
        .replace('{SUBTITLE}', data.items[i].subtitle);
    }
    var carouselContentEle = document.querySelector('#' + id + ' .carousel-content');
    carouselContentEle.innerHTML = carouselElemItemStr;
  })
  .do('change current index to 0', function (signal) {
    data.current = 0;
  })
  .do('show current item', function (signal) {
    var oldCurrentItem = document.querySelector('.current');
    if (oldCurrentItem) oldCurrentItem.classList.remove('current');
    var newCurrentItem = document.querySelector('#' + id + ' li.carousel-item:nth-of-type(' + (data.current+1) + ')');
    if (newCurrentItem) oldCurrentItem.classList.add('current');
  })
  .map('generate "items changed" message', function (signal) {
    return signal.new({
      msg: 'items changed',
      items: signal.get('items')
    });
  })
  .to(output);

Через выходной узел отправляется сообщение об изменении элементов со списком элементов в поле items.

Обработка сообщений «предыдущий элемент» и «следующий элемент»

Когда получено сообщение «предыдущий элемент» или «следующий элемент», мы меняем текущий индекс в переменной данных и отображаем новый текущий элемент, добавляя к нему «текущий» класс.

input  
  .when('next item')
  .do('change current index to next')
  .do('show current item')
  .map('generate "current item changed" message') 
  .to(output);

Вот реализация:

input  
  .when('next item', function (signal) {
    return signal.get('msg') === 'next item';
  })
  .do('change current index to next', function (signal) {
    data.current = (data.current + 1) % data.items.length;
  })
  .do('show current item', function (signal) {
    var oldCurrentItem = document.querySelector('.current');
    if (oldCurrentItem) oldCurrentItem.classList.remove('current');
    var newCurrentItem = document.querySelector('#' + id + ' li.carousel-item:nth-of-type(' + (data.current+1) + ')');
    if (newCurrentItem) oldCurrentItem.classList.add('current');
  })
  .map('genereate "current item changed" message', function (signal) {
    return signal.new({
      msg: 'current item changed',
      current: data.current    
    });
  })
  .to(output);

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

input  
  .when('previous item', function (signal) {
    return signal.get('msg') === 'next item';
  })
  .do('change current index to next', function (signal) {
    data.current = data.current === 0 ? data.items.length - 1 : data.current - 1;
  })
  .do('show current item', function (signal) {
    var oldCurrentItem = document.querySelector('.current');
    if (oldCurrentItem) oldCurrentItem.classList.remove('current');
    var newCurrentItem = document.querySelector('#' + id + ' li.carousel-item:nth-of-type(' + (data.current+1) + ')');
    if (newCurrentItem) oldCurrentItem.classList.add('current');
  })
  .map('genereate "current item changed" message', function (signal) {
    return signal.new({
      msg: 'current item changed',
      current: data.current    
    });
  })
  .to(output);

Перенести сообщение в NodeAPI

Теперь у нас есть 4 сообщения для карусели:

  1. инициализировать карусель
  2. набор предметов
  3. следующий элемент
  4. предыдущий элемент

Мы можем обернуть эти 4 сообщения в API асинхронного обратного вызова, похожее на узел (используя API воротника toNode):

collar.toNode(input, output) : Function

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

function nodeFunc(message, callback, [non-interruptible]);

Обратный вызов принимает два аргумента: ошибку и выходной сигнал. В отличие от стандартной асинхронной функции, похожей на узел, третий аргумент сообщает Collarjs, может ли сообщение быть прервано. По умолчанию Collarjs освобождает ресурс потока, когда сообщение обрабатывается узлом, и дает возможность другим сообщениям быть обработанными. Иногда вы хотите, чтобы сообщение завершило обработку до того, как будут обработаны любые другие сообщения, это можно сделать, установив false в качестве третьего аргумента.

Давайте создадим 4 API, обернув 4 сообщения:

var apiFunc = window.collar.toNode(input, output);
function carousel() {  
  return {
    init : function (id, done) {
      apiFunc({
        msg: 'init carousel',
        id: id
      }, done, false);    // use the false argument to make the message non-interruptible         
    },
    setItems : function (items, done) {
      apiFunc({
        msg: 'set items',
        items: items
      }, done, false);                
    },
    next : function (done) {
      apiFunc({
        msg: 'next item'
      }, done, false);                
    },
    prev : function (done) {
      apiFunc({
        msg: 'previous item'
      }, done, false);                
    }
  }
}

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

var myCarousel = carousel();  
myCarousel.init('carouselId');  
myCarousel.setItems([  
  {
    img: 'https://unsplash.it/600/?random&abc=1',
    title: 'Example Title 1',
    subtitle: 'subtitle',
  },
  {
    img: 'https://unsplash.it/600/?random&abc=2',
    title: 'Example Title 2',
    subtitle: 'subtitle',
  },
  {
    img: 'https://unsplash.it/600/?random&abc=3',
    title: 'Example Title 3',
    subtitle: 'subtitle',
  }
]);
myCarousel.next();

Поймите, как работает карусель

Сначала установите Collar-dev-сервер и запустите его.

sudo npm install collar-dev-server -g
collar-dev-server
// check dev tool from http://localhost:7500

Запустите пример

Клонировать с github

git clone https://github.com/bhou/collar-example-carousel.git  
sudo npm install http-server -g  
http-server .

Откройте карусель в браузере

http://localhost:8080/index.html

Или напрямую запустить онлайн-пример

http://collarjs.com/examples/carousel/index.html

Теперь проверьте страницу сервера разработки воротника по адресу http://localhost:7500, чтобы увидеть, как работает карусель. Вы можете записать сигналы времени выполнения, нажав кнопку записи в инструменте разработки воротника.

Ресурсы

Код можно найти в репозитории github (collar-example-carousel)

Поиграйте с примером на сайте Collar.js