FormStamp − библиотека виджетов для AngularJS

от автора

AngularJS − это стремительно набирающий популярность JS-фреймворк, упрощающий разработку сложных и динамичных веб-приложений. Наша команда использует AngularJS в ряде проектов со сложным пользовательским интерфейсом, и в процессе работы мы остро ощутили нехватку хорошей библиотеки, предоставляющей набор единообразных виджетов, таких как datetime picker, select, multiple select и так далее. Конечно, нам было известно о проекте Angular UI, но некоторых виджетов, которые нам были нужны, AngularUI не предоставлял.

Кроме того, мы хотели иметь аналог рельсового 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *