bem-site / bem-forum-content-ru

Content BEM forum for Russian speak users
MIT License
56 stars 6 forks source link

Назначить клас элементу в this.ctx.items #337

Open uliantsev-a opened 9 years ago

uliantsev-a commented 9 years ago

Здравствуйте, подскажите пожалуйста по следующией конструкции bemhtml. Элементу item, из условия необходимо добавить произвольный класс. Но не могу понять доступную конструкцию обрабатываемого элемента. Вызывается ошибка "TypeError: string is not a function" Пробовал item.cls()('my-class') и item.ctx.cls()('my-class') и this.. Но кажется что-то я совсем не то делаю.

this.ctx.items.forEach(function(item){
                var sub = [],
                num = 0;

  if(item.items != null){            
     item.cls()('my-class');
     item.items.forEach(function(subItem, i){
          sub.push({
             elem: 'sub-item',
             content: {
            elem: 'link',
            url: subItem.url,
            content: [subItem.text]
          }
       });
      });
  }
...
}) 
Guria commented 9 years ago

Внутри вашего forEach не надо использовать bemhtml конструкции. Укажите поле cls прямо в bemjson элемента sub-item

oozywaters commented 9 years ago

Может просто переопределить моду cls у нужного айтема?

block('block').elem('item')(
    cls()(function() {
        if(this.ctx.items !== null) {
            return 'my-class';
        } else {
            return 'another-class';
        }
    })
)
uliantsev-a commented 9 years ago

сразу в bemjson определить клас к сожалению не получится, т.к. ctx в bemhtml переопределяется и затирает cls из bemjson.

Тут я пытаюсь реализовать типичный блок навигации в шапке, через шаблоны. Взяв за основу пример из руководства про модификаторы: https://github.com/bem/bem-js-tutorial/tree/master/pure.bundles/006-before-set-mod

вот полная версия моего bemhtml:

block('nav')(
    tag()('ul'),
    js()(true),
    content()(function(){
        var content = [];
        this.ctx.items.forEach(function(item){
                var sub = [],
                num = 0;

                if(item.items != null){

                    item.items.forEach(function(subItem, i){
                        sub.push({
                            elem: 'sub-item',
                            content: {
                                elem: 'link',
                                url: subItem.url,
                                content: [
                                    subItem.text
                                ]
                            }
                        });
                    });
                }
                content.push({
                    elem: 'item',
                    mods: { current: item.current, disabled: item.disabled },
                    content: [
                        {
                            elem: 'link',
                            content: [
                                item.text
                            ]
                        },
                        {
                            elem: 'sub-menu',
                            content: sub
                        }
                    ]
                })
            });
            return content;
    }),

    elem('item')(
        tag()('li')
    ),
    elem('link')(
        tag()('a'),
        attrs()(function(){
            var attrs = {};
            this.ctx.url && (attrs.href = this.ctx.url);
            return attrs;
        })
    ),
    elem('sub-menu')(
        tag()('ul')
    ),
    elem('sub-item')(
        tag()('li')
    )
)

bemjson:

{
                        block: 'menu',
                        mix: { block: 'header', elem: 'menu' },
                        content: {                            
                            block: 'nav',
                            items: [
                                {
                                    url: '#',
                                    title: 'Lean more about BEM',
                                    text: 'Главная',
                                    current: true,
                                },
                                {
                                    url: '#',
                                    title: 'Favorite repositories',
                                    text: 'Контакты'
                                },
                                {
                                    url: '#',
                                    title: 'Favorite repositories',
                                    text: 'О нас'
                                },
                                {
                                    url: '#',
                                    title: 'Favorite repositories',
                                    text: 'Ещё',
                                        items: [
                                            {
                                                text: 'Hotmail',
                                                url: '#'
                                            },
                                            {
                                                text: 'Yahoo',
                                                url: '#'
                                            },
                                            {
                                                text: 'Gmail',
                                                url: '#'
                                            }
                                        ]
                                }
                           ]
                        }
                    },

