Skip to content

Latest commit

 

History

History
355 lines (283 loc) · 18.8 KB

roles.md

File metadata and controls

355 lines (283 loc) · 18.8 KB

Роли

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

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

Основные области применения ролей: отслеживание событий в интерефейсе (трекинг поведения пользователя) и обеспечение адресации для автоматизированных тестов.

DEMO

Создание

Роль может быть назначена любому элементу разметки. Обычно роли прописываются для элементов, у которых есть атрибуты с префиксом event- (т.е. у которых слушаются события).

Значение роли всегда берётся из компонента (владельца шаблона) как значение специального биндинга $role. Роль назначается с помощью атрибута b:role. Главный элемент шаблона маркируется атрибутом b:role без значения.

Шаблон

<div class="component" b:role/>

Компонент

var component = new Node({
  role: 'foo',
  template: resource('./template/component.tmpl')
});

Итоговая разметка

<div class="component" role-marker="foo"></div>

Второстепенные (или вспомогательные) элементы маркируются атрибутом b:role со значением. Например, если роль компонента foo, а в шаблоне задан атрибут b:role="bar", то в итоговой разметке у элемента будет роль foo/bar. Рассмотрим пример с диалогом. Основной блок диалога — это основной элемент. Вспомогательными элементами могут быть, например, кнопка закрытия диалога и подложка.

Шаблон

<div class="dialog-wrapper" b:role="overlay">
  <div class="dialog" b:role>
    <button class="close-button" b:role="close-button">
      Close dialog
    </button>
  </div>
</div>

Компонент

var dialog = new Node({
   role: 'dialog',
   template: resource('./template/dialog.tmpl')
});

Итоговая разметка

<div class="dialog-wrapper" role-marker="dialog/overlay">
  <div class="dialog" role-marker="dialog">
    <button class="close-button" role-marker="dialog/close-button">
      Close dialog
    </button>
  </div>
</div>

Только у одного элемента в шаблоне может быть атрибут b:role без значения, в противном случае это может привести к конфликтам путей — таких ситуаций стоит избегать.

Особый случай – повторяющиеся компоненты, такие как элементы списка. Для них нельзя задать уникальную роль, но у каждого элемента обычно есть некоторое уникальное значение, например, идентификатор. В таких случаях используется свойство roleId, которое определяет, какой биндинг описывает идентификатор. Важно, чтобы указанный биндинг был определен, иначе роль не будет проставлена.

Шаблон

<ul class="menu" b:role/>
<li class="menu-item" b:role>
  <span b:role="caption">{id}</span>
</li>

Компонент

var menu = new Node({
  role: 'menu',
  template: resource('./template/menu.tmpl'),
  childClass: {
    role: 'item',
    roleId: 'id',
    template: resource('./template/menu-item.tmpl')
  },
  childNodes: [
    { id: 'foo' },
    { id: 'bar' },
    { id: 'baz' }
  ]
});

Такое описание позволит сгенерировать для каждого дочернего элемента уникальную роль вида item(id) (для основного элемента) или item(id)/subrole (для второстепенных элементов).

Итоговая разметка

<ul class="menu" role-marker="menu">
  <li class="menu-item" role-marker="item(foo)">
    <span role-marker="item(foo)/caption">foo</span>
  </li>
  <li class="menu-item" role-marker="item(bar)">
    <span role-marker="item(bar)/caption">foo</span>
  </li>
  <li class="menu-item" role-marker="item(baz)">
    <span role-marker="item(baz)/caption">foo</span>
  </li>
</ul>

Особенности синтаксиса ролей в некоторых случаях

В <b:include> можно задать роль для корневого элемента подключаемого фрагмента. В этом случае префикс b: для атрибута role не указывается.

<b:include src="..." role="foo"/>

Во всех остальных случаях префикс b: является обязательным. Например, для <b:svg> префикс обязателен, иначе он не будет распознан как специальный.

<b:svg src="..." b:role="baz"/>

Чтобы необходимо установить, изменить или удалить роль в подключаемом шаблоне, необходимо использовать инструкции <b:role> (или <b:set-role>) и <b:remove-role>.

<b:include src="...">
  <b:role ref="name" value="foo"/>
  <b:remove-role ref="another-name"/>
</b:include>

Для части компонент в basis.js еще не прописаны роли. Это будет исправлено в будущем. Вы можете помочь, расставив роли в шаблоне и сделав PR в репозитории basisjs.

Инспектирование ролей

Для тестирования и трекинга нужны ролевые пути. Ролевые пути — уникальные пути из цепочки роль-маркеров, по которым можно однозначно определить элемент на странице (по аналогии с XPath).

Для определения ролевых путей и работы с ролями в панели разработчика basisjs есть специальные режимы — инспектор ролей (R с лупой) и базовый режим отображения ролей (Roles).

basisjs-devtools

В обоих режимах включается отображение ролей и трекинга на странице, которые подсвечиваются блоками определенных цветов:

  • серый (1) — роль есть, трекинг не ведётся
  • зеленый (2) — роль и трекинг есть
  • красный (3) — есть некоторые проблемы:
    • у элемента есть атрибут с префиксом event-, но не задана роль
    • конфликт путей (на странице есть элемент с таким же путем)
    • и другие ошибки

roles indicators

Инспектор ролей

devtools - roles pick inspector

– детальное инспектирование отдельных ролей

Особенности: При нажатии на элемент в режиме инспектирование ролей открывается модальное окно, в котором отображается ролевой путь до конкретного элемента, информация по трекингу и дерево элементов с ролями на странице (можно выбрать интересующий элемент и увидеть детали: полный путь к элементу и подробности трекинга).

role paths and tracking info

Roles

devtools - roles view

– базовый режим отображение ролей

При активации этого режима при взаимодействии с элементом всплывает окно с информацией по трекингу. Тип взаимодействия зависит от того, какое событие было прописано для активации трекинга.

tracking info

Трекинг

Модуль basis.tracker упрощает отслеживание событий в интерфейсе.

Для того, чтобы начать собирать события, необходимо:

  1. Добавить обработчик событий
  2. Подключить карту трекинга (tracking map)
var tracker = require('basis.tracker');

// добавляем обработчик
tracker.attach(function(info){
  // функция срабатывает, когда для заданного пути в карте сработало событие, которое нас интересует
  // info - объект содержащий информацию о пути, событии и пданных
});

// загружаем карту
tracker.loadMap({
  'role path': {
    click: {
      some: 'data'
    }
  }
});

Метод loadMap может быть вызван произвольное количество раз. Так модули могут загружать свой фрагмент карты событий, для той части интерфейса, что они описывают. Карты можно хранить в отдельных файлах:

tracker.loadMap(require('./tracking-map.js'));

Tracking map

Что описывается:

  • ролевые пути до компонентов
  • какие события нужно отслеживать (например, клики по ссылкам или ввод текста)
  • какую информацию логировать при наступлении этих событий
  • куда отправлять эту информацию

Пример содержимого:

module.exports = {
  // главная роль страницы, отправляет отчет о показе страницы
  'example-page': {
    show: {
      trackingService: 'Example - View page'
    }
  },
  // событие keydown на поле ввода инициирует отправку отчета
  // 'Example - Change value' в сторонний трекинг-сервис
  'example-page input': {
    keydown: {
      trackingService: 'Example - Change value'
    }
  },
  // клик на второстепенный элемент-переключатель у поля ввода
  // отправит отчет 'Example - Toggle status'
  'example-page input/toggle': {
    click: {
      trackingService: 'Example - Toggle status'
    }
  }
}

Можно дополнительно передавать параметры. Это особенно актуально для повторяющихся компонентов.

Пример (сокращенная запись, вместо звёздочки подставится значение из биндинга)

module.exports = {
  'example-page menu(*)/item': {
    click: {
      trackingService: {
        id: 'Example - Toggle sell/rent',
        params: {
          value: '*'
        }
      }
    }
  }
}

Эквивалентно следующему описанию

module.exports = {
  'example-page menu(foo)/item': {
    click: {
      trackingService: {
        id: 'Example - Select menu-item',
        params: {
          value: 'foo'
        }
      }
    }
  },
  'example-page menu(bar)/item': {
    click: {
      trackingService: {
        id: 'Example - Select menu-item',
        params: {
          value: 'bar'
        }
      }
    }
  },
  'example-page menu(baz)/item': {
    click: {
      trackingService: {
        id: 'Example - Select menu-item',
        params: {
          value: 'baz'
        }
      }
    }
  }
}

Встроенная обработка событий ввода

Для пользовательских браузерных событий keyup, keydown, keypress, input описание, которое будет отправлено в некоторый trackingService, расширяется свойством inputValue, равным event.target.value.

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

Динамическое формирование описания

Начиная с версии 1.11.0 (начиная с коммита) можно воспользоваться функцией transformWithUIEvent.

Пример:

    ...
    'input/password': {
        input: {
            transformWithUIEvent: function(data, event){
                return {
                    trackingService: {
                        id: 'input password input'
                        valueStat: event.target.value.length
                    }
                };
            }
        }
    },
    ...

Таким образом для элемента на странице, который ищется по xpath-like строке 'input/password' (см выше "ролевой путь"), будет логироваться обычное браузерное событие input. Когда событие произойдет, будет вызвана функция, описанная в свойстве transformWithUIEvent. Значение valueStat в демонстрационных целях формируется из объекта event. С тем же успехом можно было написать valueStat: data.inputValue.length, учитывая, что в этом примере трекается событие ввода, для которого есть data.inputValue.

Примеры

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