bem-site / bem-forum-content-ru

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

Динамика(меню) #214

Open Drekrosh opened 9 years ago

Drekrosh commented 9 years ago

Здравствуйте , у меня есть меню и там 10 списков li , как мне сделать так, чтобы я в бэм дереве его статически не описывал, а как то динамически вывести его. Хотя бы подсказку увидеть, в какую сторону мне двигаться чтобы этого добиться.

tadatuta commented 9 years ago

@Rahnar что именно подразумевается под динамикой?

Drekrosh commented 9 years ago

Ну , например из json дергать инфу , использовать его как некую такую базу данных. Либо как - то так, чтобы я не расписывал колбасу из 10 штук , написал один раз нужную вложенность, начиная с блока меню , соотвественно его элемент один li , который был бы описан динамически , а на выходе я получил уже собранное меню из 10 шт. Или вообще нет смысла от этих манипуляций ?? Как Вы друзья из Яндекс, описываете подобные вещи? Извините за идиотские вопросы , щас уровень наберу и все будет ок)

tadatuta commented 9 years ago

@Rahnar Похоже, все равно получается 3 варианта ответа :)

1. Чтобы «не писать колбасу».

BEMJSON — это JavaScript и можно смело пользоваться такими конструкциями:

{
    block: 'menu',
    content: [
        { title: 'Заголовок 1', url: 'http://site1.com/' },
        { title: 'Заголовок 2', url: 'http://site2.com/' },
        { title: 'Заголовок 3', url: 'http://site3.com/' }
    ].map(function(item) {
        return {
            elem: 'item',
            content: {
                block: 'link',
                url: item.url,
                content: item.content
            }
        };
    })
}

Очевидно, что это по-прежнему будет статика, мы просто избавляемся от колбасы средствами JS прямо в статичном bemjson.js-файле.

2. Чтобы «написал один раз нужную вложенность».

Можно унести все в шаблон. bemjson.js:

{
    block: 'menu',
    content: [
        { title: 'Заголовок 1', url: 'http://site1.com/' },
        { title: 'Заголовок 2', url: 'http://site2.com/' },
        { title: 'Заголовок 3', url: 'http://site3.com/' }
    ]
}

bemhtml:

block('menu').content()(function() {
    return applyNext().map(function(item) {
        return {
            elem: 'item',
            content: {
                block: 'link',
                url: item.url,
                content: item.content
            }
        };
    });
});

3. Пункт «чтобы из json дергать инфу» предполагает несколько очень разных вариантов решения:

3.1. Написать node.js-скрипт, который использовать вместо dev-сервера:

build.js

var data = require('db.json'),
    BEMHTML = require('./desktop.bundles/index/index.bemhtml.js').BEMHTML;

console.log(BEMHTML.apply({
    block: 'menu',
    content: data.map(function(item) {
        return {
            elem: 'item',
            content: {
                block: 'link',
                url: item.url,
                content: item.content
            }
        };
    })
})); // вернется HTML-строка

Это, конечно, неудобно. Код я привел, чтобы было понятно, что там происходит под капотом.

3.2. В ветке bem-tools есть коммит про поддержку require прямо в bemjson.js-файлах.

Можно склонировать ее в node_modules, сказать там npm link и пользоваться bem server для сборки. Это уже удобнее, но придется пользоваться пока еще невыпущенной версией сборщика.

3.3. Настроить сборку так, как описано в документе «Шаблонизация данных в bem-core».

Если коротко, то схема предполагает 2 этапа шаблонизации:

  1. Берем сырые данные (из БД, какого-нибудь API или того самого JSON-файла на файловой системе) и преобразуем их в BEMJSON. Для этого можно использовать как простую JS-функцию, так и шаблоны вроде bem-priv или BEMTREE.
  2. Полученный в первом пункте BEMJSON отдать в BEMHTML/BH, чтобы получить HTML и вернуть его в браузер.

Пример готовой настройки для ENB можно взять тут https://github.com/tadatuta/bem-bemtree-static-project-stub Отличия от официального project-stub видны в этом коммите:

Важное отличие от подхода из project-stub: теперь декларацией для сборки служит не bemjson.js, а bemdecl.js, т.к. bemjson.js генерируется на основе BEMTREE-шаблонов динамически в процессе сборки. В данном конкретном случае точкой входа служит блок root. Все остальные блоки попадают в сборку по зависимостям.

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

4. Полноценное динамическое приложение

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

Я обязательно напишу про этот пункт отдельный подробный пост и сделаю демо-репозиторий. А пока рекомендую посмотреть https://github.com/bem/sssr и сопровождающее видео с BEMUp-а.

belozer commented 9 years ago

@tadatuta сейчас возникла таже необходимость :)

Из перечисленного делаю 2-ым способом, но больше всех нравится 3.2 (т.к. для 2-го варианта необходимо "чистить" входные данные, чтобы визуально они не засоряли bemjson)

Drekrosh commented 9 years ago

Ого!!Спасибо огромное , Владимир! Очень все доходчиво! И вот такой вопрос еще, начал изучать фреймверк i-bem.js , все никак не пойму, он полностью заменяет нативный javascript, тобишь мы пишем на нем используя ваш синтаксис и ООП. Он позволяет решать небольшие задачи , типа слайдер написать? И можно ли обходиться без него?Хотя , я намерен его использовать ,просто хочу понять диапазон его применения.Или на нем можно сделать все то, что можно сделать на жуке(jquery)?

belozer commented 9 years ago

@tadatuta а можно использовать переменные? Допустим у меня данные дублируются, но используются в разных блоках по разному. Как это будет выглядить в bemjson?

apsavin commented 9 years ago

@Rahnar i-bem.js - это вполне себе нативный js. Да, он позволяет решать небольшие задачи, "типа слайдер написать". Вот вам пример карусели. Под капотом у i-bem jquery, так что да, на нем можно сделать все то, что можно сделать на jquery.

tadatuta commented 9 years ago

@Rahnar Хорошими примерами компонентов на i-bem.js могут служить блоки из bem-components.

По поводу i-bem.js и jQuery: у i-bem-блоков есть поле domElem, которое является указателем на jQuery-chain данного блока. Т.е. this.domElem в i-bem-блоке button — это то же самое, что $('.button').eq(0). Кроме того все элементы представлены jquery-коллекциями (this.elem('control') == $('.button__control')).

Можно сказать, что i-bem.js добавляет к jQuery возможность декларативно описывать компоненты и предоставляет хелперы для работы с предметной областью БЭМ (проверить, установить или поменять модификаторы, найти элементы и т.п.).

tadatuta commented 9 years ago

@belozyorcev

а можно использовать переменные?

Вот прямо совсем как в любом JS-файле ;) Ну, например,

(function() {

var ololo = [1, 2, 3];

return {
    block : 'page',
    title : 'Title of the page',
    favicon : '/favicon.ico',
    head : [
        { elem : 'css', url : '_index.css' }
    ],
    scripts: [{ elem : 'js', url : '_index.js' }],
    mods : { theme : 'islands' },
    content : ololo[2]
};

})()
belozer commented 9 years ago

@tadatuta это именно в bemjson ? Или в шаблонах? В bemhtml я свободно пользуюсь, а вот задать где-то глобально в bemjson не пойму как

tadatuta commented 9 years ago

@belozyorcev В bemjson. Можно вот прямо так целиком скопировать сниппет, который я привел выше, он будет работать ;)

bemjson.js-файл в project-stub просто эвалится в пустом контексте (vm.runInNewContext()) и результат отдается в BEMHTML.apply(). Т.е. важно, чтобы в результате выполнения bemjson.js вернулась строка или js-объект, а внутри может быть произвольный JS-код.

PS: ENB для выполнения bemjson.js-файлов использует метод requireOrEval, что означает, что в bemjson-файлах можно использовать require уже сейчас, если экспортировать их как common.js-модули:

index.bemjson.js:

var bemjson = {
    block: 'page'
};

bemjson.content = require('./data.json');

module.exports = bemjson;
Guria commented 9 years ago

Я тоже сейчас пилю двухуровневое меню на базе bem-components. Надеюсь через неделю выложить на общий суд и собрать фидбек.

awinogradov commented 9 years ago

По-моему, самый простой и верный способ собирать такое меню в bemtree шаблоне. Выглядеть будет примерно так:

block('header')(
    content()(function() {
        var menu = data.items.map(function(item) {
            return {
                block : 'menu-item',
                mods: { type: 'link' },
                url : item.url,
                content : item.title
            };
        });
        return [
            {
                 elem : 'menu',
                 content : [
                     {
                          block : 'menu',
                          content : menu
                     }
                 ]
             }
        ];
    })
);
belozer commented 9 years ago

@tadatuta - а путь require('./data.json') относительно чего строится?

belozer commented 9 years ago

@tadatuta. С путём разобрался :) Относительно bemjson файла.

Вот другая задача. Делаю следующее

...
{
    block: 'b-map-list-p',
    js: true,
    mix: [{block: 'b-map-filters', elem: 'profgroups'}],
    mods: {theme: 'second'},
    content: [
        {
            elem: 'inner',
            list: (function() {
                var content = require('../../json/profgroups.json');
                module.exports = content;
            })()
        }
    ]
}
...

но получаю при перезагрузке браузера undefined. Пока не знаю куда копать...

в list я должен получить массив элементов, которые затем обработать в bemtree/bemhtml

tadatuta commented 9 years ago

@belozyorcev Если я правильно угадываю, чего хочется добиться, то должно быть как-то так:

var bemjson = [
// ...
{
    block: 'b-map-list-p',
    js: true,
    mix: [{block: 'b-map-filters', elem: 'profgroups'}],
    mods: {theme: 'second'},
    content: [
        {
            elem: 'inner',
            list: require('../../json/profgroups.json')
        }
    ]
}
// ...
];

module.exports = bemjson;
belozer commented 9 years ago

@tadatuta я не понимаю, куда это вставлять в bemjson файле? У нас же оно построенно как объект JS. Но тут у нас появляется переменная bemjson. У меня начинает путаница возникать в голове...

П.Н. Что делает это код понимаю, а вот как использовать этот код в bemjson файле нет.

tadatuta commented 9 years ago

@belozyorcev bemjson.js — это просто javascript-файл. все, что требуется — это чтобы в результате его выполнения экспортировалось дерево. а код может быть совершенно произвольным. поэтому то, что я привел выше — это и есть контент bemjson.js-файла. если начинать от page, то он может выгядеть, допустим, так:

// cat desktop.bundles/index/index.bemjson.js

module.exports = {
    block : 'page',
    title : 'Title of the page',
    favicon : '/favicon.ico',
    head : [
        { elem : 'meta', attrs : { name : 'description', content : '' } },
        { elem : 'meta', attrs : { name : 'viewport', content : 'width=device-width, initial-scale=1' } },
        { elem : 'css', url : '_index.css' }
    ],
    scripts: [{ elem : 'js', url : '_index.js' }],
    mods : { theme : 'islands' },
    content : {
        block: 'b-map-list-p',
        js: true,
        mix: [{block: 'b-map-filters', elem: 'profgroups'}],
        mods: {theme: 'second'},
        content: [
            {
                elem: 'inner',
                list: require('../../json/profgroups.json')
            }
        ]
    }
};
belozer commented 9 years ago

@tadatuta теперь в моей голове всё прояснилось и стало на свои места :) Спасибо, всё заработало :) Теперь bemjson файл похудеет раза в 3-4


После перевода на require подход - размер bemjson уменьшился в 5 раз. Стало быстрее и проще ориентироваться в "чертеже" проекта

Drekrosh commented 9 years ago

Делаю сейчас меню по такому принципу:

bemjson:

{
                            block: 'menu',
                            content: [
                                { title: 'Главная',  url: 'http://site1.com/' },
                                { title: 'Услуги',     url: 'http://site2.com/' },
                                { title: 'Контакты', url: 'http://site3.com/' }
                            ]
                        },

в бемхтмл кладу

block('menu').content()(function() {
    return applyNext().map(function(item) {
        return {
            block: 'item',
            content: {
                block: 'link',
                url: item.url,
                content: item.title
            }
        };
    });
});

выдает ошибку Cannot read property '0' of undefined а если засунть мой массив с title и url в сам bemhtml и сделать на него map, то он увидит содержимое массива...

Guria commented 9 years ago

Блок menu из bem-components поддерживает только menu-item или menu__group в свойствах content

Drekrosh commented 9 years ago

@Guria тобишь так

block('menu').content()(function() {
return applyNext().map(function(item) {
return {
elem: 'menu-item',
content: {
block: 'link',
url: item.url,
content: item.title
}
};
});
});
Guria commented 9 years ago

Почти. menu-item это отдельный блок: https://ru.bem.info/libs/bem-components/v2.0.0/desktop/menu-item/ PS. код можно оформлять с помощью github-flawored markdown

Guria commented 9 years ago

Вот пример из моей реализации меню: https://github.com/Guria/bem-drawer-menu/blob/master/common.blocks/kg-menu/__items/_type/kg-menu__items_type_main.bh.js#L10-L32

Drekrosh commented 9 years ago

@Guria а почему может не работать блок link я обернул в нее блок image и он как тэг определился сам , а вот блок линк не хочет((

Guria commented 9 years ago

@Rahnar продемонстрируй код

tadatuta commented 9 years ago

@Rahnar Предположу, что не хватает зависимостей в deps.js

Drekrosh commented 9 years ago

как правильно код вставить , красивый ? Я оборачиваю в тег <code> чет не получается..

Drekrosh commented 9 years ago

@tadatuta Вы правы Владимир, написал deps и все прекрасно увидел.

tadatuta commented 9 years ago

как правильно код вставить , красивый ? Я оборачиваю в тег <code> чет не получается..

Три бек-тика (`) и опционально слитно язык (например, js) в начале и просто три бек-тика в конце.

Drekrosh commented 9 years ago

@tadatuta

    block('social').content()(function() {
    return applyNext().map(function(item) {
        return {
            elem: 'item',
            content: {
                block: 'link',
                url: item.url,
                content: {
                    block: "image",
                    url: item.img
                }
            }
        };
    });
});

Я написал вот такой вот код, все работает , deps написал для вложенных блоков в блок social. Но не видит теперь элемент item. Он его в коде рисует, но стили не могу к нему применить. Опять в deps дело?

tadatuta commented 9 years ago

@Rahnar думаю, да. общее правило: все сущности, которых нет в bemjson (если речь о статичной странице) и которые вынесены в отдельный файл на файловой системе, должны быть задекларированы в deps.js, чтобы сборщик знал, что их нужно собирать.

Drekrosh commented 9 years ago

@tadatuta Я разобрался , короче все дело в том, что я то писал в social.deps.js где явно указал на елемент item , чтобы сборщик его видел. Но он по факту его не видел , я решил дать другое имя , не item а items и тут же все заработало. item- это что, зарезервированное имя какое-то?Почему он на него так реагирует. На любые другие имена , пожалуйста.

Drekrosh commented 9 years ago

Вопрос снимается, все ответы на вопросы мною найдены и разобраны. Спасибо за терпение и помощь!)

nejtr0n commented 7 years ago

Так и не смог победить кэш require (bemjson). Пытаюсь разбить страницы на участки и динамично инклудить, но всё портит кэш. Победить вообще никак?

qfox commented 7 years ago

Победить вообще никак?

  1. Использовать https://github.com/gulp-bem/node-eval с fs.readFileSync ВМЕСТО require.
  2. Использовать https://github.com/sindresorhus/clear-require вместе с require, непосредственно ПЕРЕД.
nejtr0n commented 7 years ago

Спасибо за наводку! буду пробовать )