В предыдущих статьях из цикла “JavaScript библиотека Webix глазами новичка” вы узнали как создать приложение на основе компонентов Webix UI. В этой публикации я хочу подробно рассмотреть создание аналогичного приложения с использованием архитектуры и возможностей фреймворка Webix Jet, а также какие преимущества это дает. Статья будет полезна как для тех кто уже знаком с Webix UI, так и для новичков, ведь код библиотеки интуитивно понятен и удобен в использовании. Webix — это библиотека UI компонентов. Для создания полноценного приложения на основе Webix UI вам не обязательно использовать дополнительные инструменты. Однако по мере расширения и увеличения количества кода могут возникнуть трудности с его организацией. Команда Webix позаботилась о решении этой проблемы и разработала собственный фреймворк для построения архитектуры приложения.  Фреймворк Webix Jet позволяет создавать очень гибкое и хорошо управляемое одностраничное приложение, используя паттерн Model-View. При таком подходе, логика работы с данными и отображение элементов интерфейса четко разделяются. Каждый компонент разрабатывается и тестируется отдельно от других. Также имеются готовые решения многих задач, которые реализуются через API, плагины и конфигурации. Архитектура Jet-приложения основана на модулях view компонентов. Каждый компонент являет собой независимую часть интерфейса. Вы можете комбинировать модули, использовать повторно в разных частях приложения и управлять их отображением с помощью URL. Подробнее о работе с Webix-jet вы узнаете на примере создания собственного SPA. Процесс создания приложения мы рассмотрим в следующем порядке: Интерфейс приложения Структура приложения Создание и конфигурация приложения View модули интерфейса Модели работы с данными Локализация приложения В документациях Webix UI и Webix Jet можно ознакомиться с использованными в статье компонентами и API фреймворка. С кодом готового приложения можно ознакомиться тут.   Начнем с описания интерфейса нашего приложения и разделения его на смысловые части. Сделать это крайне важно, так как каждую обособленную часть мы будем разрабатывать и хранить в отдельном модуле. Интерфейс разделяем на 3 части: Тулбар  Сайдбар Панель сменяемых модулей Тулбар находится в верхней части экрана. Он содержит лейбл с названием приложения в левой части и кнопки переключения локализации в правой. Сайдбар находится в левой части экрана и содержит меню для навигации между сменяемыми модулями интерфейса.  Панель сменяемых модулей составляет основу приложения и расположена в центральной части экрана. Здесь вы сможете видеть 3 автономных модуля, которые содержат определенную часть интерфейса приложения. Они будут сменять друг друга по клику на опции меню.  У нашего приложения будет следующая структура: файл index.html является стартовой страницей и единственным html файлом   файл sources/myapp.js содержит необходимые конфигурации приложения папка sources/views состоит из следующих файлов:  top.js содержит главный модуль, который объединяет все элементы интерфейса  toolbar.js содержит модуль с описанием тулбара  form.js содержит модуль с описанием формы  films.js содержит сменяемый модуль   users.js содержит сменяемый модуль   products.js содержит сменяемый модуль  папка sources/models содержит модули для операций с данными папка sources/styles содержит CSS файлы папка sources/locales содержит модули локализации. Такая организация кода помогает разделять разные аспекты разработки на отдельные составляющие и эффективно управлять ими. Это особенно актуально при работаете с большим количеством кода. Мы будем создавать интерфейс приложения комбинируя несколько subview модулей в файле top.js. Subview - это модули интерфейса, которые входят в состав других view. Они могут быть статическими и динамическими. Давайте подробнее их рассмотрим. Статические subview импортируются при помощи команды import и включаются в интерфейс компонента напрямую. Например, мы будем использовать статический модуль toolbar.js в главном модуле top.js.  Для понимания принципа работы динамических subview важно упомянуть о такой сущности Jet, как URL. Он позволяет формировать иерархию отображения view модулей и осуществлять навигацию между ними. URL отображается в адресной строке браузера после имени домена и спецсимвола `/#!/`. Например, с помощью URL мы можем указать модуль, который будет отображаться первым:  http://localhost:8080/#!/top - приложение отобразит интерфейс файла top.js http://localhost:8080/#!/top/films - приложение отобразит интерфейс файла top.js и films.js (если в top.js указан плейсхолдер для films.js). Мы реализуем 3 динамических subview модуля, каждый из которых будет отображаться при соответствующем значении URL: films.js ( http://localhost:8080/#!/top/films ) users.js ( http://localhost:8080/#!/top/users ) products.js ( http://localhost:8080/#!/top/products ) Навигация между динамическими subview реализуется при помощи изменения URL. Например, если нам надо отобразить модуль users на панели сменяемых модулей, мы можем изменить текущий URL на http://localhost:8080/#!/top/users и увидим интерфейс файла top.js и users.js. Переключение между опциями меню работает по такому же принципу. Навигация с помощью URL удобна и тем, что браузер хранит историю посещения разных url-адресов, и при клике на кнопку “Назад” в  браузере, мы можем перейти к предыдущему модулю. Также URL хранит состояние приложения. Если пользователь переключится на вкладку Users при помощи меню и обновит страницу, то приложение отобразит тот модуль, URL которого был установлен в адресной строке браузера до перезагрузки. Если такое отображение URL неудобно, вы можете либо убрать спец. символы из URL, либо вовсе не показывать его в адресной строке - на работу приложения это не повлияет. Подробнее о настройках URL можно узнать тут. Подробнее ознакомиться с тонкостями использования subview вы можете тут. Итак, мы разобрали интерфейс и структуру файлов нашего демо приложения. Сейчас пришло время непосредственно заняться его созданием. Для начала важно правильно настроить нужные конфигурации и подключить необходимые инструменты. В файле sources/myapp.js находится модуль конфигурации приложения. Он расширяется базовым классом JetApp, который содержит все необходимые инструменты управления. Импортируем и подключаем стили css и базовый класс JetApp. Создаем класс MyApp нашего приложения и наследуем его от базового JetApp. В конструкторе класса прописываем необходимые настройки. Для активации режима отладки нужно установить параметр debug:true. Теперь фреймворк будет отображать все ошибки, которые могут возникнуть в процессе разработки модулей. Преимущество работы с Jet состоит в том, что сбои в работе отдельных модулей не будут влиять на работоспособность всего приложения, но без debug:true вы о них не узнаете.   Для определения стартовой страницы нужно установить параметр start и указать значение URL для начальной загрузки: start:"/top/films". Теперь при первоначальной загрузке приложения мы увидим главный модуль top.js и сменяемый модуль films.js. После создания класса и внесения необходимых конфигураций, нам нужно его инициализировать. Далее отображаем приложение через метод render() и оборачиваем его в webix.ready(), чтобы HTML страница успела загрузиться до начала выполнения кода. Мы создали и настроили наше приложение. Теперь самое время перейти к непосредственной разработке view модулей интерфейса.   Мы будем создавать view компоненты как классы ES6 и наследовать их от базового JetView. Такой подход позволяет расширить модуль встроенными Jet методами и получить доступ к управлению жизненным циклом компонента. В начале каждого модуля необходимо подключить базовый JetView класс, который хранит конфигурации и инструменты для управления логикой view модулей.  В собственных классах мы используем методы config() и init(), которые наследуются от JetView, а также можем создавать собственные методы для хранения кастомной логики. Элементы интерфейса описываем и возвращаем через метод config(). Работу с данными осуществляем через метод init(). Давайте создадим главный модуль TopView в файле top.js, который будет включать сайдбар, футер, тулбар и динамические subview модули. В адресной строке он будет отображаться как #!/top/ . Тулбар мы создадим в другом модуле и импортируем сюда. Он будет включаться как статический subview. Импортируем базовый класс JetView и модуль ToolbarView. Создаем и наследуем класс TopView от базового JetView. В методе config() описываем основные элементы интерфейса и возвращаем комбинированный view.    Обратите внимание, что созданный класс необходимо экспортировать при помощи команды export default, чтобы Webix Jet включил его в приложение. Внутри метода config() мы описываем сайдбар, который содержит меню с опциями для переключения динамических модулей интерфейса. Для этого используем компонент List библиотеки Webix UI. В свойстве data указываем названия опций меню и их id, которые будут использоваться для формирования URL. Описываем футер, который содержит информацию копирайта и находится в самом низу экрана. Мы не выделяем его в отдельный модуль, так как это незначительный элемент. Для этого используем компонент Template.   Теперь нужно объединить все компоненты и сформировать структуру интерфейса приложения. Помимо собственных компонентов сайдбар и футер, мы включаем статический ToolbarView и динамические subview.   Возвращаем комбинированный view ui,  который включает в себя все элементы интерфейса. Теперь нужно настроить панель сменяемых модулей (плейсхолдер {$subview:true} ).  При выборе той или иной опции меню, наше приложение будет задавать необходимый сегмент URL (films, users, products) и отображать одноименный модуль на месте плейсхолдера {$subview:true}. Обязательное условие, чтобы id опций меню и названия файлов, хранящих модули интерфейса, совпадали.  Для того чтобы установить необходимый URL, воспользуемся встроенным методом JetView this.show(id). В качестве аргумента передаем id соответствующей опции меню. Этот id задает URL относительно того модуля, в котором вызван метод show(). В нашем случае метод изменит значение URL после #!/top/ и установит туда переданный в аргументе id.  Таким образом, полный URL будет #!/top/{id}. Например, при клике на опцию Users, виджет вызовет this.show("users") и установит URL  #!/top/users. На месте { $subview:true } отобразится модуль файла views/users.js . Определяем модуль ToolbarView в файле toolbar.js, который  включается как статический subview в главном модуле файла top.js.  Создаем и наследуем класс ToolbarView от базового JetView. В методе config() описываем необходимые компоненты и возвращаем их. Описываем тулбар, который содержит лейбл с названием приложения и кнопки переключения локализации. Для этого используем такие компоненты Webix UI как Toolbar, Label и Segmented. Теперь пришло время перейти к созданию динамических subview, которые будут сменяться в зависимости от сегмента URL, следующего после #!/top/. У нас будет 3 сменяемых модуля: FilmsView UsersView ProductsView. Каждый модуль мы будем разрабатывать в отдельном файле. Интерфейс модуля состоит из таблицы фильмов и формы для их редактирования. Таблица состоит из 5 столбцов, в которых мы реализуем сортировку и фильтрацию данных. Форму создадим в отдельном модуле и будем включать ее как статический subview. Определяем модуль FilmsView в файле films.js, который будет включаться как динамический subview в файле top.js. Модуль будет отображаться при значении URL: #!/top/films. В начале кода импортируем модуль FormView из файла form.js, который содержит форму для редактирования фильмов и будет включаться как статический subview. Создаем и наследуем класс FilmsView от базового JetView. В методе config() описываем основные элементы интерфейса и возвращаем комбинированный view. Описываем таблицу фильмов с помощью компонента Datatable. Через свойство columns конфигурируем столбцы. У каждого столбца должен быть уникальный id. Сортировка данных столбца реализуется при помощи свойства sort. Для столбцов с текстовыми значениями мы используем sort:"text", а для числовых sort:"int". Чтобы добавить фильтр в хедер столбца, мы используем конфигурацию {content:"textFilter"} для текстовых значений и {content:"selectFilter"} для числовых.  Формируем интерфейс модуля. Включаем собственный компонент film_table с таблицей фильмов и статический модуль FormView, который хранит интерфейс формы для редактирования фильмов. Интерфейс модуля содержит форму для редактирования фильмов. Форма состоит из 4 полей ввода, а также кнопок “Сохранить” и “Очистить”.  Определяем модуль FormView в файле form.js. Создаем и наследуем класс FormView от базового JetView. В методе config() описываем основные элементы интерфейса и возвращаем комбинированный view. Описываем форму для редактирования фильмов с помощью компонента Form. Через свойство elements конфигурируем поля ввода и кнопки для управления формой. Интерфейс модуля состоит из тулбара, списка пользователей и диаграммы пользователей. В тулбаре находится строка поиска, кнопка для добавления новых пользователей, а также 2 кнопки для сортировки списка пользователей по возрастанию и убыванию. Список пользователей содержит имя, возраст и страну. Диаграмма отображает возраст пользователей.  Определяем модуль UsersView в файле users.js, который будет включаться как динамический subview в файле top.js. Модуль будет отображаться при значении URL: #!/top/users. Создаем и наследуем класс UsersView от базового JetView. В методе config() описываем элементы интерфейса при помощи компонентов List, Toolbar и Chart.   Описываем список пользователей при помощи компонента List. Через свойство template конфигурируем шаблон списка, который будет включать имя, возраст и страну. Описываем тулбар для работы со списком при помощи компонента Toolbar . Через свойство elements конфигурируем строку поиска и кнопки для управления списком пользователей.   Описываем диаграмму пользователей при помощи компонента Chart. Задаем тип диаграммы type:"bar", значения для столбцов value:"#age#".  Конфигурируем оси при помощи свойств xAxis и yAxis.   Формируем интерфейс модуля. Комбинируем и возвращаем компоненты list_toolbar, list и chart. Интерфейс модуля состоит из древовидной таблицы, которая содержит элементы высшего и вложенного уровней.  Определяем модуль ProductsView в файле products.js, который будет включаться как динамический subview в файле top.js. Модуль будет отображаться при значении URL: #!/top/products. Создаем и наследуем класс ProductsView от базового JetView. В методе config() описываем древовидную таблицу при помощи компонента Treetable. Через свойство columns конфигурируем столбцы таблицы. Используем конфигурацию template:"{common.treetable()} #title#",  чтобы задать вложенные элементы. Мы описали интерфейс нашего приложения. Теперь пришла очередь рассказать о том,  как осуществляется работа с данными.   Давайте разбираться с тем, как работать с моделями данных. В принципе, подключать данные не обязательно, и можно загружать их сразу в компоненты минуя модели. Но для серьезного приложения такие сущности как view и model лучше разделять. Логика работы с моделями данных хранится в файлах директории sources/models. Предполагается, что мы взаимодействуем с сервером и загружаем данные асинхронно. Для этого используем метод webix.ajax().  Функция webix.ajax() делает асинхронный запрос по указанному в аргументе адресу и возвращает промис-объект. В случае успешной загрузки мы получим объект, значения которого будем загружать в необходимые нам модули. Данные нам нужны для 3 сменяемых subview:  FilmsView UsersView ProductsView. Для каждого модуля создаем отдельную модель, как на примере ниже. Все 3 модели будут отличаться только аргументом запроса, в котором мы указываем путь к данным на сервере. В нашем случае данные хранятся в папке ../../data/. Теперь для каждого view модуля необходимо импортировать метод getData() из соответствующей модели. Далее мы должны передать данные из модели в необходимый компонент. Интерфейс модуля описывается внутри метода config() и становится доступным после инициализации. Внутри специального метода init() класса JetView, мы можем получить доступ к нужному data-компоненту (List, Datatable, Chart) и загрузить в него данные при помощи метода parse().   В метод init() приходит параметр view, который ссылается на объект, возвращенный методом config() того же модуля. Это может быть layout с таблицей и другими компонентами. Внутри layout мы ищем таблицу по названию компонента ( view.queryView("datatable"); ), а затем загружаем в нее данные. Чтобы передать данные из промис-объекта который возвращает нам метод getData(), нужно вызвать метод parse() для Webix компонента и передать туда промис в качестве аргумента.  Когда данные будут загружены, компонент их обработает и отобразит в интерфейсе приложения. Инструментарий фреймворка Webix Jet позволяет с легкостью реализовать локализацию приложения. Для этого мы будем использовать встроенный сервис Locale.  Этот сервис нужно подключить в файле sources/myapp.js, т.к. он будет использоваться глобально для всего приложения. Для этого мы импортируем модуль plugins, который содержит встроенные сервисы, вместе с базовым JetApp. После создания и инициализации класса MyApp, мы подключаем сервис plugins.Locale. Сделать это необходимо до вызова метода render().   Сервис Locale устанавливает связь с файлами локализации, которые находятся в директории sources/locales. Файлы en.js и ru.js хранят объекты с элементами локализации. Далее, в каждом отдельном view модуле мы получаем метод из сервиса локализации.   Этот метод мы будем вызывать каждый раз, когда нам надо перевести какое-либо текстовое выражение. В качестве аргумента нужно передавать ключи, по которым будут подтягиваться значения из объекта файла установленной локали: _("some text"). При смене локализации, Jet перерисует приложение с учетом значений установленной локали.  Почти готово, осталось только рассказать о том, каким образом пользователь сможет переключать языки в нашем приложении. Как вы помните, в тулбаре мы создали кнопку Segmented со значениями En и Ru. Теперь нам нужно создать обработчик, который будет вызываться при клике на соответствующую кнопку и применять нужную локализацию. Для этого мы создаем функцию-обработчик toggleLanguage() как метод класса ToolbarView. Здесь же находится сегментная кнопка, при клике на которую мы будем вызывать наш обработчик. Внутри функции получаем доступ к сервису локализации, считываем значение заданной опции (значение берется из id  - "en" или "ru") и устанавливаем выбранную локаль с помощью метода  setLang(). При описании функции используем метод this.getRoot(). С его помощью мы получаем доступ к view,  возвращенный нам методом config() - здесь это Toolbar. Внутри view мы ищем компонент с именем segmented, чтобы получить объект кнопки и считать ее значение. Вот такими простыми манипуляциями в Webix Jet  реализуется смена локализации. С помощью фреймворка Webix Jet мы создали структурированное одностраничное приложение. Интерфейс разделен на модули view, которые хранятся в отдельной директории sources/views. Логика работы с данными отделена от компонентов интерфейса и хранится в директории sources/models. Реализована локализация приложения за счет использования плагина Locale базового класса JetApp.  Сейчас это мини-приложение, но с такой архитектурой его легко масштабировать и поддерживать. С кодом готового приложения можно ознакомиться тут. Webix Jet имеет достаточно широкий спектр инструментов и конфигураций, которые значительно упрощают процесс разработки приложения на основе компонентов Webix UI. Более детально мы рассмотрим их в следующих статьях посвященных Webix Jet.  old-school-developer