ну наверное придется пока отказаться подобной сложности из-за нехватки опыта с bemhtml =) сделаю просто в bemjson и стилями. К тому же сразу не заметил, такой подход добавлял ненужные вложенные элементы elem('sub-menu').

veged commented 9 years ago

не могу понять, зачем понадобилось задавать кастомный класс через cls?

veged commented 9 years ago

по мелочи: вместо

{
  elem: 'link',
  content: [
    item.text
  ]
}

можно просто

{ elem: 'link', content: item.text }
Guria commented 9 years ago

@Bumerang47 под bemjson я имел ввиду не файл, а кусок bemjson с элементом sub-item который пушится в коде вашего шаблона. Теперь когда я повнимательнее изучил ваш фрагмент за компом, поясню.

item.cls()('my-class'); не работает, потому что в переменной item находится кусок исходного bemjson соответсвующего этому item. Если я правильно понял, то вы зачем то хотите установить кастомный класс для элемента 'item' у которого есть подпункты. Этого можно достичь следующими способами:

item.cls = 'my-class';
sub.push({
  elem: 'sub-item',
  content: {
    elem: 'link',
    url: subItem.url,
    content: [subItem.text]
  }
})

И правильным, который я сейчас напишу отдельным постом. А вообще хотелось бы услышать ответ на вопрос @veged.

UPD: исправил

Guria commented 9 years ago

Второй вариант, обрабатывать дерево в отдельных шаблонах. Получилось приблизительно так (код не проверял, но должен передать общую идею о принципе):

block('nav')(
    tag()('ul'),
    js()(true),
    content()(function(){
      // к каждому пункту item добавить поле elem: item и обработать как bemjson
      applyCtx(this.ctx.items.forEach(function(item){
        return ctx.extend(item, { 
          elem: 'item',
          mods: { current: item.current, disabled: item.disabled }
        });
      }));
    }),
    elem('item')(
        tag()('li'),
        content()(function(){
          applyCtx([
            { 
              elem: 'link',
              url: this.ctx.url,
              content: this.ctx.text
            },
            this.ctx.items && {
              elem: 'sub-menu',
              content: applyCtx(this.ctx.items.forEach(function(subItem){
                return ctx.extend(subItem, { 
                  elem: 'sub-item',
                  mods: { current: subItem.current, disabled: subItem.disabled }
                });
              }))
            }
          ]);
        })
    ),
    elem('link')(
        tag()('a'),
        attrs()(function(){
            var attrs = {};
            this.ctx.url && (attrs.href = this.ctx.url);
            return attrs;
        })
    ),
    elem('sub-menu')(
        tag()('ul'),
        cls('my-class')
    ),
    elem('sub-item')(
        tag()('li'),
        content(function(){
          applyCtx({ 
            elem: 'link',
            url: this.ctx.url,
            content: this.ctx.text
          })
        })
    )
)

До сих пор, правда остался небольшой копипаст и в целом шаблон сложно воспринимать.

Вообще данные шаблоны лучше разделить на шаблоны в bemtree и bemhtml. В первых обходить исходное дерево блока nav и генерировать соответсвующие элементы блока в bemjson. А на bemhtml останется непосредственное превращение bemjson в ожидаемый html.

Guria commented 9 years ago

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

Совсем скоро я его переделаю на bemtree + bemhtml.

uliantsev-a commented 9 years ago

Спасибо за ответы, сейчас изучу, протестирую код подробней.

По поводу вопроса @veged, хочу добавить, как верно предположил @Guria, для элемента 'item' у которого есть подпункты стиль, выделяющий его среди прочих подпунктов.

qfox commented 9 years ago

@Bumerang47 Если я правильно понял кейс, то такой класс правильнее задавать через модификатор. Свойство cls существует для гибкости и, если хотите, математической полноты, но используется крайне редко, в случаях, когда нет возможности использовать модификаторы или миксы — например, для работы с какими-то плагинами, которые используют кастомные классы, и которые нет возможности поправить. В общем случае использование cls не рекомендуется, как и изменять DOM напрямую.

uliantsev-a commented 9 years ago

@zxqfox, верно подметили. Миксы и модификаторы скорее больше подойдут, пробовал их применять, написал про cls, т.к. он был как последняя попытка, более грубого приёма. =\ Думаю от этого суть не сильно изменится, если получиться назначить cls, можно будет заменить на mix?

@veged, спасибо за подсказки "по мелочи", это важно :)

@Guria, первый метод про item.cls = 'my-class'; sub.push({ результата не дает, класс не добавляется. Судя по всему т.к. в этом блоке суть в переменной sub, которая потом через json подставляется в контент ко всем item.

Концепт второго варианта мне очень понравился, попробую его развить. Сейчас этот код вызвал ошибку "AssertionError: mode literal predicates can't have arguments", к моему стыду пока не могу понять почему. Подобная проблема была когда контент не был обернут в функцию, но тут везде как надо.

qfox commented 9 years ago

@Bumerang47 Да, суть, как раз, останется той же, но появится некоторый простор для маневра. Кастомные классы сильно сложнее шаблонизировать, сложнее слушать их изменения, и пр. пр.

Guria commented 9 years ago

@Bumerang47 в моём примере ошибка в использовании cls. Надо заменить на: cls()('my-class'). А лучше на соответствующие mods или mix.

uliantsev-a commented 9 years ago

@Guria cls()('my-class') не решили ошибку, если речь о втором примере. Ошибку вызывает что-то тут кажется (могу ошибаться):

      applyCtx(this.ctx.items.forEach(function(item){
        return ctx.extend(item, {
          elem: 'item',
          mods: { current: item.current, disabled: item.disabled }
        });
      }));
veged commented 9 years ago

у меня главный вопрос — зачем вообще городить всё это с преобразованием кастомного json в БЭМ-термины? почему бы просто не написать примерно так:

{
    block: 'menu',
    mix: { block: 'header', elem: 'menu' },
    content: {
        block: 'nav',
        content: [
            {
                elem: 'item',
                mods: { current: true },
                url: '#',
                title: 'Lean more about BEM',
                content: 'Главная',
            },
            {
                elem: 'item',
                url: '#',
                title: 'Favorite repositories',
                content: 'Контакты'
            },
            {
                elem: 'item',
                url: '#',
                title: 'Favorite repositories',
                content: 'О нас'
            },
            {
                elem: 'item',
                url: '#',
                title: 'Favorite repositories',
                content: 'Ещё',
            },
            {
                block: 'sub-menu',
                content: [
                    {
                        elem: 'sub-item',
                        content: 'Hotmail',
                        url: '#'
                    },
                    {
                        elem: 'sub-item',
                        content: 'Yahoo',
                        url: '#'
                    },
                    {
                        elem: 'sub-item',
                        content: 'Gmail',
                        url: '#'
                    }
                ]
            }
        ]
    }
}

и дальше простые шаблоны, без всякого ада с applyCtx и подменами какими-то не прозрачными

veged commented 9 years ago

если говорить, почему не работает applyCtx(this.ctx.items.forEach(function(item){, то потому, что в applyCtx нужно передавать новый контекст, а функция forEach ничего такого не возвращает — нужно её заменить на map (во втором использовании this.ctx.items.forEach тоже)

uliantsev-a commented 9 years ago

@veged откровенно говоря до меня после нескольких постов этой темы дошло, что тут попахивает избыточным подходом. Да и компонент я делал на основе примера из руководства, задачи которых немного разные. Хотя нагородив в шаблоне bemhtml, можно минимизировать код в файле.bemjson, удобно для быстого конфигурирования блока в будущем на других проектах... но это не было конечно целью и притянуто за уши. Так что вы скорее правы :)

qfox commented 9 years ago

@Bumerang47 Он же дедушка @veged, конечно он прав ;-)