TodoMVC, если кто не знает, это такой стандартный UI-хелловорлд, позволяющий сравнить решения одной и той же задачи — условного «Списка дел» — средствами разных фреймворков. Задачка, при всей своей простоте (ее решение на dap влезает «в один экран»), весьма иллюстративна. Поэтому на ее примере я попробую показать, как типичные для веб-фронтенда задачи реализуются с помощью dap.
Искать и изучать формальное описание задачи я не стал, а решил просто среверсить один из примеров. Бэкенд в рамках этой статьи нам не интересен, поэтому сами мы его писать не будем, а воспользуемся одним из готовых с сайта www.todobackend.com, оттуда же возьмем и пример клиента и стандартный CSS-файл.
Для использования dap вам не нужно ничего скачивать и устанавливать. Никаких
npm install
и вот этого всего. Не требуется создавать никаких проектов с определенной структурой каталогов, манифестами и прочей атрибутикой IT-успеха. Достаточно текcтового редактора и браузера. Для отладки XHR-запросов может еще потребоваться веб-сервер — достаточно простейшего, типа вот этого расширения для Chrome. Весь наш фронтенд будет состоять из одного-единственного .html-файла (разумеется, ссылающегося на скрипт dap-движка и на стандартный CSS-файл TodoMVC)Итак, с чистого листа.
1. Создаем .html файл
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Todo -- dap sample</title>
<link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/>
<script src="https://dap.js.org/0.4.js"></script>
</head>
<body>
<script>
// здесь будет dap
</script>
</body>
</html>
Обычная html-заготовка, в которой подключаем CSS-файл, любезно предоставляемый сайтом www.todobackend.com и dap-движок, не менее любезно предоставляемый сайтом dap.js.org
2. Копируем DOM-структуру оригинального примера
Чтобы пользоваться стандартным CSS-файлом без переделок, будем придерживаться той же DOM-структуры, что и оригинальный пример. Открываем его в браузере Chrome, жмем Ctr+Shift+I, выбираем вкладку Elements и видим, что собственно приложение находится в элементе
section id="todo-app">
Последовательно раскрывая это поддерево, переписываем его структуру в наш .html файл. Сейчас мы просто срисовываем по-быстренькому, а не пишем код, поэтому просто пишем сигнатуры элементов в 'одинарных кавычках', а в скобках их детей. Если детей нет — рисуем пустые скобочки. Следим за индентами и балансом скобок.
// здесь будет dap
'#todoapp'(
'#header'(
'H1'()
'INPUT#new-todo placeholder="What needs to be done?" autofocus'()
)
'#main'(
'#toggle-all type=checkbox'()
'UL#todo-list'(
'LI'(
'INPUT.toggle type=checkbox'()
'LABEL'()
'BUTTON.destroy'()
)
)
)
'#footer'(
'#todo-count'()
'UL#filters'(
'LI'()
)
'#clear-completed'()
)
)
Oбратите внимание: повторяющиеся элементы (например, здесь это элементы
LI
) мы пишем в структуру по одному разу, даже если в оригинале их несколько; очевидно, что это массивы из одного и того же шаблона. Формат сигнатур, думаю, понятен любому, кто писал руками HTML и CSS, поэтому останавливаться на нем подробно пока не буду. Скажу лишь, что теги пишутся ЗАГЛАВНЫМИ буквами, а отсутствие тега равносильно наличию тега DIV. Обилие здесь #-элементов (имеющих id) обусловлено спецификой подключаемого CSS-файла, в котором используются в основном как раз id-селекторы.
3. Вспоминаем, что dap-программа — это Javascript
Чтобы избавить нас от лишних скобочек в коде, dap-движок внедряет прямо в
String.prototype
несколько методов (я в курсе, что внедрять свои методы в стандартные объекты — это айяйяй, но… короче, проехали), которые преобразует строку-сигнатуру в dap-шаблон. Один из таких методов — .d(rule, ...children)
. Первым аргументом он принимает правило генерации (d-правило), и остальными аргументами — произвольное число чайлдов.Исходя из этого нового знания, дописываем наш код так, чтобы вместо каждой открывающей скобки у нас была последовательность
.d(""
, а перед каждой открывающей одинарной кавычкой, кроме самой первой, была запятая. Лайфхак: можно воспользоваться автозаменой.'#todoapp'.d(""
,'#header'.d(""
,'H1'.d("")
,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("")
)
,'#main'.d(""
,'#toggle-all type=checkbox'.d("")
,'UL#todo-list'.d(""
,'LI'.d(""
,'INPUT.toggle type=checkbox'.d("")
,'LABEL'.d("")
,'BUTTON.destroy'.d("")
)
)
)
,'#footer'.d(""
,'#todo-count'.d("")
,'UL#filters'.d(""
,'LI'.d("")
)
,'#clear-completed'.d("")
)
)
Вуаля! Мы получили дерево вызовов метода
.d
, которое уже готово трансформироваться в dap-шаблон. Пустые строки ""
— это зародыши будущих d-правил, а чайлды стали перечисленными через запятую аргументами. Формально, это уже валидная dap-программа, хоть пока и не совсем с тем выхлопом, который нам нужен. Но ее уже можно запустить! Для этого после закрывающей корневой скобки дописываем метод .RENDER()
. Этот метод, как понятно из его названия, рендерит полученный шаблон.Итак, на данном этапе имеем .html-файл вот с таким содержанием:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Todo -- dap sample</title>
<link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/>
<script src="https://dap.js.org/0.4.js"></script>
</head>
<body>
<script>
'#todoapp'.d(""
,'#header'.d(""
,'H1'.d("")
,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("")
)
,'#main'.d(""
,'#toggle-all type=checkbox'.d("")
,'UL#todo-list'.d(""
,'LI'.d(""
,'INPUT.toggle type=checkbox'.d("")
,'LABEL'.d("")
,'BUTTON.destroy'.d("")
)
)
)
,'#footer'.d(""
,'#todo-count'.d("")
,'UL#filters'.d(""
,'LI'.d("")
)
,'#clear-completed'.d("")
)
)
.RENDER() // рендерим полученный dap в документ
</script>
</body>
</html>
Можно открыть его в браузере, чтобы убедиться, что DOM-элементы генерятся, CSS-стили применяются, осталось только наполнить этот шаблон данными.
4. Получаем данные
Идем на страничку-оригинал, открываем в инструментах вкладку Network, включаем фильтр XHR, и смотрим, откуда берутся данные, и в каком виде.
Окей, понятненько. Список дел берется прямо из todo-backend-express.herokuapp.com в виде json-массива объектов. Замечательно.
Для получения данных в dap имеется встроенный конвертор
:query
который асинхронно «конвертирует» URL в данные, с него полученные. Сам URL мы не будем писать прямо в правиле, а обозначим его константой todos
; тогда вся конструкция по добыче данных будет выглядеть так:todos:query
а саму константу
todos
пропишем словаре — в секции .DICT
, прямо перед .RENDER()
:'#todoapp'.d(""
...
)
.DICT({
todos : "https://todo-backend-express.herokuapp.com/"
})
.RENDER()
Получив массив
todos
, строим из него список дел: для каждого дела берем название из поля .title
и пишем его в элемент LABEL
, а из поля .completed
берем признак «завершенности» — и пишем в свойство checked
элемента-чекбокса INPUT.toggle
. Делается это так: ,'UL#todo-list'.d("*@ todos:query" // Оператор * выполняет повтор для всех элементов массива
,'LI'.d(""
,'INPUT.toggle type=checkbox'.d("#.checked=.completed") // # обозначает "этот элемент"
,'LABEL'.d("! .title") // Оператор ! просто добавляет текст в элемент
,'BUTTON.destroy'.d("")
)
)
Обновляем эту нашу страничку в браузере и… если вы запускаете ее из файловой системы, то ничего не происходит. Проблема в том, что современные браузеры не разрешают кросс-доменные XHR-запросы из локальных документов.
Пришло время смотреть нашу страничку через http — с помощью любого локального вебсервера. Ну, или если вы пока не готовы писать dap своими руками, смотрите последовательные версии странички по моим ссылкам (не забывайте смотреть исходники — в Хроме это делается с помощью Ctrl+U)
Итак, заходим на нашу страничку по http:// и видим, что данные приходят, список строится. Отлично! Вы уже освоили операторы
*
и !
, конвертор :query
, константы и доступ к полям текущего элемента массива. Посмотрите еще раз на получающийся код. Он вам все еще кажется нечитаемым?5. Добавляем состояние
Возможно, вы уже попробовали понажимать на галочки в списке дел. Сами галочки меняют цвет, но, в отличие от оригинала, родительский элемент
LI
не меняет свой стиль («завершенное дело» должно становиться серым и зачеркнутым, но этого не происходит) — дела не меняют свое состояние. А никакого состояния эти элементы пока и не имеют и, соответственно, не могут его менять. Сейчас мы это поправим.Добавим элементу
LI
состояние «завершенности». Для этого определим в его d-правиле переменную состояния $completed
. Элементу INPUT.toggle
, который может это состояние менять, назначим соответствующее правило реакции (ui-правило), которое будет устанавливать переменную $completed
в соответствии с собственным признаком checked
(«галка включена»). В зависимости от состояния $completed
элементу LI
будем либо включать, либо выключать CSS-класс «completed». ,'UL#todo-list'.d("*@ todos:query"
,'LI'.d("$completed=.completed"// Переменная состояния, инициализируем из поля .completed
,'INPUT.toggle type=checkbox'
.d("#.checked=.completed") // Начальное состояние галочки берем из данных
.ui("$completed=#.checked") // при нажатии обновляем $completed
,'LABEL'.d("! .title")
,'BUTTON.destroy'.d("")
)
.a("!? $completed") // в зависимости от значения $completed, включаем или выключаем css-класс completed
)
Подобные манипуляции с CSS-классами — вещь довольно частая, поэтому для них в dap имеется специальный оператор
!?
Обратите внимание, делаем мы это в а-правиле (от слова accumulate). Почему не в d-правиле? Отличие между этими двумя типами правил в том, что d-правило при обновлении полностью перестраивает содержимое элемента, удаляя старое и генеря все заново, тогда как a-правило не трогает имеющееся содержимое элемента, а «дописывает» результат к тому, что уже есть. Смена отдельного атрибута элемента
LI
не требует перестройки остального его содержимого, поэтому рациональней это делать именно в a-правиле.Смотрим на результат. Уже лучше: нажатия на галочки меняют состояние соответствующего элемента списка дел, и в соответствии с этим состоянием меняется и визуальный стиль элемента. Но все еще есть проблема: если в списке изначально присутствовали завершенные дела — они не будут серенькими, т. к. по умолчанию a-правило не исполняется при генерации элемента. Чтобы исполнить его и при генерации, допишем в d-правило элемента
LI
оператор a!
,'LI'.d("$completed=.completed; a!" // Сразу же после инициализации переменной $completed используем ее в a-правиле
Смотрим. Окей. С состоянием
$completed
разобрались. Завершенные дела стилизуются корректно и при начальной загрузке, и при последующих ручных переключениях.6. Редактирование названий дел
Вернемся к оригиналу. При двойном клике по названию дела включается режим редактирования, в котором это название можно поменять. Там это реализовано так, что шаблон режима просмотра «view» (с галкой, названием и кнопкой удаления) целиком прячется, а показывается элемент
INPUT class="edit"
. Мы сделаем чуть иначе — прятать будем только элемент LABEL
, т. к. остальные два элемента нам при редактировании не мешают. Просто допишем класс view
элементу LABEL
Для состояния «редактирование» определим в элементе
LI
переменную $editing
. Изначально оно (состояние) сброшено, включается по dblclick
на элементе LABEL
, а выключается при расфокусе элемента INPUT.edit
. Так и запишем: ,'LI'.d("$completed=.completed $editing=; a!" // Теперь у нас две переменные состояния
,'INPUT.toggle type=checkbox'
.d("#.checked=.completed")
.ui("$completed=#.checked")
,'LABEL.view'
.d("? $editing:!; ! .title") // Если $editing сброшена, то показываем этот элемент
.e("dblclick","$editing=`yes") // По dblclick включаем $editing
,'INPUT.edit'
.d("? $editing; !! .title@value") // Если $editing непустой
.ui(".title=#.value") // обновляем .title по событию change (ui событие по умолчанию для INPUT)
.e("blur","$editing=") // сбрасываем $editing по событию blur
,'BUTTON.destroy'.d("")
).a("!? $completed $editing") // отображаем состояния $completed и $editing в css-классе элемента 'LI'
Теперь мы можем редактировать названия дел.
7. Отправка данных на сервер
Ок, в браузере мы дела редактировать уже можем, но эти изменения нужно еще и передавать на сервер. Смотрим, как это делает оригинал:
Внесенные изменения отправляются на сервер методом PATCH с неким URL вида
http://todo-backend-express.herokuapp.com/28185
, который, очевидно, является уникальным для каждого дела. Этот URL указывается сервером в поле .url
для каждого дела, присутствующего в списке. То есть все, что от нас требуется для обновления дела на сервере — это отправить PATCH-запрос по адресу, указанному в поле .url
, с измененными данными в формате JSON: ,'INPUT.edit'
.d("? $editing; !! .title@value")
.ui(".title=#.value; (@method`PATCH .url (@Content-type`application/json)@headers (.title):json.encode@body):query")
.e("blur","$editing=")
Здесь мы используем все тот же конвертор
:query
, но в более развернутом варианте. Когда :query
применяется к простой строке, эта строка трактуется как URL и выполняется GET-запрос. Если же :query
получает сложный объект, как в данном случае, он трактует его как детальное описание запроса, содержащее поля .method
, .url
, .headers
и .body
, и выполняет запрос в соответствии с ними. Здесь мы сразу после обновления .title
отправляем серверу PATCH-запрос c этим обновленным .title
Но есть нюанс. Поле
.url
мы получаем от сервера, оно выглядит примерно так: http://todo-backend-express.herokuapp.com/28185
, то есть в нем жестко прописан протокол http:// Если наш клиент тоже открыт по http://, то все нормально. Но если клиент открыт по https:// — то возникает проблема: по соображениям безопасности браузер блокирует http-трафик от https-источника.Решается это просто: если убрать из
.url
протокол, то запрос будет проходить по протоколу страницы. Так и сделаем: напишем соответствующий конвертер — dehttp
, и будем пропускать .url
через него. Собственные конверторы (и прочий функционал) прописывается в секции .FUNC
: .ui(".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query")
...
.FUNC({
convert:{ // конверторы - это функции с одним входом и одним выходом
dehhtp: url=>url.replace(/^https?\:/,'')// удаляем протокол из URL
}
})
Еще имеет смысл вынести объект headers в словарь, чтобы использовать его и в других запросах:
.ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title):json.encode@body):query")
...
.DICT({
todos : "//todo-backend-express.herokuapp.com/",
headers: {"Content-type":"application/json"}
})
Ну и для полного фэншуя воспользуемся еще одним полезным свойством конвертора
:query
— автоматическим кодированием тела запроса в json в соответствии с заголовком Content-type:application/json
. В итоге правило будет выглядеть так: .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title)):query")
Итак, смотрим. Окей, названия дел теперь меняются не только в браузере, но и на сервере. Но! Меняться-то может не только название дела, но и его состояние завершенности —
completed
. Значит, его тоже нужно отправлять серверу.Можно элементу
INPUT.toggle
дописать аналогичный PATCH-запрос, просто вместо (.title)
отправлять (.completed)
: ,'INPUT.toggle type=checkbox'
.d("#.checked=.completed")
.ui("$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query")
А можно вынести этот PATCH-запрос «за скобки»:
,'LI'.d("$completed=.completed $editing= $patch=; a!" // $patch - "посылка" для сервера
,'INPUT.toggle type=checkbox'
.d("#.checked=.completed")
.ui("$patch=($completed=#.checked)") // кладем в $patch измененный completed
,'LABEL.view'
.d("? $editing:!; ! .title")
.e("dblclick","$editing=`yes")
,'INPUT.edit'
.d("? $editing; !! .title@value")
.ui("$patch=(.title=#.value)") // кладем в $patch измененный title
.e("blur","$editing=")
,'BUTTON.destroy'.d("")
)
.a("!? $completed $editing")
// если $patch не пустой, отправляем его серверу, потом сбрасываем
.u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=")
Тут дело вот в чем. Правила реакции относятся к группе «up-правил», которые исполняются «снизу вверх» — от потомка к родителю, до самого корня (эта последовательность может быть прервана при необходимости). Это чем-то похоже на «всплывающие» события в DOM. Поэтому какие-то фрагменты реакции, общие для нескольких потомков, можно поручить их общему предку.
Конкретно в нашем случае выигрыш от такого делегирования не особо заметный, но если бы редактируемых полей было больше, то вынос этого громоздкого (по меркам dap, конечно) запроса в одно общее правило сильно помог бы сохранять код простым и читабельным. Так что рекомендую.
Смотрим: Теперь на сервер отправляются и изменения названия, и изменения статуса.
В следующей статье, если будет интерес, рассмотрим добавление, удаление и фильтрацию дел. А пока можно посмотреть финальный результат и другие примеры dap-кода на dap.js.org/docs
Комментарии (26)
jooher Автор
18.12.2019 11:59-2Дисклеймер: если вы зашли в комментарии для того, чтобы сообщить о том, что dap вам не нравится и вы никогда не будете им пользоваться — сначала загляните, пожалуйста сюда. Спасибо за понимание.
Если у вас вопрос «а как на dap сделать X, да так чтобы Y» или аналогичный — буду рад ответить во всех подробностях.KaneUA
18.12.2019 12:20+1А можно прежде чем посылать на страницу «Всем похуй» на Lurkmore перечитать свой пост? А то выглядит как задел под монетизацию. Или безразличие крайней степени — ваш девиз?
jooher Автор
18.12.2019 14:53Ок, заменил табы на пробелы. Лучше, да.
KaneUA
18.12.2019 15:37Кусок кода на сайте вообще не должен начинаться с какого-либо отступа, ведь это самостоятельный кусок кода.
jooher Автор
18.12.2019 15:40Это как раз не самостоятельные куски кода, а фрагменты, в которые внесены изменения. Если сравнивать эти фрагменты с предыдущими — отступы помогают сориентироваться, какая именно строка изменена. Ну так, по крайней мере, задумывалось. Такое «скользящее окно».
RuGrof
18.12.2019 12:13+1Возможно теги Angular, ReactJS, всё таки лишние для этого.
Получение данных в UI не лучший подход.
Почему у этого лицензия не MIT, не понятно.jooher Автор
18.12.2019 12:46Получение данных в UI не лучший подход. — это было бы справедливо, если вместо "UI" написать "view".
Dap — программа это не view.
TargetSan
18.12.2019 12:26+1Заголовок кликбейтный. Думал — какой-то маленький движок как альтернатива V8. Оказалось — очередной "гениальный фреймворк".
jooher Автор
18.12.2019 14:28Пожалуй, да. Убрал про «js-движок». Теперь просто Dap в действии. Про «гениальный фреймворк» — подумаю… может так и напишу.
hrie
18.12.2019 13:48Я поймал себя вот на какой мысли: явно JS это не то, что вам интересно.
Пойдите другим путём — сделайте отдельный язык, компилируемый в JS. Тогда всё встанет на свои места. Всё это безумие решёток, вопросительных знаков, кавычек и скобочек обретёт смысл, плюс вам не придётся выслушавать от JS-еров вопли «мои глаза». Потому что это будет уже не JS.
Ну и фраза «Никаких npm install и вот этого всего» это минус, а не плюс. В современной веб-разработке под браузер всё идёт через npm и если вашу либу нельзя проинсталить и скормить в babel/webpack, то никто ею и пользоваться не будет.jooher Автор
18.12.2019 16:15На какой-то странной мысли вы себя поймали. Те, кому не интересен JS, пишут на TS, на JSX и прочих «более интересных» языках, как раз таки компилируемых в JS. Все как вы хотите.
А вот dap как раз обходится средствами голого JS. И почему вы думаете, что выслушивать вопли «JS-еров»-неофитов — это что-то плохое? :)
Непонятно, что вы называете безумием решеток и кавычек. Обилие решеток здесь — это «безумие» CSS-файла, который писал не я, и который к dap отношения не имеет. Кавычки и скобочки вас пугают? А вы когда на JS/HTML последний раз писали? Сравните, пожалуйста, на количество кавычек и скобочек (и их видов) вот эти два выражения:
'#toggle-all type=checkbox' <div id="toggle-all" type=checkbox>
Мою либу вы можете проинсталить, просто взяв файл dap.js.org/0.4.js если хотите.
Другое дело, что это именно «не нужно», в смысле «не требуется».hrie
18.12.2019 16:24"$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query"
А, так это CSS. Похоже я сильно отстал от него )))jooher Автор
18.12.2019 16:48А, так вы безумием называете вот эту решетку, наличествующую аж в 2-х экземплярах?
d("#.checked=.completed") // # обозначает "этот элемент"
Похоже, вы не от CSS сильно отстали :)hrie
18.12.2019 17:03Вооот, а теперь смотрите:
// Оператор * выполняет повтор для всех элементов массива // # обозначает "этот элемент" // Оператор ! просто добавляет текст в элемент "? $editing:!; ! .title" ... "? $editing; !! .title@value" ... ".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query"
Очевидно же, что если я хочу писать на dap, JS мне не помощник. Нужно учить особый синтаксис. Так зачем тогда вообще нужен JS, если вся мякотка dap'а — в этих?!!! ` @
Избавьтесь от балласта, запилите свой компилятор. Elm, PureScript, ClojureScript, CoffeeScript все пошли этим путём и выйграли.jooher Автор
18.12.2019 17:41Если вы хотите писать на dap, то JS — ваш главный помощник, на самом деле. Из второй части этого туториала это будет более понятно.
Мякотка dap вовсе не в этих *?!
я мог бы их назвать for-each, if, print. Просто зачем? Я вот сейчас реально перечислил все три(!) основных оператора, которые вам нужно «выучить», чтобы начать писать на dap. Выучить три оператора — это сложно, я понимаю, но мы не на ЕГЭ, тут дело добровольное. А, собственно, мякотка dap в том, что он позволяет просто и коротко описывать то, что средствами JS пишется громоздко, муторно и просто некрасиво. При этом dap не пытается лезть туда, где все можно просто и красиво сделать на JS. Дождитесь второй части туториала.
Зачем нужен JS — кстати это хороший вопрос, я вам с удовольствием отвечу. Изначально (2008-м году, bankreports.dapmx.org) дап строился на HTML/XML. Правила писались в атрибутах d, u и т.п. В принципе это было вполне удобно (в отличие от js-строк, SGML-атрибуты могут быть многострочными, этого сейчас иногда не хватает) и выглядело в те времена доминирования XML вполне логично (хотя синтаксис самих dap-правил был действительно адский). Но в какой-то момент я подумал: а нахрена? Зачем писать это тегами, потом парсить это все, собирать в объекты, иметь вот этот весь головняк, если все это можно прекрасно сделать средствами самого JS? Так же как сейчас я не понимаю этого прикола JSX писать в JS-коде HTML-теги.
Поэтому сейчас dap строится средствами JS, и манипулировать dap-шаблонами можно любыми доступными в JS средствами — это обычные JS объекты из обычных JS-объектов. Смысла городить отдельный язык с компилятором и монадами я просто не вижу.
В отличие от помянутых PureScript, ClojureScript, CoffeeScript, которые «вместо» JS, dap не вместо.
XAHTEP26
19.12.2019 09:16Это очень сильно похоже на то, во что компилируется JSX или шаблоны Vue, только хуже.
ru.vuejs.org/v2/guide/render-function.htmljooher Автор
19.12.2019 12:49Похоже оно только тем, что и там и там JS. Для некоторых граждан любой JS-код на одно лицо, только хуже.
XAHTEP26
19.12.2019 12:58Уверенности в себе вам не занимать. )
jooher Автор
19.12.2019 13:11Просто аналогичные игры в ассоциации уже были в комментах к предыдущей статье. Кто какой js раньше видел, тому то и мерещится. Я думаю, это нормально.
XAHTEP26
19.12.2019 13:33Насколько я вижу js у вас только под капотом. А используя Dap надо писать строки текста на каком-то другом языке и передавать их в функции.
jooher Автор
19.12.2019 13:55Да, строки текста на другом языке это и есть дап. С помощью js под капотом эти строки компилируются в шаблоны и реактивные связи между ними. Но js тут не только под капотом. Эти dap правила сами оперируют js-данными и js-функциями. Во второй части туториала это будет подробно. Пока можно посмотреть примеры на dap.js.org если интересно
token
Что я только что прочитал?
jooher Автор
Забей. Это не твое