Кроме того, мы хотели иметь аналог рельсового form builder-а, но на фронтенде. Form builder позволяет программисту описать форму декларативно, беря на себя генерацию разметки и вывод ошибок.
Решением этих проблем стала разработанная нами библиотека FormStamp, которая предоставляет:
- Form Builder − наивысший уровень для работы с формами, созданный по аналогии с генераторами форм из экосистемы Ruby on Rails;
- набор виджетов, покрывающих 80% задач, встречающихся при работе с формами и не решаемых стандартными элементами HTML5;
- низкоуровневые компоненты, позволяющие собирать новые виджеты.
При разработке в библиотеку были заложены следующие принципы:
- все виджеты написаны с нуля с использованием директив AngularJS, что позволяет сократить код и сделать его более читаемым;
- полная интеграция с AngularJS (поддержка ngModel, ngRequired…);
- стилизация по умолчанию с помощью Bootstrap.
Инструкция по установке
FormStamp может быть подключен в ваш проект с помощью пакетной системы Bower:
bower install angular-formstamp
Form Builder
Выразительный декларативный подход AngularJS снижает количество кода, который нужно написать для создания UI. Однако даже с использованием этого подхода при создании простой формы с проверками заполненности полей и отображением сообщений об ошибках приходится писать много повторяющегося кода:
<form class="form-horizontal" role="form" name="form" ng-app="form-demo"> <div class="form-group" ng-class="{'has-error': form.username.$invalid}"> <label for="username" class="col-sm-2 control-label">Username</label> <div class="col-sm-10"> <input type="text" class="form-control" id="username" placeholder="Username" required="required" ng-pattern="/awesome/" name="username" ng-model="username" /> <p class="alert alert-danger" ng-show='form.username.$error.pattern'> Username should be awesome </p> </div> </div> <div class="form-group" ng-class="{'has-error': form.email.$invalid}"> <label for="email" class="col-sm-2 control-label">Email</label> <div class="col-sm-10"> <input type="email" class="form-control" id="email" placeholder="Email" required="required" name="email" ng-model="email" /> <p class="alert alert-danger" ng-show='form.email.$error.email'> Email should be valid </p> </div> </div> <div class="form-group" ng-class="{'has-error': form.password.$invalid}"> <label for="password" class="col-sm-2 control-label">Password</label> <div class="col-sm-10"> <input type="password" class="form-control" id="password" placeholder="Password" required="required" name="password" ng-model="password" ng-minlength='6' /> <p class="alert alert-danger" ng-show='form.password.$error.minlength'> Password should be longer </p> </div> </div> <div class="form-group"> <label for="birthDate" class="col-sm-2 control-label">Birth Date</label> <div class="col-sm-10"> <input type="date" class="form-control" id="birthDate" placeholder="Birth Date" ng-model="birthDate" /> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">Sign up</button> </div> </div> </form>
Эту проблему решает компонент Form Builder − для создания формы достаточно указать:
- модель, с которой связана форма;
- атрибуты модели, которые отображаются в форме;
- типы элементов формы, соответствующие каждому из отображаемых в форме атрибутов.
С помощью Form Builder указанную выше форму с подсветкой ошибок можно создать намного меньшим количеством кода:
<fs-form-for model="samurai"> <fieldset class="form-horizontal"> <fs-input as="text" name="username" required="" label="Name"></fs-input> <fs-input as="email" name="email" required="" label="Email"></fs-input> <fs-input as="password" name="password" required="" label="Email"></fs-input> <fs-input as="fs-date" name="birthdate" required="" label="Date of Birth"></fs-input> </fieldset> </fs-form-for>
Пояснения:
fsFormFor
− директива, создающая форму, атрибутmodel
указывает на модель, для которой создается форма;fsInput
− директива, описывающая каждый элемент в форме со следующими атрибутами:as
− тип элемента формы;name
− имя атрибута модели;label
− текст метки.
Все остальные атрибуты делегируются элементу формы, указанному в атрибуте as
.
Набор виджетов
Чем сложнее ваше приложение, тем меньше вам будет хватать стандартных элементов форм и тем скорее вам понадобятся дополнительные виджеты. На данный момент существует не так много виджетов, рассчитанных на интеграцию с AngularJS, а из тех, что есть, часть является оберткой над jQuery-виджетами. Библиотека FormStamp содержит написанные с нуля с использованием API AngularJS виджеты, решающие те задачи, с которыми мы сталкивались чаще всего в нашей работе:
- select с возможностью фильтрации по введенному значению;
- select с поддержкой free text (combo box) ;
- multiselect с возможностью фильтрации по введенному значению;
- multiselect с поддержкой free text (tags input);
- radio group;
- checkbox group;
- виджеты для работы с датой и/или временем и календарь.
Рассмотрим работу с select виджетом, для создания которого используется директива fsSelect
. Директива поддерживает атрибуты freetext
, items
, ng-model
, ng-required
, ng-disabled
.
freetext
Атрибут (по умолчанию false) определяет поведение виджета. При freetext=false
виджет ведет себя как select, то есть позволяет выбрать один элемент из списка вариантов. При freetext=true
виджет ведет себя как combo box, то есть позволяет выбрать значение из списка вариантов или ввести любое другое.
items
Атрибут указывает, какое свойство скоупа содержит список вариантов, отображаемых в виджете. При freetext=false
варианты могут быть как объектами, так и примитивными типами. При freetext=true
варианты могут быть только строками.
ng-model
Атрибут является стандартной директивой ngModel.
ng-disabled
Атрибут указывает, какое свойство скоупа определяет, будет ли виджет disabled/enabled.
Для создания combo box, варианты которого содержатся в $scope.arrayOfOptions
, выбранный вариант связан со $scope.selectedOption
, а состояние disabled/enabled зависит от $scope.flag
, запишем директиву следующим образом:
<div fs-select items=”arrayOfOptions” ng-disabled=”flag” ng-model=”selectedOption” freetext=”true”></div>
Примеры работы с остальными виджетами и Form Builder размещены на странице библиотеки.
Директивы
Для того чтобы облегчить написание дополнительных виджетов, мы начали выделять части функциональности в низкоуровневые директивы:
fsList
− отображает список элементов, позволяет выделять элемент в списке и перемещать выделение с клавиатуры;fsNullForm
− скрывает элемент формы, связанный с ngModel, от родительской формы;fsInput
− упрощает обработку событий клавиатуры и смены фокуса;fsCalendar
− отображает календарь и позволяет помечать дату как выбранную.
Для примера создадим плей-лист для плеера, используя fsList
и fsInput
. Работа с fsList
происходит с помощью взаимодействия с listInterface
свойством на $scope
. listInterface
имеет следующие свойства:
selectedItem
− текущее выбранное значение. Только на чтение.onSelect(value)
− обработчик события выбора значения. Должен быть реализован пользователем.move(d)
− функция, которая перемещает указатель на указанное количество элементов.
Создадим директиву, которая будет оборачивать в себя audio
тег из html5:
app.directive("demoAudio", function() { return { restrict: "E", scope: { track: '=' }, template: "<audio controls />", replace: true, link: function($scope, $element, $attrs) { return $scope.$watch('track', function(track) { $element.attr('src', track.stream_url + "?client_id=8399f2e0577e0acb4eee4d65d6c6cce6"); return $element.get(0).play(); }); } }; });
Подключим SoundCloud SDK
<script src="http://connect.soundcloud.com/sdk.js"></script>
Далее создадим контроллер для связывания этих элементов:
function ListDemoCtrl($scope) { // Инициализация SoundCloud SDK SC.initialize({ client_id: '8399f2e0577e0acb4eee4d65d6c6cce6' }); // Реализация поиска по SoundCloud $scope.$watch('search', function () { SC.get('/tracks', { q: $scope.search, license: 'cc-by-sa' }, function(tracks) { $scope.$apply(function() { $scope.tracks = tracks }) }) }); $scope.search = 'bach'; $scope.tracks = []; // Оборачиваем функцию для перемещения выбранного значения в fsList $scope.move = function (d) { $scope.listInterface.move(d); }; // Добавляем обработчик выбранного значения в fsList $scope.listInterface = { onSelect: function (selectedItem) { $scope.select(selectedItem) } }; $scope.select = function(selectedItem) { $scope.selectedTrack = selectedItem || $scope.listInterface.selectedItem; }; }
И само приложение:
<div ng-controller="ListDemoCtrl" style="postion: relative;"> <div class="row"> <div class="col-xs-7"> <!-- Инициализируем fsInput и добавляем обработчики клавиатуры --> <input class="form-control" autofocus="1" fs-input fs-up="move(-1)" fs-down="move(1)" fs-enter="select()" ng-model="search"> <!-- Инициализируем fsList и передаем ему список треков --> <div fs-list="" items="tracks" class="no-popup"> <!-- Описываем внутренний темплейт переданных треков --> <img src="{{ item.artwork_url }}" width="30" height="30"> {{item.title}} <small class="text-muted">{{item.genre}}</small> </div> </div> <div class="col-xs-5"> <!-- Связываем выбранное значение в fsList с аудио-плеером --> <demo-audio track="selectedTrack"></demo-audio> <pre style="margin-top: 20px;">Selected Item: {{ selectedTrack | json }}</pre> </div> </div> </div>
В результате получаем вот такой плеер:
Живой пример вы можете посмотреть здесь.
В следующей статье мы более подробно рассмотрим создание формы с использованием FormStamp.
ссылка на оригинал статьи http://habrahabr.ru/post/216391/
Добавить комментарий