fest icon indicating copy to clipboard operation
fest copied to clipboard

Работа с BEM

Open RubaXa opened this issue 11 years ago • 25 comments

Друзья, есть серьёзный вопрос, по поводу шаблонов и BEM/toolkit.

Чем дольше я их пишу, тем больше мне всё это не нравиться. Слишком избыточно, даже читать шаблон тяжело. Вот пример простого checkbox:

https://gist.github.com/RubaXa/5077273#file-cbx-fest-xml

И это достаточно простой пример, что уж говорить про другие компоненты, такие как b-datalist. Работать с эти просто не возможно, открывая такой шаблон, приходиться минут 5 вникать. Самое смешное, что 5 минут, это если ты сам и написал. Рефакторить ещё сложней.

Вместо этого месива, предлагаю сделать свой синтаксис, для работы с BEM терминологией, например такой:

https://gist.github.com/RubaXa/5077273#file-cbx-bem-xml https://gist.github.com/RubaXa/5077273#file-b-datalist-bem-xml

По мне так этот код понятен и лаконичен, но самое главное, это будут уже не set. Компиляроватся всё будет в единую функию, которая описывает весь блок, со всеми его элементами. Сейчас это у нас set с множеством get'ов, что тормозит.

Что думаете?

RubaXa avatar Mar 03 '13 18:03 RubaXa

Все зависит от вариантов использования https://gist.github.com/RubaXa/5077273#comment-790093 С другой стороны, я за поддержку препроцессора или сахара для феста при работе БЭМ. Готов участвовать в обсуждении.

Upd: https://gist.github.com/RubaXa/5077273#comment-790105

eprev avatar Mar 04 '13 07:03 eprev

https://gist.github.com/RubaXa/5077273#comment-790109

Давайте тут общаться, чтобы не распылять внимание.

RubaXa avatar Mar 04 '13 07:03 RubaXa

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

    &bem b-datalist {
        ctx.head ? &__head ctx.head

        for item in ctx.items {
            &__item item
        }

        ctx.foot ? &__foot ctx.foot
    }


    &bem b-datalist__item {
        cols = $block.getCols();

        mods: {
            "first": $first && !$parent.head
            "last": $last
            "selected": $parent.selected[ctx.id]
        }

        cols.cbx ? &__cbx {
            &cbx { checked: ctx.selected }
        }

        cols.pic ? &__pic {
            style: "background-image: url('{$block.getItemPic(ctx)}');"
        }
    }

Он только для работы с BEM, есть возможность работы с $parent контекстом. Но самое главное, это код будет мега оптимизирован, никаких " b-name_" + mods.join(" b-name_").

Готов рассматривать альтернативу, но как по мне, лаконичнее этого, только телепатия.

RubaXa avatar Mar 04 '13 07:03 RubaXa

Я не буду тебе мешать :-) Мне нравится. Я тебя правильно понимаю, что &bem ... будет трансформирован в fest:script? И как будет происходить вызов блока в шаблоне?

eprev avatar Mar 04 '13 08:03 eprev

Действительная реальность состоит в том, что params.mods не пригоден для реальной разработки, рассмотрим пример:

<fest:set name="b-input">
    <div class="b-input{ params.mods ? ' b-input_' + params.mods.join(' b-input_') : '' }">
        <fest:attributes>
            <fest:attribute test="Array.indexOf(params.mods, 'disabled') != -1" name="aria-disabled">disabled</fest:attribute>
        </fest:attributes>
        <input type="text" class="b-input__el" value="{params.value}">
            <fest:attributes>
                <fest:attribute test="Array.indexOf(params.mods, 'disabled') != -1" name="disabled"/>
                <fest:attribute test="Array.indexOf(params.mods, 'readonly') != -1" name="readonly"/>
            </fest:attributes>
        </input>
    </div>
</fest:set>

Ну как, читабельно? Быстро ли будет работать такой код? Нет и нет!

Но как бы было красиво, если бы оно выглядело подобным образом:

&bem b-input {
    mods: {
        "disabled": ctx.disabled
        "readonly": ctx.readonly
    }

    &__el {
        tag: "input"
        attrs: {
            type: "text"
            value: $block.value
            readonly: $block.mods.readonly
        }
        disabled: $block.mods.disabled
    }
}

В шаблоне вызов будет примерно так:

<fest:template>
     &b-input {
         value: json.username
     }
</fest:template>

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

RubaXa avatar Mar 04 '13 08:03 RubaXa

Ошибки при компиляции и т.п. всё будет, это не проблема. Конечно цельной картины у меня ещё нет, есть наброски, как и что компилировать, в какую примерно функцию всё это превращать. Сейчас главное понять, всех ли это устраивает. Моё мнение думаю понятно :]

RubaXa avatar Mar 04 '13 08:03 RubaXa

На счет быстро – покажут только тесты ;-) Выглядит круто, мне нравится что через entities реализовано. Переопределение блоков будет?

eprev avatar Mar 04 '13 08:03 eprev

Если сделаешь тесты на производительность, то давай.

AndrewSumin avatar Mar 04 '13 08:03 AndrewSumin

А если не сделает?

eprev avatar Mar 04 '13 08:03 eprev

Если сделаешь тесты на производительность, то давай.

Андрей, прежде чем делать, нужно понять, как видят другие это, может только я ною, а всем остальным хорошо. Важно мнение всех, кто сейчас использует fest.

RubaXa avatar Mar 04 '13 08:03 RubaXa

Хотелось бы еще поддержку такого:

<fest:template>
     &b-page {
         title: json.title,
         head: (
             &b-page__style { href: '....' }
         ),
         html: ( 
             &b-header json.header
             &b-footer json.footer
        )
     }
</fest:template>

eprev avatar Mar 04 '13 08:03 eprev

Нужно думать, но это уже сложней.

RubaXa avatar Mar 04 '13 08:03 RubaXa

Хотелось бы чтобы препроцессор был ориентирован на работу с HTML-сущностями. Добавления классов и id я вижу так:

div#id.foo
    if true 
        &.bar
     else 
         &.baz
<div id="id" class="foo bar" />

Без & результат трансляции будет как в Jade:

<div id="id" class="foo">
    <div class="bar" />
</div>

monolithed avatar Mar 04 '13 09:03 monolithed

Саш, в том то и дело, я не собираюсь делать Jade, мне это не нужно, это никак не поможет в решении той задачи, про которую я говорю. Я хочу получить инструмент для создания BEM компонентов.

RubaXa avatar Mar 04 '13 09:03 RubaXa

Jade, который создаёт БЭМ-компоненты

Объявление

@block bem_block_input(value):disabled:readonly // псевдо-классы == модификаторы
  .b-input                            // Тэг по-умолчанию - div
    input.&__el(                      // & работает относительно блока
      value=value
      , disabled=matches(:disabled)  // Вместо matches и псевдо-класса, возможна другая функция
      , readonly=matches(:readonly)  //  например mod(disabled)
    )
  #{__context__}

@export bem_block_input as b-input

Использование

@import b-input     // Компоненты могут импортироваться по-умолчанию, поэтому эта строчка не обязательна

+b-input("Значение инпута"):disabled   // :disabled - выставляет модификатор
  span.hint Подсказка, которая будет вставлена в __context__

Пример со страницей:

@block page(title)
  html
    head
      title #{title}
    body.b-page
      div.&__body=body
      div.&__footer
        =footer          //div внутри b-page__footer

@export page as b-page

Использование

@import b-page

+b-page("Заголовок страницы")
  =body
    div
      span Контент страницы
  =footer
    footer
      div Футер страницы

termi avatar Mar 04 '13 10:03 termi

Дополнение к ответу Егора:

# инлайновый блок
@set __block1(title)
....

@set block2(title)
    @get  __block1
.... 

@set block3(title)
    @get  __block1
.... 

@export block1 as b-page1, block2 as b-page2

monolithed avatar Mar 04 '13 10:03 monolithed

Во, Егор дело говорит, не в смысле что мне все нравиться, а в смысле он понимает меня :] Но, по мне так это вырви глаз, но это моё никчемное мнение.

Егор, вот покажи, как у тебя будет выглядить такой блок:

Описание:

&bem b-dropdown {
    mods: {
        "disabled": ctx.disabled
        "expanded": ctx.expanded
    }

    data: {
        group: ctx.group
        mnemo: ctx.mnemo || ctx.group
    }

    for item in ctx.items {
        if item == 'hr'
            &__hr
        else
            &__item item
    }
}


&bem b-dropdown__item {
    mods: {
        "active": ctx.id == $block.actveId
        "disabled": ctx.disabled
    }

    data: {
        "id": item.id
        "name": item.name
    }

    context: ctx.text
}

Вызов

&b-dropdown {
     name: 'markAs'
     items: [
        { name: '...', text: '...' },
        'hr',
        { etc }
     ]
}

RubaXa avatar Mar 04 '13 10:03 RubaXa

Как-то так. Для читаемости, можно предусмотреть пробелы и переносы строк в модификаторах, но это уже не по jade-синтаксису

@mixin dropdown_block_item(name, id):active=&.actveId:disabled //:active=&.actveId - Значение по-умолчанию и & - ссылка на родительский блок
  .&__item(data-id=id, data-name=name)
    @yield                       // Вывод контента, тоже самое, что и #{__context__}

@block dropdown_block( group, items
  , mnemo=group // Значение по-умолчанию
):disabled:expanded
  .b-dropdown(data-group=group, data-mnemo=mnemo)
    @each item in items
      @if item == "hr"
        .&__hr
      @else
        +dropdown_block_item(item.name, item.id):item...     // :<var>... - означает "берём модификаторы"

@export dropdown_block as bem.b-dropdown

Вызов:

@import bem.b-dropdown as b-dropdown

@var items = [
        { name: '...', text: '...' },
        'hr',
        { etc }
     ]

+b-dropdown('markAs', items)

