Тенденция последних лет в веб-разработке — фреймворки и компиляторы (сборщики, если кому то угодно). Они везде: в javascript, css, подключаемых библиотеках и т.д. Javascript фреймворки повсеместно используют шаблоны для отрисовки всего и вся. Но у всех них есть одна общая черта — HTML. Громоздкий, свёрстанный, местами от проекта к проекту контрл+ц — контрл+в… Весь этот html всегда лежит кусками, прикрепляется конструкциями вида script type=«text/template». В общем, как будто здесь еще не придумали, как с ним бороться.

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

И сделал — randr. Библиотечка небольшая, работает шустро.

Работает так: на вход одноимённой функции даёте определенный json, на выходе — готовый DOM (или какая-то его часть):

var json = {
 node: 'div',
 content: 'Hello World'
}

document.body.appendChild(randr(json));

randr из этого json`а сделает div с текстом «Hello World» внутри.

Еще можно передавать атрибуты:

var json = {
 node: 'div',
 defaults: [
  { type: 'class', data: 'super-class' },
  { type: 'attr', data: 'some attr value' }
 ],
 content: 'Hello World'
}

document.body.appendChild(randr(json));

Отлично! у нашего div`а появились атрибуты class и attr. Однажды мне finom подсказал сделать «defaults» в виде объекта со свойствами, так говорит компактнее. Сказано — сделано:

var json = {
 node: 'div',
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: 'Hello World'
}

document.body.appendChild(randr(json));

Да, стало компактнее. Оставил оба способа. Но уменьшать объем можно и другим способом. Обычно текст (в роли контента) заключен в теги «p». Именно, так мы и поступим, уберём ноду «div»:

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: 'Hello World'
}

document.body.appendChild(randr(json));

Получилось! По умолчанию если отсутствует «node» а «content» является текстом — рисуем «p», иначе будет «div». Да, можно просто не писать эти пресловутые div:

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: {
   content: {
     content: 'Хватит уже изголяться'
   }
 }
}

document.body.appendChild(randr(json));

Надеюсь ясно, что тут будет параграф, обёрнутый в 2 div`а. И да, контент может быть разный. Например массив:

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: [
  { content: 'параграф 1 с текстом' },
  { content: 'параграф 2 с текстом' }
 ]

document.body.appendChild(randr(json));

Вот уже лучше, но можно добавить еще кое-что! Наследование атрибутов:

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 implement: true,
 content: [
  { content: 'параграф 1 с текстом' },
  { content: 'параграф 2 с текстом' }
 ]

document.body.appendChild(randr(json));

Видно, что появился флаг implement: true — именно он отвечает за передачу атрибутов дочерним элементам. Но дети бывают капризные и не захотят принимать это наследство:

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 implement: true,
 content: [
  { content: 'параграф 1 с текстом', extend: false },
  { content: 'параграф 2 с текстом' }
 ]

document.body.appendChild(randr(json));

Думаю, тут тоже ясно. Отрицательный флаг extend не позволяет наследовать атрибуты.
Наследуются атрибуты по принципу затирания родительских дочерними, кроме стилевых классов. У них происходит склеивание — «родительский-класс дочерний-класс».

Зачем вообще наследовать их кончено каждый решит сам. Я лично использую для вспомогательных классов вроде «float-right, center, background-red». Думаю, придумать можно. Но никто не заставляет пользоваться.

Есть еще фишечка — создание своих собственных «нод». Так сказать готовых блоков хитро сверстанных элементов или просто повторяющихся заготовок:

var myNodes = {
 askDelete: function(content){
  var container = document.createElement('div');
  var bYes = document.createElement('button');
  var bNo = document.createElement('button');
  bYes.innerHTML = content.yes;
  bNo.innerHTML = content.no;
  container.appendChild(bYes);
  container.appendChild(bNo);
  return container;
 }
}

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: [
  { content: 'параграф 1 с текстом' },
  {
    node: 'askDelete',
    content: {
      yes: 'Да, удалить',
      no: 'Нет конечно'
    }
  }
 ]

document.body.appendChild(randr(json, myNodes));

В этом коде мы создали нашу волшебную ноду «askDelete» в объекте «myNodes» и передали объект вторым параметром в randr. А в json мы вызываем эту волшебную ноду и в качестве параметров используем свойство «content». Таким образом можно создавать свои заготовочки и внедрять их в разнообразные проекты.

Если же вам нужно получить от randr не DOM, а верстку в виде текста, то просто добавьте третий параметр «true» в randr:

var myNodes = {
 askDelete: function(content){
  var container = document.createElement('div');
  var bYes = document.createElement('button');
  var bNo = document.createElement('button');
  bYes.innerHTML = content.yes;
  bNo.innerHTML = content.no;
  container.appendChild(bYes);
  container.appendChild(bNo);
  return container;
 }
}

var json = {
 defaults: {
  class: 'super-class',
  attr: 'some attr value'
 },
 content: [
  { content: 'параграф 1 с текстом' },
  {
    node: 'askDelete',
    content: {
      yes: 'Да, удалить',
      no: 'Нет конечно'
    }
  }
 ]

document.body.innerHTML = randr(json, myNodes, true);

На этом всё. Думаю, такая реализация вёрстки с помощью json будет вам полезна. По крайней мере занимает меньше места и можно передавать в Ajax в виде объектов.

Комментарии (19)


  1. rumkin
    23.06.2015 18:40
    +4

    Идея концептуально верная, и в общем-то похожа на веб-компоненты, но реализация слишком механическая и лучше подходит как формат для визуального редактора кода, чем для написания руками. Сравним:

    <img src="./smile.png"/>
    

    и

    {
        "node":"img",
        "defaults":[
            {"type":"src", "data":"./smile.png"}
        ]
    }
    

    Это не наглядно и громоздко. Почему не использовать такой вариант:

    {
        "IMG":{"src":"./smile.png"}
    }
    


    Более сложный пример:
    {
        "DIV": {"class":"plate"},
        "content":[
            {"H3":"Good news everyone"},
            {"P":"It works!"}
        ]
    }
    

    Да, многие недолюбливают капс, но в данном случае это не большая плата за наглядность и компактность.


    1. dolphin4ik Автор
      23.06.2015 18:48

      Да, ваш вариант короче и возможно доделать с таким синтаксисом, но в основном опирался на наглядность самого дерева в виде объекта


    1. alcanoid
      23.06.2015 19:23

      Тогда уж, наверное, как-то
      { "DIV": { "class": "plate", "content": [ {"H3": "Good news everyone"}, {"P": "It works!"} ] } }

      upd: извините за не слишком читаемый вид — от благодати тэгов я, похоже, отлучён.


      1. rumkin
        23.06.2015 22:44

        Нет, именно так, как я указал – у мета-тегов атрибут «content» – это именно атрибут.


        1. alcanoid
          24.06.2015 01:52

          И что это меняет? Обработчик вполне может распознать тип и по нему создать либо атрибут, либо набор вложенных тэгов. А структура в таком виде выходит проще и элегантнее.


    1. BuranLcme
      23.06.2015 22:19

      У себя сделали так

      load(["div", { "class": "plate" }, [
          ["h3", { innerHTML: "Good news everyone" }],
          ["p", { innerHTML: "It works" }]
      ]]);
      

      тип элемента однозначно позволяет определить что это: имя, свойства или дети элемента.


    1. shpaker
      24.06.2015 04:56

      По мне такое было бы приятный:
      {
      ".plate": {
      " h3": «foo»,
      " p#text": «bar»
      }
      }
      Да и не понятно зачем вообще нужны отдельно div, class и пр. Массив в content имхо тоже избыточен.
      P's: сорри за сумбур — пишу с мобилы.


    1. john_samilin
      24.06.2015 10:08
      +3

      А в чем принципиальная разница между

      <img src="./smile.png"/>
      

      и
      "IMG":{"src":"./smile.png"}
      


      И вообще (это уже комментарий к самой статье), я так и не понял зачем менять HTML на JSON


  1. iDennis
    23.06.2015 19:31
    +2

    Я бы написал свой транслятор (в json), убрав кавычки,: и { }. А за основу сделал бы блоки как в coffeescript (пробелами и новыми строками). Да и по умолчанию лучше создавать textnode а не p.


    1. neolink
      23.06.2015 20:05
      +6

      ru.wikipedia.org/wiki/Haml


  1. Andchir
    23.06.2015 22:27

    Ещё на эту тему пример на D3.js

    Код
    var cols = ['name', 'sex', 'age'];
    var data = [
        {name: 'Ivan', sex: 'Male', age: 27},
        {name: 'Denis', sex: 'Male', age: 20},
        {name: 'Elena', sex: 'Female', age: 23},
    ];
    var table = d3.select('body').append('table');
    
    table
        .append('thead')
        .append('tr').selectAll('th').data(cols)
        .enter().append('th')
        .style('padding','5px')
        .text(function (d) {
            return d.toUpperCase();
        });
    
    table
        .append('tbody').selectAll('tr').data(data)
        .enter().append('tr').selectAll('td').data(function (row) {
            return d3.permute(row, cols);
        })
        .enter().append('td')
        .style('padding','5px')
        .text(function (d) { return d; });
    


  1. Grox
    24.06.2015 02:31

    Сделали свой проект это классно. Вы потом поискали, что уже сделали другие?
    Лучший из известных мне результатов это Haml, ссылку на который выше уже привели. Краток, гибок. Этих двух характеристик хватает для того, чтобы он был почти идеален.


    1. dolphin4ik Автор
      24.06.2015 09:39
      -1

      Haml просто так вы не пришлёте с сервера, просто сгенерив скажем на php. Ну и на стороне клиента развернуть — ни css подключить. Json один из самых ходовых форматов, именно он и был взят за основу, так как веб всё ещё состоит из css+html+js. Одной составляющей яваскрипта является json… И да, я предварительно поглядел штук 12-15 библиотек, которые из json собирают страницу. Все они громоздкие и с сложным синтаксисом. Мой оказался проще.


      1. Grox
        24.06.2015 16:46

        Громоздкий, свёрстанный, местами от проекта к проекту контрл+ц — контрл+в… Весь этот html всегда лежит кусками, прикрепляется конструкциями вида script type=«text/template». В общем, как будто здесь еще не придумали, как с ним бороться.
        Я ответил на вот эту вашу претензию к HTML. Хотя она скорее к криворуким верстальщикам. И то, что бороться с этим уже придумали как.

        И HAML можно отправлять так же, как и JSON, и рендерить на стороне клиента, как вы делаете это с JSON.
        А вот указанная вами проблема с громоздкостью в вашем подходе не снята, закрывающие теги заменены на закрывающие скобки, что ещё хуже, потому что по закр. тегу видно от чего он, а по скобкам нет. Хотя это тоже всё давно решено подсветкой в среде программирования. Но HAML принципиально убирает проблему громоздкости, чего не делает ваш подход.

        И да, я предварительно поглядел штук 12-15 библиотек, которые из json собирают страницу. Все они громоздкие и с сложным синтаксисом. Мой оказался проще.
        Я имел ввиду решения с громоздкостью HTML, а не генераторы для вашего подхода. Т.е. вы сделали своё решение, а потом посмотрели, кто уже его реализует. А нужно было посмотреть кто реализует решение описанной в первом абзаце проблемы.


  1. cmepthuk
    24.06.2015 07:33
    -1

    1. Почему не указано, что для работы требуется jquery?
    2. Бегло глянув сорс заметил что jquery используется лишь для setAttribute & getAttribute & appendChild и добавление эвента. Вы блин серьезно для этого таскаете всю либу, что делается 2..3 строчками на pure js?
    3. Описание html в json в результате очень громозкое, как мне кажется
    4. Практическое применение — это легкая альтернатива шаблонизаторам или замена тех зачастую железобетонных html паттернов, что инклудятся в документ из js в обход createNode для повышения быстродействия в целом? На первое совсем не похоже, а второе — нет, ну правда, покажите пример с практической потребностью?
    5. Это опыт, вы наверняка не плохо потренировали свои навыки во время работы над этим проектом, молодцом!


    1. dolphin4ik Автор
      24.06.2015 09:33
      -1

      Нет, jquery там совсем не нужен. Беглый взгляд вас подвёл. А насчёт ивентов, так я их и не описывал. Это тестовая наработка, которая никого и не касается. В самом начале я так и написал что фреймверки грузят страшные шаблоны пачками, а можно их облагородить, вот и всё. Сразу понасливали карму…


      1. cmepthuk
        24.06.2015 10:01

        А это видимо так, просто значок доллара :)
        Строка 49 «randr.js»: $(node).on(k.events[i].type,k.events[i].action);

        Ваша тестовая наработка находится в паблике, что является… не совсем правильно. Есть такая штука как "ответвления", и она довольно удобна, должен признать. Не говоря уже про то, что она принята стандартом де-факто.

        И на счет вашего крайнего предложения в комментарии — никогда не говорите о том, что вы упомянули в крайнем предложении своего комментария (с) хабранародная мудрость