Я уже знаю ваши мысли: «ээээ, 2017ый год на календаре, везде ангуляр да реакт, jsx там всякие, ты о чём вообще?». Но, вся проблема в том что, к сожалению, в мире существуют ещё очень и очень много проектов с frontend на ванильном js или на лапше из jquery, и зачастую есть ряд причин не переводить их на современные фреймворки. А поддерживать дальше их надо. Написав очередное уродство в стиле
'<spаn id="' + id + '">'
я задумался — да неужели нет варианта поярче?
А что не так с готовыми решениями?
Следует начать с того что со стандартом ES6 у нас появились замечательные string templates, которые, на самом деле, решают несколько проблем генерации html вручную. Они умеют в шаблонизацию, и можно забыть о всех этих мерзких '" + text + "'.
Но, при этом всём, бывают случаи когда и темплейты не спасают. Например, они не умеют в автоматическую экранизацию, даже с ними можно промахнуться с какой-нибудь скобкой атрибутов html, и.т.д. Поэтому у js есть свои методы для создания валидного html, но, как всегда, использовать их напрямую крайне неудобно. Чтобы по фен-шую создать один блок с текстом внутри нам надо сделать целую кучу действий:
var div = document.createElement('div').append(document.createTextNode('text'))
Многословненько, как для такого простого действия. Именно поэтому существуют библиотеки которые делают это более компактно. Что же с ними не так? Всё та же громоздкость. Я перебрал несколько библиотек, и все они делают более-менее одно и тоже. Вкратце, все выглядят примерно как-то вот так (скопировано с официальной документации github.com/insin/DOMBuilder):
with(DOMBuilder.dom) {
var article =
DIV({'class': 'article'}
, H2('Article title')
, P('Paragraph one')
, P('Paragraph two')
)
}
ИМХО, выглядит сомнительно, сложночитаемо, и надо читать документацию чтобы понять, например, как установить дата-атрибут, есть ли все теги, что возвратит это вот всё, и.т.д. Поэтому, как принято у js-разработчиков, написал своё решение.
Idobi.js
Именно так я назвал свою, если её можно так назвать, библиотеку.
Библиотека предоставляет «текучий» интерфейс. Чтобы установить атрибут — нужно просто вызвать функцию с именем этого атрибута, и передать в неё значение. Чтобы добавить вложенный элемент — вызвать всю цепочку как функцию, и передать в неё вложенные элементы. В конце всего нужно вызвать get(), чтобы получить dom элемент, или же getString(), чтобы получить html-строку. Вот и все правила! Впрочем, лучше один раз показать
// underscore-нотация преобразовывается в кебаб-нотацию
_.div.data_test('42').getString() // <div data-test="42"></div>
// вообще любые теги, так как они нигде не захардкожены, и подтягиваются динамически
_.custom("content").getString() // <custom>content</custom>
// вложенные теги
_.div.class('row')(
_.div.class('col-md-6')("one"),
_.div.class('col-md-6')(_.h1.id("header")("two")),
).getString()
/*
<div class="row">
<div class="col-md-6">one</div>
<div class="col-md-6">
<h1 id="header">two</h1>
</div>
</div
*/
Исходный код на Github
Комментарии (7)
vlreshet Автор
02.11.2017 21:04Aingis А что с экранированием? Насколько я знаю, еs6 шаблоны ничего сами не экранируют, и xss вполне себе пройдёт, в случае чего.
kahi4
02.11.2017 21:23А документацию открыть? Функция (в данном случае render) получит исходную разобранную на составляющие строку и массив аргументов, которые нужно в нее подставить. Берите и экранируйте.
kahi4
02.11.2017 21:20class
,get
являются зарезервированными словами, крайне не стоит их использовать не по назначению (особенно как метод)
continue в конце тела цикла без условия? Оригинально, мягко говоря.
Тут либо должна быть проверка на массив перед этой конструкцией и использование for-of, либо hasOwnProperty
Proxy не работает в <= IE11 и не полифилится.
_
давно зарезервировано underscore (и братом-плагиатом lodash), будут конфликты имен.
2017 год, говорите? А где umd? Установка через npm и подобные странные никому не нужные вещи.
Для
getString
естьtoString
, почему не переопределить его?
class BaseElement extends Function {
оригинально. Зачем?
- Aingis абсолютно прав про Template literals (ну и hyper).
bgnx
02.11.2017 21:41Возьмите плагин babel-plugin-transform-react-jsx замените настройку вызова создания реакт-элементов на свою функцию
{ "plugins": [ ["transform-react-jsx", { "pragma": "h" }] ] }
дальше заимпортируйте функцию h, которая будет создавать дом-элементы
function h(tag, attrs, ...children){ const el = document.createElement(tag); for(const key in attrs){ el.setAttribute(key, attrs[key]); } for(const child of children){ el.appendChild(typeof child === 'string' ? document.createTextNode(child) : child) } return el; }
И у вас получится еще более удобный интерфейс для создания html как например
const TodoApp = (state)=>( <div> <div style={state.style}>{state.title}</div> {state.todos.map(todo=>Todo(todo)} </div> ) const Todo = (todo)=>( <div>{todo.text}</div> ) const app = <TodoApp state={{title: 'todo app', style:'background: aqua', todos: [{text: 'todo1'}, {text: 'todo2'}]}}/>; document.body.appendChild(app)
где вообще не нужен ни какой-то текучий интерфейс ни болерплейта вызова функций и сразу видно где верстка а где код. При этом получаем широкую поддержку редакторов, различных линтеров и статической типизации.
А дальше если захотите добавить оптимизацию чтобы при обновлении интерфейса не пересоздавались лишние дом-элементы (не было лишних вызовов createElement и setAttribute) можно в функции h() создавать и возвращать только временные js-объектов описывающие верстку{tag: 'div', attrs : {style: '...'} , children: [..]}
а потом отдельная функция рекурсивно пройдется по новым объектам и сравнив их с объектами из предыдущего прохода и применит только нужные изменения в реальном думе. И вуаля — вы придумаете виртуальный дум. А если добавите еще 20 тысяч строк кода — у вас получится реакт ;)
vintage
02.11.2017 21:46Смотрите как это можно выразить на JSX с кастомным обработчиком:
// создали дом const dom = <div className="row"> <div className="col-md-6">one</div> <div className="col-md-6"> <h1 id="header">two</h1> </div> </div> // сериализовали в строку dom.outerHTML // нашли заголовок по селектору dom.queryselector( '#header' )
Aingis
Если уж говорить за ES6, то проще взять Template literals и hyper(HTML), который заодно предоставляет виртуальный DOM — то есть обновляет только те части HTML, в которых изменились данные.