Debian, Dojo, Django, Python

Делюсь опытом в описанных технологиях. Блог в первую очередь выполняет роль памяток для меня самого.

Показаны сообщения с ярлыком material. Показать все сообщения
Показаны сообщения с ярлыком material. Показать все сообщения

md-autocomplete со значением по умолчанию

Задача

С помощью Angular Material создать autocomplete со значением по-умолчанию. Статья навеяна вопросом со StackOverflow.

Самому как раз сегодня понадобилось решить эту же задачу. Результат можно посмотреть здесь.

Решение

HTML-разметка

<body ng-app="app" flex layout="column" layout-margin ng-controller="Main">
  <md-content layout="column" class="md-whiteframe-z1" layout-margin>
    <md-toolbar>
      <div class="md-toolbar-tools">
        <h3>Form</h3>
      </div>
    </md-toolbar>
    <md-content class="md-whiteframe-z1">
      <div class="md-padding">
        <md-input-container>
          <label for="firstname">First name</label>
          <input type="text" name="firstname" ng-model="user.firstname" />
        </md-input-container>
        <md-input-container>
          <label for="lastname">Last name</label>
          <input type="text" name="lastname" ng-model="user.lastname" />
        </md-input-container>
        <md-autocomplete md-selected-item="user.group" md-items="item in loadGroups(filterText)" md-item-text="item.title" md-search-text="filterText">
          <md-item-template>{{ item.title }}</md-item-template>
          <md-not-found>No items.</md-not-found>
        </md-autocomplete>
      </div>
    </md-content>
  </md-content>
  <md-content class="md-whiteframe-z1" layout-margin>
    <md-toolbar>
      <div class="md-toolbar-tools">
        <h3>Model as JSON</h3>
      </div>
    </md-toolbar>
    <md-content class="md-padding">
      <p>
        {{ user | json }}
      </p>
    </md-content>
  </md-content>
</body>

JavaScript

(function(A) {
  "use strict";

  var app = A.module('app', ['ngMaterial']);

  function main(
    $q,
    $scope,
    $timeout
  ) {
    $timeout(function() {
      $scope.user = {
        firstname: "Maxim",
        lastname: "Dunaevsky",
        group: {
          id: 1,
          title: "Administrator"
        }
      };
    }, 500);

    $scope.loadGroups = function(filterText) {
      var d = $q.defer(),
        allItems = [{
          id: 1,
          title: 'Administrator'
        }, {
          id: 2,
          title: 'Manager'
        }, {
          id: 3,
          title: 'Moderator'
        }, {
          id: 4,
          title: 'VIP-User'
        }, {
          id: 5,
          title: 'Standard user'
        }];

      $timeout(function() {
        var items = [];

        A.forEach(allItems, function(item) {
          if (item.title.indexOf(filterText) > -1) {
            items.push(item);
          }
        });

        d.resolve(items);
      }, 1000);

      return d.promise;
    };
  }

  main.$inject = [
    '$q',
    '$scope',
    '$timeout'
  ];

  app.controller('Main', main);
}(this.angular));

Теперь немного о том, что происходит.

Сначала происходит инициализация модели. В примере задержка ответа эмулируется с помощью $timeout, на практике следует использовать подгрузку данных с помощью $http или $resource. При этом поле, значение которого должно быть связано с полем автодополнения, уже инициализировано, при этом в нём находится целая модель. Вопрос, как получить основную модель, содержащую вложенное поле, излишен. Этим должен заниматься сервер. Например, в Django REST Framework для таких полей используются сериализаторы объектов.

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

Немного о Material Design Lite и Angular Material

Хочу сказать пару слов про библиотеку Material Design Lite (MDL).

TL;DR Если нужны только стили, сгодится. Если требуется качественный JS и интеграция с Angular - посмотрите в сторону Angular Material. Впрочем, в своих проектах я использую их вместе, это не так уж сложно.

Стили

Что касается стилей, тут всё довольно неплохо. Во-первых, даже при установке через bower скачиваются исходные файлы. Можно открыть нужный .scss и внести правки. Не уверен, что многие часто пользуются этой возможностью, но в любом случае она есть, и для этого даже не нужно клонировать репозиторий с GitHub.

В библиотеке используется БЭМ от Яндекса. Может, подход и хорош, но я пока не оценил всех его преимуществ и он кажется мне несколько многословным. Директивы или полифиллы мне как-то ближе, впрочем, даже к такой "разговорчивости" фреймворка привыкаешь довольно быстро.

Очень даже хорошо, что из коробки идёт поддержка стилей для таблиц, карточек, панели навигации и некоторых других объектов. По сравнению с Angular Material - шаг вперёд.

JS

А вот с JS не всё гладко. Во-первых, поведение меню. В Angular Material разработчики предусмотрели позиционирование таким образом, чтобы оно не уезжало за экран. В MDL приходится самому заботиться об этом и подбирать нужные стили. Впрочем, это не такая уж большая проблема. Есть кое-что ещё.

MDL выполняет обновление компонентов и установку обработчиков на них только в момент загрузки страницы. Да, он дожидается DOMReady, после чего выполняет заполнение свойств элементов. При использовании какого-либо роутинга в Angular сразу появляется проблема. Решается довольно легко, спасибо StackOverflow:

Приложение
Application.run(['$rootScope', '$location', '$timeout', function (
    $rootScope,
    $location,
    $timeout
) {
    $rootScope.$on("$viewContentLoaded", function () {
        $timeout(function () {
            componentHandler.upgradeAllRegistered();
        });
    });
}]);

Нет некоторых нужных полей, например, поле для выбора даты, которое появилось в Angular Material 0.11.0. Впрочем, обе библиотеки прекрасно работают вместе.

Если пишете SPA на Angular, для создания навбара и верхнего тулбара лучше используйте те, что идут в Angular Material. Почему? Потому что эти элементы статичны в MDL, а нам же нужно будет иногда менять их содержимое, не так ли? Да и привязка к контроллерам будет проще, если сделать один ng-view, без использования ng-include или сторонних вещей типа ui-route.

Angular Material и md-list - проблема с дополнительным действием

В Angular Material есть такой хороший компонент - md-list, и работающий с ним в паре md-list-item. Из них можно делать красивые списки, обладающие весьма важными свойствами. Во-первых, каждая строка реагирует на нажатие. Можно реализовать возможность перехода по ссылке. Во-вторых, к каждой строке можно добавить кнопку действия.

Это я всё к чему? А к тому, что сегодня почти час убил на то, чтобы разобраться, почему скопированный почти один в один пример с официальной доки работает у них и не работает у меня.

Разметка
<md-list>
    <md-list-item ng-repeat="item in items" ng-click="openDetail(item)">
        <img class="md-avatar" alt="" src=""/>
 <p>{{ item.name }}</p>
        <md-icon class="material-icons md-secondary md-warn" ng-click="remove(item)">remove</md-icon>
    </md-list-item>
</md-list>
Вся проблема заключалась в классах CSS для тега md-icon. Чтобы он превратился в кнопку, ему должен быть назначен среди прочих класс md-secondary.

Если кому интересно, то ниже код контроллера. Обратите внимание, я не отлавливаю объект события и не вызываю для него stopPropagation() и preventDefault(), это не требуется.

ctrl.js
(function (A) {
    "use strict";

    var inject = [
        '$location',
        '$mdDialog',
        '$scope'
    ];

    function Ctrl(
        $location,
        $mdDialog,
        $scope
    ){
        // Ничто не мешает загружать записи с сервера
        $scope.items = [
            {id: 1, name: 'Запись №1'},
            {id: 2, name: 'Запись №2'},
            {id: 3, name: 'Запись №3'},
            {id: 4, name: 'Запись №4'}
        ];

        function openDetail(item){
            // Переход на другой вид
            $location.path('/items/' + item.id);
        }

        function remove(item){
     // Запрос на удаление записи
            $mdDialog.show(
                $mdDialog
                    .confirm()
                    .title("Подтверждение")
                    .content('Удалить "' + item.name + '"?')
                    .ok("Да")
                    .cancel("Нет")
                ).then(function () {
                    $scope.items = $scope.items.splice($scope.items.indexOf(item), 1);
            });
        }

        $scope.openDetail = openDetail;
        $scope.remove = remove;  
    }

    Ctrl.$inject = inject;

    A.module('app').controller('Ctrl', Ctrl);
}(this.angular));

LESS для Google Material Icon Font

У Google для Web-разработки с использованием Angular Material есть даже специальный набор иконок, а так же репозиторий на GitHub с возможностью установки через Bower. Там всё хорошо, но вот CSS для иконок приходится по кускам собирать из официальной документации. Тут я и публикую такой LESS/CSS, собранный собственноручно по результатам чтения официальных доков.

Установка через Bower

bower install material-design-icons

Помимо шрифта в архиве куча иконок в разных форматах, так что будьте осторожны - bower скачает около 30 Мб, а потом будет его некоторое время распаковывать.

У меня все сторонние библиотеки хранятся в каталоге static/libs/, вам же следует изменить пути к шрифтам (переменная @BASE_PATH) на подходящие.

material-icons.less

@BASE_PATH: '/static/libs/material-design-icons/iconfont/MaterialIcons-Regular.';
@font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: url("@{BASE_PATH}eot");
    /* For IE6-8 */
    src: local('Material Icons'),
         local('MaterialIcons-Regular'),
         url("@{BASE_PATH}woff2") format('woff2'),
         url("@{BASE_PATH}woff") format('woff'),
         url("@{BASE_PATH}ttf") format('truetype');
}

.material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    /* Preferred icon size */
    display: inline-block;
    width: 1em;
    height: 1em;
    line-height: 1;
    text-transform: none;
    letter-spacing: normal;
    word-wrap: normal;
    -webkit-font-smoothing: antialiased; /* Support for all WebKit browsers. */
    text-rendering: optimizeLegibility;  /* Support for Safari and Chrome. */
    -moz-osx-font-smoothing: grayscale;  /* Support for Firefox. */
    font-feature-settings: 'liga';       /* Support for IE. */
}

.material-icons.md-18 { font-size: 18px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }

// Rules for using icons as black on a light background.
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }

// Rules for using icons as white on a dark background.
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }

Работает данный шрифт через лигатуры. В отличие от FontAwesome, который оперирует классами для тегов <span> и <i>, здесь нужно использовать и класс, и лигатуру:

Пример использования

<md-icon>
    <i class="material-icons md-24">menu</i>
</md-icon>

Полный список лигатур находится в каталоге material-design-icons/iconfont/codepoints. Так же есть отдельный ресурс с описанием и показом всех иконок.