npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@webtechart/tao-bem

v1.3.0

Published

Frontend TAO/BEM

Downloads

114

Readme

TAO.BEM

tao-bem - базовая ООП библиотека, соответвующая методологии БЭМ. Представляет собой описание блоков в виде js-классов и инкапсуляции необходимого состояния в объектах этих классов.

БЭМ (Блок, Элемент, Модификатор) - опенсорс-технология разработки сайтов, реализующая принцип разделения интерфейса на независимые блоки с возможностью многкратного их использования.

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

Установка

Для установки пакета применяем команду yarn.

yarn add @webtechart/tao-bem

Подключение и использование

Для описания минимального рабочего блока необхоидимо создать отдельный класс, описывающий этот блок и наследуемый от базового класса TAO.BEM. В этом классе обязательно переопределить статический геттер blockName(), который должен возвращать css-класс данного блока.

Далее необходимо провести инициализацию созданного класса в системе tao-bem, вызвав в нем статический метод register().

import BEM from 'tao-bem'

class News extends BEM.Block {
    static get blockName() {
        return 'b-news';
    }
}

News.register()

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

Ниже приведен пример расширения реализации класса блока.

import BEM from 'tao-bem'

class News extends BEM.Block {
    static get blockName() {
        return 'b-news';
    }

    onInit() {
        this.debug = 'is_init';
        console.log('Instance of News initialized!');
    }

    static get events() {
        return {
            click: function(event) {
                this.makeSomeNoise();
                event.stopPropagation();
            },
            mouseout: 'makeSomeNoise'
        };
    }

    static get modsEvents() {
        return {
            onMod: {
                'test-mod': function(modValue, modName) {
                    this.makeSomeNoise(modValue);
                }
            },
            onElemMod: {
                'button' : {
                    'disabled': function (modValue, modName) {
                        this.makeSomeNoise();
                    }
                }
            }
        };
    }

    static get elementsEvents() {
        return {
            'button.click': function(element, allElements, event) {
                window.location.reload();
            }
        };
    }

    makeSomeNoise(noiseLevel = 0) {
        alert('Noise!' + (noiseLevel ? ' Going to noise level ' + noiseLevel : ''));
    }
}

News.register();

Переопределив метод onInit(), мы получим возможность произвести необходимую подготовку в тот момент, когда TAO.BEM обнаруживает в DOM-дереве элемент блока и обрабатывает связанные с ним скрипты. Пока для примера оставим простое сообщение в консоли.

Переопределив статический геттер events(), мы подписываемся на события вызываемые на блоке. В данном случае мы вызываем метод makeSomeNoise при клике по блоку и выводе курсора с блока.

Переопределив статический геттер modsEvents(), мы добавили обработчик на изменение модификатора test-mod самого блока и изменении модификатора disabled у элемента button этого блока (т.е. на переключение класс b-news__button--disabled у элемента с классом b-news__button).

Переопределив статический геттер elementsEvents(), мы добавили обработчик на клик по элементу button (класс b-news__button).

Инициализация блоков

При подключении TAO.BEM скрипт обходит имеющееся DOM-дерево, отыскивает DOM-элементы с классами зарегистрированных блоков (тех, чьи блоки возвращают из статического геттера blockName), и для каждого создаются экземпляры соотвествующих классов BEM-блоков.

Также скрипт подписывается на изменения DOM-дерева, и при последующих добавлениях новых DOM-элементов для них также при необходимости будут созданы экземпляры BEM-блоков.

При необходимости стартовая автоматическая инициализация блоков может быть заменена "ленивой" инициализацией при первом появлении блока на экране. Для этого нужно переопределить статический геттер forced() у класса блока и вернуть false.

Организация структуры блоков

После инициализации ссылки на все созданные блоки попадают в Регистр. Если есть ссылка на DOM-элемент, который представляет блок, то из Регистра можно получить соответствующий ему BEM-объект с помощью метода getInstance($node, name).

import BEM from 'tao-bem';

let $node = $('.b-news');
let News = BEM.Registry.getInstance($node, 'b-news');
News.makeSomeNoise();

Для удобства манипулирования наборами блоков введено поятие Коллеций.

Коллеция - это специальный объект, который позволяет хранить несколько блоков и выполнять над ними групповые операции.

Объект коллекции наследует коллекцию из TAO.Core, поэтому в нём доступны все методы родителя. Помимо этого существуют несколько специфичных методов, которые позволяют изменять модификаторы всем элементам коллекции (addMod(name, state), delMod(name, state), toggleMod(name, state)) и метод byMod(name, state) для фильтрации элементов коллекции по значению модификатора.

Для получения коллекции блоков необходимо воспользоваться Регистром - методом getCollection(name). Или воспользоваться статическим методом getCollection() класса требуемого блока.


import BEM from 'tao-bem';

// ниже два примера идентичного кода
let collection = BEM.Registry.getCollection('b-news');
collection.first().makeSomeNoise();

let sameCollection = News.getCollection();
sameCollection.first().makeSomeNoise();

У каждого блока с помощью метода elems(name) можно получить коллекцию его элементов, и первый из элементов с помощью метода elem(name).

import BEM from 'tao-bem';

let $node = $('.b-news');
let newsBlock = BEM.Registry.getInstance($node, 'b-news');
let buttons = newsBlock.elems();
let firstButton = newsBlock.elem();

buttons.delMod('disabled');
firstButton.addMod('disabled');

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

Обработка событий блока

Во время жизни блока, скорее всего, ему придётся как-то взаимодействовать с окружающим его кодом и реагировать на действия пользователей. Такие взаимодействия удобно организовывать в виде событий. Для обработки событий блока в tao-bem существует несколько статических геттеров:

events()

Используется для подписки на события самого блока. Этот метод должен вернуть хэш-таблицу (обычный объект JS), ключами которой являются имена событий, на которые мы хотим подписатсья, а значениями - обработчики. В качестве обработчика может выступать как привычная всем анонимная функция (можно использовать и стрелочные функции, но в них будет потерян контекст this, а значит придётся явно получать объект блока, на котором произошло событие), так и строка, содержащая имя метода класса блока.

modsEvents()

Используется для подписки на события изменений значений модификаторов блока. Удаление модификатора обрабатывается как событие изменения значения модификатора на false, а добавление модификатора без значения обрабатывается как событие изменения значения модификатора на true.

В обработчик будет передано новое значение - модификатор и имя модификатора. Текущим контекстом будет класс блока.

Важно понимать, что обработка событий будет происходить только при изменении модификаторов через api TAO.BEM, т.е. вызовами методов addMod, delMod, toggleMod и mod. Также обработчик однократно сработает при инициализации блока для всех модификаторов, которые присутствуют на DOM-элементе.

Вернув из обработчика false, можно отменить изменение модификатора (однако обработчик вызванный на событии при инициализации блока не сможет таким образом "сбросить" модификаторы).

Помимо общих обработчиков на изменение модификаторов возможно подписываться на присвоение некоторым модификаторам определённых значений. В этом случае обработчик будет срабатывать только если модификатор получает подходящее значение. Ниже приведён пример.

import BEM from 'tao-bem'

class News extends BEM.Block {
    static get blockName() {
        return 'b-news';
    }

    static get modsEvents() {
        return {
            onMod: {
                'some-mod': {
                    'some': function(modValue, modName) {
                        this.makeSomeNoise(modValue);
                    },
                    'someOther': function(modValue, modName) {
                        this.makeSomeNoise(modValue);
                    }
                }
            }
        };
    }

    makeSomeNoise(noiseLevel = 0) {
        alert('Noise!' + (noiseLevel ? ' Going to noise level ' + noiseLevel : ''));
    }
}

News.register()

Также можно навешивать обработчики на изменение модификаторов элементов блока. Работает это аналогично добавлению обаботчиков на изменение модиикаторов блока, только ключом будет не onMod, а onElemMod. Ниже пример.

import BEM from 'tao-bem'

class News extends BEM.Block {
    static get blockName() {
        return 'b-news';
    }

    static get modsEvents() {
        return {
            onElemMod: {
                'button': {
                    'disabled': function(modValue, modName) {
                        this.makeSomeNoise(modValue);
                    },
                }
            }
        };
    }

    makeSomeNoise(noiseLevel = 0) {
        alert('Noise!' + (noiseLevel ? ' Going to noise level ' + noiseLevel : ''));
    }
}

News.register()

Автомодификаторы

Для блока можно включить автоматическое переключение определённых модификаторов при наступлении определённых событий блока. На текущий момент поддерживаются следующие события:

  • hover - наведение курсора мыши
  • press - клик
  • focus - получение фокуса

Для включения автомодификаторов нужно переопределить статический геттер mods() и вернуть из него строку, содержащую список необходимых модификаторов, разделённых пробелом.

import BEM from 'tao-bem'

class News extends BEM.Block {
    static get blockName() {
        return 'b-news';
    }

    static get mods() {
        return 'hover press';
    }
}

News.register();

Взаимодействие между блоками

Как описывалось выше все блоки можно получить через регистр коллекций: BEM.Registry.getCollection('b-news'). Но тут есть небольшая тонкость.

Так как инициализация блоков происходит последовательно, то нет гарантиии, что в момент инициализации конкретного блока уже будут иметься в коллекции экземпляры других блоков. Поэтому внутри метода onInit не стоит надеяться найти BEM.Registкy коллекцию других блоков. Для решения данной проблемы существует 2 способа:

  1. Получить нужный блок через промис.
  2. Производить инициализацию только после создания экземпляров всех связанных блоков.

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

Получение блока через промис

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

Получить промис можно через регистр: BEM.Registry.waitBlock('block_name'), аргументом метода является строка с именем блока. Метод возвращает промис, который пока не поддерживает ошибок (будет ждать бесконечно), а в момент появления требуемого блока в регистре вызовет резолв промиса с параметром, являющимся объектом-экземляром данного блока. Пример:

BEM.Registry.waitBlock('b-gallery').then(gallery => gallery.goto(0));

Ждем пока будет инициализирован блок b-gallery, после чего на нем вызываем метод goto

Ожидание связанных блоков

Тут все сложнее:

  • Для начала нужно указать классы всех связанных блоков через статический геттер relatedBlocks.
  • Внутри метода onInit произвести инициализацию только логики, не относящейся к связанным блокам. А остальную инициализацию проводить в методе onRelatedBlocksInit, который будет вызван только полсле того, как будут инициализированы все связанные блоки (или не будет вызван, если не будет нужных блоков - будьте осторожны с опечатками в именах).
  • После вызова onRelatedBlocksInit, как минимум, по одному экземляру для каждого из зависимых блоков будет находиться в регистре блоков. Но для упрощения доступа можно воспользоваться свойством relatedBlocks текущего блока, который содержит Set для первых найденных инстанцев для кадого из зависимых блоков.

Пример:

import BEM from 'tao-bem'

class Gallery extends BEM.Block {
    static get blockName() {
        return 'b-gallery';
    }

    static get relatedBlocks() {
        return ['b-gallery-controls', 'b-page'];
    }

    onRelatedBlocksInit() {
        this.relatedBlocks.get('b-page').initSlider();
        this.relatedBlocks.get('b-gallery-controls').setSlider(this);
    }
}