Блог

17 мая 2017

Представляем right-angled, конструктор гридов для angular

Сегодня мы расскажем о нашей open source библиотеке right-angled. Данная библиотека предназначена для построения гридов (aka списков, aka таблиц) в приложениях на angular

На данный момент библиотека стабильна и хорошо работает как в JIT, так и в AOT режимах. Также right-angled поддерживает server side rendering.

Right-angled распространяется по лицензии MIT. Исходный код доступен на github

Также мы разместили на github-pages демо-приложение, детально описывающее возможности библиотеки с живыми демо и примерами кода. Если вам вдруг захочется посмотреть на исходники демо-приложения, они здесь.
 

Что умеет right-angled?

Как уже сказано выше, right-angled это библиотека для создания списков в angular-приложениях.

Также в него входит продвинутая модель selection, которая работает отдельно от списков. Это просто selection чего угодно.

Еще одной важной идеей является декларативная настройка свойств компонентов для:

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

Зачем мы сделали “еще одну библиотеку гридов”?

Когда мы выбирали для работы имеющиеся библиотеки гридов под angular 2, то пришли к выводу, что они слишком “тяжелые” и сложные. Например, шаблон простейшего грида с типичной библиотекой гридов для angular 2 выглядит примерно так:

<grid-component [dataSource]="data">
 <grid-column-component fieldName="Id" title="Id">
  </grid-column-component>
  <grid-column-component fieldName="Name" title="Name">
  </grid-column-component>
  <grid-column-component field="Price" title="Price" width="230">
  </grid-column-component>
  <grid-column-component field="IsDiscounted" title="Is Discounted" width="120">
     <row-template let-dataItem>
       <input type="checkbox" [checked]="dataItem.IsDiscounted" />
     </row-template>
  </grid-column-component>
</grid-component>

 
В данном шаблоне слишком мало HTML и слишком много “библиотеки гридов”. Много компонентов, много настроек, слишком много того, что придется запомнить. 

Эта сложность показалась нам излишней при современном подходе к разработке. И мы решили попробовать сделать что-то более легковесное и приятное. 

Также, следствием первого недостатка является второй – верстка, которую генерируют подобные компоненты.

Грид – это достаточно сложный контрол. И HTML, генерируемый такими компонентами, не всегда выглядит хорошо будучи встроенным в конечное приложение. Не говоря уже о том, что этот HTML может быть просто откровенно плохим.

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

Простая стилизация, к слову, является одним из самых важных моментов, поскольку “клонированные” сайты в стиле bootstrap заказчиков уже давно не устраивают. И каждый новый проект – это нередко и новый, уникальный дизайн.

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

Минимум компонентов

Библиотека должна содержать минимум компонентов и встраиваться в верстку конечного приложения, а не генерировать свою. То же самое касается стилей — right-angled не содержит какого-либо css и внешний вид вашего приложения остается целиком за вами.

Ниже приведен пример шаблона простейшего списка. Как вы можете заметить, это обычная верстка с использованием bootstrap (его использование совсем не обязательно, он взят просто для примера) и совсем немного кастомных директив. 

<table class="table table-striped" [rtList]="getData" #list="rtList">
 <thead>
   <tr>
     <th>Id</th>
     <th>Name</th>
     <th>Price in USD</th>
     <th>Is Discounted</th>
   </tr>
 </thead>
 <tbody>
   <tr *ngFor="let item of list.items">
     <td>{{item.Id}}</td>
     <td>{{item.Name}}</td>
     <td>{{item.Price}}</td>
     <td><input type="checkbox" [checked]="item.IsDiscounted" /></td>
   </tr>
 </tbody>
</table>

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

Простая функциональность

Чтобы “минимум компонентов” не превратился в “минимум функционала”, мы укомплектовали библиотеку набором функциональных сервисов. На них опираются компоненты самой библиотеки и их же пользователь может внедрить в свои компоненты при помощи Dependency Injection, чтобы реализовать нужное поведение самостоятельно. 

