Close

11.12.2015

А у меня новый JS движок для компонентов — просто прелесть

Lisp ушка , матушка, опять ты нас всех выручаешь 🙂

я уж было подумал грешным делом, что придется какой-то язык прикручивать, который вJS компилируется, ну не хватало возможностией.. все выглядело как костыли… а тут придумал. переделал немного AL и

ТЕПЕРЬ МЫ МОЖЕМ КОМПОНЕНТЫ ДЕЛАТЬ КОТОРЫЕ МОЖНО ИСПОЛЬЗОВАТЬ В ЛЮБЫХ КОНЦЕПЦИЯХ ВЕБ ПРИЛОЖЕНИЙ.

А шаблонизатор-то какой получился… тащусь!

представляю ??? названия нету пока.

 

предыстория

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

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

Легко и просто получались простые компоненты типа: редакторы значения на месте,  всякие выбиралки значений, хинты, в общем такие, у которых структура внутреннего  HTML была фиксированной на протяжении их жизни.

А вот когда надо было менять структуру html я захотел использовать директивы AL внутри компонента. Это было ошибкой и об этой проблеме я написал об этом маленький пост: «облом с AL (Angular Light) для разметки».

 

текст спёртый с хабра

Когда я работал в предыдущей (прошлой?) фирме, при выборе шаблонизатора Смарти был выбран именно из-за верстальщика. Сколько потом мата было высказано, сколько костылей было придумано, кода перерыто, функционала переписано — история отдельная. Бывшие коллеги мучатся до сих пор. А поезд ушел. Переписывать шаблоны поздно. И теперь умные/способные ребята, а также бизнес стал заложниками Smarty. Да, фреймворк — Symfony, если что.

А пользоваться шаблонизаторами, или писать свои, или вообще на голом РНР делать — выбор каждого свой, и зависеть будет от предпочтений и требований.
В этой статье я не проследовал цели изменить ваши взгляды. Если вам нравится Smarty, не стоит от него отказываться. Если вы обожаете FastTemplate и ему подобные движки, это тоже ваше право. В каждом выборе есть свои плюсы и минусы. Главное что бы выбор решения был на основе четких критериев и полного осознания их веса в широком выборе имеющихся вариантов. Руководствуясь навязанными стереотипами, вы сами лишаете себя права такого выбора.
Cлишком часто я в последнее время слышу слово «шаблонизатор».

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

 

Что хочется совместить в хорошем решении

Сложность написания веб компонентов в том, что надо придумать красивый и удобный способ совместить:

  1. Однажды создав компонент его легко использовать в разных ипостасиях (архитектурах веб приложений)
    1. Управлять им из нативного JS и jQuery
    2. Добавлять настроенный компонент на страницы через шаблоны (без дополнительной генерации JS)
      1. на клиенте
      2. на сервере
      3. DOM шаблонизаторы
      4. строковые шаблонизаторы
    3. Использовать в приложениях с архитектурой подобной Knockout JS, EmberJS, Angular итд
  2. Внутренняя логика компонента НЕ должна конфликтовать с логикой самого приложения (если и сам компонент внутри и приложение, его использующее, будут написаны на одинаковых директивах — это станет проблемой, которая противоречит пункту 1 )
  3. Элементы фреймворка должны быть полезными вне фреймворка.

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

15 шаблонизаторов для фронтенд-разработки

Эволюция шаблонных систем для JavaScript

Lisp style template engine — или код в студию.

Для описания самого приложения и страниц лучше всего подходит HTML и директивы AL, а вот для внутрянки нужен мощный, гибкий и независимый механизм.

Я предлагаю описывать внутрянку компонента на JS, иные варианты могли быть хуже.

Элемент описывается примерно так

["span", 
    {class:"comp_combo_s_option", style:'background-image:url({{url}});'}, 
    "TEXT + {{size}}"  ] 

Здесь мы видим list, который начинается со строки — это имя тега.
Далее в любом порядке можно описать вложенные элементы, аттрибуты, текст и т. д.

Ещё, например,

P.selectOnClick=function(e,param){
	console.log('en event!');
}
[ "div", {size:'{{size}}', class:'abc', click: [P.selectOnClick, this, "bla"]}, ...]

Здесь словарь — это совмещённое описание аттрибутов и событий.
Если значение в словаре это строка — это атрибут; список , где первый элемент функция — значит это событие для которого назначается Listener.

Здесь в словаре описаны атрибуты и одно событие «click». Оно описано как [P.selectOnClick, this, «bla»].
Первый элемент это функция, видимая из любого доступного контекста JS, далее любое число параметров. Внутри функции волшебный this будет элементом, на котором возникло событие.

В значении атрибута class используется просто текст, а вот в значении атрибута size используется переменная {{size}} из контекста шаблона (о котором ниже). Значение size будет туда подставляться при изменении значения автоматически.

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

var P = alight.Scope();	// контекст  :)
// наполняем полями
P.onclickactivation = true;
P.onhoveractivation = false;
P.signal_activate =null;
P.signal_select =null;
P.select = function(e, event, e2){
	console.log(e,event, e2);
}
ap.bindTemplate(element, template, P /*, false*/);
// прогон 
P.$scan();

ap.bindTemplate имеет в конце не обязательный параметр, который по умолчанию false (здесь закоментирован).
Это параметр after.
Он управляет тем, куда добавятся элементы шаблона внутрь element (по умолчанию, .append) или после (.after).

Ok, можем строить DOM, а можем мы больше ? Например, директивы ?

да!

[ap.directives.if, 'size==1',
["option", { style:'background-image:url();', selected:'1'}, 
    ["span", {class:"comp_combo_s_option", style:'background-image:url({{url}});'}, "TEXT + {{size}}"] 
]
]

Во-первых, тут показан пример как вкладывать элементы — это просто вложенный список.
Во-вторых, здесь в первом списке находится не имя тега, а функция, это воспринимается как директива. Ей передаётся она сама и контекст и она может гибко влиять на порядок построения DOM, например, у меня она написана так:

		ap.directives.if = function(e , directiveTemplate, scope ){
			var condition = directiveTemplate[1];
			var tagTemplate = directiveTemplate[2];
			var elem = ap.bindTemplate(e,tagTemplate, scope);
			if( scope.$compile(condition).apply(scope) )
				$(elem).show();
			else
				$(elem).hide();
			scope.$watch(condition, function(cond){
				cond ? $(elem).show() :  $(elem).hide();
			})
			return elem;
		}

ap.bindTemplate(e,tagTemplate, scope) возвращает добавленный корневой элемент (если шаблон об одном элементе) или несколько элементов (если шаблон это список элементов)
Директива получает и себя, и вместе с собой вложенные в неё элементы.
Если директива, что-то создаёт или меняет она должна вернуть созданное содержимое: либо один элемент/поддерево/ветка, либо список поддеревьев.
Иначе директивы не получится вкладывать друг в друга и совмещать их влияние друг на друга, например:

[ap.directives.if, 'size==1',
[ap.directives.repeat, 'list', //первый способ делать директивы
["option",  {class:"", value:"{{$index}}", style:'background-image:url();', selected:'1', anyname_events:{click:""} }, 
	["span", {class:"comp_combo_s_option", style:'background-image:url();'}, "{{$item.text}}"] 
] ]
]

Здесь внутри находятся элементы элемент Option. Я написал «элементы» т.к. код option обрамляет директива repeat.
Она повторит option столько раз, сколько элементов содержит список list в контексте шаблона.

Сама repeat охвачена действием директивы if, которая покажет содержимое (список option) только если в рамках контекста шаблона выполнится условие ‘size==1’.

Надо отметить, что это не значит скрыть/показать один элемент в котором их много, это скрыть/показать список идущих подряд элементов в рамках какого-то родителя.

 премущества такого решения

Решение замечательное т.к.

  1. Оно чистое, чистое решение. Идет простое построение DOM из JS
  2. Полная поддержка JS синтаксиса и всех его возможностей
  3. Доступ к DOM в момент рендеринга шаблона.
  4. Полная поддержка всех JS библиотек внутри шаблона ( lodash, underscore, jquery… ect )
  5. Поддержка шаблонов в любом текстовом редакторе — ибо они на самом деле представляют из себя чистый JS синтаксис
  6. Построение происходит один раз. И перестраиваются только те части, которые требуют изменения.
  7. События к элементам можно назначать не только на функции контекста шаблона, а вообще, куда угодно, гибко варьируя переметры. Ещё о событиях:
    1. Это не текстовый шаблон, к которому потом события сложно привязать
    2. Это не спец. язык, который транслируется в JS (хотя решение напрашивалось)
    3. В параметрах событий могут учавствовать и обычные значения и переменные контекста — очень гибко
  8. Итоговый и исходный html не содержит никаких директив, которые могли бы конфликтовать с различными движками биндингов.
  9. Тем не менее директивы есть в привычном понимании, они обеспечивают требуемую гибкость

 

То, что это JS, а не текст — тут я не вижу проблемы. Особенно, если учесть, что мы собираемся использовать его для написания логики компонентов, которая и без того пишется на JS, и тестовые шаблонизаторы в JS выглядят всегда костыльно и ограничено.

в результате получился движок с тем же констуктом как и AL, но, на мой взгляд, даже удобнее и возможно мощнее.

Часть вторая: как делать компоненты, которые легко использовать всегда ?

Задача автора компонента такова : «один раз написать компонент так, чтобы с ним было как можно больше способов взаимодействия и он сам минимально требовал от приложения — был максимально автономен»

в нашем подходе пользователь очевидно сможет взаимодействовать с компонентами следующими способами:

  1. вставлять настроенные компоненты в текст html на сервере и на клиенте
  2. использовать для генерации кода текстовые и DOM шаблонизаторы
  3. управлять компонентом легко из JS/JQuery
  4. делать композиции из компонентов

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

Код , который управляет компонентом имеет удобную для программиста форму.

Пример  простейшего компонента, proof of concept. (без шаблонного движка)

попробуйте поменять  у тега аттрибуты через инструменты разработчика

у нас есть рабочие примеры таких компонентов и их список будет расширятся:

			/* компонент "картинка", радактируемая, (варианты)
			 * при щелчке выпадает рядом с ней хинт с тыкабельными вариантами. 
			 * тыкаешь выбираешь - картинка изменяется.
			 * 
			 * зависимость : jquery , AL (для внутреней отрисовки), "webui-popover":http://sandywalker.github.io/webui-popover/demo/
			 * 
			 * использование , надо несколько атрибутов:
			 * ap-enumselector=""			 							// активация компонента
			 * es-list='[ {img:"", text:""}, {img:"", text:""} ]'		// вход: список (json) вариантов картинка и текст
			 * es-value="1"												// вход/выход: выбранный вариант
			 * es-active="false"										// вход: для ручного управления
			 * es-onclickactivation="true"								// вход: при клике активироваться
			 * es-onhoveractivation="false"								// вход: при наведении активироваться
			 * es-signal-activate="SOMENAME"							// вход/сигнал: сигнал при активации
			 * es-signal-select="SELECT"								// вход/сигнал: сигнал при выборе
			 * es-mainiconclass="undefined"								// класс для главной img
			 * 
			 * */

пример кода


результат

Снимок экрана от 2015-12-11 22:56:57

при наведении можно выбрать элемент. индекс выбранного элемента появится в аттрибуте es-value, список задаётся через es-list.

еще компонент, редактирование на месте:
пример кода элемента


 понимает форматы редактирования :
  1.  markdown
  2. html
  3. text

результат — в аттрибуте «eip-value».

выглядит так:

Выделение_002
до щелчка редактирования
1
после щелчка редактирования

Нас ждёт работа по созданию библиотеки компонентов и развитию набора директив для шаблонного движка и главное: «наконец то ГУИ можно будет делать с удовольствием!» (выдох с улыбкой 🙂

5 Comments on “А у меня новый JS движок для компонентов — просто прелесть

Михаил Павлов
11.12.2015 в 22:33
Михаил Павлов
02.03.2016 в 22:45

да блин! блинский !
https://dombuilder.readthedocs.org/en/latest/

var article =
  ['div', {'class': 'article'}
  , ['h2', 'Article title']
  , ['p', 'Paragraph one']
  , ['p', 'Paragraph two']
  ]

правда у них это только билдер статического дом.
я пошел дальше — скрестил его с контекстом AL, и, построив, работаю уже с контекстом…

можно поробовать

1) соединить обычный билдинг типа такого

var article =
  ['div', {'class': 'article'}
  , ['h2', 'Article title']
  , ['p', 'Paragraph one']
  , ['p', 'Paragraph two']
  ]

2) jQuery плагин свой написать , чтобы хавал именно это для append, after, prepened,...
3) Добавить в этот плагин связывание с контекстом AL.

будет мощная штука.
плохо получается только с готовыми директивами. если по честному биндить, то делать это в setTimeout, чтобы дерево уже было готово.

типа такого получится

// вместо 
var s = alight.Scope();
s.m1 = '.wer';

var el = $('').on('click', function(){} ).al(s)
	// after .al some functions are patched, for example 
	.attr('some_attr', '.{{m1}}')   // when we change s.m1 and $scan() attr are changed
	.text('{{m1}} is a value of my dreams').append(
		$('').text('1'), 
		$('').text('2')
	);

//можно писать так 
var article =
  ['div', {'class': '{{m1}} .cl2'}
  , ['h2', '{{m1}} title']
  , ['p', 'Paragraph one']
  , ['p', 'Paragraph two']
  ];

// al_* - plugin with optional config
$(e).al(/*scope*/ s).al_append(article, {deep:true, }); 

Ответить
Михаил Павлов
02.03.2016 в 23:32

еще раз убедился что такой хороший штук как хаскелл (haskell) в разных руках готовит фигню:

вот поллный ахинейный бред, вам захочется закрыть я думаю. и я бы так строить DOM не стал бы
https://hackage.haskell.org/package/haste-perch

ну вот как вам ?

main= do
  body < - getBody
  (flip build) body $ do
      div ! atr "class" "modify" $ "click"
      div $ "not changed"
      div ! atr "class" "modify" $ "here"
      
      addEvent this OnClick $ _ _ -> do
          forElems' ".modify" $ this ! style "color:red" `child` " modified"

а такое ?

main :: IO ()
main = do
  withElem "idelem" $ build $ do
    div $ do
      addEvent this OnClick $ _ _ -> alert "hello, world!"
      div $ do
        p "hello"
        p ! atr "style" "color:red" $ "world"
  return ()
Creates these element:

hello

world

бред-то какой. :'(

Ответить
Михаил Павлов
03.03.2016 в 15:17

https://habrahabr.ru/post/277113/ надо будет подумать над этим SAM подробнее

Ответить
Михаил Павлов
27.06.2016 в 16:37

Я отказался от LISP STYLE TEMPLATE ENGINE синтаксиса, но вторая часть поста про компоненты актуальна.

LISP STYLE TEMPLATE ENGINE я заменил другим ситаксисом. Я сделал JQuery Plugin. и внутренний код компонента теперь позволяем смешивать jquery и AL.

Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *