Commit ba2dfddf authored by Elizaveta Kakhovskaya's avatar Elizaveta Kakhovskaya

Update workplace.rst

parent bc03aca2
Модуль Методолога
Рекомендация: всегда начинайте разработку приложения с рисования прототипа, выделяя структуру страниц и переходы между ними. Такой подход позволяет существенно сэкономить время на реализацию.
......@@ -672,962 +671,3 @@ Cледует позаботиться о том, чтобы у пользова
Теперь необходимо настроить передачу данных из записей указанного реестра в компоненты на итераторе. Для этого у итератора есть специальный диалог “Сопоставление”, доступный из свойств компонента:
Клик по свойству “Сопоставление” открывает диалог настройки:
Добавим новое сопоставление:
Здесь:
Выпадающий список “Код компонента” содержит коды всех компонентов, расположенных внутри текущего итератора. Выбран компонент “Надпись”, который должен отображать заголовок услуги.
Выпадающий список “Свойство” содержит доступные свойства выбранного типа компонента. Мы выбрали компонент “Надпись”, и список содержит основные свойства надписи - ее текст, ширину, высоту и флаг, является ли компонент скрытым. Использовано свойство ``text``, чтобы значение компонента формы было отображено в тексте нашей надписи.
Поле ввода “Параметр” предназначено для ввода имени параметра элемента источника данных итератора. У нас источником данных являются записи реестра, его элементы - отдельные записи, а параметры этих элементов - компоненты форм. Поэтому в данном поле указан ID компонента формы - “Однострочное поле”, куда должно быть введено название услуги.
Выпадающий список “Тип значения” предназначен для параметров - компонентов форм, у которых есть ``key`` и ``value``. В нашем случае параметр - однострочное поле, у него отсутствует ``key``, а содержимое хранится в ``value``, поэтому в этом списке выбран вариант “Значение”.
Подсказка:
Для каждого типа компонента форм необходимо выбирать, какой тип значения использовать. Для понимания этой структуры рекомендуем обратиться к Руководству разработчика Synergy.
Другой способ понять, какой тип значения нужно использовать - изучение данных записи по форме. Для этого нужно открыть запись в Synergy и скачать основной файл по форме (кликнуть правой кнопкой мыши по приложению с расширением ``*.asfdocx`` и выбрать пункт “Скачать”):
В скачанном файле необходимо найти блок, соответствующий нужному компоненту. Для компонента ``item_name`` он выглядит таким образом:
```
{"id":"item_name","type":"textbox","value":"Американские горки"}
```
Здесь видно, что значащее содержимое компонента - текст, введенный пользователем в поле ввода - содержится в свойстве ``value``. Его и необходимо использовать в качестве типа значения.
Таким же образом настроим сопоставление для надписи с кратким описанием и кнопки:
Сохраним изменения и перейдем в режим просмотра, чтобы проверить, что получилось:
Для каждой записи реестра услуг отрисована собственная плашка с надписями и кнопкой. При этом в каждой плашке тексты надписей и кнопок уникальны и соответствуют содержимому полей в соответствующих записях реестра.
Теперь вернемся в режим конструктора и рассмотрим способ получения изображений из Synergy.
Компонент “Изображение”
Рассмотрим свойства компонента “Изображение”:
Получать изображение для того, чтобы отобразить его в приложении, можно разными способами:
из внешней сети по URL изображения
из компонента формы, указывая ID компонента и UUID записи по форме. Этот компонент должен быть типа “Файл”.
из Хранилища по идентификатору файла
Кроме источника, для компонента можно указать плейсхолдер, включить адаптивное позиционирование флагом “Настраивать позиционирование”, а также, как и для любого другого компонента, указать размеры и скрыть при необходимости.
В первую очередь настроим источник изображения. Нам необходимо получать его из компонента формы для каждой из записей реестра. Изучим содержимое компонента “Файл” формы, в который пользователь загружал изображение:
```
{"id":"item_image","type":"file","value":"slide_1.jpg","key":"5b775925-73f9-4dd5-8f48-c976c0979f6c"}
```
Здесь видно, что в ``value`` компонента хранится имя загруженного файла, а в ``key`` - идентификатор этого файла в Хранилище.
Для работы с изображениями в итераторе мы рекомендуем оперировать именно идентификаторами Хранилища, а не компонентами форм. Поменяем источник в свойствах компонента:
Теперь добавим сопоставление для этого компонента (свойства итератора - диалог “Сопоставление”) :
Здесь:
Код компонента - выбран код компонента “Изображение”, расположенный на итераторе.
В списке свойств компонента выбрано свойство ``fileIdentifier`` - идентификатор отображаемого изображения в Хранилище.
Имя параметра соответствует коду компонента “Файл” на форме.
В качестве типа значения выбран вариант “Ключ”, поскольку идентификатор файла хранится в свойстве ``key`` компонента.
Сохраним изменения и снова проверим, что получилось:
Картинки были получены из записей реестра, но очень сильно сжались. Это произошло потому, что у нас в свойствах компонента “Изображение” были жестко зафиксированы его размеры, и при этом была отключена настройка адаптивного позиционирования. Исправим это:
Включен флаг “Настраивать позиционирование”, в свойстве “Позиционирование изображения” выбран вариант “Адаптивный”. Также обязательно указываем размеры компонента: ширина - 100% (чтобы изображение занимало всю ширину своей плашки), высота - 150px (фиксируем высоту абсолютной величиной). Снова проверяем текущий вид приложения:
Под каждую картинку выделено ровно 150 пикселей, однако для них соблюдено соотношение сторон - из-за этого для услуги “Лотерея” картинка выглядит меньше, чем для других услуг.
Кроме того, видно, что размеры плашек не одинаковые. Фактически каждая плашка растягивается под свое содержимое - в нашем случае, под ширину текста в кратком описании. Для решения этой проблемы зафиксируем размеры итератора 300px на 350px:
Размеры компонента-итератора регулируют размер, который займет одна его плашка. Мы указали, что одна плашка должна быть размером 300х350 пикселей. Однако при этом заметно, что кнопка в итераторе не прижата к нижнему краю плашки:
Как уже подробно рассматривалось в предыдущем примере, за позиционирование элементов отвечают их родительские панели. Однако у панели-итератора свойств расположения элементов нет. Поэтому добавим в итератор новую панель и переместим в нее все компоненты, используя вырезание и вставку компонентов:
Теперь укажем для этой панели, что она должна быть вертикальной, ширину и высоту в 100% (относительно всего итератора) и расположение содержимого с пространством между содержимым:
Все компоненты теперь растянуты по всей высоте плашки. Текущий вид страницы:
Теперь размеры всех плашек одинаковы.
Стили компонентов
Мы вывели список записей реестра в виде плашек, однако хочется визуально отделить эти плашки друг от друга, очертив границы. Изменить внешний вид компонента - его цвет, шрифт, границы, тени и прочее - можно с помощью стандартного CSS. Конструктор позволяет сделать это двумя способами: напрямую в свойствах компонента или через отдельные файлы стилей. Рассмотрим первый способ.
У каждого компонента есть свойство “Стили” - многострочное поле ввода:
В этом поле можно вводить свойства CSS. Например, пропишем здесь границы:
Обратите внимание: в тот момент, когда был изменен вид границ компонента, он перестал визуально выделяться пунктирной линией в режиме конструктора. Это произошло потому, что стили компонента применяются сразу же по мере ввода, в том числе без перехода в режим просмотра приложения, и имеют приоритет выше, чем встроенные стили. Граница компонента по умолчанию - синий пунктир - заменена стилем с более высоким приоритетом. Такое поведение не является ошибкой Конструктора.
Проверим результат и убедимся, что у плашек появились границы:
Страница “Паспорт услуги”
Паспорт услуги должен содержать детальное описание выбранной услуги. Получать это детальное описание необходимо из компонентов соответствующей записи по форме.
Поскольку мы хотим, чтобы данные об услуге были отображены не просто проигрывателем форм, а в более приятном для глаз виде, будем использовать отдельные компоненты конструктора.
Нам потребуются:
компоненты “Надпись” для заголовка услуги, ее краткого и подробного описания
компонент “Изображение” для приложенной картинки услуги
компонент “Кнопка”
Разместим эти компоненты на странице (в панели, вложенной в корневую), и сразу позаботимся о кодах для компонентов:
Здесь:
у вложенной панели указан тип “Вертикальная”, чтобы все ее компоненты располагались сверху вниз, и ширина 80%;
все компоненты-надписи имеют ширину 100%;
кнопка расположена внутри собственной панели с шириной 100% и выравниванием содержимого по центру - таким образом мы расположили кнопку в центре основной панели, не растягивая ее и не влияя на позиционирование других компонентов;
для изображения сразу включен флаг “Настраивать позиционирование”, указана ширина 100% и высота в 300px.
Выполнение запроса данных формы
Мы хотим заполнять значения компонентов на странице приложения из компонентов записи по форме. Конструктор позволяет разработчику настроить выполнение запроса данных выбранной записи и использование результатов этого запроса с помощью комбинирования возможностей:
параметры страницы
выполнение кейсов
параметры компонентов
Общая схема работы такова:
на странице предварительно настраиваются параметры:
строковый параметр для значения UUID записи;
параметр типа “Форма” для сохранения полученных данных записи;
страница получает значение UUID данных записи по форме (извне, например, в результате выполнения перехода на эту страницу), сохраняет его как параметр строкового типа;
в момент загрузки страницы выполняется действие запроса данных по форме; входным параметром для этого запроса является UUID данных, полученный на шаге 2;
страница получает от сервера результат выполнения запроса (файл JSON) и сохраняет его как параметр специального типа “Форма”;
у целевого компонента на странице настраивается получение его свойств из параметра, полученного на шаге 4.
Можно представить эту схему в виде диаграммы:
Рассмотрим этот процесс на примере получения паспорта услуги.
Добавление параметров страницы
В диалоге “Свойства страницы” добавим у страницы “Паспорт услуги” два параметра:
Параметр ``dataUUID``, тип “Строковый”, будет хранить UUID записи, выбранной из Каталога услуг.
Параметр ``formData``, тип “Форма”, в который будет записан JSON данных формы.
Страница Конструктора приложений также поддерживает тип параметра “Документ”, он предназначен для хранения результатов выполнения запроса данных документов Synergy.
Кейс выполнения API
В диалоге “Кейсы страницы” добавить новый кейс ``loadData``, выполняемый по событию загрузки страницы ``page_load``:
В списке действий нужно выбрать вариант “Выполнить API”:
У действия “Выполнить API” доступны параметры:
“Входной параметр” - произвольный текст или значение одного из параметров текущей страницы;
“Метод” - выбор одного из преднастроенных методов Synergy;
“Выходной параметр” - выбор одного из параметров страницы, в который должен быть записан результат выполнения метода.
Примечание: тип параметра, указываемого в качестве выходного, обязательно должен соответствовать используемому методу. Иными словами, если выполняется метод “Данные по форме”, то выходной параметр страницы должен быть типа “Форма”. Если же выполняется метод “Данные документа”, то выходной параметр должен иметь тип “Документ”.
Настроенный и сохраненный кейс:
Параметры компонентов
Ранее мы рассматривали работу со свойствами компонентов - вручную указывали текст для надписей, подписи кнопок, типы панелей и прочие настройки, специфичные для каждого типа компонента. Их особенность в том, что эти значения постоянны и не получаются извне.
Конструктор позволяет динамическое получение основных свойств для каждого типа компонента с помощью параметров компонента.
Для каждого из компонентов страницы укажем, откуда он должен получать значения своих свойств. Для этого используется раздел “Параметры”, располагающийся в левой части Конструктора, ниже раздела “Кейсы”:
Для настройки параметров компонента нужно кликнуть по иконке . Откроется диалог вида:
Для каждого компонента в этом диалоге можно настроить несколько параметров - для каждого из доступных свойств. Для добавления параметра нужно нажать на кнопку “Добавить”:
Текущий выбранный компонент - надпись, отображающая название услуги. Основным ее свойством является ``text`` - отображаемый текст. Нам необходимо отобразить текст, введенный на форме, в компоненте с кодом ``item_name``:
Значение этого компонента получено страницей в параметре ``formData``. Нам необходимо получить из этого параметра значение именно поля ``item_name``:
Здесь:
в списке “Параметры страницы” выбран параметр ``formData``, хранящий данные формы; после выбора этого параметра становятся доступны для редактирования поля “ID параметра” и “Тип значения”;
в поле “ID параметра” введен код компонента формы ``item_name``;
в поле “Тип значения” выбран вариант “Value” - потому что нам известно, что у однострочного поля формы введенный текст хранится как ``value`` (подробно разбиралось выше, при настройке сопоставления в итераторе).
Сохраненные изменения отображаются как новая строка в разделе “Параметры” компонента:
Таким же образом настроим заполнение компонентов “Краткое описание”, “Подробное описание” и кнопки:
Компонент “Краткое описание”, текст которого получаем из компонента ``item_shortDescription``
Компонент “Подробное описание”, текст которого получаем из компонента ``item_longDescription``
Компонент кнопки, подпись для которой получаем из компонента ``item_button2``
Отдельно рассмотрим получение изображения из формы. Можно воспользоваться тем же методом, который был рассмотрен для итератора: в качестве источника указать идентификатор файла в Хранилище, а потом в параметрах компонента настроить получение свойства ``fileIdentifier`` из ``key`` компонента формы.
Однако здесь можно использовать другой способ - зафиксировать у изображения код компонента формы, где приложен файл, и динамически получать UUID записи. для этого сначала укажем, что компонент “Изображение” должен получать данные из формы Synergy:
Здесь же укажем ID компонента формы - ``item_name``.
Для получения ID данных по форме воспользуемся параметрами компонента:
Свойство компонента “ID данных по форме” ``dataUUID`` необходимо получать из параметра страницы ``dataUUID``.
Сохраним новый параметр компонента.
Кейсы итератора
Вернемся к странице “Главная”, чтобы настроить переход к паспорту услуги по клику на кнопку в ее плашке.
Выделим кнопку на главной странице и добавим для нее новый кейс:
Как и для обычных кейсов перехода на страницу, необходимо указать код кейса, событие компонента, выбрать действие “Перейти на страницу” и выбрать целевую страницу из списка.
Однако кнопка, по клику на которую должен выполняться переход, находится на панели-итераторе. Поэтому для нее отличается передача параметра целевой странице: нам необходимо сообщить странице идентификатор именно той записи, которая соответствует плашке итератора. Добавим в кейс передаваемый параметр с типом “Параметры итератора”:
Итератор здесь служит источником параметров для всех компонентов, расположенных на нем. При этом названием параметра итератора является название получаемого элемента источника - ID компонента формы или элемента произвольного JSON. Также у итератора есть специальный параметр ``dataUUID`` - идентификатор записи, отображаемой в текущей плашке. Этот параметр имеет смысл только в том случае, когда в качестве источника итератора выбраны записи реестра.
Именно этот параметр нам и следует сообщить странице с паспортом услуги:
Сохраним новый кейс кнопки и проверим общую работу приложения:
Использование модальных окон
Ранее мы рассматривали возможность создания новых записей реестров Synergy на отдельной странице. Теперь на примере Витрины услуг реализуем возможность подачи заявки в модальном окне.
Вернемся к странице “Паспорт услуги” и добавим в нее новую панель (ниже кнопки):
Модальное окно в терминах Конструктора приложений - это особый вид панели, в свою очередь содержащей компоненты. Для новой панели ``panel-modal`` выберем тип “Модальная”:
Для модального окна можно задать его заголовок - “шапку”:
Основное свойство модальной панели - флаг “Скрытый”. В большинстве случаев модальные окна отображаются после какого-то действия пользователя, а не сразу же при переходе на страницу, поэтому рекомендуем всегда, добавляя на страницу модальную панель, сразу же включать флаг “Скрытый”, чтобы по умолчанию панель не была отображена:
В нашем случае панель будет отображаться, когда пользователь будет нажимать на кнопку подачи заявки. Реализуем это с помощь. кейса кнопки:
Отображение модальной панели реализовано с помощью действия “Выполнить на странице”.
Обратите внимание: у компонентов, используемых в кейсе, уже настроены легко понимаемые коды компонентов: например, кнопка, которая должна отображать модальную панель, имеет код ``button-showModal``, а сама модальная панель - ``panel-modal``. Как уже говорилось, такой подход позволяет избавиться от путаницы при работе с компонентами страницы.
Проверим работу этого кейса:
По клику по кнопке “Оставить заявку” отображается модальное окно с настроенной шапкой. Клик по области вне этого окна закрывает его.
Пользователь будет заполнять заявку в проигрывателе форм и отправлять ее нажатием на кнопку. Добавим соответствующие компоненты в нашу модальную панель:
Обратите внимание, что компоненты, добавляемые на модальную панель, затемнены. Это происходит потому, что панель, на которую они добавляются, скрыта. Такие компоненты видны в режиме конструктора, но не будут отображены пользователю в режиме просмотра приложения.
Для компонента “Проигрыватель форм” настроим отображение формы заявки и возможность создание записей реестра заявок:
Для компонента указано, что он должен отображать форму ``showcase_example_request`` в режиме редактирования, а также должен создавать и отправлять на активацию записи реестра ``example_showcase_requests``.
Для кнопки отправки настроим кейс создания новой записи по форме при клике:
В компоненте “Проигрыватель форм” также настроим кейс: в случае успешного создания записи уведомить пользователя о том, что его заявка принята, и скрыть модальное окно:
Проверим, что получилось:
Стартовое состояние: модальное окно не отображено.
Клик по кнопке “Оставить заявку” отображает модальную панель с проигрывателем форм и кнопкой. Пользователю предложено оставить свои контактные данные и выбрать интересующую его услугу из списка.
После нажатия на кнопку “Отправить” модальное окно скрывается, а пользователю отображено сообщение об успехе.
Шапка страницы
У собранного нами приложения остался один недостаток: у пользователя нет возможность вернуться из паспорта услуг обратно на главную страницу. Напомним, что все приложения, реализуемые в Конструкторе, являются Single Page Application, и браузерная кнопка “Назад” для них выполнит полный выход из приложения, а не возврат на предыдущую страницу. Поэтому необходимо самостоятельно заботиться о навигации пользователя по страницам приложения.
Мы реализуем возврат на стартовую страницу с помощью клика по логотипу компании, расположенному в шапке страницы. Для этого сначала добавим на страницу “Паспорт услуги” новую панель и переместим ее так, чтобы она располагалась в самом начале страницы:
Эта панель будет содержать логотип компании. Для отображения логотипа используем компонент “Изображение”:
Наш логотип мы добавим в папку Хранилища:
Обратите внимание: все файлы и изображения, которые должны быть отображены в приложении, должны быть загружены в те папки Хранилища, к которым есть доступ у всех пользователей приложения. В противном случае вместо файла пользователь будет видеть сообщение об ошибке. Такое поведение не является ошибкой Конструктора приложений.
Нам понадобится идентификатор загруженного логотипа. Найти его можно при просмотре файла, раздел “Свойства”:
Этот идентификатор нужно вставить в свойство изображения “Идентификатор файла”:
Здесь для компонента “Изображения” указаны свойства:
источник - из Хранилища;
идентификатор файла, полученный из свойств файла;
размеры 46х46 пикселей, соответствующие размеру логотипа.
Для того, чтобы клик по этому изображению возвращал пользователя к списку всех услуг, добавим для изображения соответствующий кейс:
Сохраним кейс и перейдем в режим просмотра приложения:
Имеет смысл визуально отделить панель с логотипом от остального контента страницы. В Конструкторе приложений реализованы встроенные CSS классы ``header`` и ``footer``, которые определяют внешний вид соответствующей панели. Для того, чтобы к какой-либо панели на странице были применены эти встроенные стили, необходимо в ее свойстве “CSS класс” добавить имя класса:
Проверим результат:
У нашей шапки страницы были добавлены границы и тень. При необходимости можно переопределить их с помощью свойства компонента “Стили”.
Как правило, шапка имеет один и тот же вид для всех страниц приложения, поэтому нам нужно добавить такую же шапку на странице “Главная”. Для этого достаточно выделить панель с шапкой, скопировать ее, а потом вставить на нужной странице:
Панель была успешно скопирована вместе со своими свойствами, вложенными компонентами и их кейсами.
Отображение списка файлов на странице
Страница паспорта услуги должна содержать список документов, относящихся к ней, с возможностью их скачивания. Для этого в Конструкторе приложений используется тип компонента “Список файлов”.
Добавим на страницу “Паспорт услуги” компонент “Список файлов” и разместим его между подробным описанием услуги и кнопкой ее заказа:
Компонент “Список файлов” отображает файлы, содержащиеся в указанной папке Хранилища Synergy, без отображения вложенных в нее папок. Клик по файлу выполняет его скачивание на локальный компьютер. Для примера отобразим в компоненте содержимое папки “Документы на услуги”, ранее настроенной в Хранилище:
Скопируем путь к текущей папке
Вставим путь к папке в свойство компонента “Папка в хранилище”
В режиме просмотра компонент отображает файл, который содержится непосредственно в указанной папке, и не отображает другие папки.
Компонент “Список файлов” позволяет регулировать сортировку элементов и отображаемые свойства файлов с помощью флагов.
Для того, чтобы в списке отображались именно файлы текущей услуги, необходимо компоненту сообщить путь к соответствующей папке. Для этого в форме услуги, настроенной в Synergy, мы предусмотрели поле ввода:
Предполагаем, что при создании записи в Каталоге услуг пользователь должен:
Предварительно создать в Хранилище папку, вложенную в основную папку “Документы на услуги”, и добавить в нее необходимые файлы:
Получить путь к этой папке и указать его на форме услуги:
Далее в Конструкторе приложений настроим параметры компонента “Список файлов” таким образом, чтобы он использовал значение этого текстового поля как путь к папке:
Теперь укажем для компонента ширину в 100% и проверим его работу:
Примечание
Однако ввиду особенностей работы Конструктора приложений при отображении списка файлов необходимо учитывать один нюанс: если в услуге не был указан путь к папке с документами, то при отображении ее паспорта в приложении список файлов будет отображать документы от предыдущей открытой услуги. Решить эту проблему можно таким образом:
Создать в Хранилище специальную папку, которая всегда будет пустой.
Для услуги, не предполагающей отображения файлов, указывать путь к этой пустой папке:
При такой настройке для этой услуги список файлов вовсе не будет отображаться:
Наше приложение готово к работе!
Модуль Synergy Developer
Подключение произвольного кода
В предыдущих примерах мы рассматривали разработку приложений исключительно штатными средствами Конструктора, без применения какого-либо скриптинга. Однако возможности штатных средств заведомо ограничены и не могут покрыть весь спектр возникающих задач. Поэтому Конструктор поддерживает подключение произвольного кода JavaScript с помощью ресурсов.
Внимание: если ресурсы изменяются для уже задеплоенного приложения, то для применения внесенных изменений необходимо передеплоить приложение.
Предположим, что в приложении необходимо реализовать:
фильтрацию услуг по поисковому запросу;
регистрацию пользователей на портале.
Поиск и фильтрация услуг
На главную страницу Витрины услуг необходимо добавить поле, предназначенное для фильтрации списка услуг. Поиск должен выполняться по следующим данным:
название услуги
краткое описание
подробное описание
Сначала выполним предварительную настройку компонентов: на главную страницу приложения добавим поле ввода и кнопку, которая будет выполнять поиск:
На этом этапе важно зафиксировать коды компонентов поля поиска и кнопки “Найти”. В нашем примере поле ввода имеет код ``search_field``, а кнопка - ``search_button``.
Далее добавим в ресурсы приложения скрипт, который будет выполнять поиск по реестру “Каталог услуг” и менять содержимое итератора, отображая в нем только те записи, которые соответствуют запросу.
Добавление произвольного кода на JavaScript
Раздел “Ресурсы” расположен в левой нижней части рабочей области. По умолчанию этот раздел пуст:
Нам нужно добавить в этот раздел новый файл. Для этого нажмем на кнопку “Добавить ресурс” . Откроется диалог, в котором необходимо ввести имя нового файла:
Обратите внимание: имя файла обязательно вводится вместе с его расширением. Для кода JavaScript используется расширение ``*.js``.
Добавленный файл отображен в разделе “Ресурсы”:
Файл ресурса открывается точно так же, как и страница приложения - кликом по иконке . Работа с этим файлом происходит в отдельной вкладке:
Основная рабочая область ресурса - редактор кода. В правой части вкладки доступна иконка - она открывает модальное окно, содержащее синтаксис и пример вызовов встроенных методов Конструктора:
Полный скрипт поиска записей реестра и фильтрации записей итератора выглядит так:
```
/**
* Сюда добавляем колонки которые мы хотим сравнивать
* добавляем как на примере, внутрь квадратных скобок, добавляем фигурные и в них добавляем name, variant
* name - компонент в форме
* variant - ключ или значение
*/
function getColumns() {
return [
{
name: 'item_name',
variant: 'value' // key or value
},
{
name: 'item_shortDescription',
variant: 'value' // key or value
},
{
name: 'item_longDescription',
variant: 'value' // key or value
}
];
}
/*выполнять скрипты в ресурсе, когда пользователь находится на странице “Главная”*/
pageHandler('start', function () {
//подписываемся на событие клика по кнопке “Найти”
addListener('button_click', 'search_button', function (e) {
//получаем значение поля поиска
let searchWrap = getCompByCode('search_field').text;
//для облегчения отладки выведем значение поля поиска в консоль браузера
console.log('search_field:', searchWrap);
let params = '';
//формируем параметры для события изменения данных в итераторе
if (searchWrap && searchWrap !== '') {
params += '&groupTerm=or'; // закоментировать если не нужно склеивания групп условий
getColumns().map((col, i) => {
let index = i === 0 ? '' : i;
params += `&field${index}=${col.name}&condition${index}=CONTAINS&${col.variant + '' + index}=${searchWrap}`;
}); // Должно получиться: &groupTerm=or&field=formInput&condition=CONTAINS&value=ThisIsMySearchText&field1=formLabel&condition=START&value=ThisIsMySearchText
}
/*генерируем событие Конструктора, которое изменяет содержимое итетатора с кодом `panel-1`*/
fire({
type: 'change_repeater_search_params',
params: params
}, 'panel-1');
});
});
```
Примечание: все события отдельных компонентов Конструктора опубликованы здесь.
Написанный скрипт в ресурсе:
Переключимся в режим просмотра приложения и попробуем найти услугу:
Стартовое состояние: отображены все услуги
Поиск по запросу “лотерея” вернул одну подходящую услугу
Поиск выполняется по подстроке: отображены услуги, в названии или описании которых содержится символ “!”
Регистрация пользователя
Как правило, при регистрации пользователя помимо собственно добавления его в список пользователей Synergy требуется выполнить некоторые действия, специфичные для каждой конкретной задачи: где-то необходимо использовать ИИН пользователя в качестве логина, где-то - заполнять личные карточки, часто нужно при регистрации сразу включить нового пользователя в определенные группы.
Поэтому регистрация в приложениях Конструктора всегда выполняется разработчиками самостоятельно, средствами подключаемых ресурсов.
Сначала подготовим в приложении отдельную страницу для ввода регистрационных данных:
Если планируется подключать какой-либо кастомный скриптинг для компонентов страницы приложения, обязательно нужно обращать внимание на коды страницы и компонентов на ней. Например, в данном примере мы используем такие коды:
код страницы: ``registration``
код полей ввода:
“Электронная почта”: ``email``
“Имя”: ``fname``
“Фамилия”: ``lname``
“Пароль”: ``password``
“Подтверждение пароля”: ``passwordConfirm``
код кнопки “Зарегистрироваться”: ``register``
Теперь добавим новый ресурс, который будет содержать скрипт регистрации пользователя:
Еще раз уточним требования к регистрации:
Функция должна обеспечивать обязательность всех полей ввода.
Функция должна выполнять проверку введенных данных: корректность адреса электронной почты, совпадение значений полей “Пароль” и “Подтверждение пароля”; если данные некорректны, функция не должна выполнять создание пользователя.
Если введенные данные корректны, функция должна выполнять запрос создания нового пользователя с параметрами:
логин - поле ``email``;
пароль - поле ``password``;
доступ в систему - разрешен;
дополнительные роли для нового пользователя отсутствуют.
Необходимо обрабатывать варианты ответа сервера, если создание пользователя завершилось с ошибкой (переданный логин уже существует, или превышено максимальное количество пользователей по лицензии). Пользователю должно быть отображено аргументированное сообщение об ошибке.
В случае, если пользователь был создан успешно, функция должна включить его в группу пользователей “Клиенты Showcase”, код группы - ``showcase_example_groupClients``.
После завершения процесса необходимо отобразить пользователю сообщение об успешной регистрации.
Полный код соответствующего скрипта:
```
//если поле не заполнено - выделить его как невалидное
function emptyValidator(input) {
if (input && input.text !== '') {
fire({type: 'input_highlight', error: false}, input.code);
return true;
} else {
fire({type: 'input_highlight', error: true}, input.code);
return false;
}
}
//валидация введенного email
function emailValidator(input) {
if (emptyValidator(input)) {
let regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (input && regex.test(input.text)) {
fire({type: 'input_highlight', error: false}, input.code);
return true;
} else {
fire({type: 'input_highlight', error: true}, input.code);
return false;
}
}
return false;
}
// Выполняем скрипт только в странице регистрации
pageHandler('registration', function () {
// Выполняем действие только при нажатии на кнопку register
addListener('button_click', 'register', function () {
// Получаем все нужные компоненты
let fname = getCompByCode('fname');
let lname = getCompByCode('lname');
let email = getCompByCode('email');
let passwordConfrim = getCompByCode('passwordConfirm');
let password = getCompByCode('password');
//обязательные поля заполнены, email корректен
let success = emptyValidator(fname)
& emptyValidator(lname)
& emptyValidator(password)
& emptyValidator(passwordConfrim)
& emailValidator(email);
if (!success) {
//отображаем сообщение с типом “ошибка”, текст сообщения локализован
showMessage(localizedText('Данные не корректны', 'Данные не корректны', 'Данные не корректны', 'Данные не корректны'), 'error');
return;
}
if (password.text === passwordConfrim.text) {
// Собираем данные в один объект
let data = {
login: email.text,
password: password.text,
email: email.text,
firstname: fname.text,
lastname: lname.text,
pointersCode: lname.text + '_' + fname.text,
isConfigurator: false,
};
// Отправляем запрос в Synergy
rest.synergyPost('api/filecabinet/user/save', data, "application/x-www-form-urlencoded; charset=UTF-8", function (success) {
// Обрабатываем успешный ответ (Выводим сообщение);
if (success && success.errorCode && success.errorCode === '0') {
rest.synergyGet('api/storage/groups/add_user?groupCode=showcase_clients&userID='+success.userID, function(success){
showMessage(localizedText('Регистрация прошла успешно', '', '', ''), 'success');
fire({type: "goto_page", pageCode: "catalog", pageParams: []}, "register");
}, function(error){
console.error(error);
});
} else if (success && success.errorCode && success.errorCode !== '0') {
showMessage(localizedText(success.errorMessage, '', '', ''), 'error');
} else {
console.error(success);
}
}, function (err) {
console.error(err);
});
} else {
showMessage(localizedText('Пароли не совпадают', 'Пароли не совпадают', 'Пароли не совпадают', 'Пароли не совпадают'), 'error');
}
});
});
```
Проверим работу этой функции:
Вводим тестовые данные
После нажатия на кнопку и появления сообщения об успехе проверяем наличие пользователя в Административном приложении:
Примечание: все методы Synergy, используемые в приложении, выполняются от имени текущего пользователя - гостевого или авторизованного. Функция регистрации в нашем случае выполняется от имени гостевого пользователя, указанного в свойствах приложения. Однако используемый метод ``rest/api/filecabinet/user/save`` требует наличия у пользователя роли “Администратор”. Поэтому для корректной работы необходимо присвоить пользователю, используемому как гостевой, роли администратора в SynergyAdmin.
При разработке скриптов для приложений необходимо следить за тем, чтобы у пользователя всегда были требуемые роли и права доступа к объектам Synergy.
Пользовательские компоненты
Конструктор позволяет использовать в приложении компоненты, которые не входят в его “ядро”. В качестве примера таких компонентов можно привести:
панель с вкладками
выпадающий список с элементами из справочника Synergy
иконка “Корзина” со счетчиком товаров в ней и возможностью перехода на страницу заказа по клику
Каждый из таких компонентов обладает собственным внешним видом и логикой работы. Несмотря на то, что Конструктор не имеет таких встроенных компонентов, он предоставляет возможность для самостоятельной реализации пользовательских компонентов (ПК) - такими, какие нужны в каждом отдельном приложении.
Реализация пользовательского компонента - это указание для него верстки HTML, стилей и поведения на JavaScript.
Поскольку в Конструктор приложений встроена поддержка классов UIkit, мы рекомендуем начинать разработку нового ПК с поиска стандартного компонента из библиотеки UIkit и использования его разметки (``markup``) как основы для нового компонента.
Конструктор предусматривает переиспользование любого реализованного компонента. Для этого разделяются понятия типа ПК и его экземпляра:
Разработчик, описывая внешний вид и поведение компонента, создает новый тип компонента и присваивает ему собственное имя.
В дальнейшем, размещая ПК на странице, разработчик создает экземпляр этого компонента.
Таким же образом работают и встроенные компоненты: например, есть тип компонента “Кнопка”, а есть конкретный экземпляр этой кнопки, размещенный на странице приложения.
При изменении исходного кода типа компонента эти изменения будут применены ко всем экземплярам этого компонента в приложении.
Рассмотрим здесь пример реализации выпадающего списка.
Выпадающий список с элементами справочника
Выведем в приложении список городов, сгруппированным по странам, в виде выпадающего списка. Элементы для списка будут получены из справочника Synergy.
Для этого в первую очередь выберем в библиотеке компонентов UIkit компонент, наиболее подходящий для наших целей - это компонент Dropdown.
Далее переходим к созданию нового типа пользовательского компонента. Для этого нужно в разделе “Компоненты” нажать на кнопку , затем в открывшемся диалоге указать название и код нового типа ПК:
Назовем наш компонент “Список городов”:
Созданный тип ПК отображен в разделе “Компоненты”, папка “Пользовательские компоненты”:
У компонентов в списке доступна иконка редактирования : она позволяет изменять тип выбранного компонента: его название, исходный код и верстку. Клик по самой строке с компонентом добавляет его экземпляр на страницы.
Отредактируем наш созданный тип ПК, нажав на иконку . Откроется вкладка выбранного ПК:
Вкладка “Свойства” содержит название и код типа пользовательского компонента.
Вкладка “Помощь” содержит список методов, доступных в компоненте:
Вкладки “HTML”, “Стили” и “Скрипт” содержат редактор кода:
Для нашего списка городов сначала добавим лейбл “Выберите город”:
Нажмем на кнопку “Применить”, чтобы внесенные в редакторе кода изменения были применены ко всем экземплярам текущего ПК, а потом перейдем на страницу приложения:
Теперь, выделив на странице панель, в списке компонентов выберем “Список городов”. Результат:
Если выделить добавленный компонент, в правой части будут отображены свойства этого экземпляра ПК: код компонента, его класс CSS, стили, размеры и прочее:
Теперь вернемся к вкладке “Список городов” и дополним его разметку (вкладка HTML):
Содержимое вкладки:
```
<div className="uk-inline">
<button className="uk-button uk-button uk-button-text uk-text-small uk-text-muted" type="button">
<span name=''>Выберите город</span>
<span uk-icon="icon: chevron-down"></span>
</button>
<div uk-dropdown="mode: click; pos: bottom-justify">
<ul className="uk-nav uk-dropdown-nav"></ul>
</div>
</div>
```
Снова применим изменения и перейдем на страницу:
Внешний вид компонента изменился и соответствует настроенной нами разметке.
Теперь настроим логику компонента. Для этого во вкладку “Скрипт” ПК введем скрипт, который получает элементы справочника и отображает их в структурированном виде:
Полный код скрипта:
```
(function(){
/**
* Весь контейнер именно вашего пользовательского компонента
*/
let container;
/**
* Код справочника со списком городов
*/
const dictCode = 'minimum-custom-component';
/**
* Метод подписывается на подгрузку определенного HTML элемента в DOM
* @param {func} handle callback
*/
const subscribeReadyHTML = handle => {
const sub = setInterval(() => {
if(comp instanceof Object && comp.code){
container = $('#' + comp.code);
handle();
clearInterval(sub);
}
}, 800);
}
/**
* Метод для конвертации данных справочника
*/
const convertDict = data => {
let columnCodes = {};
let res = {};
data.columns.forEach(x => columnCodes[x.columnID] = x.code);
data.items.forEach((x, k) => {
res[k] = {};
x.values.forEach(x => res[k][columnCodes[x.columnID]] = x.translation || x.value);
});
return res;
}
/**
* Подписываемся на событие подгрузки нашего пользовательского компонента и выполняем его скрипт
*/
subscribeReadyHTML(() => {
rest.synergyGet('api/dictionary/get_by_code?dictionaryCode=' + dictCode, response => {
const list = container.find('[uk-dropdown]').find('ul');
const convert = convertDict(response);
const listItems = Object.values(convert).reduce((previous, key) => {
if(!previous[key.regionCode]){
previous[key.regionCode] = [];
}
previous[key.regionCode].push(key);
previous[key.regionCode].sort((a,b) => {
a = a.city.toLowerCase();
b = b.city.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
});
return previous;
}, {});
for(let i in listItems){
if(listItems[i].length){
$('<li class="uk-nav-header">' + listItems[i][0].region + '</li>').appendTo(list);
listItems[i].forEach((item, key) => {
const itemList = $('<li><a href="#">' + item.city + '</a></li>');
itemList.on('click', () => {
list.find('.uk-active').removeClass('uk-active');
list.find('.uk-text-primary').removeClass('uk-text-primary');
itemList.addClass('uk-active');
itemList.find('a').addClass('uk-text-primary');
container.find('button span[name]').text(item.city);
alert('Выбран город: ' + item.city);
});
itemList
.data('name', item.city)
.appendTo(list);
});
}
}
});
});
}());
```
Здесь мы используем справочник Synergy с кодом ``minimum-custom-component``. Структура и содержимое этого справочника:
Применим изменения в скрипте ПК и вернемся на страницу приложения, перейдем в режим просмотра:
Примечание: если у вас не отображаются элементы списка, проверьте высоту панели, где расположен экземпляр ПК. В нашем случае он размещен напрямую в корневую панель, и мы задали для этой панели высоту 1000px для наглядности.
Локализация ошибок
При разработке приложений неизбежно будут возникать ситуации, когда что-то, что вы настроили, работает не так, как предполагалось. Для того, чтобы выявить проблему, необходимо владеть навыком локализации ошибок.
Приложения конструктора выполняются в браузере клиента. Соответственно, логи приложения также отображаются в консоли браузера.
В качестве примера рассмотрим локализацию часто возникающих ошибок, связанных с правами доступа и с выполнением скриптов в ресурсах.
Ошибки доступа
Вернемся к приложению “Витрина услуг” и откроем его стартовую страницу. В режиме конструктора настроенные плашки выглядят корректно:
Однако в режиме просмотра приложения плашки не отображаются:
Для того, чтобы понять, что произошло, будем использовать консоль браузера, входящую в инструменты разработчика любого популярного браузера. Мы будем рассматривать их применение на примере браузеров Google Chrome и Mozilla Firefox.
Как правило, инструменты разработчика открываются по нажатию на клавишу F12.
Mozilla Firefox
В браузере Firefox консоль выглядит так (выделена вкладка “Консоль”):
Текст ошибки в консоли:
```
Ошибка синтаксического анализа XML: корневой элемент не найден
Адрес: http://192.168.11.6:8080/Synergy/rest/api/registry/data_ext?…=item_shortDescription&fields=item_button1&fields=item_image
Строка 1, символ 1:
```
Видно, что ошибка возникла при попытке выполнения API ``rest/api/registry/data_ext``, используемого для получения записей реестра итератором. Однако формулировка “Ошибка синтаксического анализа XML” нам мало полезна. Уточним информацию во вкладке “Сеть”:
Здесь видно, что один первый запрос был выполнен успешно (статус HTTP 200, выделен зеленым), а остальные запросы завершились со статусом HTTP 401. Для того, чтобы узнать подробности, выделим один из методов:
В правой части приведены параметры метода, завершившегося с ошибкой. Здесь видно, что метод ``rest/api/registry/data_ext`` завершился со статусом 401 Unauthorized. То есть API запроса данных реестра завершилось на попытке авторизации пользователя.
Google Chrome
Консоль браузера Chrome в этом случае отображает другой текст ошибки, причем сразу видно, почему метод не отработал:
Текст ошибки в консоли:
```
xhr.js:172 GET http://192.168.11.6:8080/Synergy/rest/api/registry/data_ext?registryCode=showcase_example_items&fields=item_name&fields=item_shortDescription&fields=item_button1&fields=item_image 401 (Unauthorized)
```
Детальное описание параметров метода и его результатов так же, как и в Firefix, приведено в разделе “Сеть”:
Анализ ошибки
Здесь уместно напомнить, что абсолютно все методы, вызываемые в приложении, выполняются от имени текущего пользователя. В разных ситуациях “текущим пользователем” может быть:
в режиме конструктора: авторизованный пользователь, выполняющий разработку приложения;
в режиме просмотра приложения в случае, когда страница/приложение требуют авторизации: пользователь, чьи логин и пароль были указаны на специальной странице авторизации;
в режиме просмотра приложения в случае, когда страница/приложение не требуют авторизации: пользователь, чьи логин и пароль были указаны в свойствах клиента в качестве пользователя по умолчанию - то есть гостевой пользователь.
Теперь вернемся к нашему приложению. Ошибка возникает только в режиме просмотра. При этом видно, что страницы авторизации у него нет:
То есть единственное место, где может быть указан пользователь, от имени которого выполняется запрос данных реестра - это свойства клиента:
Далее следует проверить пункты:
пользователь с таким логином и паролем действительно существует в SynergyAdmin, его статус “Активен” (есть доступ в систему);
указанный пользователь включен в группу, обладающую правами на данные того реестра, который используется в итераторе.
В нашем случае достаточно первого пункта: ошибка в логине пользователя. После указания корректного логина для гостевого пользователя плашки итератора в режиме просмотра приложения отображены корректно, в консоли ошибок нет:
Ошибки выполнения ресурсов
Снова вернемся к Витрине услуг. На ее главной странице расположено поле поиска услуги и кнопка “Найти”. Ранее был приведен пример скрипта, который должен выполняться по клику по кнопке.
Предположим, что в этот скрипт были внесены некоторые изменения, после которых поиск перестал работать: в режиме просмотра приложения нет реакции на кнопку “Найти”. Для локализации ошибки снова воспользуемся консолью браузера:
В консоли пусто, по клику по кнопке “Найти” ничего не происходит. Значит, не выполняется событие клика по кнопке. Проверим скрипт:
```
/*выполнять скрипты в ресурсе, когда пользователь находится на странице “Главная”*/
pageHandler('start', function () {
//подписываемся на событие клика по кнопке “Найти”
addListener('button_click', 'search_buttonZ', function (e) {
//получаем значение поля поиска
let searchWrap = getCompByCode('search_field').text;
console.log('search_field', searchWrap);
let params = '';
//формируем параметры для события изменения данных в итераторе
if (searchWrap && searchWrap !== '') {
params += '&groupTerm=or'; // закоментировать, если не нужно склеивания групп условий
getColumns().map((col, i) => {
let index = i === 0 ? '' : i;
params += `&field${index}=${col.name}&condition${index}=CONTAINS&${col.variant + '' + index}=${searchWrap}`;
}); // Должно получиться: &groupTerm=or&field=formInput&condition=CONTAINS&value=ThisIsMySearchText&field1=formLabel&condition=START&value=ThisIsMySearchText
}
/*генерируем событие Конструктора, которое изменяет содержимое итетатора с кодом `panel-3`*/
fire({
type: 'change_repeater_search_params',
params: params
}, 'panel');
});
});
```
В заголовке функции видно, что обработчик ожидает события ``button_click`` у кнопки ``search_buttonZ``, расположенной на странице ``start``. Необходимо проверить, что эти объекты действительно существуют:
страница, где расположены поле и кнопка поиска, имеет код ``start``;
кнопка поиска имеет код ``search_buttonZ``;
у кнопки существует событие ``button_click``.
В нашем случае первая ошибка была найдена: код компонента кнопки “Найти” был указан с ошибкой:
Исправленный код скрипта:
```
/*выполнять скрипты в ресурсе, когда пользователь находится на странице “Главная”*/
pageHandler('start', function () {
//подписываемся на событие клика по кнопке “Найти”
addListener('button_click', 'search_button', function (e) {
//получаем значение поля поиска
let searchWrap = getCompByCode('search_field').text;
console.log('search_field', searchWrap);
let params = '';
//формируем параметры для события изменения данных в итераторе
if (searchWrap && searchWrap !== '') {
params += '&groupTerm=or'; // закоментировать, если не нужно склеивания групп условий
getColumns().map((col, i) => {
let index = i === 0 ? '' : i;
params += `&field${index}=${col.name}&condition${index}=CONTAINS&${col.variant + '' + index}=${searchWrap}`;
}); // Должно получиться: &groupTerm=or&field=formInput&condition=CONTAINS&value=ThisIsMySearchText&field1=formLabel&condition=START&value=ThisIsMySearchText
}
/*генерируем событие Конструктора, которое изменяет содержимое итетатора с кодом `panel-3`*/
fire({
type: 'change_repeater_search_params',
params: params
}, 'panel');
});
});
```
Продолжим проверку: переключимся в режим просмотра и нажмем на кнопку “Найти”:
Поведение изменилось: в консоли браузера отображено значение поля поиска (выводимое командой ``console.log('search_field', searchWrap)``, при этом отображено сообщение об ошибке с текстом:
Не найден компонент ``panel`` для выполнения события ``change_repeater_search_params``.
Конструктор явно указал нам, что ошибка произошла во время выполнения данного участка скрипта:
```
fire({
type: 'change_repeater_search_params',
params: params
}, 'panel');
```
Синтаксис метода ``fire`` приведен в подсказках к ресурсам:
Он ожидает на вход имя события и код компонента, в котором будет выполнено это событие. Здесь необходимо проверить пункты:
событие ``change_repeater_search_params`` действительно поддерживается Конструктором, и его можно сгенерировать посредством метода ``fire``;
компонент с кодом ``panel`` существует на той странице, где выполняется ресурс;
тип компонента с кодом ``panel`` соответствует типу, ожидаемому событием ``change_repeater_search_params`` - в нашем случае это тип “Итератор”.
Для проверки первого пункта сверимся со списком событий Конструктора. Синтаксис события ``change_repeater_search_params``:
```
let event = {
type: 'change_repeater_search_params',
params: '?onlyImages=true&bigText=false'
}
fire(event, 'my_repeater_code');
```
Событие существует, в примере использования приведен метод ``fire`` - пункт 1 проверен.
Во время проверки пункта 2 была найдена вторая ошибка: код компонента “Итератор” на странице не совпадает с указанным в скрипте:
Исправленный скрипт:
```
/*выполнять скрипты в ресурсе, когда пользователь находится на странице “Главная”*/
pageHandler('start', function () {
//подписываемся на событие клика по кнопке “Найти”
addListener('button_click', 'search_button', function (e) {
//получаем значение поля поиска
let searchWrap = getCompByCode('search_field').text;
console.log('search_field', searchWrap);
let params = '';
//формируем параметры для события изменения данных в итераторе
if (searchWrap && searchWrap !== '') {
params += '&groupTerm=or'; // закоментировать, если не нужно склеивания групп условий
getColumns().map((col, i) => {
let index = i === 0 ? '' : i;
params += `&field${index}=${col.name}&condition${index}=CONTAINS&${col.variant + '' + index}=${searchWrap}`;
}); // Должно получиться: &groupTerm=or&field=formInput&condition=CONTAINS&value=ThisIsMySearchText&field1=formLabel&condition=START&value=ThisIsMySearchText
}
/*генерируем событие Конструктора, которое изменяет содержимое итетатора с кодом `panel-3`*/
fire({
type: 'change_repeater_search_params',
params: params
}, 'panel-3');
});
});
```
Снова перейдем в режим просмотра и проверим работу кнопки поиска:
В консоли браузера пусто, услуга была найдена.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment