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



    Сегодня хотим рассказать о том, как мы решили отдать долг open source сообществу и создали библиотеку right-angled. Только вчера мы перевели ее в статус beta и решили поделиться этой отличной новостью с Хабрасообществом c самым первым.

    Что делает right-angled


    В первую очередь, данная библиотека предназначена для построения гридов (aka списков, aka таблиц) в приложениях на angular 2.

    Во вторую, это весьма продвинутая модель selection. Работающая не обязательно в связке с гридами. Это просто selection. Чего угодно.

    Еще одной (пока не до конца оформившейся) идеей является декларативная настройка свойств компонентов для:

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

    без написания кода вручную.

    Бесплатная?


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

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

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


    Когда мы выбирали для работы имеющиеся библиотеки гридов под 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 заказчиков уже давно не устраивают. И каждый новый проект – это нередко и новый, уникальный дизайн.

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

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


    Библиотека должна содержать минимум компонентов и встраиваться в верстку конечного приложения, а не генерировать свою. То же самое касается стилей — 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>
    

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

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


    Чтобы “минимум компонентов” не превратился в “минимум функционала”, мы укомплектовали библиотеку набором функциональных сервисов. На них опираются компоненты самой библиотеки и их же пользователь может внедрить в свои компоненты при помощи 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>
    

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

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


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

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

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

    Дальнейший план


    Только вчера мы перевели библиотеку в статус беты, поэтому работы еще очень много. Ближайшие планы следующие:

    1. Стабилизировать библиотеку и вывести ее в релиз.
    2. Перевести демо-приложение на английский язык и составить полноценную документацию с дальнейшим выводом библиотеки в мировое сообщество.
    3. Доработать демо-приложение, вытащив наружу все возможности нашей библиотеки, поскольку пара фич пока еще спрятана.

    Еще мы хотим обратиться к сообществу с просьбой


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

    Помимо создания issue на github, вы можете связаться с автором статьи и главным разработчиком библиотеки прямо здесь, на Хабре. Также вы можете подписаться в twitter на аккаунт right-angled, в котором мы будем публиковать новости о нашей библиотеке.

    На этом прощаемся. Спасибо за внимание :)
    Метки:
    • +16
    • 6,4k
    • 9
    EastBanc Technologies 64,45
    Компания
    Поделиться публикацией
    Комментарии 9
    • 0
      Вроде бы добротно! Для тех, кому не критична легковесность, могу порекомендовать DataTable из PrimeNG. Поддерживается почти всё, что только можно себе представить.
      • 0
        Отличный компонент, но есть проблемы производительности на больших таблицах.
        https://github.com/primefaces/primeng/issues/706
        • 0
          Пробовал из PrimeNG компонент datepicker, потом выяснилось, что он вешал нам всю страницу. Выявили через инструмент разработчика в google chrome на вкладке Timeline.
        • 0
          Неплохо. А в виртуальную таблицу можете? С подзагрузкой по скоролу, определённым количеством элементов в DOMe.
          • 0
            Виртуальный скролл пока не можем, но в планах по развитию стоит.
          • +1
            Прорекламировал статью в Facebook группе и Telegram чате по Angular 2.

            От себя могу предложить 1 feature request. Во время загрузки данных не удалять старые а показывать маску загрузки поверх них. Чтобы таблица не «прыгала»
            • 0
              Добрый день. За рекламу большое спасибо )

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

              Суть сэмпла в том, что управление массивом элементов можно взять на себя. В сэмпле записи копируются в поле «myOwnItemsCollection» и в шаблоне теперь используется это поле, вместо обычного «list.items».
              Теперь имеющиеся записи не уничтожаются, а перетираются новыми. Плюс на время запроса за данными добавлена маска, как вы и написали.

              Вообще RTList никакой магии с коллекцией записей не делает. Он лишь опционально вызывает метод destroy элементов при уничтожении (про него мы вот тут написали). И еще он определяет, почистить или нет уже загруженные записи для случая когда список буферный. Эту логику очень легко воспроизвести в своем коде и реализовать нужное поведение по остальным вопросам. Мы решили выбрать такой подход, вместо добавления сомнительных настроек типа «уничтожать записи до/после загрузки»,

              А текущее поведение сделали основным поскольку уничтожение имеющихся записей, пока идет загрузка новых, позволяет уменьшить «лаг» при отрисовке большого количества данных (особенно если логика уничтожения записей в методе destroy «тяжелая»), что тоже важно.
              • 0
                В итоге сделали feature request о котором вы говорили. Детали, как всегда, в демо-приложении.

                А еще мы сегодня вышли в релиз :)
              • 0
                Мне понравился ваш подход. Думаю мне пригодится в скором времени. Спасибо.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое