Расширенный HTML

в 10:45, , рубрики: ajax, declarative ui, html, javascript, JS, turbolinks, UI, Разработка веб-сайтов

В этой статье хотел бы рассказать немного про библиотеку, первую версию которой я создал еще в конце прошлого года. Суть очень простая — расширить возможности языка HTML, чтобы можно было без JavaScript'а писать простые и рутинные вещи: отправка формы в json формате, загрузка HTML тимплейтов на определенную страницу(по сути модульная система для HTML через http/s запросы), турболинки(привет пользователям RoR), простая шаблонизация на основе ответов ajax запросов и немного еще.

image

Библиотека называется EHTML или Extended HTML. Основана она на небезызвестной идее веб компонентов. Она доступна на гитхабе, там довольно таки структурированная документация с примерами. В этой статье я просто опишу основные идеи, возможно кому-то это зайдет.

Подключение библиотеки

Начнем с того, что для использования либы не нужен ни npm, ни cli тула для создания проекта, все что требуется — это HTML странички, где вы включаете скрипт библиотеки:

<head>
  <script src="/../js/ehtml.bundle.min.js" type="text/javascript"></script>
</head>

Модульная система

Начнем с простого — составление HTML странички из множества других:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
  <head>
    <script src="/../js/ehtml.bundle.min.js" type="text/javascript"></script>
  </head>

  <body class="main">
    <div class="articles">
      <e-html data-src="/../html/first.html"></e-html>
      <e-html data-src="/../html/second.html"></e-html>
      <e-html data-src="/../html/third.html"></e-html>
    </div>
  </body>
</html>

Для этого мы используем кастомный элемент e-html, который имеет единственный атрибут data-src, где мы указываем где раздается HTML часть страницы. То есть если например first.html выглядит как-то так:

<div class="article">
  <!-- some content of the first article -->
</div>

то в конечном счете, наша страница будет зарендерена следующим образом:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
  <head>
    <script src="/../js/ehtml.bundle.min.js" type="text/javascript"></script>
  </head>

  <body class="main">
    <div class="articles">
      <div class="article">
        <!-- content of the first article -->
      </div>
      <div class="article">
        <!-- content of the second article -->
      </div>
      <div class="article">
        <!-- content of the third article -->
      </div>
    </div>
  </body>
</html>

То есть теги e-html будут заменены контентом из HTML файла, который мы указывали в data-src атрибуте.

В библиотеке широко применяются элементы с тегом <template/>. Тут вы можете найти информацию про этот элемент. Одним из примеров использования этого элемента является e-wrapper, который появился в последней версии либы. По сути это <template/> элемент с атрибутом is="e-wrapper" и он позволяет заврапить динамический контент каким-нибудь статическим шаблоном. То есть скажем у вас есть статический шаблон следующего вида:

<!-- /../html/wrapper.html -->
<div class="base">
  <p>
    Header content
  </p>
  <p id="dynamic-content">
    <span>Default content</span>
  </p>
  <p>
    Footer content
  </p>
</div> 

И скажем у вас есть динамический контент, который вы хотите заврапить им:

<body class="main">
  <template 
    is="e-wrapper" 
    data-src="/../html/wrapper.html" 
    data-where-to-place="#dynamic-content" 
    data-how-to-place="instead">
    <p>
      Variation of content
    </p>
  </template>
</body>

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

<div class="base">
  <p>
    Header content
  </p>
  <p>
    Variation of content
  </p>
  <p>
    Footer content
  </p>
</div> 

С помощью атрибута data-src вы указываете HTML файл, откуда вы хотите взять статический шаблон. В атрибуте data-where-to-place вы указываете селектор элемента в статическом шаблоне куда вы хотите поместить динамический контент, который как раз находится внутри e-wrapper тимплейта. Ну и с помощью data-how-to-place вы описываете как именно вы хотите заврапить. Есть три возможные опции: instead(контент просто заменит элемент с селектором, который указан в data-where-to-place), before(контент будет помещен до указанного элемента) и соответсвенно after (контент будет помещен после элемента). Таким образом, элементы e-html и e-wrapper тимплейт позволяют комбинировать html шаблоны для составления страниц. Конечно при рендеринге делаются дополнительные http запросы, чтобы загрузить страницы, однако если использовать кэш запросов — то это проблема полностью нивелируется.

Шаблонизация ajax запрсов

Скажем у вас есть эндпоинт, который отдает информацию о музыкальном альбоме в json формте:

/../album/{title}
title = 'Humbug'
{
  "title": "Humbug",
  "artist": "Arctic Monkeys",
  "type": "studio album",
  "releaseDate": "19 August 2009",
  "genre": "psychedelic rock, hard rock, stoner rock, desert rock",
  "length": "39:20",
  "label": "Domino",
  "producer": "James Ford, Joshua Homme"
}

Тогда используя тимплейтный элемент с атрибутом is="e-json", вы можете загрузить и замапить ответ на HTML:

<template is="e-json" data-src="/../album/Humbug" data-object-name="albumResponse">
  <div data-text="Title: ${albumResponse.body.title}"></div>
  <div data-text="Artist: ${albumResponse.body.artist}"></div>
  <div data-text="Type: ${albumResponse.body.type}"></div>
  <div data-text="Release date: ${albumResponse.body.releaseDate}"></div>
  <div data-text="Genre: ${albumResponse.body.genre}"></div>
  <div data-text="Length: ${albumResponse.body.length}"></div>
  <div data-text="Label: ${albumResponse.body.label}"></div>
  <div data-text="Producer: ${albumResponse.body.producer}"></div>
</template>

Атрибут data-src указывает, от куда вы загружаете информацию в json формате, data-object-name — это имя объекта ответа, который содержит основные компоненты http ответа: body, headers и statusCode. Атрибут data-text позволяет вам использовать значения объекта в качестве текста, который будет помещен в шаблон внутри e-json тимплейта.

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

title = 'Humbug'
{
  "title": "Humbug",
  "artist": "Arctic Monkeys",
  "songs": [
    { "title": "My Propeller", "length": "3:27" },
    { "title": "Crying Lightning", "length": "3:43" },
    { "title": "Dangerous Animals", "length": "3:30" },
    { "title": "Secret Door", "length": "3:43" },
    { "title": "Potion Approaching", "length": "3:32" },
    { "title": "Fire and the Thud", "length": "3:57" },
    { "title": "Cornerstone", "length": "3:18" },
    { "title": "Dance Little Liar", "length": "4:43" },
    { "title": "Pretty Visitors", "length": "3:40" },
    { "title": "The Jeweller's Hands", "length": "5:42" }
  ]
}

Тогда с помощью тимплейта e-for-each можно показать весь список песен:

<template is="e-json" data-src="/../album/Humbug" data-object-name="albumResponse">
    <div data-text="Title: ${albumResponse.body.title}"></div>
    <div data-text="Artist: ${albumResponse.body.artist}"></div>

    <div><b data-text="${albumResponse.body.songs.length} songs:"></b></div>
    <template 
      is="e-for-each"
      data-list-to-iterate="${albumResponse.body.songs}"
      data-item-name="song">
      <div class="song-box">
        <div data-text="No. ${song.index}/${album.songs.length}"></div>
        <div data-text="Title: ${song.title}"></div>
        <div data-text="Length: ${song.length}"></div>
      </div>
    </template>
</template>

В атрибуте data-list-to-iterate мы указываем список из ответа, который мы хотим итерировать. А в атрибуте data-item-name мы декларируем имя переменной для каждого айтема, которое мы будем использовать внутри e-for-each тимплейта.

Также можно например использовать в комбинации тимплейты e-for-each и e-if для отображения лишь определенных песен:

<template is="e-json" data-src="/../album/Humbug" data-object-name="albumResponse">
    <div data-text="Title: ${albumResponse.body.title}"></div>
    <div data-text="Artist: ${albumResponse.body.artist}"></div>

    <div><b data-text="${albumResponse.body.songs.length} songs:"></b></div>
    <template is="e-for-each" 
      data-list-to-iterate="${albumResponse.body.songs}" 
      data-item-name="song">
      <template 
         is="e-if"
         data-condition-to-display="${(song.length.split(':')[0] * 60 + song.length.split(':')[1] * 1) <= 210}"
      >
        <div class="song-box">
          <div data-text="No. ${song.index}/${album.songs.length}"></div>
          <div data-text="Title: ${song.title}"></div>
          <div data-text="Length: ${song.length}"></div>
        </div>
      </template>
    </template>
</template>

В атрибуте data-condition-to-display тимплейта e-if мы указываем условие, при котором тот или иной айтем отображается.

Отправка формы

Последнее что хочется показать в этой статье — это отправка форм. Очень частно хочется отправлять данные из формы в json формате, поэтому был создан элемент e-form, который предоставляет такую возможность.

Допустим у нас есть следующий эндпоинт, который позволяет создавать/сохранять новые альбомы:

/artist/{name}/albums/add 
name = 'Arctic Monkeys'
POST
Example of expected request body: {
  "title": "Humbug",
  "type": "studio album",
  "releaseDate": "19 August 2009",
  "genre": ["psychedelic rock", "hard rock", "stoner rock", "desert rock"],
  "length": "39:20",
  "label": "Domino",
  "producer": "James Ford, Joshua Homme"
}

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

<e-form>

  Title:
  <input type="text" name="title">

  Type:
  <input type="radio" name="type" value="studio album" checked>
  <label for="one">One</label>

  <input type="radio" name="type" value="live album" checked>
  <label for="one">One</label>

  Release date:
  <input type="date" name="releaseDate">

  Genre:
  <input type="checkbox" name="genre" value="psychedelic rock">
  <input type="checkbox" name="genre" value="hard rock">
  <input type="checkbox" name="genre" value="stoner rock">
  <input type="checkbox" name="genre" value="desert rock">

  Total length:
  <input type="time" name="totalLength">

  Producer:
  <input type="text" name="producer">

  <button
    id="send"
    data-request-url="/artist/Arctic_Monkeys/albums/add"
    data-request-method="POST"
    data-request-headers="{}"
    data-ajax-icon="#ajax-icon"
    data-response-name="savedAlbum"
    onclick="this.form.submit(this)"
    data-actions-on-response="
      logToConsole('response: ', '${savedAlbum}');
    "
  />

  <img id="ajax-icon" src="/../images/ajax-loader.gif"/>

</e-form>

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

Больше информации

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

Буду рад ответить на вопросы, если такие возникнут. И спасибо за внимание!

Ссылка на репу.

Автор: guseynchick

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js