bem / bem-components

Set of components for sites development
https://bem.info/libraries/classic/bem-components/6.0.0/
Other
333 stars 87 forks source link

Вопрос: Как правильно наследоваться от стандартных блоков? #1744

Closed wKich closed 8 years ago

wKich commented 8 years ago

Возможно я что-то делаю не так. В bemjson страницы описал свой блок:

{
  block: 'foo',
  js: true,
  mix: [{ block: 'textarea', js: true }],
  tag: 'textarea'
}

Далее в js технологии блока foo описываю декларацию:

modules.define(
  'foo',
  [ 'textarea', 'i-bem__dom', 'BEMHTML' ],
  function (provide, Textarea, BEMDOM, BEMHTML) {
    provide(BEMDOM.decl({ block: 'foo', baseBlock: Textarea } , {
      onSetMod: {
        js: {
          inited: function () {
            var self = this
            this.bindTo('keydown', function (e) { console.log(self.getVal()) })
          }
        }
      }
    }))
  }
)

Функция инициализации блока input вызывается корректно, но вот this.elem('control').val(); возвращает undefined.

tadatuta commented 8 years ago

Причина в том, что на примиксованные блоки не применяются шаблоны. Чтобы получить ожидаемое поведение, нужно, чтобы примиксованным был foo:

{
  block: 'textarea',
  mix: { block: 'foo', js: true }
}
wKich commented 8 years ago

Всё равно возвращается undefined. А при вызове this.elem('control') возвращается пустой список.

tadatuta commented 8 years ago

А указан ли textarea в зависимостях?

wKich commented 8 years ago

Конечно, иначе бы была ошибка can't resolve dependence В файле foo.deps.js прописывал:

[{
    mustDeps : { block : 'i-bem', elems : 'dom' },
    shouldDeps : [
        {
            mods : ['disabled', 'focused']
        },
        'control',
        'textarea'
    ]
}]
tadatuta commented 8 years ago

я так понимаю, что в вызове this.elem('control') this указывает на foo?

дело в том, что control в данном случае — это элемент блока textarea, соответственно, искать его нужно так:

this.findBlockOn('textarea').elem('control');
wKich commented 8 years ago

Я добавлял console.log(this.elem('control')) в файл libs/bem-components/common.blocks/input/inpus.js внутрь функции onSetMod.js.inited

tadatuta commented 8 years ago

Отвечает ли этот пример на вопрос: https://jsfiddle.net/tadatuta/83nmrfgy/1/ ?

wKich commented 8 years ago

Возможно для меня в виду недостаточного понимания архитектуры БЭМ компонентов кажется не совсем очевидной необходимость вызова findBlockInside, если мы обозначили Textarea в качестве родительского блока для foo.

wKich commented 8 years ago

В таком случае, нам даже ненужно объявлять textarea как родительский блок и указывать его в зависимостях и всё будет работать.

tadatuta commented 8 years ago

В таком случае, нам даже ненужно объявлять textarea как родительский блок и указывать его в зависимостях и всё будет работать.

Да, все так. Дело в том, что приведенный код наследует только JS-реализацию. Но чтобы она нормально отработала, нужно, чтобы у отнаследованного блока была ожидаемая разметка (в данном случае — чтобы элемент control оказался элементом блока foo, а не блока textarea).

Если говорить про общую практику при использовании i-bem, то могут быть такие реальные варианты:

  1. На проекте (или в рамках данного бандла) необходимо всем блокам textarea изменить какие-либо свойства. Тогда пользуемся уровнями переопределения и доопределяем блок под старым именем.
  2. Требуется сохранить как исходную реализацию textarea, так и создать чуть-чуть отличающуюся. Тогда это выражается через модификатор (наследование происходит автоматически).
  3. Требуется значительно отличающийся блок, который тем не менее основывается на textarea. В этом пункте возможны несколько вариантов решения:
    • Композиция на уровне миксов: примешиваем на DOM-узел отдельный блок, который может обращаться к методам textarea на своей ноде, но обладает дополнительными свойствами.
    • Композиция на уровне оберток: дополнительная функциональность пишется для родителя, а textarea лежит внутри.
    • Наследование средствами baseBlock.

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

wKich commented 8 years ago

Спасибо большое за развернутый ответ. Скорее всего в моём случае будет лучше воспользоваться вторым вариантом.

Если можно, у меня есть ещё один вопрос, но касающийся BEMHTML. Я вижу в примере используется функция apply, которая возвращает скомпилированную html разметку. Подобный пример описывается в статье "Работа с DOM-деревом", но почему-то при создании блока в проекте project-stub вызов BEMHTML.apply() всегда возвращает пустую строку. И если я правильно понимаю, именно так она и должна отрабатывать из-за того как объявлено в файле enb-bemxjst/techs/bemhtml.js. Подскажите, пожалуйста, каким образом можно подключить в project-stub работающий BEMHTML?

tadatuta commented 8 years ago

На самом деле BEMHTML подключен, это вопрос про зависимости.

По умолчанию зависимости работают в режиме «технология BEMHTML блока b1 зависит от технологии BEMHTML блока b2», т.е. нужные шаблоны попадают в отдельный «серверный» бандл. Его можно подключить на клиент как обычный JS-файл и все будет работать, но зачастую это неэффективно, т.к. у нас уже есть страница с обвязкой, отрендеренная на сервере, а на клиенте нужно менять только «внутренности».

Для сборки на клиент только тех шаблонов, которые действительно нужны, используется технология depsByTech, которая позволяет выражать зависимости вида «технология JS блока b1 зависит от технологии BEMHTML блока b2».

Подробнее о том, как писать такие зависимости см. документацию: https://ru.bem.info/technology/deps/about/#Подключение-зависимостей-по-технологии