termi avatar Mar 04 '13 10:03 termi

Егор, я конечно всё понимаю, но просто взгляни: http://www.rubaxa.org/screenshot/add7ccaeca04b744d4f71a39a542.png

И главное, твой пример не тоже самое, что у меня.data-group, модификатор active, всё не так.

RubaXa avatar Mar 04 '13 10:03 RubaXa

Ну это просто пример синтаксиса. Реализацию можно сделать чего-угодно. Хотя, я тоже понимаю, что выходит несколько громоздко. Но, зато, это почти jade

termi avatar Mar 04 '13 10:03 termi

Jade решает свою задачу, у нас она иная.

RubaXa avatar Mar 04 '13 10:03 RubaXa

Костин вариант выглядит проще.

Если сделать, так что &bem b-page будут скомпилированы в обычный сет-блок (тебе по сути без-разницы, это обычная функция от параметров ctx и __fest_context), то будет возможность вызвать p-page и передавать в него XML данные.

eprev avatar Mar 04 '13 11:03 eprev

Как вариант:

@set block (data) {
    div
        @xml::attr name as name:
            name = | data.disabled => \foo
                   | otherwise     => \bar

        @xml::attr class as class:
            @if data.foo:
                class = \foo
            @else:
                class = \bar

            class += \ baz
}

@get block (data)

Пытался решить такую задачу:

<fest:template>
    <fest:set name="block">
        <div>
            <fest:attributes>
                <fest:attribute name="name">
                    <fest:choose>
                        <fest:when test="data.disabled">
                            <fest:text>foo</fest:text>
                        </fest:when>

                        <fest:otherwise>
                            <fest:text>bar</fest:text>
                        </fest:otherwise>
                    </fest:choose>
                </fest:attribute>

                <fest:attribute name="name">
                    <fest:choose>
                        <fest:when test="data.foo">
                            <fest:text>foo</fest:text>
                        </fest:when>

                        <fest:otherwise>
                            <fest:space />
                            <fest:text>bar</fest:text>
                        </fest:otherwise>
                    </fest:choose>

                    <fest:space />
                    <fest:text>baz</fest:text>
                </fest:attribute>
            <fest:attributes>
        </div>
    </fest:set>
</fest:template>

</fest:template>    
     <fest:get name="block">data</fest:get>
</fest:template>

PS: Есть основные управляющие конструкции типа: set, get, if, else ..., а также собственные расширения которые предполагается использовать через неймспейсы: xml::attr

По поводу модификаторов еще придумал как лучше это организовать, но предполаю что также через расширение (по аналогии с атрибутами)

monolithed avatar Mar 26 '13 09:03 monolithed

Если честно, я ничего не понял, аля Jade Егора выглядит лучше.

RubaXa avatar Mar 27 '13 07:03 RubaXa

Да сам еще не определился как это должно выглядеть (мысли в слух) :)

@set block (data) {
    div
        @xml:attr name as name {
            @switch
                @when data.disabled:
                    name = 'foo'

                @else:
                    name = 'bar'
        }

        @xml::attr class as class {
            @if data.foo:
                class = 'foo'
            @else:
                class = 'bar'

            class += ' baz'
        }

        input
            @xml::bem::modifier input {
                @name checked    :
                    data.checked

                @name disabled   :
                    data.disabled
            }

            @xml::attr {
                name: 'verify'
                type: 'checkbox'
            }
}

Результат:

<fest:template>
    <fest:set name="block">
        <div>
            <fest:attributes>
                <fest:attribute name="name">
                    <fest:choose>
                        <fest:when test="data.disabled">
                            <fest:text>foo</fest:text>
                        </fest:when>

                        <fest:otherwise>
                            <fest:text>bar</fest:text>
                        </fest:otherwise>
                    </fest:choose>
                </fest:attribute>

                <fest:attribute name="class">
                    <fest:choose>
                        <fest:when test="data.foo">
                            <fest:text>foo</fest:text>
                        </fest:when>

                        <fest:otherwise>
                            <fest:space />
                            <fest:text>bar</fest:text>
                        </fest:otherwise>
                    </fest:choose>

                    <fest:space />
                    <fest:text>baz</fest:text>
                </fest:attribute>
            <fest:attributes>

            <input type="ckeckbox" name="verify">
                <fest:attributes>
                    <fest:attribute name="class">
                        <fest:text>block__input</fest:text>

                        <fest:if test="data.checked">
                            <fest:space />
                            <fest:text>block__input_checked</fest:text>
                        </fest:if>

                        <fest:if test="data.disabled">
                            <fest:space />
                            <fest:text>block__input_disabled</fest:text>
                        </fest:if>

                    </fest:attribute>
                <fest:attributes>
            </input>

        </div>
    </fest:set>
</fest:template>

Т.е. в своем примере я не убиваю работу с атрибутами, классами и основыми управляющими конструкциями. В тоже время, здесь есть возможно расширять синтаксис как угодно (пример с модификаторами)

UPD: обновил пример

monolithed avatar Mar 27 '13 09:03 monolithed