Если же делать отдельные компоненты лениво, то доступ к этим сервисам можно получить прямо в шаблоне, обращаясь к директивам-хостам. Всего таких директив четыре — rtList с функционалом списков, rtSelectionArea с функционалом работы с selection и компоненты rt-buffered-pager и rt-pager-pager для работы с paging.

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

Ниже вы можете увидеть код шаблона списка из финального примера в quick tour нашего приложения. В него добавлены:

  • selection
  • сортировки
  • фильтры
  • кнопки для загрузки данных/отмены запроса/сброса параметров
  • упомянутый выше выше paging
  • сериализация состояния списка в query string

В данном шаблоне мы как раз обращаемся к сервисам описанным выше «ленивым» способом — напрямую в шаблоне. 

Шаблон получился пухлый, но ведь и функционала добавлено немало. И это по прежнему довольно-таки чистый, стилизуемый HTML, который вдобавок очень легко разбить на компактные и переиспользуемые компоненты (в демо-приложении этого не сделано для простоты восприятия примера).


<form>
   <div class="row">
     <div class="col-md-4 col-sm-6">
       <div class="form-group">
         <label>Airport name</label>
         <input type="text" class="form-control" [(ngModel)]="airportName" name="airportName" />
       </div>
     </div>
     <div class="col-md-4 col-sm-6">
       <div class="form-group">
         <label>Country</label>
         <input type="text" class="form-control" [(ngModel)]="countryName" name="countryName" />
       </div>
     </div>
     <div class="col-md-4 col-sm-6">
       <div class="form-group">
         <input (click)="list.loadData()" [disabled]="list.busy" type="submit" class="btn btn-load" title="Load data" />
         <input (click)="list.cancelRequests()" [disabled]="list.ready" type="button" class="btn btn-cancel" title="Cancel loading"
         />
         <button (click)="list.resetSettings()" [disabled]="list.busy" type="button" class="btn btn-reset" title="Reset settings"></button>
       </div>
     </div>
   </div>
 </form>
 <div class="table-responsive">
   <table class="table table-striped" [rtList]="getAirports" #list="rtList" [loadOnInit]="false">
     <thead>
       <tr>
         <th><span rtSort="iataCode">IATA</span></th>
         <th><span rtSort="name">Airport name</span></th>
         <th><span rtSort="countryName">Country</span></th>
       </tr>
     </thead>
     <tbody rtSelectionArea>
       <tr *ngFor="let airport of list.items" [rtSelectable]="airport">
         <td>{{airport.iataCode}}</td>
         <td>{{airport.name}}</td>
         <td>{{airport.countryName}}</td>
       </tr>
     </tbody>
     <tfoot>
       <tr>
         <td colspan="3">
           <rt-demo-paged-footer>
           </rt-demo-paged-footer>
         </td>
       </tr>
     </tfoot>
   </table>
 </div>

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

Минимум зависимостей

right-angled не зависит от таких библиотек, как bootstrap, jquery, jquery UI и т.п. Данные библиотеки, безусловно, хороши и полезны, но решение об их использовании лучше принимать конечному пользователю библиотеки. А при реализации гридов без них вполне можно обойтись. 

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

e2e4, в свою очередь, вообще не имеет зависимостей. Но, если вы работаете в браузере, не поддерживающем es6, то вам понадобится какой-либо es6 полифил. Например, es6 shim или core js. Впрочем, shim и так нужен для работы angular.

Заключение

В данной статье мы в общих чертах рассмотрели библиотеку right-angled. Также библиотека имеет множество дополнительных опций, с которыми вы можете познакомиться в демо-приложении и посмотреть работающие примеры, демонстрирующие различные варианты использования.

Если вам интересна наша библиотека, вы можете начать следить за проектом на github или подписаться на аккаунт right-angled в твиттере.

 

Хотите задать нам вопрос — задайте его!

Задать нам вопрос

Задать нам вопрос

Хороший вопрос!

Мы уже задумались над ответом