22 October 2019

Интерактивное веб-приложение без программирования? Легко! Mavo вам в руки

ГК ЛАНИТ corporate blogWebsite developmentCSSProgrammingHTML
Tutorial
Вы владеете HTML и CSS и умеете создавать простые (и не очень) статические веб-страницы, а хотели бы вдохнуть в них больше «жизни» и интерактивности? У вас есть работы (картины, фотографии, стихи, коллекция марок и т. п.), которыми вам хотелось бы поделиться с миром, но создание сайта-портфолио или блога, куда можно их разместить и без лишних усилий обновлять, вам не под силу? Или мечтаете вести дневник путешественника, или собирать необычные кулинарные рецепты, или отслеживать свою фитнес-активность и делать всё это онлайн на собственном сайте? Возможно, у вас есть любимый питомец, уход за которым требует особых процедур, и их обязательно нужно отслеживать и оперативно фиксировать? 

Но от упоминания JavaScript вас бросает в лёгкую (а иногда и не очень) дрожь, а количество технологий и концепций, которыми нужно овладеть, чтобы реализовать ваши задумки, приводит вас в замешательство и отчаяние? В итоге вы задаётесь вопросами: «Почему веб-программирование должно быть таким трудным? Неужели нельзя что-то придумать, чтобы сделать его проще?». 

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

Автор оригинальных картинок: Mart Virkus (toggl.com)

Маститые и умудрённые опытом фронтенд-разработчики, не спешите закрывать статью с криками, что сейчас тут будут что-то «втирать» новичкам и вам здесь делать нечего. Я уверен, вы тоже сможете почерпнуть для себя что-то полезное. Главное, помните: если что-то станет простым для новичков, это автоматически станет простым для всех! А значит, в какой-то мере упростит жизнь и вам. Это неплохо, согласитесь?

Итак, к делу! Эта статья будет целиком и полностью посвящена Mavo.

Mavo — это новый подход к разработке интерактивных веб-приложений только за счёт написания HTML и CSS, без необходимости написания кода на языке JavaScript и разворачивания собственного сервера.

Mavo разрабатывается группой учёных Haystack Group лаборатории компьютерных наук и искусственного интеллекта Массачусетского технологического института (MIT CSAIL) под руководством Лии Веру.

Следуй за белым кроликом!

Хотите узнать чуть больше о том, какие идеи были положены в основу Mavo, какие черты своих предшественников он впитал, какие развил, а от каких пришлось отказаться и почему? Тогда очень рекомендую к прочтению эти две публикации, специально подготовленные для ежегодного форума, посвящённого иновациям в области человеко-машинного взаимодействия, — The ACM Symposium on User Interface Software and Technology (UIST):

  1. Mavo: Creating Interactive Data-Driven Web Applications by Authoring HTML.
  2. Extending a Reactive Expression Language with Data Update Actions for End-User Application Authoring.


Небольшое отступление для читателей из России!

Сайт Mavo хостится на серверах Netlify, адреса которых попали в «чёрный список» в результате «войны» с мессенджером Telegram. А значит, ресурсы, размещённые на этих серверах, равно как и все связанные с ними сервисы заблокированы на территории России (по крайней мере, на момент написания этой статьи).

Есть, правда, и хорошая новость: мы с Лией Веру (Lea Verou) активно работаем над решением этой проблемы. Надеюсь, в скором времени мы и это уладим. А до тех пор, пожалуйста, пользуйтесь, например, VPN для работы с Mavo (это справедливо как при доступе к сайту Mavo, так и при изучении демо-приложений на Codepen, а также и при создании собственных веб-приложений).

(ОБНОВЛЕНИЕ) Те, кто не готов и/или не хочет связываться с VPN, могут поиграть с Mavo на Stackblitz или подключить его к свому проекту через cdnjs.

Я убежден (и, думаю, я не одинок), что лучший способ «пощупать» новую технологию и оценить, на что она способна, — это построить что-то полезное с её помощью. Не очень сложное, иначе мы можем просто не увидеть леса за деревьями, но нужное. Осталось только выбрать что. О! Есть идея.

Думаю, многие из вас изучают иностранный язык (может быть, для кого-то таким является язык программирования). И, скорее всего, для его изучения вы используете какое-нибудь приложение. Новые слова в этих приложениях обычно изучают с помощью карточек. Кстати, этот приём изучения иностранного языка отлично работает и в офлайне. Но сейчас не об этом.

Так вот, почему бы нам с вами вместе не разработать такое приложение? Назовём его… (барабанная дробь!) «Карточки». Это будет полноценное CRUD-приложение для изучения иностранного языка с помощью карточек, которое позволит:

  • создавать, удалять, изменять карточки, а также упорядочивать их с помощью перетаскивания;
  • импортировать карточки в приложение из файла и экспортировать их в файл;
  • отслеживать прогресс (функция самооценки).

Финальная версия приложения будет выглядеть примерно так:


Годится? Надеюсь, да. Тогда в путь!

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

Следуй за белым кроликом!

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

Практика — путь к совершенству!

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

Запоминай через руки!

Готовы, следуя за нитью моего изложения, сразу применять новые знания на практике? Отлично! Ведь, как известно, мы лучше запоминаем то, что делают наши руки. Вы можете постепенно разрабатывать своё приложение, читая статью от начала и до конца, либо можете начать с любого интересующего вас этапа. 

В таких фрагментах будут приводиться ссылки на соответствующие версии приложения на Codepen.

Теперь всё. Начинаем!

Статическая веб-страница


Чтобы проиллюстрировать, как Mavo расширяет возможности HTML, мы создадим статическую HTML-страницу, которую затем с помощью Mavo превратим в полнофункциональное веб-приложение.

Предположим, у нас есть следующий HTML-код внутри элемента <body>:

<header>
  <h1>Карточки</h1>
</header>
<main>
  <article>
    <p>Слово или фраза</div>
    <p>Перевод</div>
  </article>
</main>

В приведённом выше фрагменте кода элемент <article> соответствует одной карточке.

Добавим немного стилей, чтобы наша страничка стала больше похожа на будущее приложение:


Хотите полностью увидеть исходный код? Вы можете сделать это здесь.

Подключаем Mavo


Пока у нас есть только статический макет. Настало время превратить его в полноценное веб-приложение. Вот тут как раз Mavo и вступает в игру!

Чтобы использовать Mavo, нам необходимо подключить его JavaScript- и CSS-файлы к нашей странице. Для этого добавим в элемент <head> две следующие строки:

<head>
  ...
  <script src="https://get.mavo.io/mavo.min.js"></script>
  <link rel="stylesheet" href="https://get.mavo.io/mavo.css">
  ...
</head>


Небольшое отступление для читателей из России!

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

(ОБНОВЛЕНИЕ) Вы также можете подключить Mavo к свому проекту через cdnjs.

Следуй за белым кроликом!

Возможно, ваше приложение должно корректно работать в старых браузерах? Или вы хотите иметь возможность заглянуть в исходный код Mavo и ожидаете, что он будет читаемым? Всё в ваших руках. Вы можете тонко настроить, с какой сборкой и/или версией Mavo вы хотите поработать, ответив на несколько простых вопросов.

Подключив Mavo, нам необходимо указать, какой элемент страницы будет содержать наше будущее приложение. Таким элементом может быть любой HTML-элемент, даже <body> или <html>! К этому элементу нам необходимо добавить атрибут mv-app, а в качестве его значения указать имя нашего приложения — уникальный в пределах всей HTML-страницы идентификатор. Элемент с атрибутом mv-app называется корневым элементом приложения.

Следуй за белым кроликом!

Если не присвоить атрибуту mv-app значение и при этом в том же самом элементе нет ни одного из атрибутов: id или name, — Mavo автоматически присвоит приложению имя mavo1, mavo2 и т. п.

Однако я настоятельно рекомендую вам явно указывать имя приложения, так как оно используется (явно и неявно) в других местах приложения.

Запоминай через руки!

https://codepen.io/dsharabin/pen/PoYxNjN

Итак, пусть в нашем случае элемент <main> будет содержать будущее приложение. Добавим к нему атрибут mv-app, а в качестве его значения укажем имя нашего приложения — flashcards:

<main mv-app="flashcards">
  ...
</main>

Атрибут property


Теперь пора сообщить Mavo, какие элементы приложения являются важными, то есть будут редактироваться и/или сохраняться, а также, возможно, будут использоваться в выражениях (но об этом чуть позже — запаситесь терпением).

В нашем приложении пока есть два таких элемента — это элементы <p>. Добавим к этим элементам атрибут property, сообщив тем самым Mavo, что эти элементы содержат данные, с которыми нужно работать. Элементы с атрибутом property называются свойствами (properties).

Следуй за белым кроликом!

Атрибут property можно добавить к любому HTML5-элементу — Mavo знает, как сделать его редактируемым. Например, содержимое элемента <span> вы сможете редактировать вручную с клавиатуры, а значение элемента <time> (дату или время) вы сможете установить с помощью соответствующего виджета.

Набор правил, по которому Mavo делает элементы редактируемыми, может быть легко расширен с помощью плагинов. Нужно, чтобы пользователь мог форматировать текст с помощью панели инструментов, аналогичной той, что есть у таких программ, как Microsoft&reg; Wordpad или Microsoft&reg; Word, т. е. работать с текстом, как в любом WYSIWYG-редакторе? Или вы хотите разрешить пользователю набирать текст с использованием команд языка Markdown? Нет никаких ограничений. Просто активируйте соответствующие плагины. Нет подходящего плагина? Не проблема. Напишите свой. Mavo поддерживает соответствующий функционал прямо «из коробки».

Имейте в виду, что значение атрибута property должно описывать назначение элемента так, как обычно его описывают значения атрибутов id или class.

Превратим наши элементы <p> в свойства:

...
  <p property="source">Слово или фраза</p>
  <p property="translation">Перевод</p>
...

Следуй за белым кроликом!

Если у элемента уже есть атрибут id, class или itemprop, точно описывающий его назначение, можно не присваивать атрибуту property значение. Например, одно из свойств нашего приложения можно было бы описать так: <p property class="source">.

Заметили какие-нибудь изменения в нашем приложении после добавления в него свойств? Уверен, что да. Вверху приложения появилась панель инструментов Mavo (Mavo bar) с кнопкой Edit. Эта кнопка позволяет переключаться между двумя режимами работы приложения: режимом чтения и режимом редактирования. Сейчас наше приложение находится в режиме чтения. Это означает, что мы не можем редактировать его данные непосредственно в окне браузера.

Следуй за белым кроликом!

Панель инструментов Mavo полностью настраиваемая (как и все элементы интерфейса, автоматически добавляемые Mavo в приложение): вы можете изменить её положение, внешний вид, задать набор доступных на ней кнопок или даже назначить ей свой HTML-элемент.

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

Что ж, самое время переключиться в режим редактирования и познакомиться с ним. Нажмите кнопку Edit.

Что же изменилось? Поменялось название кнопки: теперь её имя Editing. Это визуальный сигнал для нас: внимание, мы находимся в режиме редактирования. Попробуйте навести курсор мыши на любой из абзацев с текстом («Слово или фраза» или «Перевод»). Mavo сообщает нам с помощью жёлтой подсветки, что по этому фрагменту можно щёлкнуть и отредактировать его.


Смелее! Щёлкните по тексту и внесите в него изменения. Ну разве это не здорово?! Мы можем редактировать содержимое страницы непосредственно в окне браузера!

Практика — путь к совершенству!

Предположим, что помимо изучаемого слова (или фразы) и его перевода, мы хотим, чтобы на карточке присутствовал пример употребления этого слова (фразы) в предложении. Мы же знаем, что именно так нужно учить иностранные слова — в контексте, правда?

Усовершенствуйте приложение, добавив в него недостающий элемент, и дайте ему имя, например, example.

Атрибут mv-multiple


На данный момент в нашем приложении всего одна карточка. Откровенно говоря, это не очень-то полезно! Для полноценного приложения нам не хватает возможности добавлять новые карточки, удалять ненужные, а также переставлять карточки так, как нам удобно. Но как мы можем всё это реализовать?

Запоминай через руки!

https://codepen.io/dsharabin/pen/PoYxNmN

Будучи разработчиками приложения, мы могли бы добавить в него новые карточки, просто добавив ещё элементов <article> в его HTML-код. Но тогда каким образом пользователь сможет добавлять и удалять карточки самостоятельно?

К счастью, в Mavo есть то, что позволит нам добавить в приложение соответствующий функционал на раз-два — атрибут mv-multiple. С помощью этого атрибута мы указываем Mavo, какие элементы приложения могут быть размножены. Атрибут mv-multiple превращает элемент, к которому он добавлен, в редактируемую коллекцию элементов (collection). При этом Mavo добавит к элементам такой коллекции элементы управления, с помощью которых можно добавлять в коллекцию новые элементы, удалять существующие и упорядочивать элементы коллекции с помощью перетаскивания. И да, стиль этих элементов управления тоже полностью настраиваемый!

Следуй за белым кроликом!

Если добавить атрибут mv-multiple к элементу без атрибута property, Mavo автоматически исправит эту ситуацию: добавит property со значением collection (или collection2, collection3, чтобы сохранить уникальность имени).

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

Что ж, давайте добавим атрибут mv-multiple к элементу <article> приложения, чтобы превратить нашу одинокую карточку (flashcard) в полноценную редактируемую коллекцию карточек (обратите внимание, что при этом мы добавили ещё и атрибут property):

<article property="flashcard" mv-multiple>
  ...
</article>

Следуй за белым кроликом!

Mavo позволяет указать имя свойства в качестве значения атрибута mv-multiple. Таким образом, мы могли бы описать коллекцию карточек чуть короче: <article mv-multiple="flashcard">.

Обратите внимание, что атрибут mv-multiple должен добавляться к размножаемому элементу, а не к контейнеру, в котором размещается коллекция. Очень распространена ошибка, когда разработчики пишут <ul mv-multiple> вместо <li mv-multiple>. И поначалу подобную ошибку достаточно трудно обнаружить. Пока, например, используемые в приложении стили не сделают её очевидной.

Теперь смело переключайтесь в режим редактирования. Заметили, что под карточкой появилась кнопка Add flashcard? Устроим ей тест-драйв: добавьте с её помощью пару новых карточек. Это просто что-то невероятное: мы можем динамически добавлять новые карточки в приложение, даже если в его исходном коде нет соответствующих элементов!

Следуй за белым кроликом!

Заметили, что добавление атрибута property к элементу <article> не сделало его содержимое редактируемым? Mavo считает этот элемент группой. Так происходит, когда атрибут property добавляется к элементу, содержащему внутри себя другие свойства, то есть элементы с атрибутом property.

Но и это ещё не всё! Наведите курсор мыши на любую из карточек. Видите, что у правого верхнего угла карточки Mavo добавил три новые кнопки (слева направо): для удаления карточки, добавления новой и перемещения существующей. И наводя курсор мыши на любую из этих кнопок, можно легко догадаться по генерируемой Mavo подсветке, к какой именно карточке они относятся.


Удобно, правда?

Следуй за белым кроликом!

Генерируемые Mavo кнопки работы с элементами коллекции полностью настраиваемые. Например, вы можете создать свою собственную кнопку перемещения элемента коллекции, добавив класс mv-drag-handle к соответствующему HTML-элементу.

Также имейте в виду, что со всеми кнопками работы с элементами коллекции можно взаимодействовать с помощью клавиатуры. Например, поместите фокус ввода на кнопку перемещения карточки и с помощью клавиш управления курсором изменяйте положение карточки внутри коллекции.

Атрибут mv-storage


