Original photo by Irvan Smith on Unsplash
Original photo by Irvan Smith on Unsplash

Будни типичного разработчика состоят из монотонных нажатий на клавиши и перерывов на кофе. Хорошим же считается тот программист, который может получить желаемый результат при минимальном количестве нажатий по клавиатуре. И речь здесь не идет о комбинации "Ctrl+C / Ctrl+V", как вы могли подумать :) В этой статье я хочу вам рассказать о том, как сэкономить драгоценные время и нервы, если нужно написать красивую и функциональную форму для вашего приложения. 

Есть множество библиотек, которые предоставляют огромное количество фич для работы с формами. И сегодня я хочу рассмотреть один из самых удобных и производительных инструментов для построения интерфейса приложений любой сложности. Речь пойдет о библиотеке Webix и ее возможностях.

Чтобы лучше изучить вопрос, я создадал небольшое одностраничное приложение для продажи книг и на наглядном примере покажу вам как реализовать форму заказа с помощью компонентов Webix.

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

Webix и его возможности

Webix UI — это JavaScript библиотека, с помощью которой вы можете создавать отзывчивый дизайн, не беспокоясь о производительности вашего приложения. Диапазон ее возможностей представлен UI компонентами различной сложности, от обычной кнопки — до комплексных решений. У каждого из виджетов есть целый набор свойств и методов для гибкой настройки и управления компонентом. Помимо этого, библиотека владеет дополнительными инструментами. К ним можно отнести механизм обработки событий, методы работы с данными, взаимодействие с сервером, темы для стилизации и многое другое. Более детально о всех тонкостях можно узнать в обширной документации. А сейчас самое время перейти к практической части.

Базовые настройки

Для того чтобы использовать библиотеку Webix, необходимо подключить ее в главном файле index.html нашего приложения. Здесь стоит упомянуть о том, что у библиотеки есть 2 версии: базовая GPL и расширенная Pro-версия. GPL версия распространяется бесплатно и предоставляет достаточно большой набор возможностей, которые покрывают 97,26% наших потребностей. Но при работе с формами, а статья именно о них, нам понадобятся некоторые фичи расширенной версии, о которых я расскажу в процессе. Исходя из этого, мы воспользуемся триалкой расширенной Pro-версии, чтобы получить максимальный профит. 

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

В полученном каталоге нас больше всего интересует содержимое папки webix/codebase/, а именно, два сакральных файла: “webix.js” и “webix.css”. Для того чтобы Webix-магия начала работать, нужно перенести эту папку в корень нашего проекта и включить в index.html необходимые ссылки:

<script type="text/javascript" src="codebase/webix.js"></script>
<link rel="stylesheet" type="text/css" href="codebase/webix.css" charset="utf-8">

Стоит отметить, что эти файлы также доступны через CDN по следующим ссылкам:

<script type="text/javascript" src="//cdn.webix.com/site/webix.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.webix.com/site/webix.css">

Мы же воспользуемся локальными файлами, так как они работают быстрее и не требуют доступа к интернету (а поверьте, бывает всякое). Теперь index.html файл выглядит так:

<!DOCTYPE html>
<html>
  <head>
    <title>Book Store Service</title>
    <meta charset="utf-8">

    <!--Webix sources -->
    <script type="text/javascript" src="codebase/webix.js"></script>
    <link rel="stylesheet" type="text/css" href="codebase/webix.css" charset="utf-8">

    <!--App sources -->

  </head>
  <body>
    <script type="text/javascript">
      ...
    </script>
  </body>
</html>

Мы создали базовый index.html файл и подключили все необходимые инструменты. А сейчас самое время перейти непосредственно к приложению и формам. 

Обзор приложения

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

  • модуль с каталогом товаров (в нашем случае это книги)

  • модуль с описанием конкретного товара и формой заказа.

Модуль с каталогом будет состоять из небольших блоков, расположенных по 2 элемента в одном ряду. В каждом блоке будет краткое описание товара и кнопка для перехода к его оформлению. Здесь стоит уточнить, что для создания этого модуля используется компонент dataview библиотеки. В браузере мы увидим такой результат:

Модуль с каталогом товаров
Модуль с каталогом товаров

При клике по кнопке "Buy now", пользователь перейдет к модулю с детальным описанием товара. Для создания этого модуля используется компонент template. Рядом с описанием мы расположим нашу форму, которой и посвящена данная статья. В браузере мы увидим такой результат:

Модуль с описанием товара и модуль для будущей формы
Модуль с описанием товара и модуль для будущей формы

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

  • информацию о заказе и его доставке

  • информацию о пользователе

  • информацию о оплате.

Любая форма должна содержать поля ввода и различные контролы выбора, такие как чекбоксы, переключатели, селекторы, кнопки и прочее. А судя по данным, которые нам нужно собрать, форма будет достаточно большой. 

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

  • в первом табе мы разместим форму заказа, с возможностью выбора между самовывозом и доставкой. Если пользователь выберет доставку, то снизу появятся дополнительные поля для ввода адреса

  • во втором табе будет находиться форма для личных данных пользователя

  • в третьем — форма оплаты, где нужно будет ввести информацию о банковской карте.

Интерфейс каждой формы мы будем создавать в отдельном файле и сохранять в переменную, которая понадобится для сборки лейаута в index.html. 

Лейаут модуля с формами

В этой статье мы не будем подробно разбирать построение лейаута всего приложения. Если вы пожелаете глубже изучить эту тему, то можете ознакомиться со статьей "Создаем Booking приложение с Webix UI", где достаточно детально описана работа с лейаутом. Также будет полезно обратиться к документации компонента layout, где вы найдете исчерпывающее описание всех свойств и методов виджета с наглядными примерами.  

Здесь же нас интересует именно та часть лейаута, где будут находиться вкладки с нашими формами, которые будут динамически сменять друг друга. Для таких случаев Webix предусматривает специальный компонент multiview. Нужные модули (а в нашем случае это формы) необходимо поместить в массив свойства cells этого компонента. У каждого из модулей должен быть уникальный id, который понадобится для навигации. Стоит также отметить, что при первоначальной загрузке будет отображаться вкладка, которая задана первым элементом массива. Код лейаута с формами будет выглядеть так:

...
{
  id:"order_section", 								
  cols:[
    order_description, //интерфейс модуля с детальным описанием товара
    {
      id:"multiview_form",
      cells:[	//массив со сменными модулями/табами							
        order_form, //переменная с интерфейсом формы заказа
        user_form, //переменная с интерфейсом формы пользователя
        payment_form //переменная с интерфейсом формы оплаты
      ]
    }
  ]
}

С лейаутом мы разобрались. Теперь давайте займемся самими формами. И начнем мы с формы регистрации заказа.

Форма регистрации заказа

Форма регистрации заказа будет состоять из двух логических частей. В первой части будут находиться поля для сбора информации о заказе, а во второй — о его доставке.

Изначально, в форме будет отображаться только секция с полями для информации о заказе:

Форма с секцией "Order Information"
Форма с секцией "Order Information"

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

Форма с секциями "Order Information" и "Delivery Information"
Форма с секциями "Order Information" и "Delivery Information"

Для навигации по вкладкам с формами мы будем использовать кнопки "Back" и "Next". Первая кнопка вернет нас к каталогу с товарами, а вторая отобразит следующую форму для ввода данных о пользователе.

Теперь давайте детально рассмотрим как все это реализовать.

Для сбора данных у библиотеки Webix предусмотрен специальный компонент form и целый ряд сопутствующих контролов. По своей организации форма похожа на layout. Ее можно разделять на ряды и столбцы, в которых будут находиться нужные контролы. 

В файле order_form.js мы создаем форму и сохраняем ее в переменную  order_form с помощью следующей строки:

const order_form = { 
  view:"form",
  id:"order_form",
  elements:[ ... ]
}

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

Здесь нам также нужно определить лейаут формы. Для этого у виджета есть свойства cols, rows и elements. Первые два позволяют делить интерфейс компонента на столбцы и ряды. А свойство elements, которое мы и будем использовать, позволяет разместить элементы формы в виде рядов (как и rows). Все что остается сделать — это поместить две секции с нужными полями внутрь массива этого свойства. Теперь давайте займемся этими секциями.

Секция "Order information"

А начнем мы, пожалуй, с первой секции под названием "Order information", которая будет объединять несколько полей для сбора информации о заказе. Для деления полей на секции нам понадобится компонент fieldset. Название секции определяется свойством label, а в объекте свойства body мы и будем описывать нужные нам контролы.

{ 
	...
	elements:[ 
   {
      view:"fieldset", 
      label:"Order information", 
      body:{ ... }
   }
	]
}

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

Поле с названием товара

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

{
  view:"fieldset", 
  label:"Order information", 
  body:{
    rows:[
      {
        cols:[
          { view:"label", label:"Order", width:110 },
          { view:"label", name:"order_name" }
        ]
      }
    ]
  }
}

Секция теперь выглядит так:

Поле с названием товара
Поле с названием товара

Здесь стоит обратить внимание на тот факт, что для второго лейбла мы задали имя order_name через его свойство name. Дело в том, что значение этого контрола будет устанавливаться и считываться динамически через API формы. А для того чтобы получить к нему доступ — нужно сначала присвоить уникальное имя. Подобные имена необходимо задать и другим полям, со значениями которых нам нужно будет работать.

Счетчик для количества товаров

Дальше у нас идет специальный счетчик, с помощью которого пользователь сможет устанавливать нужное количество товара. Для этого можно использовать контрол counter. Ему мы также задаем имя и лейбл. Давайте заодно определим диапазон, в пределах которого пользователь сможет устанавливать нужное количество товара. Сделать это можно через соответствующие свойства min и max.

body:{
  rows:[
    { cols:[/* Лейбл с названием товара */] },
    { view:"counter", name:"order_count", label:"Quantity", min:1, max:5 }
  ]
}

Секция теперь выглядит так:

Счетчик для количества товаров
Счетчик для количества товаров

Поле для подарочной карты

Теперь давайте создадим поле, в котором пользователь может указать номер подарочной карты. Здесь лучше всего использовать контрол text. Помимо имени и лейбла, мы можем задать плейсхолдер через свойство placeholder и специальную иконку в правой части поля через свойство clear. Иконка будет появляться только тогда, когда пользователь введет данные в поле. При клике по ней, введенные данные будут удалены, а сама иконка исчезнет.

body:{
  rows:[
    { cols:[/* Лейбл с названием товара */] },
    { view:"counter", ... },
    { view:"text", label:"Gift card", name:"gift_card", 
     	placeholder:"Enter code", clear:true }
  ]
}

Секция теперь выглядит так:

Поле для подарочной карты
Поле для подарочной карты

Поле со стоимостью заказа

Дальше у нас на очереди поле, которое будет отображать стоимость заказа. Цена, также как и название товара, будет устанавливаться динамически и меняться в зависимости от выбранных параметров. Здесь мы делаем все тоже самое что и с названием товара: используем 2 компонента label, размещаем их в виде столбцов и второму присваиваем имя через свойство name.

body:{
  rows:[
    { cols:[/* Лейбл с названием товара */] },
    { view:"counter", ... },
    { view:"text", ... },
    { 
      cols:[
        { view:"label", label:"Price", width:110 },
        { view:"label", name:"total_price" }
      ] 
    }
  ]
}

Секция теперь выглядит так:

Поле со стоимостью заказа
Поле со стоимостью заказа

Радиокнпки для выбора способа получения товара

В этой секции нам осталось создать поле, в котором пользователь сможет выбирать способ получения товара между самовывозом (pick-up) и доставкой (delivery). Мы реализуем такую возможность через контрол radio с двумя опциями. Лейбл этого контрола можно оставить пустым, ведь названия радиокнопок и так достаточно информативны. Опции для контрола задаем через массив свойства options.

body:{
  rows:[
    { cols:[/* Лейбл с названием товара */] },
    { view:"counter", ... },
    { view:"text", ... },
    { cols:[/* Лейбл со стоимостью товара */] },
    { view:"radio", label:" ", name:"delivery", 
      options:[
        { id:1, value:"Pick-up" },
        { id:2, value:"Delivery" }
      ]
    }
  ]
}

А сейчас давайте сделаем так, чтобы при переключении между радиокнопками "Delivery" и "Pick-up", секция "Delivery information" могла отображаться и прятаться соответственно. Напомню, что изначально выбрана опция Pick-up (самовывоз), а секция спрятана.

Чтобы установить обработчик на событие переключения между радиокнопками, нужно добавить в конструктор контрола radio специальное свойство on. В объекте этого свойства необходимо указать событие, которое мы отслеживаем (в нашем случае это onChange), и присвоить ему обработчик.

{ 
  view:"radio", 	
  ...
  on:{
    onChange:function(){
      const value = this.getValue(); //получаем значение контрола
      if(value == 1){											
        $$("delivery_section").hide(); //прячем секцию
      }else{
        $$("delivery_section").show(); //отображаем секцию
      }
    }
  }
}

Сначала мы получаем текущее значение контрола radio через его метод getValue(). Если значение равно 1 (выбран самовывоз), то мы прячем секцию "Delivery information" через ее метод hide().

Если значение равно 2 (выбрана доставка), то мы отображаем секцию "Delivery information" через ее метод show().

Финальный вид секции будет таким:

Радиокнопки для выбора способа получения товара
Радиокнопки для выбора способа получения товара

Секция "Delivery information"

Теперь перейдем к секции под названием "Delivery information", которая будет объединять поля для ввода адреса доставки. Мы создаем ее по аналогии с предыдущей секцией "Order information". Как уже упоминалось выше, секция изначально будет спрятана. Для этого нужно присвоить ее свойству hidden значение true.

{ 
  view:"form",
  elements:[ 
    { view:"fieldset", label:"Order information", ... },
    {
    	view:"fieldset", 
     	id:"delivery_section",
     	hidden:true,
     	label:"Delivery information", 
     	body:{ 
     		rows:[ /* контролы */ ]
			}
		}
	]
}

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

  • страна

  • адрес внутри страны:

    • регион

    • город

    • улица и дом

    • почтовый индекс

  • желаемая дата отправки

  • дополнительные инструкции.

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

Контрол для выбора страны

Давайте сделаем так, чтобы пользователь не вводил название страны, а выбирал ее из выпадающего списка. Для этого мы воспользуемся контролом combo библиотеки Webix. Помимо стандартного набора свойств ( label, name, clear и placeholder ), этому контролу нам нужно передать данные для опций выпадающего списка. Делается это через его свойство options, которому можно задать либо массив с данными (если они находятся на клиенте), либо путь к данным на сервере. Структура данных должна быть следующей:

[
	{ "id":1, "value":"Canada" },
	{ "id":2, "value":"United Kingdom" },
	...
]

Функционал библиотеки позволяет сделать поле обязательным для заполнения. Для этого нужно установить свойство required в значении true. Теперь возле лейбла появится специальный маркер в виде красной звездочки. Забегая вперед, скажу, что если оставить такое поле пустым, то при валидации оно подсветится красным цветом, а данные формы не отправятся на сервер. Но обо всем по порядку.

body:{
  rows:[
    { view:"combo", clear:"replace", label:"Country", name:"country",
     	placeholder:"Select country", options:country_data, required:true }
  ]
}

Секция теперь выглядит так:

Контрол для выбора страны
Контрол для выбора страны

Поля для адреса доставки

Все поля для адреса внутри страны будут содержать одинаковые настройки. Для их создания мы используем уже знакомый нам компонент text и набор стандартных свойств: label, name, clear, placeholder и required. Отличаться они будут только своими значениями.

body:{
  rows:[
    { view:"combo", ... },
    { view:"text", clear:true, label:"Region", name:"region", 
     	placeholder:"Enter region name", required:true },
    { view:"text", clear:true, label:"City", name:"city", 
     	placeholder:"Enter city name", required:true },
    { view:"text", clear:true, label:"Street adress", name:"street", 
     	placeholder:"Enter street address", required:true },
    { view:"text", clear:true, label:"Zip Code", name:"zip_code", 
     	placeholder:"Enter Zip code", required:true }
  ]
}

Секция теперь выглядит так:

Поля для адреса доставки
Поля для адреса доставки

Контрол для выбора даты отправки

А сейчас нам нужно получить от пользователя желаемую дату отправки заказа. И здесь, вместо обычного поля ввода, Webix предлагает нам специальный виджет datepicker. При клике по нему, возле поля появится компактный календарь, в котором можно выбрать необходимую дату. Помимо стандартных свойств, упомянутых выше, здесь мы дополнительно указываем формат отображения даты через свойство format. Для более детального ознакомления с возможностями виджета вы можете посетить страницу его документации.

body:{
  rows:[
    { view:"combo", ... },
    { view:"text", ... /* Поля для адреса внутри страны*/ },
    ... ,
    { view:"datepicker", clear:"replace", name:"ship_date", 
    	label:"Ship date", placeholder:"Select desired ship date", 
    	format:"%d %M %Y" }
	]
}

Секция теперь выглядит так:

Контрол выбора даты отправки
Контрол выбора даты отправки

Поле для дополнительных инструкций

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

Давайте учтем такую возможность в нашей форме. Лучшим вариантом для такого поля будет компонент textarea. Если вы работали с обычными HTML формами, то этот элемент должен быть вам хорошо знаком. Помимо стандартных настроек, мы зададим для него фиксированную высоту через универсальное свойство height.

body:{
  rows:[
    { view:"combo", ... },
    { view:"text", ... /* Поля для адреса внутри страны*/ },
    ... ,
    { view:"datepicker", ... },
    { view:"textarea", label:"Instruction", name:"instruction", 
      placeholder:"Enter additional delivery instructions", height:100 }
  ]
}

Секция теперь выглядит так:

Поле для дополнительных инструкций
Поле для дополнительных инструкций

Контрол для страхования

Давайте предоставим нашему пользователю возможность застраховать доставку товара.

Для активации страхования мы будем использовать контрол switch. Он представляет собой тогл-кнопку, которая позволяет выбирать между двумя противоположными значениями (0 и 1). Помимо основного лейбла и имени, мы зададим этому контролу 2 внутренних лейбла (для активного и пассивного состояния контрола соответственно). Сделать это можно через его свойства onLabel и offLabel.

body:{
  rows:[
    { view:"combo", ... },
    { view:"text", ... /* Поля для адреса внутри страны*/ },
    ... ,
    { view:"datepicker", ... },
    { view:"textarea", ... },
    {
      cols:[
        { view:"switch", name:"insurance", label:"Insurance",
          onLabel:"yes", offLabel:"no" },
        { ... }
      ]
    }
  ]
}

Контрол выбора цвета

Как уже упоминалось выше, в качестве бонуса мы предоставим пользователю возможность выбрать цвет упаковки товара. Для этого у библиотеки есть несколько виджетов, а именно colorpicker, colorselect и colorboard

Так как у нас есть только 4 вида цветных упаковок, то лучшим вариантом будет виджет colorboard с ограниченной палитрой. 

Задать набор цветов палитры можно через массив свойства palette виджета, а цвет по умолчанию — через его свойство value. Помимо этого, нам нужно установить фиксированные размеры для каждого блока с палитрой через свойства width и height, а заодно, убрать рамку вокруг компонента при помощи его свойства borderless.

{
  view:"colorboard",									
  name:"package_color",
  value:"#FFB955",
  palette:[ ["#FFB955", "#F34336", "#2196F3", "#FFEA3B"] ], 
  borderless:true,
  width:120, height:30
}

Финальный вид секции будет таким:

Секция "Delivery Information"
Секция "Delivery Information"

Кнопки для навигации

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

Для создания кнопок мы будем использовать компонент button. Чтобы применить встроенную стилизацию, необходимо задать свойству css кнопки соответствующий webix класс. В нашем случае это будут классы webix_secondary и webix_primary. 

Название кнопки можно задать через ее свойство label. Здесь также стоит уточнить, что вместе с лейблом каждой кнопки мы разместим небольшую иконку в виде стрелки, которая будет указывать на предназначение контрола. Стрелка влево — переход назад (в данном случае к списку товаров), а стрелка вправо — переход к следующей форме. Встроенные иконки webix можно задать через соответствующие css классы. В завершение, давайте установим для каждой кнопки фиксированную ширину через ее свойство width.

Эти 2 кнопки нам нужно отобразить в виде столбцов. Для этого мы поместим их в массив свойства cols формы.

{ 
  view:"form",
  id:"order_form",
  elements:[ 
    { view:"fieldset", label:"Order information", ... },
    { view:"fieldset", label:"Delivery information", ... },
    {}, //спейсер
    {
      cols:[
        { view:"button", width:150, css:"webix_secondary", 
         	label:`<span class="webix_icon mdi mdi-arrow-left"></span>
          <span>Back</span>` },
        { view:"button", width:150, css:"webix_primary", 
         	label:`<span>Next</span>
          <span class="webix_icon mdi mdi-arrow-right"></span>` },
        {} //спейсер
      ]
    }
  ]
}

Кнопки будут выглядеть так:

Кнопки для навигации
Кнопки для навигации

Интерфейс формы регистрации заказа мы создали. Нам остается только подключить файл order_form.js в index.html и поместить переменную order_form, которая хранит интерфейс формы, в код лейаута (смотрите раздел про лейаут формы).

В браузере мы увидим такой результат:

Модуль с описанием товара и формой регистрации заказа
Модуль с описанием товара и формой регистрации заказа

Форма регистрации пользователя

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

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

  • имя и фамилия

  • возраст

  • пол

  • контактная информация:

    • электронная почта

    • номер телефона.

Форма будет выглядеть так:

Форма регистрации пользователя
Форма регистрации пользователя

Давайте посмотрим как это реализовать.

А начнем мы с того, что в файле user_form.js создадим форму и сохраним ее в переменную user_form, а также определим секцию "User information", в которой будут находиться вышеуказанные поля.

Поля для имени и фамилии

Теперь давайте займемся полями, где пользователь сможет ввести свое имя и фамилию. Для их создания нам нужно прибегнуть к старому доброму контролу text. Как и для полей с предыдущей формы, для этих контролов мы задаем стандартный набор свойств: label, name, placeholder, clear и required, предназначение которых нам уже хорошо известно.

view:"fieldset", 
label:"User information", 
body:{
  rows:[
    { view:"text", clear:true, label:"First Name", name:"first_name", 
     	placeholder:"John", required:true },
    { view:"text", clear:true, label:"Last Name", name:"last_name", 
     	placeholder:"Smith", required:true }
  ]
}

Секция теперь выглядит так:

Поля для имени и фамилии
Поля для имени и фамилии

Контролы для возраста и пола

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

Counter

Чтобы получить возраст, можно использовать контрол counter, который мы уже применяли для определения количества товаров. 

Возраст нашего заказчика должен находиться в диапазоне от 18 до 100. Задать эти параметры можно с помощью свойств min и max

Radio

Чтобы выбрать пол, можно использовать контрол radio. Мы его также применяли для выбора способа получения товара. В массиве свойства options нужно указать соответствующие опции для радиокнопок. В нашем случаи это Male и Female.

body:{
  rows:[
    { view:"text", label:"First Name", ... },
    { view:"text", label:"Last Name", ... },
    { view:"counter", label:"Age", name:"age", min:18, max:100 },
    { view:"radio", label:"Gender", name:"gender", 
     	options:[
       { id:1, value:"Male" },
       { id:2, value:"Female" }
     	]
    }
  ]
}

Секция теперь выглядит так:

Контролы для возраста и пола
Контролы для возраста и пола

Поля для контактной информации

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

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

Прописываем стандартные свойства ( label, name, placeholder, clear и required ) и останавливаемся на поле для ввода номера телефона. 

Дело в том, что функционал библиотеки позволяет применить специальную маску для ввода номера телефона (и не только для него). Для этого нужно задать свойству pattern готовую маску webix.patterns.phone. Здесь стоит уточнить, что это одна из тех фич, которая доступна только в PRO версии библиотеки. Подробнее о форматировании полей ввода можно узнать в этой статье.

Маска позволяет вводить только 11 цифр номера (что соответствует стандарту телефонных номеров США), добавляет "+" в начале номера и выделяет круглыми скобками код телефонного оператора. Другие символы будут игнорироваться.

Давайте дадим небольшую подсказку для заполнения этого поля и снизу добавим короткое сообщение о том, как правильно вводить номер. Для этого мы используем свойство bottomLabel.

body:{
  rows:[
    { view:"text", label:"First Name", ... },
    { view:"text", label:"Last Name", ... },
    { view:"counter", label:"Age", ... },
    { view:"radio", label:"Gender", ... },
    { 
      view:"text", clear:true, label:"Email", name:"email", 
      placeholder:"johnsmith@gmail.com", required:true
    },
    { 
      view:"text", clear:true, label:"Phone", name:"phone", 
      placeholder:"+1 (123) 321-3456", required:true, 
      pattern:webix.patterns.phone, 
      bottomLabel:"*The phone number must have 11 characters" 
    }
  ]
}

Финальный вид секции будет таким:

Секция "User Information"
Секция "User Information"

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

Интерфейс формы регистрации пользователя мы создали. Сейчас нам остается только подключить файл user_form.html в index.html и поместить переменную user_form, которая хранит интерфейс формы, в код лейаута. В браузере мы увидим такой результат:

Модуль с описанием товара и формой регистрации пользователя
Модуль с описанием товара и формой регистрации пользователя

Форма регистрации оплаты

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

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

  • номер карты

  • имя владельца

  • период действия

  • CVV код.

Форма будет выглядеть так:

Форма регистрации оплаты
Форма регистрации оплаты

В файле payment_form.js мы создаем форму и сохраняем ее в переменную payment_form, а также определяем секцию "Payment information", в которой будут находиться вышеуказанные поля.

Первым у нас идет поле для номера карты. Помимо стандартных свойств контрола text (label, name, placeholder, clear и required), мы дополнительно указываем готовую маску для ввода номера карты: pattern:webix.patterns.card. Это значит, что пользователь сможет ввести только 16 цифр, которые зачастую указаны на лицевой стороне его карты. Другие символы будут игнорироваться. Хочу напомнить, что маски доступны только в Pro версии.

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

Большего внимания заслуживают контролы для определения периода действия карты. Здесь мы используем 2 контрола richselect. При клике по этому виджету, снизу появится выпадающий список опций, данные для которых задаются в массиве свойства options.

{ 
  view:"richselect", clear:"replace", name:"month", width:75, 
  options:[ "01", ... "12" ], required:true 
},
{ 
  view:"richselect", clear:"replace", name:"year", width:90, 
  options:[ "2021", ... "2030" ], required:true 
}

Теперь переходим к полю для ввода cvv кода. Здесь пользователь должен ввести 3 секретных номера карта, которые находятся на тыльной ее стороне. Контрол text этого поля также будет включать стандартный набор свойств. В дополнение к ним, мы укажем тип поля через его свойство type в значении password. Это позволит скрыть введенные данные, а вместо них отображать звездочки. Мы также укажем кастомную маску через свойство pattern, которая позволит вводить только 3 цифры кода.

{ 
  view:"text", clear:true, 
  label:"CVV", name:"cvv_code", 
  type:"password", placeholder:"Enter CVV code", 
  required:true, pattern:{ mask:"###", allow:/[0-9]/g } 
}

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

{ 
  view:"checkbox", name:"default_payment", 
  labelRight:"Set as default payment method" 
}

Секция готова. Нам осталось добавить кнопку для перехода к предыдущей форме и кнопку для отправки заказа на обработку. Здесь мы проделываем такой же хитроумный фокус, как и в предыдущем примере, а именно — копируем код с кнопками, предварительно изменив название последней на "Make order".

Интерфейс формы оплаты мы создали. Сейчас нам нужно подключить файл payment_form.html в index.html и указать переменную payment_form, которая хранит интерфейс формы, в коде лейаута. В браузере мы увидим такой результат:

Модуль с описанием товара и формой регистрации оплаты
Модуль с описанием товара и формой регистрации оплаты

Заставляем приложение работать

Выше мы описали лейаут и интерфейс форм нашего интернет магазина. Но это только половина пути. Теперь давайте сделаем приложение рабочим. А для этого у Webix есть все необходимые инструменты.

Оживляем список товаров

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

Блок каталога товаров
Блок каталога товаров

При клике по этой кнопке, нам нужно:

  • показать модуль с формой заказа

  • заполнить форму данными выбранной книги.

Чтобы реализовать вышеуказанное, нам необходимо установить обработчик на событие клика по кнопке. Сделать это можно через соответствующее свойство onClick виджета dataview. Внутри объекта этого свойства мы прописываем css класс нашей кнопки и присваиваем ей функцию-обработчик.

view:"dataview",
...
onClick:{
  store_btn:function(e, id){ 
    //получаем данные блока
    const data = this.getItem(id); 
    //заполняем темплейт с описанием
    $$("order_description").parse(data); 

    //заполняем поля формы данными заказа
    $$("order_form_view").setValues({
      //запоминаем начальную цену (не отображается) 
      initial_price:data.price, 
      //устанавливаем название товара
      order_name:data.title,
      //указываем самовывоз по умолчанию 
      delivery:1,
      //сбрасываем количество товаров 
      order_count:1, 
      //отключаем страховку (по умолчанию)
      insurance:0, 
      //устанавливаем стоимость заказа
      total_price:"$" + (data.price).toFixed(2) 
    }, true);
    //переключаем multiview на блок с описанием и формами
    $$("order_section").show(); 

  }
}

Чтобы заполнить форму регистрации заказа, мы можем воспользоваться ее методом setValues(), которому можно передать объект с необходимыми данными. 

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

Если передать методу только объект с данными, то он сначала сотрет все значения формы, а потом установит только те, которые мы указали в объекте. В этом случае, информация о доставке товара также будет удалена, а пользователю придется вводить адрес заново.

Чтобы избежать подобных неудобств, методу можно передать значение true в качестве второго параметра. Это позволит изменить только те значения формы, которые мы передаем в объекте.  

После всех манипуляций с данными, мы переключаем компонент multiview на следующий модуль с помощью его метода show().

Подсчет стоимости заказа 

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

Чтобы подписаться на любое изменение полей формы, необходимо добавить ей уже знакомое нам свойство on. В объекте этого свойства мы можем указать нужное событие и присвоить ему обработчик.

{
  id:"order_form_view",
  view:"form",
  ...
  on:{ onChange:priceCounter }
}

А сейчас давайте создадим этот обработчик. И выглядеть он будет так:

function priceCounter(){
  const form = $$("order_form_view");
  const vals = form.getValues();
  let price = vals.initial_price * vals.order_count;

  if($$("delivery_section").isVisible()){
    total_price = vals.insurance ? price *= 1.2 : price;
  }

  form.setValues( { total_price:"$" + (price).toFixed(2) }, true );
}

Здесь мы получаем объект со значениями полей формы через ее метод getValues(). Стоит отметить, что в объект попадут значения только тех полей, у которых есть свойство name

В переменную price мы сохраняем стоимость товаров, которая будет зависеть от изначальной цены (установленной в скрытое поле с именем initial_price) и количества товаров. 

Далее нужно проверить страховку, которая может подключаться в секции "Delivery information" и будет влиять на цену заказа. В условном операторе if мы проверяем видимость секции "Delivery information" с помощью метода isVisible(), который возвращает true — если секция отображается, и false — если спрятана.

Если секция отображается, тогда нужно проверить состояние контрола switch (если включен — значение 1, если отключен — 0 ) и сформировать окончательную стоимость заказа. При активации страховки мы будем увеличивать стоимость на 20%.

Когда у нас есть финальная стоимость заказа, нужно обновить ее в соответствующем поле Price. Для этого мы воспользуемся уже знакомым методом setValues() формы, но установим не все ее значения, а только нужное нам поле.

Теперь стоимость заказа будем меняться динамически при изменении количества товаров и подключении страхования.

Навигация между формами

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

Кнопка "Back" будет переключать multiview на предыдущий модуль, а кнопка "Next" — на следующий. Чтобы сделать кнопки рабочими, нужно поймать событие клика по ним и установить соответствующий обработчик.

Для установки обработчика на клик по кнопке, у контрола button есть специальное свойство click.

cols:[
  { view:"button", label:"Back", …, click:goBackCustomHandler },
  { view:"button", label:"Next", …, click:goNextCustomHandler },
  {}
]

В обработчике кнопки "Back" формы регистрации пользователей мы переходим обратно к каталогу товаров, вызывая у него метод show().

function goBackCustomHandler(){
  $$("trade_list").show(); //переходим к каталогу товаров
}

А в таких же обработчиках двух других форм мы будем возвращаться к предыдущему модулю через метод back() виджета multiview, в котором находятся вкладки с формами.

function goBackCustomHandler(){
  $$("form_multiview").back(); //переходим к предыдущей форме
}

В обработчике кнопки "Next" мы также используем метод show(), но вызываем его у компонента, находящегося в одной из ячеек multiview, которую нужно показать.

function goNextCustomHandler(){
  $$("id_of_next_form").show();
}

Вот таким образом реализуется динамическая смена вкладок с формами. 

Валидация форм

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

function goNextCustomHandler(){
  if($$("id_of_current_form").validate()){
    $$("id_of_next_form").show();
  }
}

Здесь мы запускаем валидацию текущей формы через ее метод validate(). Он вернет true — если проверка пройдет успешно, и false — если будет ошибка. Стоит уточнить, что метод проверяет только те поля формы, для которых установлены правила. А Webix позволяет задавать эти правила как для отдельного поля через его свойства required и validate, так и для всей формы через объект ее свойства rules.

Надеюсь вы еще помните, что для обязательных полей каждой формы мы определили свойство required, которое добавляет красную звездочку к лейблам. По умолчанию, такие поля проверяются встроенным правилом webix.rules.isNotEmpty и должны быть заполнены. Так вот, если хотя бы одно из этих полей не заполнено, то метод валидации формы вернет ошибку и поля подсветятся красным цветом.

Форма регистрации заказа, не прошедшая валидацию, будет выглядеть так:

Форма регистрации заказа, не прошедшая валидацию
Форма регистрации заказа, не прошедшая валидацию

Если же все обязательные поля этой формы заполнены, или секция "Delivery information" спрятана, то мы перейдем к следующей форме. 

Помимо свойства required с его проверкой по умолчанию, мы можем применить и другие встроенные правила, также как и задать собственные. Давайте установим такие правила для проверки электронной почты и номера телефона в форме регистрации пользователя. А сделать это можно в объекте свойства rules формы.

{
  view:"form",
  ...
  rules:{
    //встроенное правило для проверки email
    "email":webix.rules.isEmail, 
    //пользовательское правило
    "phone":function(value){ return value.length === 11 } 
  }
}

Для проверки электронного адреса мы применяем встроенное правило webix.rules.isEmail, а для номера телефона — собственное условие. Так как номер должен состоять из 11 цифр, мы будем возвращать ошибку, если его длина будет меньше нужной. 

Чтобы сделать валидацию полей более информативной, давайте добавим подсказку, которая будет появляться только при ошибке валидации. Для этого, каждому обязательному полю нужно добавить свойство invalidMessage с сообщением об ошибке. 

Форма регистрации пользователя, не прошедшая валидацию, будет выглядеть так:

Форма регистрации пользователя, не прошедшая валидацию
Форма регистрации пользователя, не прошедшая валидацию

На случай, если пользователь перейдет к предыдущей форме, а потом вернется обратно, давайте уберем метки валидации текущей формы. Для этого нужно вызвать метод clearValidation() формы в обработчике ее кнопки "Back".

Делаем заказ

В последней форме регистрации оплаты, вместо кнопки "Next" у нас есть кнопка "Make order". Она запустит валидацию и, в случае успеха, соберет данные из всех трех форм, очистит текущую форму с данными о карте и отобразит главную страницу с перечнем товаров. Код обработчика будет таким:

function makeOrder(){
  const payment_form = $$("payment_form_view");
  const user_form = $$("user_form_view");
  const order_form = $$("order_form_view");

  if(payment_form.validate()){
    //собираем данные всех трех форм для последующей обработки
    const order_vals = order_form.getValues();
    const user_vals = user_form.getValues();
    const payment_vals = payment_form.getValues();

    payment_form.setValues({}); //очищаем форму оплаты
    order_form.show(); //переходим к модулю с формой заказа
    $$("trade_list").show(); //переходим к каталогу товаров
  }
  /* логика обработчика */
}

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

Заключение

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

В этой статье мы детально разобрали как создавать формы с помощью библиотеки Webix, и теперь знаем:

  • как подключать библиотеку

  • как описывать UI компоненты с помощью json синтаксиса

  • как использовать методы компонентов

  • как устанавливать обработчики на события.

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

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

Комментарии (0)