ruwepro
v1.2.7
Published
A JavaScript framework for quickly creating reactive interfaces.
Maintainers
Readme
(c) 2026-contacts [email protected]
- Основы за 10 минут
- Класс компонента
- Служебные свойства
- Циклы
- Общие методы
- Взаимодействие компонентов
- ~~Маршрутизатор~~
- ~~Серверный рендеринг~~
Ruwe [Руви] – реактивные пользовательские веб-элементы (Reactive User Web Elements). Это фреймворк JavaScript для быстрого создания реактивных интерфейсов на основе современной технологи Веб-компонентов.
В Ruwe реализованы:
- поддержка многоуровневых циклов
- взаимодействие различных компонентов между собой
- простой, но гибкий маршрутизатор
- серверный рендеринг в среде выполнения Node.js
Ниже представлен пример создания простого компонента:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2 :title="message">Привет, <span>{{ title }}</span>!</h2>
<button :onclick="hide = !hide">Скрыть/Показать</button>
<button :onclick="reverseArray">Обратить массив</button>
<div :hidden="hide" #for="user of users">
<pre>{{ user }}</pre>
</div>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
// асинхронный статический метод инициализации
static async init() {
// имитировать получение данных с сервера через 1 секунду
this.users = await new Promise(ok => setTimeout(() => ok([
{
name: 'Иван',
age: 28
},
{
name: 'Мария',
age: 32
},
{
name: 'Андрей',
age: 43
}
]), 1000))
}
// статические свойства компонента
static mode = 'open'
// пользовательские данные компонента
hide = false
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
// пользовательские методы компонента
reverseArray() {
this.users.reverse()
}
}
</script>
</template>В Ruwe используется два типа файлов:
- ruwe.esm.js – для модулей ES6
- ruwe.global.js – для браузеров
Файлы с суффиксом prod предназначены для использования в готовом приложении:
- ruwe.esm.prod.js
- ruwe.global.prod.js
Все файлы имеют минимизированные версии:
- ruwe.esm.min.js
- ruwe.global.min.js
- ruwe.esm.prod.min.js
- ruwe.global.prod.min.js
Компоненты в Ruwe можно создавать двумя различными способами. При первом способе, используются обычные классы:
class RHello {
static content = `
<h2 :title="message">Привет, <span>{{ title }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
`
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}Здесь статическому свойству content класса RHello присваивается разметка компонента в виде строки. Значения пользовательских свойств title и accent, внутри разметки вставляются между двойными фигурными скобками. Чтобы значение пользовательского свойства message выводилось в атрибуте title, его имя должно начинаться с символа двоеточия « : ». Это относится к атрибутам всех типов: пользовательским, булевым (логическим), событиям и прочим.
Второй способ создания компонента заключается в определении его шаблона в элементе <template> и в возвращении из этого элемента класса с данными компонента, как показано ниже:
<template name="r-hello">
<h2 :title="message">Привет, <span>{{ title }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}
</script>
</template>Привила применяемые для компонентов создаваемых в классах, действуют одинаково и для компонентов определяемых в элементах.
После определения компонента, его необходимо подключить к странице нашего приложения, для этого нужно указать на странице тег монтирования компонента совпадающий с его названием:
<r-hello></r-hello>и передать методу create(), глобального объекта Ruwe, класс или элемент, в котором был определён компонент:
Ruwe.create(RHello) или
Ruwe.create(document.querySelector('template[name="r-hello"]'))Ниже показано содержимое файла index.html, в котором определяется компонент в элементе <template>, и его последующая передача методу создания компонентов:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента -->
<r-hello></r-hello>
<!-- элемент определения компонента -->
<template name="r-hello">
<h2 :title="message" :hidden="hide">Привет, <span>{{ title }}</span>!</h2>
<button :onclick="hide = !hide">Скрыть/Показать</button>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
hide = false
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}
</script>
</template>
<!-- подключение объекта Ruwe -->
<script src="ruwe.global.js"></script>
<script>
// передача методу create() ссылки на элемент определения
Ruwe.create(document.querySelector('template[name="r-hello"]'))
</script>
</body>
</html>Тот же самый пример, но с использованием класса для определения компонента:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента -->
<r-hello></r-hello>
<!-- подключение объекта Ruwe -->
<script src="ruwe.global.js"></script>
<script>
// класс определения компонента
class RHello {
static content = `
<h2 :title="message" :hidden="hide">Привет, <span>{{ title }}</span>!</h2>
<button :onclick="hide = !hide">Скрыть/Показать</button>
<style>
span {
color: {{ accent }};
}
</style>
`
hide = false
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}
// передача методу create() ссылки на класс определения
Ruwe.create(RHello)
</script>
</body>
</html>Вы можете использовать любой способ определения компонентов, но в дальнейших примерах компоненты будут определяться в элементах <template>.
Чтобы гарантировать отсутствие конфликтов имён между стандартными и пользовательскими элементами, имя компонента должно содержать символ дефиса « - ». Например, название my-element – это правильное имя компонента, а myelement – нет. В большинстве примеров, префикс будет состоять из одной буквы « r- », т.е. компонент с основным именем Hello будет называться r-hello.
<template name="r-hello">При определении компонента с помощью класса, его префикс и основное имя должны начинаться с заглавной буквы. RHello, MyHello и MyFirstHello – это правильные названия классов, а rHello, myHello и myFirstHello – нет, например:
// Правильно!
class RHello {}
class MyHello {}
class MyFirstHello {}
// Ошибка...
class rHello {}
class myHello {}
class myFirstHello {}При определении компонента с помощью элемента <template>, его название указывается в атрибуте name или data-name, а префикс отделяется от основного имени через дефис:
<template name="r-hello">
<template name="my-hello">
<template name="my-first-hello">
<template data-name="r-hello">
<template data-name="my-hello">
<template data-name="my-first-hello">Регистр при этом значения не имеет. Для удобства чтения, можно использовать верхний регистр в названиях компонентов:
<template name="R-HELLO">
<template name="MY-HELLO">
<template name="MY-FIRST-HELLO">При определении компонентов в элементах <template>, многие редакторы будут подсвечивать код как ошибку, из-за использования двойных фигурных скобок в значениях стилей:
В редакторе Visual Studio Code это можно исправить зайдя в меню: Файл → Настройки → Параметры, затем введите в строке поиска html validate и снимите галочку с пункта HTML › Validate: Styles, чтобы отключить проверку встроенных стилей:
Располагать все компоненты в главном файле приложения (в файле index.html) не очень удобно. При использовании классов для определения компонентов, их лучше вынести в отдельные модули или в общий для всех компонентов единственный модуль. Для работы с модулями, в Ruwe применяется второй тип файлов данного фреймворка, содержащий в своём названии суффикс esm.
- Создайте папку проекта с названием my-project
- Создайте в папке проекта файл index.html
- Создайте в папке проекта файл components.js
- Скачайте в папку проекта файл ruwe.esm.js
Ниже показана структура папки создаваемого проекта:
my-project/
index.html
components.js
ruwe.esm.jsДобавьте в файл components.js следующий код:
// получение метода create() из модуля объекта Ruwe
import { create } from './ruwe.esm.js'
// класс определения компонента
class RHello {
static content = `
<h2 :title="message" :hidden="hide">Привет, <span>{{ title }}</span>!</h2>
<button :onclick="hide = !hide">Скрыть/Показать</button>
<style>
span {
color: {{ accent }};
}
</style>
`
hide = false
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}
// передача методу create() ссылки на класс определения
create(RHello)Внесите изменения в файл index.html, как показано ниже:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента -->
<r-hello></r-hello>
<!-- подключение модуля components.js -->
<script type="module" src="components.js"></script>
</body>
</html>Для работы с модулями или фреймами, приложение должно запускаться от имени сервера. Установите lite-server следуя инструкции разработчика или введите в терминале команду:
npm i --global lite-serverЗатем зайдите в папку проекта, запустите из этой папки терминал (или перейдите в эту папку из терминала), и в терминале введите следующую команду:
lite-serverПосле этого должно открыться окно браузера по умолчанию, в котором будет показано созданное выше приложение:
Метод create() объекта Ruwe может принимать любое количество ссылок на компоненты, например:
create(RHeader, RMenu, RContent, RFooter, ...RName)Передаваемые ссылки могут быть на компоненты разных типов, как определяемые в элементах <template>, так и в классах:
create(document.querySelector('template[name="r-header"]'), RMenu)Не смотря на удобство использования модулей ES6, сам факт того, что разметка компонента для классов создаётся в строке, может вызывать определённое неудобство. Именно для этого существует возможность выносить компоненты создаваемые в элементах <template> во внешние файлы .html, которые в дальнейшем подключаются как обычные фреймы.
- Создайте в папке проекта файл components.html
- Скачайте в папку проекта файл ruwe.global.js
Ниже показана изменённая структура папки созданного выше проекта:
my-project/
index.html
components.js
ruwe.esm.js
components.html
ruwe.global.jsДобавьте в файл components.html следующий код:
<!DOCTYPE html>
<!-- подключение объекта Ruwe -->
<script src="ruwe.global.js"></script>
<!-- элемент определения компонента -->
<template name="r-hello">
<h2 :title="message" :hidden="hide">Привет, <span>{{ title }}</span>!</h2>
<button :onclick="hide = !hide">Скрыть/Показать</button>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
hide = false
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
}
</script>
</template>
<script>
// передача методу create() ссылки на элемент определения
Ruwe.create(document.querySelector('template[name="r-hello"]'))
</script>Когда компонентов будет достаточно много и чтобы не выбирать каждый элемент определения отдельно, лучше выбрать все элементы сразу и передать их методу создания за один раз:
<script>
// передача методу create() ссылок на элементы определения
Ruwe.create(...document.querySelectorAll('template'))
</script>Другими словами, содержимое файла components.html в реальном проекте может выглядеть следующим образом:
<!DOCTYPE html>
<script src="ruwe.global.prod.min.js"></script>
<template name="R-ONE">
<h2>Компонент {{ title }} </h2>
<script>
return class {
title = 'Один'
}
</script>
</template>
<template name="R-TWO">
<h2>Компонент {{ title }} </h2>
<script>
return class {
title = 'Два'
}
</script>
</template>
<script>
Ruwe.create(...document.querySelectorAll('template'))
</script>Внесите изменения в файл index.html, как показано ниже:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента -->
<r-hello></r-hello>
<!-- подключение фрейма components.html -->
<iframe src="components.html" hidden></iframe>
</body>
</html>Затем зайдите в папку проекта, запустите из этой папки терминал (или перейдите в эту папку из терминала), и в терминале введите следующую команду:
lite-serverПосле этого должно открыться окно браузера по умолчанию, в котором будет показано созданное выше приложение:
Какой способ выбрать, модули или фреймы, зависит от предпочтений разработчика. Ruwe изначально не требует сложных систем сборки (при желании можно использовать бандлеры типа: Webpack, Rollup и т.д.), позволяя без дополнительных инструментов выносить компоненты в отдельные файлы (или один общий файл), не засоряя код главного файла приложения index.html.
Любой компонент представляет собой обычный класс, в котором указываются разметка, данные (пользовательские свойства) и методы компонента:
class RHello {
// разметка компонента
static content = `
<h2>Привет, <span>{{ getTitle }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
`
// данные компонента
title = 'Ruwe'
accent = 'red'
// методы компонента
getTitle() {
return this.title
}
}При определении компонента в классе, разметка назначается статическому свойству content в виде строки, а при использовании элемента <template>, указывается в самом элементе:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, <span>{{ getTitle }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
// данные компонента
title = 'Ruwe'
accent = 'red'
// методы компонента
getTitle() {
return this.title
}
}
</script>
</template>При необходимости, можно использовать старый способ определения данных компонента в конструкторе:
<script>
return class {
constructor() {
// данные компонента
this.title = 'Ruwe'
this.accent = 'red'
}
// методы компонента
getTitle() {
return this.title
}
}
</script>Если компонент, который был определён в элементе <template> не возвращает класс с данными и методами компонента, то ему автоматически назначается пустой класс:
<template name="R-HELLO">
<h2>Компонент с пустым классом</h2>
</template>Класс компонента возвращает объект данных (экземпляр класса) для каждого нового компонента. Все данные определяемые в компоненте являются наблюдаемыми. Это означает, что при их изменении, автоматически обновляется та часть разметки, где эти данные были указаны. Но это не относится к методам компонента. Методы содержатся в прототипе класса компонента и НЕ отслеживаются!
Данные и методы компонента указываются в разметке между {{ двойными }} фигурными скобками, например:
<!-- пользовательское свойство title -->
<h2>Привет, <span>{{ title }}</span>!</h2>
<!-- пользовательский метод getTitle без вызова -->
<h2>Привет, <span>{{ getTitle }}</span>!</h2>
<!-- пользовательский метод getTitle() с вызовом -->
<h2>Привет, <span>{{ getTitle() }}</span>!</h2>или в атрибутах, названия которых начинаются с символа двоеточия « : », как показано ниже:
<!-- метод без вызова и пользовательское свойство -->
<h2 :title="getTitle" :hidden="hide">Привет, Ruwe!</h2>Кроме методов и свойств, можно использовать любые выражения:
<!-- выражение в двойных фигурных скобках -->
<h2>Привет, <span>{{ title.toUpperCase() + ' и Веб-компоненты' }}</span>!</h2>
<!-- выражение в атрибуте с символом двоеточия -->
<h2 :title="getTitle().toUpperCase() + ' и Веб-компоненты'">Привет, Ruwe!</h2>Метод можно указывать как с вызовом, добавив в конце его названия круглые « () » скобки:
<!-- пользовательский метод getTitle() с вызовом -->
<h2>Привет, <span>{{ getTitle() }}</span>!</h2>так и без вызова, указав лишь одно его название:
<!-- пользовательский метод getTitle без вызова -->
<h2>Привет, <span>{{ getTitle }}</span>!</h2>В последнем случае, кроме названия не должно быть указано ничего. Пробелы допустимы как перед именем метода, так и после него. Но никакие другие выражения или символы для методов без вызова НЕ допускаются!
Однако, для методов с вызовом допускаются любые выражения:
<!-- метод с вызовом и выражение в двойных фигурных скобках -->
<h2>Привет, <span>{{ getTitle().toUpperCase() + ' и Веб-компоненты' }}</span>!</h2>
<!-- метод с вызовом и выражение в атрибуте с символом двоеточия -->
<h2 :title="getTitle().toUpperCase() + ' и Веб-компоненты'">Привет, Ruwe!</h2>Если метод без вызова используется в атрибуте события, то ему автоматически передаётся в первом аргументе объект события Event, как показано ниже:
<template name="R-HELLO">
<!-- автоматическая передача методу без вызова объекта события Event -->
<h2 :onclick="getEvent">Привет, Ruwe!</h2>
<script>
return class {
getEvent(e) {
// доступ к свойству target объекта события Event
console.log(e.target)
}
}
</script>
</template>А если в атрибуте события используется метод с вызовом, то ему необходимо вручную передать данный объект:
<!-- ручная передача методу с вызовом объекта события Event -->
<h2 :onclick="getEvent(event)">Привет, Ruwe!</h2>Позже будут показаны различия при передаче переменных из цикла/циклов в методы с вызовом и без, как вызываемых в самих атрибутах событий, так и в других местах разметки компонента внутри цикла.
Кроме пользовательских методов и свойств, каждый класс может содержать собственные статические методы и свойства, которые влияют на работу самого компонента.
В Ruwe существует специальный статический метод init(), который может использоваться для инициализации компонента данными сразу, после возвращения классом своего экземпляра (объекта данных компонента), например:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, {{ title }}!</h2>
<script>
return class {
// статический метод init()
static init() {
// инициализировать пользовательское свойство title
this.title = 'Ruwe'
}
}
</script>
</template>Все статические методы компонента могут быть асинхронными. Особенно это полезно для метода инициализации компонента данными, например, полученными с сервера через какое-то время:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, {{ title }}!</h2>
<script>
return class {
// асинхронный статический метод init()
static async init() {
// инициализировать пользовательское свойство title через 1 секунду
this.title = await new Promise(ok => setTimeout(() => ok('Ruwe'), 1000))
}
}
</script>
</template>В примере выше, создание компонента будет приостановлено до тех пор, пока не будут получены для него данные от промиса, который выступает здесь вместо сервера для примера получения данных извне.
Далее идут стандартные методы Веб-компонентов. В Ruwe они имеют короткие названия, например, стандартный для Веб-компонентов метод connectedCallback(), в Ruwe называется просто connected() и определяется как статический метод:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, {{ title }}!</h2>
<script>
return class {
// данные компонента
title = 'Ruwe'
// статический метод connected()
static connected() {
// показать корневой элемент компонента
console.log(this.$root)
}
}
</script>
</template>Аналогичным образом вызываются и другие стандартные методы Веб-компонентов:
// метод connected() вместо disconnectedCallback()
static disconnected() {
console.log('disconnected')
}
// метод adopted() вместо adoptedCallback()
static adopted() {
console.log('adopted')
}
// метод changed() вместо attributeChangedCallback()
static changed(name, oldValue, newValue) {
console.log('changed', name, oldValue, newValue)
}
// массив attributes вместо геттера observedAttributes()
static attributes = ['attrname1', 'attrname2', 'attrname3', 'attrnameN']Подробнее о работе этих методов можно прочитать в руководстве по Веб-компонентам. Важно лишь отметить, что все статические методы в Ruwe, включая и рассмотренный метод init(), выполняются в контексте специального прокси, который предоставляет доступ через ключевое слово this одновременно и к данным компонента:
static connected() {
// показать пользовательское свойство title
console.log(this.title)
}и к его служебным свойствам:
static connected() {
/* показать корневой элемент компонента,
на который ссылается служебное свойство $root */
console.log(this.$root)
}Служебные свойства добавляются компоненту автоматически и могут быть доступны вне статических методов, например, через элемент монтирования компонента. Все служебные свойства начинаются со знака доллара « $ », и о них подробнее будет рассказано далее.
Помимо статических методов, класс может содержать статические свойства, например:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, <span>{{ title }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
// данные компонента
title = 'Ruwe'
accent = 'red'
// статические свойства компонента
static mode = 'open'
}
</script>
</template>– Статическое свойство mode добавляет компоненту Теневой DOM. Оно может принимать одно из двух значений: open или closed. Теневой DOM позволяет изолировать стили компонента, не разрешая им распространяться наружу и влиять на другие компоненты и/или элементы в приложении.
При значении closed, все служебные свойства, которые начинаются со знака доллара « $ », становятся доступны только внутри компонента. Снаружи, например, через элемент монтирования компонента, доступ к ним получить невозможно! Кроме этого, такие компоненты не рендерятся с помощью встроенного в Ruwe метода render(), о чём позже будет продемонстрировано.
– Статическое свойство serializable определяет, будет ли компонент сериализован с помощью современного метода getHTML(). Оно может принимать одно из двух значений: true или false. Например:
static serializable = trueПо умолчанию, для компонентов без Теневого DOM или для компонентов с открытым (static mode = 'open') теневым деревом, свойство serializable имеет значение true, а для компонентов с закрытым (static mode = 'closed') Теневым DOM, значение false. Важно ещё раз отметить, что встроенный метод render(), в отличие от метода getHTML(), игнорирует значение статического свойства serializable . И содержимое всех закрытых (static mode = 'closed') компонентов НЕ будет рендериться!
– Статическое свойство shadow является объектом и может принимать все те же параметры, которые принимает метод attachShadow(), когда компоненту добавляется Теневой DOM. При этом, если свойства mode и/или serializable указываются в объекте shadow, например:
static shadow = { mode: 'open', serializable: false }то они перекрывают значения этих свойств указанных по отдельности:
static mode = 'closed'
static serializable = true– Статическое свойство extends позволяет подключать компоненты в стандартные HTML-элементы, например:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, <span>{{ title }}</span>!</h2>
<script>
return class {
// данные компонента
title = 'Ruwe'
// статические свойства компонента
static extends = 'header'
}
</script>
</template>В примере выше указывается, что компонент должен быть подключён в стандартный HTML-элемент <header>. Но для того, чтобы такой компонент отобразился, тег монтирования компонента, в данном случаее <header>, должен иметь атрибут is со значением, которое соответствует названию компонента, в нашем случае r-hello, как показано ниже:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента с атрибутом is -->
<header is="r-hello"></header>
<!-- подключение фрейма components.html -->
<iframe src="components.html" hidden></iframe>
</body>
</html>– Статическое свойство with позволяет отключать инструкцию with, которая используется в Ruwe для того, чтобы к пользовательским свойствам и методам в разметке, можно было получать доступ без ключевого слова this, как показано во всех примерах ранее:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2 :title="message">Привет, <span>{{ getTitle() }}</span>!</h2>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
// данные компонента
title = 'Ruwe'
accent = 'red'
message = 'реактивные веб-элементы'
// методы компонента
getTitle() {
return this.title
}
}
</script>
</template>Без этой инструкции, к свойствам и методам в разметке, везде пришлось бы добавлять это ключевое слово:
<!-- разметка компонента -->
<h2 :title="this.message">Привет, <span>{{ this.getTitle() }}</span>!</h2>
<style>
span {
color: {{ this.accent }};
}
</style>Чтобы отключить данную инструкцию, необходимо статическому свойству with присвоить значение false, например:
static with = falseЭто сделано для того, чтобы если в будущем данная инструкция окажется под запретом, то её всегда можно было бы отключить в Ruwe.
Свойство with со значением false, физически удаляет эту инструкцию из кода, который используется при создании обратных вызовов для пользовательских методов и свойств компонента. Кроме этого, использование ключего слова this в разметке, допускается и без отключения данной инструкции!
– Статическое свойство short позволяет задать короткое имя (псевдоним) для ключевого слова this в разметке:
static short = 'o'Тогда в разметке, вмеcто этого ключевого слова можно будет использовать указанный псевдоним:
<!-- разметка компонента -->
<h2 :title="o.message">Привет, <span>{{ o.getTitle() }}</span>!</h2>
<style>
span {
color: {{ o.accent }};
}
</style>– Статическое свойство shallow позволяет ограничить глубину отслеживания значений в ссылочных (объектах и массивах) пользовательских свойствах. По умолчанию, глубина отслеживания ничем не ограничивается:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, {{ obj.title }}!</h2>
<p>{{ obj.arr }}</p>
<button :onclick="obj.title = 'Веб-компоненты'">
Изменить значение заголовка</button>
<button :onclick="obj.arr[1] = 'Д'">
Изменить второй элемент массива</button>
<script>
return class {
// данные компонента
obj = {
title: 'Ruwe',
arr: ['А', 'Б', 'В']
}
}
</script>
</template>В примере выше, при нажатии на кнопки меняется значение собственного свойства пользовательского объекта, и значение во втором элементе принадлежащего ему массива. Если бы таких массивов было тысяча, то для каждого был бы создан собственный наблюдатель (специальный прокси). Наблюдатели создаются только для ссылочных типов!
Чтобы сэкономить ресурсы на создании множества таких наблюдателей, можно ограничить глубину отслеживания ссылочных значений в объекте данных компонента, указав статическому свойству shallow значение true, например:
static shallow = trueВ этом случае, наблюдатель добавляется только для собственных ссылочных значений объекта данных компонента, но не для их потомков, которым, например, является массив в объекте выше. Поэтому значение этого массива не изменится, однако свойство title данного объекта будет меняться при нажатии на первую кнопку.
Чтобы изменить значение элемента массива, необходимо будет присвоить пользовательскому объекту новый массив:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, {{ obj.title }}!</h2>
<p>{{ obj.arr }}</p>
<button :onclick="obj.title = 'Веб-компоненты'">
Изменить значение заголовка</button>
<button :onclick="obj.arr = ['А', 'Д', 'В']">
Присвоить объекту новый массив</button>
<script>
return class {
// данные компонента
obj = {
title: 'Ruwe',
arr: ['А', 'Б', 'В']
}
// статические свойства компонента
static shallow = true
}
</script>
</template>Ограничивать глубину отслеживания ссылочных значений объекта данных компонента, при таком простом массиве, НЕ имеет смысла! Но если массивов много, и все они сложные, т.е. содержат объекты и другие массивы, то ограничение глубины отслеживания может дать неплохой прирост производительности.
– Статическое свойство space позволяет управлять пробелами для красивого форматирования объектов, массивов и коллекций типа Map, и Set, при выводе на экран браузера, используя в разметке теги <pre>, как показано ниже:
<template name="R-HELLO">
<!-- разметка компонента -->
<pre>{{ obj }}</pre>
<pre>{{ set }}</pre>
<pre>{{ map }}</pre>
<script>
return class {
// данные компонента
obj = {
title: 'Ruwe',
arr: ['А', 'Б', 'В']
}
set = new Set(['Весна', 'Лето', 'Осень', 'Зима'])
map = (new Map).set({ name: 'Коля', age: 37}, 123)
}
</script>
</template>Данный тег выведет на экран браузера следующий текст:
Используя свойство space, можно указать количество пробелов (по умолчанию два), или любых других символов, которые будут выводиться вместо них. Это свойство соответствует параметру space метода JSON.stringify(). Например, если указать двойное тире:
static space = '--'то на экран будет выведен следующий текст:
Вместо непосредственного указания символов для форматирования, можно использовать цифры, которые указывают количесто пробелов при форматировании:
static space = 4покажет следующее:
Если вместо тегов <pre> использовать другие, например <div>, как показано ниже:
<template name="R-HELLO">
<!-- разметка компонента -->
<div>{{ obj }}</div>
<div>{{ set }}</div>
<div>{{ map }}</div>
<script>
return class {
// данные компонента
obj = {
title: 'Ruwe',
arr: ['А', 'Б', 'В']
}
set = new Set(['Весна', 'Лето', 'Осень', 'Зима'])
map = (new Map).set({ name: 'Коля', age: 37}, 123)
// статические свойства компонента
static space = 4
}
</script>
</template>то форматирование сохранится лишь по горизонтали, при этом, количество пробелов не будет иметь никакого значения:
Чтобы убрать любое форматирование для массивов и объектов (к которым относятся и коллекции), необходимо использовать в разметке оператор расширения, как показано ниже:
<template name="R-HELLO">
<!-- разметка компонента -->
<pre>{{ { ...obj } }}</pre>
<pre>{{ [ ...set ] }}</pre>
<pre>{{ [ ...map ] }}</pre>
<div>{{ [ ...arr2 ] }}</div>
<script>
return class {
// данные компонента
obj = {
title: 'Ruwe',
arr: ['А', 'Б', 'В']
}
set = new Set(['Весна', 'Лето', 'Осень', 'Зима'])
map = (new Map).set({ name: 'Коля', age: 37}, 123)
arr2 = ['Г', 'Д', 'Е']
}
</script>
</template>Обернув предварительно объект с оператором расширения, в литерал объекта:
{ ...obj }а коллекции и массивы, в литерал массива:
[ ...set ]
[ ...map ]
[ ...arr2 ]Выведет на экран следующее:
– Статическое свойство timer позволяет указать название таймера консоли, которое будет выводиться в консоль после каждого изменения пользовательского свойства данных компонента. Это может пригодиться на этапе разработки приложений с большими массивами данных, например, содержащими тысячи элементов:
<template name="R-HELLO">
<!-- разметка компонента -->
<button :onclick="reverseArray">Обратить массив</button>
<ul #for="item of arr">
<li>Элемент {{ item }}</li>
</ul>
<script>
return class {
// данные компонента
arr = Array.from({ length: 10000 }, (_, idx) => idx)
// методы компонента
reverseArray() {
this.arr.reverse()
}
// статические свойства компонента
static timer = 'Update'
}
</script>
</template>В примере выше, после нажатия на кнопку "Обратить массив", в консоль браузера будет выведено название таймера и время обращения массива из десяти тысяч элементов. Время может различаться, в зависимости от используемого оборудования и системы:
Update: 18.2490234375 msВ Ruwe доступны следующие служебные свойства:
$host – предоставляет доступ к хозяину компонента, в роли которого выступает тег монтирования компонента.
$root – предоставляет доступ к корню Теневого DOM. Если компонент не имеет теневого дерева, то это свойство ссылается на хозяина компонента.
$props – предоставляет доступ из дочерних компонентов к данным, переданным из родительского.
$data – предоставляет доступ к наблюдаемому объекту данных компонента. В роли такого объекта выступает экземпляр класса компонента, который был обёрнут в специальный прокси.
$event – предоставляет доступ к методу $event(), который испольуется для создания пользовательских событий, необходимых для взаимодействия различных компонентов между собой, например, для передачи данных из одного компонента в другой или управление им.
$router – предоставляет доступ к методу $router(), который используется для создания простого, но гибкого маршрутизатора.
Почти все служебные свойства доступны в рассмотренных ранее статических методах и в разметке компонента:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, <span>{{ title }}</span>!</h2>
<style>
span {
color: {{ $host.dataset.accent }};
}
</style>
<script>
return class {
// выполняется ДО создания разметки и наблюдаемого объекта данных
static init() {
const { dataset } = this.$host
this.title = dataset.title
console.log(this.$root.querySelector('h2')) // null
console.log(this.$data) // undefined
}
// выполняется ПОСЛЕ создания разметки и наблюдаемого объекта данных
static connected() {
console.log(this.$root.querySelector('h2')) // <h2>
console.log(this.$data) // $data
console.log(this.title) // Ruwe
console.log(this.accent) // undefined
}
}
</script>
</template>Внесите изменения в файл index.html, чтобы добавить пользовательские атрибуты тегу монтирования компонента, как показано ниже:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента -->
<r-hello data-title="Ruwe" data-accent="red"></r-hello>
<!-- подключение фрейма components.html -->
<iframe src="components.html" hidden></iframe>
</body>
</html>Во время выполения кода в методе init(), разметка и наблюдаемый объект данных компонента ещё НЕ были созданы, поэтому доступ к ним получить невозможно:
static init() {
...
console.log(this.$root.querySelector('h2')) // null
console.log(this.$data) // undefined
}Но в методе connected() они становятся доступны, поскольку он выполняется почти в самом конце процесса создания компонента:
static connected() {
console.log(this.$root.querySelector('h2')) // <h2>
console.log(this.$data) // $data
...
}В разметке, служебные свойства всегда указываются без ключевого слова this, например:
<style>
span {
color: {{ $host.dataset.accent }};
}
</style>иначе будет ошибка:
<style>
span {
/* Ошибка... */
color: {{ this.$host.dataset.accent }};
}
</style>Доступ к служебным свойствам можно получить не только внутри, но и снаружи компонента. Однако, для компонентов с закрытым (static mode = 'closed') Теневым DOM, свойства: $host, $root, $props и $data, снаружи НЕ являются доступными! Если компонент был создан закрытым изначально, то по определению, доступ к свойствам влияющим на его данные и разметку, не может быть предоставлен кому-либо извне, доступ возможен только внутри него самого.
Например, создадим компонент с открытым теневым деревом:
<template name="R-HELLO">
<!-- разметка компонента -->
<h2>Привет, <span>{{ title }}</span>!</h2>
<ul #for="item of arr">
<li>Элемент {{ item }}</li>
</ul>
<style>
span {
color: {{ accent }};
}
</style>
<script>
return class {
// данные компонента
title = 'Ruwe'
accent = 'red'
arr = Array.from({ length: 10 }, (_, idx) => idx)
// статические свойства компонента
static mode = 'open'
}
</script>
</template>Чтобы можно было легко получить к нему доступ снаружи, необходимо добавить тегу монтирования данного компонента простой идентификатор, например, с названием hello. Внесите изменения в файл index.html, как показано ниже:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruwe</title>
</head>
<body>
<!-- тег монтирования компонента с идентификатором -->
<r-hello id="hello"></r-hello>
<!-- подключение фрейма components.html -->
<iframe src="components.html" hidden></iframe>
</body>
</html>Теперь откройте консоль барузера и последовательно введите следующие команды:
hello.$data.title = 'Веб-компоненты'
hello.$data.accent = 'green'
hello.$data.arr.push('ok')
hello.$data.arr.reverse()А теперь сделаем этот компонент закрытым:
// статические свойства компонента
static mode = 'closed'При попытке изменить его данные в консоли, будет выдана ошибка, что свойства $data у компонента НЕ существует!
Cлужебные свойства $event и $router (подробно будут рассмотрены далее), являются ссылками на внутренние методы Ruwe и доступны как в любом компоненте, так и из самого фреймворка. При доступе к ним напрямую из Ruwe, символ доллара указывать не нужно, например:
Ruwe.event()
Ruwe.router()В Ruwe нет специального метода типа htmlentities() для преобразования символов содержимого полученного извне в HTML-сущности. Вместо этого, всё содержимое между двумя фигурными скобками, автоматически вставляется как простой текст:
<template name="R-HELLO">
<!-- разметка компонента -->
<div>{{ message }}</div>
<script>
return class {
// асинхронный статический метод инициализации
static async init() {
// имитировать получение данных с сервера через 1 секунду
this.message = await new Promise(ok => setTimeout(() =>
ok('<script>опасный код<\/script>'), 1000))
}
}
</script>
</template>Пример выше выведет на экран обычное текстовое сообщение:
<script>опасный код</script>Ruwe поддерживает три вида циклов « for » реализованных в JavaScript. Цикл определяется с помощью специального атрибута #for и выводит HTML-содержимое элемента в котором указан данный атрибут столько раз, сколько предусмотрено условием самого цикла. Циклы могут быть вложенными друг в друга:
<template name="R-HELLO">
<!-- цикл for -->
<ul #for="i = 0; i < 4; i++">
<li>Число {{ i }}</li>
</ul>
<!-- цикл for-of -->
<ul #for="item of arr">
<li>Элемент {{ item }}</li>
</ul>
<!-- цикл for-of -->
<div #for="user of users">
<pre>{{ user }}</pre>
</div>
<!-- цикл for-of -->
<ul #for="user of users">
<!-- цикл for-in -->
<li #for="prop in user">
{{ prop }} - {{ user[prop] }}<br>
</li>
</ul>
<!-- цикл for-of -->
<ul #for="{ name, age } of users">
<li>{{ name }} - {{ age }}</li>
</ul>
<!-- цикл for-in -->
<ul #for="prop in users[0]">
<li>{{ prop }} - {{ users[0][prop] }}<br></li>
</ul>
<script>
return class {
// данные компонента
arr = Array.from({ length: 3 }, (_, idx) => idx)
users = [
{
name: 'Иван',
age: 28
},
{
name: 'Мария',
age: 32
},
{
name: 'Андрей',
age: 43
}
]
}
</script>
</template>В атрибуте цикла нельзя использовать операторы определения переменных и констант: var, let и const. Это приведёт к ошибке:
<!-- Ошибка... -->
<ul #for="var i = 0; i < 4; i++">
<li>Число {{ i }}</li>
</ul>
<ul #for="let i = 0; i < 4; i++">
<li>Число {{ i }}</li>
</ul>
<ul #for="const i = 0; i < 4; i++">
<li>Число {{ i }}</li>
</ul>Можно применять циклы с любой глубиной вложенности:
<template name="R-HELLO">
<!-- цикл for-of -->
<div #for="user of users">
<div>
<p>
<b>Имя</b>: {{ user.name }}
</p>
<p>
<b>Возраст</b>: {{ user.age }}
</p>
<!-- цикл for-in -->
<div #for="category in user.skills">
<b>{{ category[0].toUpperCase() + category.slice(1) }}</b>:
<!-- цикл for-of -->
<ol #for="item of user.skills[category]">
<li>{{ item }}</li>
</ol>
</div>
</div>
<hr>
</div>
<script>
return class {
// данные компонента
users = [
{
name: 'Иван',
age: 28,
skills: {
frontend: ['HTML', 'CSS'],
backend: ['Ruby', 'PHP', 'MySQL']
}
},
{
name: 'Мария',
age: 32,
skills: {
frontend: ['HTML', 'JavaScript'],
backend: ['PHP']
}
},
{
name: 'Андрей',
age: 43,
skills: {
frontend: ['HTML', 'CSS', 'JavaScript', 'jQuery'],
backend: ['Ruby', 'MySQL']
}
}
]
}
</script>
</template>Переменные цикла в атрибутах событий, всегда будут иметь актуальные значения. Даже после выполнения с циклом разных манипуляций, например, при обращении массива, который выводится в цикле:
<template name="R-HELLO">
<!-- разметка компонента -->
<button :onclick="users.reverse()">Обратить массив</button>
<!-- цикл for-of -->
<ul #for="{ name, age } of users">
<li :onclick="console.log(name, age)">{{ name }} - {{ age }}</li>
</ul>
<script>
return class {
// данные компонента
users = [
{
name: 'Иван',
age: 28
},
{
name: 'Мария',
age: 32
},
{
name: 'Андрей',
age: 43
}
]
}
</script>
</template>Если в циклах используются методы без вызова, то им автоматически передаётся объект с переменными цикла. При этом, если метод без вызова используется в атрибуте события, то кроме переменных цикла, ему ещё передаётся объект события Event, как показано ниже:
<template name="R-HELLO">
<!-- разметка компонента -->
<button :onclick="users.reverse()">Обратить массив</button>
<!-- цикл for-of -->
<ul #for="{ name, age } of users">
<li :onclick="consoleVars">{{ printVars }}</li>
</ul>
<script>
return class {
// данные компонента
users = [
{
name: 'Иван',
age: 28
},
{
name: 'Мария',
age: 32
},
{
name: 'Андрей',
age: 43
}
]
// методы компонента
printVars({ name, age }) {
return name + ' - ' + age
}
consoleVars({ event, name, age }) {
console.log(event.target, name, age)
}
}
</script>
</template>При использовании методов с вызовом, переменные цикла и объект события должны передаваться методам вручную:
<template name="R-HELLO">
<!-- разметка компонента -->
<button :onclick="users.reverse()">Обратить массив</button>
<!-- цикл for-of -->
<ul #for="{ name, age } of users">
<li :onclick="consoleVars(event, name, age)">{{ printVars(name, age) }}</li>
</ul>
<script>
return class {
// данные компонента
users = [
{
name: 'Иван',
age: 28
},
{
name: 'Мария',
age: 32
},
{
name: 'Андрей',
age: 43
}
]
// методы компонента
printVars(name, age) {
return name + ' - ' + age
}
consoleVars(event, name, age) {
console.log(event.target, name, age)
}
}
</script>
</template>Иногда, разные компоненты используют одинаковые по функционалу методы. Чтобы не создавать для каждого такого компонента отдельный метод, можно воспользоваться классовым наследием:
<!DOCTYPE html>
<script src="ruwe.global.js"></script>
<script>
// класс с общими методами
class Methods {
printTitle() {
return 'Компонент ' + this.title
}
}
</script>
<template name="R-ONE">
<!-- разметка компонента -->
<h2>{{ printTitle }} </h2>
<script>
// наследование класса
return class extends Methods {
// данные компонента
title = 'Один'
}
</script>
</template>
<template name="R-TWO">
<!-- разметка компонента -->
<h2>{{ printTitle }} </h2>
<script>
// наследование класса
return class extends Methods {
// данные компонента
title = 'Два'
}
</script>
</template>
<script>
Ruwe.create(...document.querySelectorAll('template'))
</script>или глобальными функциями фрейма:
<!DOCTYPE html>
<script src="ruwe.global.js"></script>
<script>
// глобальная функция фрейма
function printTitle(title) {
return 'Компонент ' + title
}
</script>
<template name="R-ONE">
<!-- разметка компонента -->
<h2>{{ printTitle(title) }} </h2>
<script>
return class {
// данные компонента
title = 'Один'
}
</script>
</template>
<template name="R-TWO">
<!-- разметка компонента -->
<h2>{{ printTitle(title) }} </h2>
<script>
return class {
// данные компонента
title = 'Два'
}
</script>
</template>
<script>
Ruwe.create(...document.querySelectorAll('template'))
</script>Раздел в разработке...