Запоминай через руки!
https://codepen.io/dsharabin/pen/WNeYwpj

Теперь, когда основной интерфейс будущего приложения разработан, предлагаю сделать следующее:

  1. Переключитесь в режим редактирования (если вы не сделали этого раньше).
  2. Добавьте несколько карточек, указав на каждой из них слово и его перевод.
  3. Вернитесь в режим чтения.
  4. И… обновите страницу.

Ой! Куда же делись все данные, которые мы только что ввели? Неужели Mavo не должен был сохранить их? Что пошло не так? Где мы ошиблись?

Только спокойствие! На самом деле, мы никогда не указывали Mavo, что он должен сохранять редактируемые данные. Тем более мы не указывали, где конкретно он должен их сохранять.

Так давайте сделаем это сейчас. Для этого в Mavo есть специальный атрибут — mv-storage. Осталось только выяснить, какие значения он может принимать. И оказывается, Mavo открывает нам широчайшие возможности. А плагины — открывают ещё большие!

Пусть наше приложение сохраняет данные в локальном хранилище браузера — localStorage. Это самый простой из доступных в Mavo вариантов и отлично подходит для нашего первого приложения. Всё, что нам нужно сделать, — просто добавить атрибут mv-storage со значением local к корневому элементу приложения (так называется элемент с атрибутом mv-app, помните?):

<main mv-app="flashcards" mv-storage="local">
  ...
</main>

А теперь взгляните на панель инструментов Mavo. Заметили новую кнопку Save? Помимо прямого назначения — сохранять изменения в приложении — у неё есть ещё одна полезная функция. Попробуйте отредактировать данные ещё раз. Обратите внимание, что кнопка Save теперь подсвечена. Наведите на неё курсор мыши, и Mavo подсветит вам данные, которые вы отредактировали, но не сохранили!


Здорово, да?

Нажмите кнопку Save и обновите страницу (при этом не обязательно переключаться в режим чтения). Ну что, теперь ваши данные сохранились? Отлично! Мы стали ещё на один шаг ближе к полноценному веб-приложению.

Атрибут mv-autosave


Сейчас наше приложение работает так, что пользователь должен нажимать кнопку Save всякий раз, когда ему нужно сохранить внесённые в приложение изменения. Это правильно с точки зрения сохранения жизненно важной информации, но часто пользователи забывают это делать. Как же быть? Вот бы наше приложение могло сохранять данные автоматически через какие-то промежутки времени! «А что, это реально можно сделать с помощью Mavo?» — спросите вы. А я с радостью и гордостью отвечу, что да, можно!

Итак, чтобы научить приложение автоматически сохранять изменения, вносимые пользователем в данные, мы можем воспользоваться атрибутом mv-autosave. Добавить его нужно к корневому элементу приложения. Значение этого атрибута — количество секунд, которое должно пройти с момента внесения пользователем изменений в данные до их сохранения приложением. Внесём соответствующие изменения в наше приложение:

<main mv-app="flashcard" mv-storage="local" mv-autosave="3">
  ...
</main>

Следуй за белым кроликом!

Атрибут mv-autosave="3" предписывает Mavo сохранять вносимые в данные изменения раз в три секунды. Наличие подобного рода задержки между сохранениями становится принципиально важным, если выбранный для сохранения данных сервис (бэкенд) хранит историю их изменения (например, GitHub и Dropbox). Отсутствие такой задержки сделает историю изменений просто-напросто бесполезной.

Чтобы заставить Mavo сохранять изменения немедленно, можно задать атрибут mv-autosave="0" или просто mv-autosave. При этом с панели инструментов Mavo будет удалена (за ненадобностью) кнопка Save.

Чтобы увидеть и оценить внесённые в приложение изменения, ещё раз обновите данные на какой-либо из карточек и обратите внимание на кнопку Save. Заметили? Сначала она была подсвеченной, но через три секунды погасла, указывая тем самым, что несохранённых данных в приложении нет. Теперь все изменения сохраняются автоматически!

Таким образом, основная часть нашего приложения выглядит сейчас следующим образом:

<main mv-app="flashcards" mv-storage="local" mv-autosave="3">
  <article property="flashcard" mv-multiple>
    <p property="source">Слово или фраза</p>
    <p property="translation">Перевод</p>
  </article>
</main>

Практика — путь к совершенству!

Мы почти закончили с альфа-версией нашего приложения. Ура-ура! Теперь ваша очередь сделать его ещё лучше. Не волнуйтесь! Вы вооружены всеми необходимыми знаниями, чтобы выполнить задание, которое я вам предложу.

Усовершенствуйте приложение так, чтобы карточки, объединённые общей тематикой, можно было собирать в группы. Например, в одну группу входят все слова, имеющие отношение к одежде, в другую — названия профессий, в третью — названия продуктов питания и т. п.

Подсказки!

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

  1. Какой HTML-элемент вы будете использовать в качестве группирующего элемента (группы)? Пользователям приложения было бы удобно, если бы группе карточек можно было дать имя (название темы), а также если бы эту группу можно было сворачивать при необходимости до её заголовка.
  2. Какой атрибут (атрибуты) вы добавите к выбранному элементу (если, конечно, будете что-то добавлять). Будет ли этот элемент свойством или коллекцией?
  3. Будет ли у пользователей приложения возможность добавлять новые группы карточек, удалять ненужные, перемещать как сами группы, так и карточки между разными группами?

Вы, кстати, можете решить, что объединять карточки в группы с помощью отдельных элементов — не ваш метод. И это нормально. Может быть, вы захотите просто добавить к карточкам соответствующие теме метки (хэштеги). И это тоже будет прекрасное решение!

Чтобы «набить руку» и чуть больше познакомиться с Mavo и его возможностями, реализуйте оба этих способа решения.

Атрибут mv-bar


Продолжим расширять функционал нашего приложения. Мы настроили его так, что данные пользователя хранятся в локальном хранилище браузера. Но наше приложение остаётся однопользовательским: пользователи не могут делиться своими карточками с другими пользователями. А было бы здорово иметь такую возможность! Да и учить иностранный язык в компании куда веселее, не так ли?

Есть ли в Mavo способ разрешить пользователям экспортировать из приложения свои карточки и импортировать в него чьи-то чужие? Нам повезло! И этот функционал поддерживается в Mavo, как говорится, «из коробки». Конечно, это не сделает наше приложение многопользовательским в каноническом понимании этого слова, но наличие подобного функционала — уже немало для столь простого приложения, согласитесь.

Запоминай через руки!

https://codepen.io/dsharabin/pen/ZEzmWBr

Для подобных задач в Mavo есть атрибут mv-bar, с помощью которого можно указывать, какие кнопки отображаются на панели инструментов Mavo (если там вообще нужны какие-либо кнопки). Обычно этот атрибут добавляется к корневому элементу приложения. Сами кнопки имеют очень логичные (с точки зрения их наименования на английском языке) идентификаторы. Вот лишь некоторые из них: edit, import, export.

Поскольку мы хотим лишь добавить кнопки к уже имеющемуся на панели инструментов Mavo набору, мы можем воспользоваться так называемым относительным синтаксисом (relative syntax). Такой синтаксис позволяет нам добавлять и удалять кнопки из дефолтного набора без необходимости явно перечислять все кнопки, входящие в него. Всё, что нам достаточно сделать в нашем случае, — просто начать значение атрибута mv-bar с ключевого слова with, а затем перечислить (через пробел) идентификаторы нужных нам кнопок:

<main mv-app="flashcards"
      mv-storage="local"
      mv-autosave="3"
      mv-bar="with import export">
      ...
</main>

Практика — путь к совершенству!

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

Выражения. MavoScript


Перефразируя известную фразу, произнесённую персонажем Евгения Евстигнеева в знаменитом советском фильме «Берегись автомобиля», спрошу: «А не замахнуться ли нам в нашем приложении на статистику?!» Предлагаю отображать где-то вверху приложения общее количество карточек в нём. Заинтригованы? На это я и рассчитывал.

Но прежде чем суметь «замахнуться» и реализовать намеченное, нам нужно узнать кое-что новое о Mavo.

Мы уже знаем, что важные элементы приложения (которые, в частности, можно редактировать и сохранять) называются свойствами. Напомню, это элементы, для которых указан атрибут property. Значение этого атрибута — имя свойства, описывающее его роль в приложении.

Так вот, мы можем использовать значение любого свойства, ссылаясь на него, в любом месте внутри приложения Mavo (даже в значениях HTML-атрибутов). Для этого нужно просто заключить имя свойства в квадратные скобки. Вот так: [имяСвойства]. Эта запись является примером так называемого простого выражения. Простые выражения позволяют нам производить вычисления всякий раз, как только что-то в приложении меняется, то есть реактивно.

Следуй за белым кроликом!

Язык выражений, поддерживаемый Mavo, называется MavoScript. Он очень похож на язык формул в электронных таблицах (таких как Microsoft&reg; Excel, Apple Numbers или Google Sheets) и позволяет производить вычисления и другие операции с числами, текстом, списками и т. п.

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

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

Более подробно о выражениях Mavo и языке MavoScript можно почитать в официальной документации.

Запоминай через руки!

https://codepen.io/dsharabin/pen/rNBQeMv

Перейдём от слов к делу и посмотрим на реактивность в действии. В качестве эксперимента, давайте добавим выражение [source] внутрь свойства flashcard. Например, между свойствами source и translation:

...
  <p property="source">Слово или фраза</p>
  [source]
  <p property="translation">Перевод</p>
...

Какие же изменения произошли в нашем приложении? Как минимум, мы видим, что значение свойства source отображается в карточке дважды. Так? Так!

Идём дальше. Переключимся в режим редактирования и попробуем изменить значение свойства source. Видите, что происходит в то время, как вы меняете значение этого свойства? Значение выражения [source] реактивно вычисляется, и изменения, которые вы вносите в значение свойства source, автоматически отображаются ниже: там, где мы использовали выражение. Ну не чудо ли?!

Это, разумеется, здорово, но пока мы не приблизились к решению нашей задачи — отображению информации о количестве карточек в приложении. Почему? Просто выражение [source], помещённое внутрь карточки, всегда будет возвращать ровно одно значение — значение свойства source именно этой карточки. А нам бы нужно как-то добраться до всех карточек в приложении.

А что если теперь мы поместим выражение [source] не внутри свойства flashcard, а снаружи? Вот так:

...
  [source]
  <article property="flashcard" mv-multiple>
    ...
  </article>
...

Как это будет отличаться от рассмотренного ранее случая? Чтобы увидеть эти самые изменения, добавьте в приложение несколько карточек. Обратите внимание, что теперь вместо одного значения над карточками мы видим список разделённых запятыми значений свойства source всех(!) имеющихся в приложении карточек. Если мы посчитаем, сколько значений в этом списке, то получим количество карточек в приложении. Согласны?

Осталось устранить одно маленькое логическое противоречие. Какое? Не кажется ли вам, что логичнее было бы считать количество самих карточек, а не количество значений их свойства source? В конце концов, добавляемая в приложение карточка уже существует до того, как пользователь заполнит её свойство source или translation. Как же поступить? Я предлагаю просто скорректировать выражение и сделать его более логичным: вместо [source] написать [flashcard]. В итоге получим:

...
  [flashcard]
  <article property="flashcard" mv-multiple>
    ...
  </article>
...

Как же отразились внесённые нами изменения на том, что мы видим в приложении? У нас всё ещё есть список разделённых запятыми значений, но значения эти теперь являются комплексными значениями — объектами. Каждый объект соответствует одной карточке и содержит все её свойства (даже если карточка пустая(!)).

Таким образом, теперь у нас есть, что посчитать (количество объектов, соответствующих карточкам, в списке), но остаётся вопрос, как это сделать. Что ж, у Mavo есть ответ и на этот вопрос. В MavoScript есть функция count(), которая позволит нам посчитать количество элементов в списке.

Следуй за белым кроликом!

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

Дело за малым — научиться правильно использовать функции в выражениях. Есть всего пара моментов, которые нужно иметь в виду.

  1. Функции являются частью выражения, следовательно, в квадратные скобки заключается всё выражение, а не функция в отдельности.
  2. Вкладывать одни квадратные скобки в другие нельзя.

Что ж, предлагаю испробовать функцию count() для подсчёта количества карточек в приложении в действии. А заодно и увидеть на примере, как правильно записать выражение с функцией:

...
<span>[count(flashcard)] шт.</span>
<article property="flashcard" mv-multiple>
  ...
</article>
...

Мы получили ровно то, к чему стремились, — теперь наше приложение показывает полезную статистику!


Практика — путь к совершенству!

Надеюсь, вы уже достаточно разогрелись и готовы продолжить экспериментировать с Mavo и… статистикой. Предупреждаю, здесь задание будет чуть позаковыристее, и вам придётся кое-что самостоятельно изучить. Но не волнуйтесь, нужными подсказками я вас вооружу.

Усовершенствуйте приложение так, чтобы статистика отображалась не только по общему количеству карточек в приложении, а также отдельно по каждой группе карточек (если вы добавляли соответствующий функционал ранее).

Подсказка!

Вам может понадобиться умение отбирать карточки, удовлетворяющие определённому критерию, и подсчитывать их количество. Для этих целей вам пригодится оператор where или функция filter().

Самооценка


У нас уже есть готовое приложение, которое позволяет создавать, редактировать и хранить набор карточек. Но как отслеживать наш прогресс: какие карточки мы уже хорошо выучили, а с какими нужно поработать ещё? Давайте подумаем, что мы можем здесь сделать.

Предлагаю остановиться на очень полезной и в то же время модной (можно сказать, хайповой) функции — функции самооценки.

Запоминай через руки!

https://codepen.io/dsharabin/pen/WNeYwxR

Итак, добавим в наше приложение две кнопки: Плохо и Хорошо. Какого поведения мы ожидаем от приложения, когда пользователь нажимает эти кнопки? В принципе, идея достаточно простая:

  • если пользователь нажал кнопку Плохо, это означает, что он ещё не выучил содержащееся на карточке слово (фразу), и приложение должно переместить её в начало — пользователь при запуске приложения будет видеть эту карточку сразу;
  • если пользователь нажал кнопку Хорошо, это означает, что он выучил соответствующее слово (фразу), и приложение должно переместить карточку в конец, чтобы предоставить пользователю возможность поработать как можно раньше с карточками, которые он ещё не выучил.

«И мы правда сможем реализовать этот функционал, не прибегая к программированию на JavaScript?» — удивитесь вы. Да. Разве я не упоминал, что Mavo — чрезвычайно мощный инструмент и может вооружить нас всем необходимым?!

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

...
<article property="flashcard" mv-multiple>
  ...
  <section>
    <h2>Оцените себя</h2>
    <button> Плохо</button>
    <button> Хорошо</button>
  </section>
</article>
...


Атрибут mv-action


Mavo предоставляет нам возможность описывать собственные правила, по которым должны обрабатываться пользовательские данные нашим приложением, — так называемые настраиваемые действия (custom actions).

Чтобы определить настраиваемое действие, необходимо добавить атрибут mv-action к соответствующему элементу приложения. Описанное с помощью данного атрибута действие выполняется при каждом щелчке по элементу, к которому добавлен этот атрибут. Это как раз соответствует тому, какое поведение мы ожидаем от кнопок самооценки — Плохо и Хорошо.

Следуй за белым кроликом!

Если добавить атрибут mv-action к элементу <form>, соответствующее настраиваемое действие выполнится при отправке формы.

Значением атрибута mv-action является выражение. При построении этого выражения можно использовать любые операции и функции, предоставляемые нам MavoScript, а также несколько специальных функций манипулирования данными: add(), set(), move() и delete().

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

И ещё один момент: Mavo ожидает в качестве значения атрибута mv-action выражение. А раз так, то нет необходимости отдельно выделять его с помощью квадратных скобок. Таким образом, правильно будет написать mv-action="выражение", а не mv-action="[выражение]". Более того, если вы напишете квадратные скобки, они будут считаться частью выражения, и вы можете получить не совсем тот результат, которого ожидаете.

Итак, вернёмся к реализации функции самооценки. При нажатии кнопок Плохо и Хорошо мы должны перемещать карточку внутри коллекции карточек на новое место: первое или последнее. Именно это действие позволяет реализовать функция move(). В качестве первого аргумента функции нужно указать перемещаемую карточку, а в качестве второго — новое место (позицию) этой карточки в коллекции. Имейте в виду, что элементы коллекции нумеруются с нуля, то есть первая карточка имеет в коллекции позицию 0.

Теперь у нас достаточно знаний, чтобы реализовать первую часть функционала самооценки: при нажатии пользователем кнопки Плохо соответствующая карточка перемещается в начало коллекции, т. е. становится первой. В коде приложения это будет выглядеть следующим образом:

...
<article property="flashcard" mv-multiple>
  ...
  <button mv-action="move(flashcard, 0)"> Плохо</button>
  ...
</article>
...

Обратите внимание, что в значении атрибута mv-action мы ссылаемся на свойство flashcard внутри самого этого свойства. Это позволяет нам работать с текущей карточкой и перемещать именно её.

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

Вспомним, что происходит при нажатии кнопки Хорошо: соответствующая карточка перемещается в конец коллекции, то есть становится последней. А чтобы сделать карточку последней, нам нужно знать, сколько всего карточек в коллекции (приложении). К счастью, мы уже знаем, как решить задачу вычисления количества карточек в приложении, и даже реализовали соответствующий функционал. Сможем ли мы воспользоваться найденным ранее способом решения в этой новой ситуации?

К сожалению, нет. И вот почему: чтобы подсчитать количество карточек в коллекции, мы ссылались на соответствующее свойство — flashcard — вне самого этого свойства. Но в этом случае нам пришлось бы сослаться на свойство flashcard внутри него. То есть нам пришлось бы написать что-то типа такого:

...
<article property="flashcard" mv-multiple>
  ...
  <button mv-action="move(flashcard, count(flashcard))">
     Хорошо
  </button>
  ...
</article>
...

И это не сработает! Вы можете легко в этом убедиться экспериментальным путём.

Так как же нам быть в этой ситуации?

Вычисляемые свойства. Элемент <meta>


Итак, с одной стороны, мы знаем, что выражение [count(flashcard)] возвращает в качестве результата количество карточек в приложении, если оно записано вне свойства flashcard. С другой стороны, нам необходимо использовать результат этого выражения внутри свойства flashcard. Да, дилемма!

Чтобы её разрешить, нам нужно суметь вычислить значение выражения [count(flashcard)] вне свойства flashcard и каким-то образом сохранить результат, чтобы впоследствии можно было его использовать в любом месте приложения. А в нашем случае — внутри свойства flashcard. Для решения такого рода задач в Mavo есть, так называемые, вычисляемые свойства (computed properties).

Запоминай через руки!

https://codepen.io/dsharabin/pen/NWKENNb

Для хранения результатов промежуточных вычислений, которые мы могли бы позже использовать в приложении, нужно выделить в коде отдельный HTML-элемент. И хотя в качестве такого элемента подойдёт любой HTML-элемент, рекомендуется использовать элемент <meta>: <meta property="имяСвойства" content="[выражение]">. У этого способа хранения результатов промежуточных вычислений есть неоспоримое преимущество: вне режима редактирования элемент <meta> скрыт как визуально, так и семантически.

Следуй за белым кроликом!

Имейте в виду, что по умолчанию вычисляемые свойства, в отличие от обычных свойств, не сохраняются.

Добавим вычисляемое свойство flashcardCount в приложение. Но не забывайте, что мы должны добавить это свойство обязательно вне свойства flashcard (но использовать его значение мы сможем в любой части приложения):

...
<meta property="flashcardCount" content="[count(flashcard)]">
<article property="flashcard" mv-multiple>
    ...
</article>
...

Теперь мы сможем реализовать недостающий шаг в функционале самооценки: при нажатии кнопки Хорошо соответствующая карточка перемещается в конец коллекции, то есть становится последней. В коде это будет выглядеть следующим образом:

...
<meta property="flashcardCount" content="[count(flashcard)]">
<article property="flashcard" mv-multiple>
  ...
  <button mv-action="move(flashcard, flashcardCount)">
     Хорошо
  </button>
</article>
...

Вот мы и закончили! Наше приложение готово. Поздравляю!

Практика — путь к совершенству!

Есть и другой способ решения этой задачи — с помощью специального свойства $all.

Специальное свойство $all, использованное внутри коллекции, соответствует ей всей. Таким образом, отпадает необходимость прибегать к вычисляемому свойству для работы со всей коллекцией внутри этой коллекции. Попробуйте самостоятельно применить специальное свойство $all вместо вычисляемого свойства flashcardCount для реализации функционала самооценки другим способом. Какой из способов вам нравится больше? Почему?

В реализованном нами приложении осталась крошечная деталь, которую нужно поправить. Теперь, когда у нас есть вычисляемое свойство, мы можем (и должны) использовать его для вычисления количества карточек в приложении. Внесите соответствующие изменения.

Выводы


Мы прошли с вами достаточно долгий путь разработки пусть и простого, но полноценного интерактивного веб-приложения. И при этом мы не написали ни одной строчки кода на языке JavaScript. Если это не чудо, то я не знаю, как это назвать по-другому. И если вы читаете эти строки, то вы оказались «крепкими орешками». Благодарю вас за это.

Как известно, образование — то, что остается после того, как забыто все, чему учили в школе. Давайте напоследок соберём воедино всё, что, я надеюсь, останется в вашей памяти после прочтения этой статьи. Итак, чтобы превратить статическую HTML-страницу в полноценное приложение Mavo, необходимо выполнить нескольких несложных шагов:

  1. Подключить (в элементе <head>) к странице JavaScript- и CSS-файлы Mavo.
  2. Добавить атрибут mv-app к корневому элементу приложения.
  3. Указать, какие элементы страницы являются важными (будут редактироваться и/или сохраняться, и/или использоваться в выражениях) для приложения, снабдив их атрибутом property.
  4. Добавить атрибут mv-multiple к свойствам, которые должны быть преобразованы в коллекции.
  5. Указать Mavo, где сохранять данные приложения, добавив атрибут mv-storage к корневому элементу приложения.
  6. Определиться, нужно ли сохранять вносимые в данные изменения автоматически. Если да, то добавить атрибут mv-autosave к корневому элементу приложения.

    Дополнительно следует знать и учитывать, что:
  7. Панель Mavo полностью настраиваемая (как и все генерируемые Mavo элементы интерфейса). С помощью атрибута mv-bar, добавленного к корневому элементу приложения, можно определить, какие системные кнопки доступны на ней.
  8. Выражения (expressions) позволяют отображать (и использовать) текущее значение свойства (property) в разных частях приложения и выполнять вычисления с ним. Значение выражения (и его тип) зависит от места, которое это выражение занимает в коде приложения. У Mavo есть свой язык выражений, называемый MavoScript.
  9. Настраиваемые действия (custom actions) позволяют модифицировать данные приложения особым образом. Для определения настраиваемых действий в приложении используется атрибут mv-action.
  10. В приложениях могут использоваться так называемые вычисляемые свойства (computed properties) — это свойства, значениями которых являются выражения. Для хранения таких свойств, а также результатов промежуточных вычислений рекомендуется использовать элемент <meta>.

Вместо эпилога


Вот мы и разработали наше приложение. Идеальное ли оно? Конечно, нет. В этом мире нет ничего идеального. Всегда есть, что можно улучшить, и всегда найдётся функционал, который можно было бы включить в приложение (например, можно сделать приложение многоязычным). Как говорится, нет предела совершенству. Дерзайте! Улучшайте приложение на ваше усмотрение. Не бойтесь экспериментировать и пробовать что-то новое — это прекрасный способ учиться и закреплять приобретённые знания! Откуда знаю? Просто уже больше 17 лет учить людей — моя профессия (работаю в учебном центре «Сетевая Академия ЛАНИТ»)!

Всё, что мы узнали про Mavo, — всего лишь верхушка айсберга, и он способен на гораздо большее. Изучите документацию и примеры реализованных с помощью Mavo проектов и убедитесь в этом лично. Вот несколько источников для самостоятельной работы: официальный сайт Mavo и коллекции демо-приложений, выполненных Лией Веру и вашим покорным слугой.

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

  • вы можете присоединиться к команде разработчиков ядра Mavo, чтобы добавлять в него новый функционал, — загляните в репозиторий Mavo и вы найдёте там массу интересного;
  • или вы можете стать автором крутого и очень полезного плагина, за который вам обязательно скажут спасибо, и уверен, не один раз;
  • готовы доработать документацию Mavo, написать самоучитель, разработать примеры, на которых будут учиться новички и не только, — будем только рады;
  • ну, или просто заглядывайте в наш чат или на страничку Mavo в Twitter, чтобы обсудить с нами, что получается, а что нет, или просто сказать «Привет» (кстати, в Gitter есть комната и для русскоговорящих поклонников Mavo);
  • а вообще, просто рассказывайте о Mavo как можно большему количеству людей — он этого достоин!

Да пребудет с вами Mavo!

Благодарности


В этой части статьи я бы хотел упомянуть двух потрясающих людей и выразить им свою признательность.

В первую очередь, я очень благодарен Лие Веру (Lea Verou), которая не только сподвигла меня к написанию оригинала этой статьи на английском языке (и помогла мне воплотить её в жизнь), но и постоянно вдохновляет меня своим примером: тем, как она делает мир веб-разработки лучше для всех. Я никогда не встречал столь одарённого человека и безумно рад, что мне выпала счастливая возможность поработать с ней!

Я также благодарю Джеймса Мура (James Moore). Примеры, которые он использует в своем курсе «Функциональное программирование в JavaScript для начинающих» на платформе Udemy, а также выбранный им подход к обучению, мотивировали меня к созданию первой версии приложения «Карточки». Он замечательный учитель!
Tags: ланит mavo
Hubs: ГК ЛАНИТ corporate blog Website development CSS Programming HTML
+58
22.2k 167
Comments 79
Ads