Документация и тесты
Objs — библиотека с открытым исходным кодом для получения и изменения элементов DOM, управления их состояниями и событиями, отправки AJAX-запросов, загрузки и кеширования скриптов, стилей, изображений и тестовых функций (unit и async тесты). Всего 10 КБ без зависимостей.
Использование
Подключите скрипт или импортируйте через NPM и вызывайте нужные функции. Используйте левую или нижнюю навигацию (на мобильных) для поиска темы или изучите примеры на главной странице.
Основа
Селектор / Получение элементов DOM
Используйте o('selector') для выбора элементов как querySelectorAll. Для первого элемента — o.first(selector). o.take(q) получает элементы как o(q); если число совпадений равно числу ранее инициализированных компонентов, возвращает эти Objs. Также можно передать DOM-элемент или массив элементов o(elements) или объект self в состоянии. Функция возвращает объект с методами Objs и DOM-элементами — подробнее ниже.
// получает все элементы и возвращает объект Objs
const objElements = o('.class');
// получает только первый элемент
const objElement = o.first('.class');
// получает новые элементы для работы
objElements.reset('.class2');
// запрос, чтобы найти всех дочерних элементов каждого элемента по селектору
objElement.find('.childClass');
// возвращает только первый дочерний элемент каждого элемента
objElement.first('.childClass');
// дать элемент или массив [] элементов, чтобы установить их для работы
const objElement = o(document.getElementById('name'));
o.take(query)
o.take(q) выбирает элементы по q (селектор, DOM-элемент или массив). Если выбранные элементы — инициализированные компоненты (имеют data-o-init от o.init().render()), o.take(q) возвращает соответствующие ObjsInstance для вызова методов состояний. Если число совпадений равно числу элементов одного инициализированного компонента, возвращается этот компонент; иначе — обычный Objs с DOM-элементами. Используйте для повторной привязки к компонентам в DOM (например, после навигации или динамической вставки).
// выбор по селектору — DOM-элементы или инициализированные компоненты
const items = o.take('.list-item'); // Objs элементов .list-item
// когда элементы созданы через o.init().render(), take() возвращает эти компоненты
const cardStates = { render: { tag: 'div', class: 'card', html: '...' }, update: ({ self }, t) => { self.html(t); } };
o.init(cardStates).render().appendInside('#container');
// позже: получить компонент и вызвать методы состояний
const card = o.take('.card'); // если один .card с data-o-init, card — ObjsInstance с .update()
if (card.update) card.update('Новый контент');
// o(number) — получить инициализированный компонент по индексу
const firstInited = o(0); // то же что o.inits[0]
Способы получить DOM-элементы из Objs:
// возвращает все выбранные элементы DOM в виде массива [el0, ..]
o('.class').els;
// количество элементов
o('.class').length;
// первый DOM элемент
o('.class').el;
// последний DOM элемент
o('.class').last;
// Выбрать элементы из набора
// устанавливает элемент i DOM для операций (начинается с 0 индекса)
objElements.select(i).el;
// устанавливает последний элемент для работы
objElements.select().el;
// снова устанавливает управление всеми элементами DOM
objElements.all().els;
Чтобы удалить выбранные элементы из DOM или управлять списком объектов, используйте это:
// удаляет все элементы набора из DOM
objElements.remove();
// удаляет элемент i из DOM и возвращает Objs
objElements.remove(i);
// добавляет элемент в список операций и устанавливает атрибут oInit родителя
objElements.add(element);
// можно использовать селектор или массив элементов
objElements.add('.class2');
// удаляет элемент i из списка объектов для работы (не из DOM)
objElements.skip(i);
// v2.0: новый метод удаления DOM и инициализированного объекта Objs
objElements.unmount();
Если вы инициировали некоторые события с помощью .on(), вы должны инициировать их для добавленных элементов с помощью .onAll(). Если вам нужно отключить все события, например, когда отображается модальное окно, используйте .offAll(). Он просто отключает события, поэтому .onAll() снова активирует их.
objElements.add(element).onAll();
objElements.offAll();
Тесты
Прямое изменение DOM элементов
Несколько методов для изменения всех или части элементов (через .select(), .skip() и т.д.)
Изменение атрибутов
// любые атрибуты
// установка
o(selector).attr('attr', 'value');
// получение
o(selector).attr('attr');
// удаление (v2.0: null удаляет, '' устанавливает пустую строку)
o(selector).attr('attr', null);
o(selector).attr('attr', '');
// получение атрибутов как массив от всех элементов [{}, ...]
const attributes = o(selector).attrs();
// получение атрибутов выбранного элемента {}
const attributes = o(selector).select(3).attrs();
// dataset
// установка
o(selector).dataset({
action: "registration"
});
// получение как массив от всех элементов [{}, ...]
const datasets = o(selector).dataset();
// получение выбранного элемента {}
const dataset = o(selector).select(3).dataset();
// style
// установка атрибута как строки
o(selector).style('value');
// установка как объект (используйте "" для свойств с дефисом)
o(selector).css({
height: "100px",
"max-width": "100px"
});
// полное удаление атрибута style (v2.0)
o(selector).style(null);
o(selector).css(null);
// получение innerHTML всех элементов одной строкой
o(selector).innerHTML();
Специальные функции для управления атрибутом class.
// прямая установка значения атрибута (заменяет всю строку class)
o(selector).setClass('class1 class2');
// добавление одного или нескольких классов (v2.0: spread)
o(selector).addClass('class');
o(selector).addClass('active', 'highlight', 'loaded');
// удаление одного или нескольких классов
o(selector).removeClass('class');
o(selector).removeClass('active', 'hidden');
// переключение наличия/отсутствия "class"
o(selector).toggleClass('class');
// переключение по условию (true = добавить, false = удалить)
o(selector).toggleClass('class', isActive);
// возвращает true, если все (или выбранные) элементы имеют класс
const hasIt = o(selector).haveClass('class');
Для добавления HTML или элементов в DOM используйте эти функции. Функции append принимают селектор или DOM-элемент.
// установка inner HTML
o(selector).innerHTML('html');
o(selector).html('html');
// получение HTML (без аргумента) — строка первого элемента или объединённая для нескольких
o(selector).html();
// очистка содержимого (пустая строка)
o(selector).html('');
// inner text
o(selector).innerText('text');
// inner text content
o(selector).textContent('text');
// создание элемента для примера ниже, подробнее в разделе Управление состояниями
const objs = o.init(states).render();
// добавление как последнего дочернего/дочерних элемента
// или в первый найденный по селектору
objs.appendInside('#root');
// добавление до, после
objs.appendBefore(element);
objs.appendAfter('#child');
Для итерации по элементам используйте .forEach(function) — получает те же параметры, что и состояние: o, self, i-индекс и el для каждого элемента.
o(selector).forEach(({self, i, el, o}) => {
// self.els[i] - каждый управляемый элемент
});
Флаг для включения отладочного вывода в консоль на объекте или глобально.
o.debug = false;// установите true для глобального console.log
o(...).debug()...;// вставьте debug() для включения отладки в объекте
val([value]) — получить или установить .value у input, textarea, select. Без аргумента — получить текущее значение; с аргументом — установить и вернуть Objs для цепочки. Предназначено для input, textarea, select; на других элементах поведение не определено.
// получение значения (первый элемент при нескольких)
o('input').val();
// установка значения и цепочка
o('input').val('новый текст');
// очистка / удаление значения
o('input').val('');
o('#search').first('input').val('').attr('placeholder', 'Поиск...'); // цепочка с другими методами
Тесты
val(), refs, className и дополнения DOM
refs — после init() каждый дочерний элемент с ref="name" доступен как component.refs.name (ObjsInstance). Используйте className в render-состоянии как алиас для class. addClass / removeClass принимают несколько аргументов. css(null) или style(null) удаляют атрибут style.
// refs: доступ к именованным дочерним элементам без селекторов
const formStates = {
render: { tag: 'form', html: '' },
setEmail: ({ self }, v) => { self.refs.email.val(v); },
disable: ({ self }) => { self.refs.submit.attr('disabled', 'true'); }
};
const form = o.init(formStates).render();
form.refs.email.val('user@example.com');
form.refs.submit.el; // сырая DOM-кнопка
Тесты
React и JSX
Для создания рендеренного компонента для JSX или React-элемента из Objs используйте .prepareFor(). Первый параметр — React или функция createElement. В v2.0 второй параметр опущен, возвращается Component с createElement('div'). В v1.1 был второй параметр React.Component для получения Component.
.prepareFor(React) возвращает Component, создающий элемент div и вставляющий HTML или элементы внутрь. Состояния можно переключать через хук useEffect() или изменение свойств.
Для инициализации событий «on» используйте useRef и передайте ref в свойствах Component. В этом случае Objs добавит DOM-элементы внутрь элемента div с событиями «on». Objs автоматически преобразует свойства типа «onClick» в «click» и добавляет слушатели.
import o from 'objs-core';
import React from 'react';
import { useRef } from 'react';
// Простой пример
const root = ReactDOM.createRoot(o('#root').el);
root.render(o.init(state).prepareFor(React));
// Пример компонента
const MyObjsComponent = o.init(state).prepareFor(React);
function App() {
const ref = useRef(null);
const clickHandle = () => {
...
};
return (
<div>
{/* использование как компонента */}
<MyObjsComponent
name="Roman"
src="/profile"
onClick={ clickHandle }
ref={ ref } />
</div>
);
}
Для использования Objs в проекте React размещайте объекты состояний в папке components и экспортируйте подготовленные компоненты.
// Пример файла компонента
import o from 'objs-core';
import React from 'react';
export const objsForm = {
render: {
...
},
loaded: {
...
}
};
export const Form = o.init(objsForm).prepareFor(React);
QA autotag и reactQA
o.autotag — установите строку (например "qa") для авто-добавления data-{autotag}="component-name" ко всем рендеренным элементам. Имя компонента берётся из states.name (camelCase в kebab-case). o.reactQA(componentName) возвращает { 'data-qa': 'kebab-name' } (или data-{autotag} если задано) для spread в React JSX. Преобразует CamelCase в kebab-case.
o.autotag = 'qa';
// Рендеренные Objs элементы получают data-qa="my-component"
// React:
<button {...o.reactQA('CheckoutButton')}>Оформить</button>
// → data-qa="checkout-button"
Тесты
События
Синонимы стандартных методов событий с поддержкой нескольких событий через ', ' (запятая и пробел). Проверяйте event.target (например, classList) в слушателе, чтобы убедиться, что событие документа вызвано нужным элементом.
// синоним addEventListener с поддержкой нескольких событий
o(selector).on('event1, event2', listener, options);
// синоним(removeEventListener)
o(selector).off('event1, event2', listener, options);
Если вы используете .select() перед включением/выключением, это будет выполнено только для выбранного элемента.
Иногда необходимо отключить все события на некоторое время. Для этого используйте методы ниже.
// выключение click слушателей
o(selector).offAll('click');
// выключение всех событий
o(selector).offAll();
// включение
o(selector).onAll();
Все инициированные события сохраняются в специальном параметре .ie - он сохраняет каждый обработчик каждого события для их включения/выключения. Вы можете получить оттуда функции и инициализировать их на других элементах.
// первая (0) функция события клика в массиве
objs.ie.click[0]
// содержит массив:
// 0: function, 1: options or useCapture or undefined, 2: wantsUntrusted if was set or undefined
// копировать события кликов в другие элементы с параметрами
objs.ie.click.forEach(handler => {
o(selector).on('click', ...handler);
});
В v2.0 добавлены функции для делегирования и родительских событий. Объект события имеет свойство .o с текущим объектом Objs.
onDelegate(event, selector, listener) — слушатель добавляется к элементам в объекте Objs, функция listener получает объект события с дополнительным свойством .delegate с родительским элементом, выбранным через el.closest(selector). Например, родительская вкладка.
onParent(event, selector, listener) — добавляет слушатель на элемент, выбранный querySelector(selector), и вызывает listener, если event.target — один из элементов.
// delegate
o(...).onDelegate('change, input', '.tab', listener);
o(...).offDelegate(eventType);// удаляет все eventListeners типа
// добавляет слушатель на один родительский узел (по элементу или селектору)
// и вызывает, если элементы Objs содержат event.target
// например, один слушатель для всех взаимодействий
o(...).onParent('click', '.parentBlock', listener);
// удаление слушателей
o(...).offParent('.parentBlock', 'click');
Тесты
Управление состояниями DOM элементов
Одна из особенностей Objs — состояния. Он получает специальный объект коллекции состояний и переключает элементы между ними. Он разделяет логику и представление с помощью образцов. Образцы крупнее компонентов и позволяют работать с модулями без микроразделения.
Объект общих состояний
Это объект с состояниями – значениями типа object / string / function.
Если элемент нужно создать - объект состояний должен содержать состояние render для создания. Установите tag с именем тега, иначе он будет установлен как 'div'.
// общая структура
const states = {
// зарезервированное название для создания элемента
render: {
tag: "div",// название тега
html: "string",
class: "string",
// можно ничего не возвращать
events: ({self}) => {
self.on('click', handler);
},
// другие атрибуты
},
// состояние для изменения элемента
stateName: {
attribute: "string",
// функция возвращает значение атрибута
class: ({disabled}) => {
return `link ${disabled ? 'hidden' : ''}`;
},
},
// состояния включения/выключения событий
startEventsName: ({self}) => {
self.onAll('click');
},
pauseEventsName: ({self}) => {
self.offAll('click');
}
}
Чтобы добавить потомков, используйте атрибут append в состоянии с передачей DOM элемента, Objs объекта или их массивом.
Используйте строку HTML от верстальщика вместо объекта в качестве состояния 'render', чтобы инициализировать элементы еще быстрее. Чтобы использовать динамический контент, установите состояние рендеринга как функцию, которая возвращает строку HTML. Если элементов несколько - они будут в контейнере div.
const states = {
render: (props) => {
return `
<h3>HTML ${props.title} здесь</h3>
<p>Оба тега будут в одном DIV и
он будет управляться, а не эти теги</p>
`;
},
shown: {
removeClass: 'hidden',
},
hidden: {
addClass: 'hidden',
}
}
Если вам нужна быстрая инициализация и рендеринг, используйте .initState(state, data) — он создает или изменяет элемент для состояния путем его немедленного рендеринга. Он равен .init(state).render(data)
// создает элемент
const link = o.initState({
tag: 'a',
href: '/',
innerText: 'Main page'
});
const activeState = {addClass: 'active'};
// быстрое изменение состояния
link.initState(activeState);
Создание элементов
В зависимости от вашей архитектуры можно использовать несколько типов значений состояний. Самый простой - это просто строка как состояние без имени состояния. Рекомендуется использовать объект состояний с состояниями, чтобы быть уверенным в инициализации элементов и возможностях управления, например. Метод .render() может создать элемент для каждых данных в массиве.
Чтобы создать элемент, используйте o.init(state).render(), а затем добавьте его как HTML или специальным методом. Без добавления элемент не появится в DOM.
// вставка как HTML
o('#root').innerHTML(o.init(states).render().html());
// вставка методом
o.init(states).render().appendInside('#root');
// установка store для связи данных с компонентом
const objs = o.init(state);
objs.store = {...data};
// методы добавления из раздела Прямое изменение DOM элементов
objs.appendInside('#root');
objs.appendBefore(element);
objs.appendAfter('#child');
Состояние как строка
// HTML строка
o.init('<p>String</p>').render();
Состояние как объект
o.init({// состояние
tag: 'img',
src: ({src, utm}) => {
return src + '?utm=' + utm;
},
alt: ({alt}) => {return alt}
}).render(props);
Функция состояния возвращает строку и .render(data) содержит информацию для 2 элементов, чтобы создать 2 и добавить в DOM.
// функция возвращает HTML строку
o.init(({title, text, data, id}) => {
return `
<div id="article${id}" class="article">
<h2>${title}</h2>
<p>${text}<br>
<hr>
${data}</p>
</div>
`;
}).render([// автоматически создаются 2 элемента
{
id: 1,
title: 'Название 1',
text: 'Текст статьи',
data: '12/13/2022'
},
{
id: 2,
title: 'Название 2',
text: 'Текст статьи',
data: '12/13/2022'
},
]).appendInside('.o-articles');// вставит в первый ".o-articles" элемент
Рекомендуется использовать объект состояний для создания и управления элементами.
Каждая функция состояния и атрибута получает self, o, i в параметрах: для инициированного объекта (self), функцию o и индекс текущего элемента, например, для событий, чтобы контролировать специальный элемент, а не все из них.
const banner = o.init({// состояния
render: {
tag: 'img',
class: 'banner-img banner-hidden',
src: ({src, utm}) => {
return src + '?utm=' + utm;
},
alt: ({alt}) => {return alt}
},
shown: {
removeClass: 'banner-hidden'
},
events: ({self}) => {
document.addEventListener('scroll', () => {
self.shown();
})
}
}).render(props).events();
Маршрутизация страниц
В v2.0 добавлены два глобальных метода для маршрутизации.
o.route(string|function|boolean, [state|callback]) принимает строку для сравнения с window.location.pathname, функцию, возвращающую true для активации маршрута, или boolean (true — всегда совпадает, false — никогда). Без callback/state o.route() возвращает объект o для инлайн-инициализации. Если callback — функция, она выполняется и возвращает true. Если объект состояний — он возвращается. Маршрутизация учитывает только pathname; hash и query не учитываются.
o.router() принимает объект с путями и функциями или состояниями. Выполняет функцию с совпадающим индексом пути или возвращает объект состояний для текущего пути для инициализации.
o.getParams([key]) — чтение GET (query) параметров из URL. Без аргумента возвращает объект всех query-параметров; со строкой key — значение этого параметра. Полезно в callback маршрутов или при инициализации компонентов: передайте o.getParams() в render(data) или читайте конкретный ключ (например o.getParams('id')) для управления состоянием или загрузкой данных.
// маршрут-функция
o.route('/', initHomePage);
// инлайн маршрут
o.route('/')?.init(HomePage).render().appendInside('#root');
// функция проверки маршрута
o.route((path) => path.startsWith('/item?id='))
?.init(ItemPage)
.render(itemData)
.appendInside('#root');
// функция проверки и возврат состояния
o.init(
o.route('/', HomePage)
|| o.route((path) => path.startsWith('/item'), ItemPage)
).render(o.getParams() || data).appendInside('#root'); // передача query-параметров в компонент
// внутри callback маршрута или компонента: чтение одного параметра
const itemId = o.getParams('id');
// Простой роутер для небольших приложений
// объект для простого роутера с функциями
o.router({
'/': initHomePage,
'/cart': initCartPage,
});
// объект-роутер как переключатель между компонентами
o.init(o.router({
'/': HomePage,
'/cart': CartPage,
})).render(data).appendInside('#root');
Тесты
Переключение состояний
Чтобы переключить состояния существующего элемента, вы просто выбираете его с помощью o(), загружаете состояния с помощью .init() и используете методы, названные как состояния, чтобы легко переключать элементы между ними. После каждой операции он возвращает Objs со всеми методами.
Чтобы изменить определенный элемент, используйте .select() перед переключением состояния. Если вам нужно изменить дочерний элемент, используйте o(self).find(selector), чтобы создать отдельный объект и управлять другими элементами.
// пример
// создание и вывод элемента
const menuID = o.init(menuStates)
.render(menuData)
.appendInside('#root')
.initID;
// изменение состояни где-то в другом месте
o(menuID).stiky();
// или с использованием селектора и специального метода
o.take('.class').stiky();
Если состояния элемента были инициализированы, они кэшируются, и все состояния доступны через o(query) или o(initID) без новой инициализации — пример выше.
Store loaders
В v2.0 добавлена функция создания loader. Позволяет запрашивать данные и связывать компоненты с ними. При получении данных запускает выбранное состояние для обновления компонента.
И loaders, и компоненты Objs имеют метод .connect().
// пример
const pageLoader = o.newLoader(o.post(...));// возвращает Loader
// Объект Loader
{
isObjsLoader: true,
reload: (promise) => {},// метод перезагрузки с новым Promise
connect: (listener, state='render', failState='') => {},// подключение компонента Objs
disconnect: (listener) => {},
listeners: [],
isFinished: () => {},// проверка завершения
getStore: () => {},// получение загруженных данных
}
// Инициализация запроса
const pageLoader = o.newLoader(o.post(...));
// Подключение компонента
o.init(states)
.connect(pageLoader, 'show')
.render()
.appendInside('#root');
// Или подключение loader
pageLoader.connect(Feed, 'update', 'internetError');
Если состояния элемента были инициализированы, они кэшируются — пример выше.
Тесты
Встроенный store (o.createStore)
o.createStore(defaults) создаёт реактивный store в виде обычного объекта. Возвращаемый объект имеет subscribe(component, stateName), notify() и reset(). Подписанные компоненты получают данные store, объединённые с контекстом состояния при каждом notify(). Без виртуального DOM; только подписанные компоненты обновляют свой DOM (O(1) на подписчика). reset() восстанавливает исходные значения по умолчанию.
const store = o.createStore({ count: 0 });
store.subscribe(myComponent, 'sync'); // myComponent.sync(storeData) при notify
store.count = 5;
store.notify();
store.reset(); // возврат к { count: 0 }
Тесты
Пример
Полный пример объекта реального состояния с состоянием рендеринга и некоторыми другими.
// пример объекта состояний
const states = {
// состояние создания элемента
render: {
tag: 'button',// по умолчанию 'div' (если не указано)
class: 'button',// используй addClass для добавления и removeClass для удаления
html: 'Click me',
onclick: 'clickFunction()',
disabled: true
},
// подключение событий
events: ({self, o}) => {
// self это объект со всеми состояниями
self.on('mouseenter', (e) => {
o(e.target).active();// элемент создан и имеет все состояния
});
self.on('mouseleave', (e) => {
o(e.target).disabled();
});
},
// другие состояни и атрибуты для изменения
special: {
// использование функции для создания любого динамического содержимого
html: (props) => {return props.name},
disabled: (props) => {return Boolean(props.disabled)}
},
error: {
// есть o(), self, i в props для управления
showError: (props) => {
// задание текста ошибки
props.el.closest('.error-text').innerText = props.errorText;
},
disabled: true
},
active: {
disabled: false,
clearError: (props) => {
props.el.closest('.error-text').innerText = '';
}
},
disabled: {
disabled: true,
}
};
Если state является функцией, он получает аргумент props с функцией o, self который содержит все функции и инициированные элементы и i индексом текущего обрабатываемого элемента.
Если attribute является функцией, она получает аргумент props (данные общие для состояний, если не массив) с теми же o, self, i.
Есть специальные атрибуты редактирования: addClass, removeClass, toggleClass. Style атрибут может быть объектом style: {} для более удобного задания.
// инициализирует существующие состояния элементов и запускает функцию для событий привязки
const btn = o('.button').init(states).events();
// примерение состояния "active" для всех кнопок
btn.active();
// переключение состояния с передачей текста
btn.special({name: 'Special state', disabled: false});
// создает элемент который можно вывести в DOM
const newBtn = o.init(states).render().el;
// создает элементы для каждого набора данных в массивы
const newBtns = o.init(states).render([prop0, prop1]).el;
// можно передавать одно состояние, оно инициируется как render()
const newSameBtn = o.init({
tag: 'button',
class: 'button',
html: 'Click me',
onclick: 'clickFunction()',
disabled: true
}).render().el;
// можно использовать HTML для рендеринга элементов
const btnState = (props) => {
return `<button class="button">${props.text}</button>`;
};
const anotherBtn = o.init(btnState).render({text: 'Button 1'});
// если параметры - массив, то элемент будет создан для каждого
anotherBtn.reset().init(btnState).render([{text: 'Button 1'}, {text: 'Button 2'}]);
Создание состояний и HTML из DOM
// возвращает HTML код всех элементов целиком в строке
const classHTML = o('.class').html();
// возвращает HTML код созданного элемента
const newBtnHTML = o.init(states).render().html();
// создает объект состояний с состоянием "render" из первого элемента
const newStates = o('.button').sample();
// создание состояний из второго элемента
const newStates = o('.button').select(1).sample();
// пример клонирования объекта из DOM для его создания
o.initState({
...newStates,
html: ({title}) => {return title},
onclick: ({key}) => {globalSelect(key)},
},
[
{title: 'Btn 1', key: 'one'},
{title: 'Btn 2', key: 'two'},
]);
Инициализированный объект Objs и DOM-элементы доступны через o(initID), где initID — параметр objs.initID. Для сохранения экземпляра по строковому имени используйте .saveAs(key) — экземпляр доступен как o.getSaved[key]. Ключи уникальны; при существующем ключе saveAs не перезаписывает (в режиме отладки выводится предупреждение).
Получает только инициализированные элементы из объекта Objs, не из DOM-дерева.
С v2.0 добавлены функции преобразования camel case и kebab case между атрибутами и переменными. Также есть редьюсеры для глобальных данных.
// примеры
o.kebabToCamel(string);// o-init в oInit
o.camelToKebab(string);// oInit в o-init
// массив всех инициализированных объектов по initId как индексу
o.getStates();
o.getStores();
o.getListeners();// включает слушатели onDelegate()
// сохранение/восстановление DOM-состояния ObjsInstance (для undo или сравнения)
const comp = o.init(states).render().appendInside('#root');
comp.saveState('initial'); // сохранить текущее DOM-состояние с id 'initial'
comp.update('Новый контент'); // изменить элемент
comp.revertState('initial'); // восстановить сохранённое состояние
comp.loseState('initial'); // удалить сохранённое состояние из памяти
// сохранение ObjsInstance по имени — в o.getSaved{} для доступа по ключу
const menu = o.init(menuStates).render().appendInside('#nav');
menu.saveAs('mainMenu'); // сохраняет экземпляр как o.getSaved['mainMenu']
// в другом месте: получить экземпляр по имени
o.getSaved['mainMenu'].updateItems(data);
o.getSaved.mainMenu; // то же; ключ — непустая строка. Не перезаписывает при существующем ключе.
Тесты
Серверный рендеринг
В v2.0 добавлен SSR. На сервере нет интерактивности, поэтому некоторые методы не влияют на элементы. В Node o.D — o.DocumentMVP (без реального DOM). Для инициализации событий и манипуляции серверно-отрендеренными элементами в браузере используйте getSSR(initId) после init(). Вызов .html() без аргументов на результате render() возвращает HTML-строку элемента(ов) — в Node используется сериализация DocumentMVP для проверки или вывода без браузера.
// через getSSR() Objs получает отрендеренные элементы на странице по initID
o.init(states).getSSR().events();
// свойство initID доступно после init() и используется в getSSR() как параметр
o.init(states).getSSR(32).events();// объект Objs перезапишет o.inits[32]
// получение HTML-строки (браузер или Node) для проверки или SSR-вывода
const htmlString = o.init(states).render().html();
На сервере метод getSSR() ничего не делает, поэтому один и тот же код работает на обеих сторонах.
Тесты
JSX-подобная форма с авто-гидрацией
Родитель — <form>; дочерние элементы — атомы дизайн-системы в отдельных блоках (form__header, form__body, form__list, form__actions). Когда форма устанавливает innerHTML с разметкой, содержащей data-o-init, Objs автоматически гидратирует эти элементы, привязывая инициализированные экземпляры к новым DOM-узлам.
Пример исходного кода
// Валидация email
const emailValid = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((v || '').trim());
// Атомы состояний: каждый рендерит один элемент (или элементы списка); события переподключаются при авто-гидрации
const FormHeaderStates = {
render: (p) => ({ tag: 'h2', className: 'form-atom form-atom__header', html: p.title }),
};
const FormFieldStates = {
render: ({ self, ...p }) => ({
tag: 'div', className: 'form-atom form-atom__field',
html: `<label>${p.label}</label><input ref="input" type="${p.type || 'text'}" name="${p.name}"><span ref="error" class="form-atom__field-error"></span>`,
events: {
blur: {
targetRef: 'input',
handler: (e) => {
const row = self.select(e);
if (!row.refs?.error) return;
const value = row.refs.input.val();
const valid = emailValid(value);
if (!valid && value.trim()) {
row.refs.error.html('Неверный email');
row.el?.classList.add('form-atom__field--error');
} else {
row.refs.error.html(''); row.el?.classList.remove('form-atom__field--error');
}
},
},
},
}),
};
const FormItemListStates = {
render: (p) => ({ tag: 'li', className: 'form-atom__list-item', html: p.title }),
};
const FormButtonStates = {
render: (p) => ({
tag: 'button', type: 'submit', className: 'form-atom form-atom__btn', html: p.label,
}),
};
// Родительская форма: инициализирует дочерние, строит HTML из блоков; информер успеха рядом с кнопкой
const FormStates = {
render: ({ self, data }) => {
const header = o.init(FormHeaderStates).render([{ title: data.title }]);
const field = o.init(FormFieldStates).render([{ label: data.emailLabel, name: 'email', type: 'email' }]);
const list = o.init(FormItemListStates).render(data.items);
const btn = o.init(FormButtonStates).render([{ label: data.submitLabel }]);
self.store = { header, field, list, btn };
const informer = `<div ref="successInformer" class="form-informer form-informer--success" style="display:none;"></div>`;
const html = `<div class="form__header">${header.html()}</div><div class="form__body">${field.html()}</div><div class="form__list"><ul class="form-atom form-atom__list">${list.html()}</ul></div><div class="form__actions">${btn.html()}${informer}</div>`;
return { tag: 'form', className: 'form', html,
events: { submit: (e) => { e.preventDefault(); self.submit?.(self); } } };
},
submit: (self) => {
const field = self?.store?.field;
const value = field?.refs?.input?.val();
const valid = value !== undefined && emailValid(value);
const errRef = field?.refs?.error;
if (!valid) {
if (errRef) errRef.html('Введите корректный email');
if (field?.el) field.el.classList.add('form-atom__field--error');
return;
}
if (errRef) errRef.html(''); if (field?.el) field.el.classList.remove('form-atom__field--error');
const inf = self.refs?.successInformer;
if (inf) { inf.html('Успешно!'); inf.css({ display: 'block' }); }
},
};
// Монтирование: append затем getSSR для привязки submit и refs к элементу в документе
const form = o.init(FormStates).render([formData]);
form.appendInside('#testJSXForm');
form.getSSR(form.initID);
Загрузка и кеширование скриптов, стилей, предзагрузка изображений
Функция o.inc() импортирует JS-скрипты / CSS-стили / изображения для модулей и HTML-состояний. Создаёт кеш в localStorage через GET-запрос (включено по умолчанию).
В v2.0 добавлена поддержка хеша. Если хеш в URL загрузки (например script.js?20250225) отличается от предыдущего, файл перезапишет кешированную версию. Для загрузки без добавления скриптов/стилей в DOM добавьте ключ "preload" (truthy) в объект sources: o.inc({ preload: true, modal: 'modal.js' }, callback) — ресурсы загружаются и кешируются, но не внедряются на страницу.
// параметры и значения по умолчанию
// установите false, чтобы отключить
o.incCache = true;
// 24 часа для хранения кеша
o.incCacheExp = 1000 * 60 * 60 * 24;
// timeOut для загрузки всех функций. После - отменяет обратный вызов и запускает функцию ошибки
o.incTimeOut = 6000;
// дополнение для src в начале
o.incSource = '';
// предотвращает повторное включение с одним и тем же идентификатором (если он был включен ранее)
o.incForce = false;
// установите false, если нужен порядок загрузки скриптов
o.incAsync = true;
// изменить разделитель хеша при необходимости
o.incSeparator = '?';
// изменить функцию получения хеша при необходимости, стандартная получает хеш вида "script.js?hash"
o.incGetHash = (path) => path.split(o.incSeparator)[1] || '';
// служебные данные
// массив всех статусов «ID: 1/0» для прямой проверки
o.incFns = {};
// массив состояний подгружаемых наборов
o.incSet = [0, ...];
Вот основная функция. Она возвращает ID набора загрузки из o.incSet[], где 1 для успешной загрузки всех скриптов и 0 при неудаче. Если скрипты в процессе загрузки, там будет функция для вызова по окончании. Если sources при вызове отсутствует - возвращает id последней загрузки.
const incId = o.inc({// возвращает ID для проверки статуса
'name': 'src'// ID имени и src для JS, CSS или файла изображения
}, callback, callbad);
// простая версия с массивом ссылок
o.inc([
'modal.js',
'modal.css',
'modalBackground.jpg',
], (setId) => {
// функция успеха получает установленный идентификатор, например, проверять статусы
});
o.incSource = 'scripts/';
o.inc({
pow: 'pow.js'// имя для идентификации и src для загрузки скрипта
powStyle: 'pow.css'// стили для модуля
banner: 'pow.jpg'// изображение для предварительной загрузки
}, () => {// функция для запуска после загрузки
console.log('Successful using script with pow()');
}, () => {// функция для запуска в случае сбоя с тайм-аутом
console.log('Loading failed');
});
// проверяет последний загруженный набор функций
if (o.incCheck(o.inc())) { ... }
// проверяет специальный набор по ID
if (o.incCheck(incId)) { ... }
// проверить загружен ли текущий скрипт, 'pow' - имя скрипта
if (o.incFns['pow']) { ... }
// очищает весь кеш localStorage
// если "all" true - очищает все системные параметры
o.incCacheClear(all);
При передаче массива в o.inc() нет управления кешем и проверки повторов. Также нет кеша для ссылок, начинающихся с 'http'. При o.incCors false (по умолчанию) cross-origin URL могут не работать, если не same-origin или сервер не разрешает.
Тесты
GET и POST запросы
Есть несколько базовых функций промисов для отправки запросов. Можно использовать только один параметр - url. Они возвращают Promise, поэтому вы можете использовать .then() или await.
// простой GET
o.get(url)
.then();
// GET с объектом данных и JSON ответом
o.get(url, {
data: {
param1: 'value1',
// значение объекта будет преобразовано в строку JSON
param2: {
...
}
}
})
.then(response => response.json())
.then((response) => {
...
});
Для POST вы можете использовать параметр body для специальных данных или параметр data для автоматического создания строки данных.
// POST с объектом data и JSON ответом
o.post(url, {
data: {
param1: 'value1',
// значение объекта будет преобразовано в строку JSON
param2: {
...
}
}
})
.then(response => response.json())
.then((response) => {
...
});
Чтобы унифицировать использование запроса .ajax() - это может быть запрос GET или POST.
o.ajax(url, {
method: 'post',// должно быть 'post' или 'get'
// остальные параметры
})
.then(response => response.json())
.then((response) => {
...
});
Используйте o.getParams(), чтобы получить массив GET-параметров страницы.
const params = o.getParams();
Тесты
Unit тесты и их параметры
Тестовая функция унифицирована и получает название теста и тесты, а последним параметром может быть функция завершения. Запускается после тестов или по тайм-ауту. Журнал результатов может быть двух видов: в стиле консоли (по умолчанию) и в формате HTML (o.tStyles = true) — оба вы найдете ниже в тестовом разделе.
Тестовое выражение или функция должны возвращать true для успешной проверки. Если это false или какая-то строка - она помечается как ошибка и строка отображается как текст ошибки.
Для тестирования асинхронных функций, например. запросы - используйте o.testUpdate() с параметрами в функции проверки.
// параметры и значения по умолчанию
// установите true для отображения успешных тестов
o.tShowOk = false;
// установите true для HTML-оформления результатов
o.tStyled = false;
// при tStyled true настройте HTML для вывода лога: o.tPre, o.tOk, o.tXx, o.tDc
// таймаут асинхронных тестов
o.tTime = 2000;
// установите true для авто console.log
o.tAutolog = false;
// массив всех тестовых сессий
// получить сеанс, который вы хотите, и проверить результаты или .join(), чтобы увидеть их все
o.tLog = [];
// массив истинных/ложных результатов всех тестовых сессий
o.tRes = [];
// массив массивов с истинными/ложными результатами каждого теста в сессиях
o.tStatus = [];
// использование
o.test("session / function title",// основное название этой тестовой сессии
// массив с заголовком проверки и выражения/функции на истинность
["test/check title", func() === val],
["разделитель тестов"],// логирует разделитель тестов
// получение info для асинхронных тестов
["test/check title", (info) => {
// используйте o.testUpdate для обновления статуса теста
setTimeout(() => {
o.testUpdate(info, true, ' - дополнительный текст');
}, 100);
}],
// опциональный последний аргумент: callback завершения (id теста); вызывается когда все тесты завершены или по таймауту
(testN) => {
console.log('Результат: \n' + o.tLog[testN]);
}
);
// o.runTest(testId, autoRun, savePrev) — savePrev: при true сохраняет sessionStorage для testId для возобновления
Функция вызывается при ошибках в функциях Objs. Установите o.onError для отображения или логирования ошибок. По умолчанию ошибки не показываются.
o.onError = (e, name) => {
if (o.showErrors) {
console.error(e, name);
} else {
o.errors.push(e);
if (name) {
o.errors.push(name);
}
}
};
Проверка типов (o.verify, o.specialTypes)
o.verify(pairs, safe?) и o.safeVerify(pairs) — проверка типов во время выполнения для аргументов функций, объектов конфигурации или ответов API. Полезно для быстрого обнаружения ошибок на границах API, валидации опций и предсказуемости кода (включая AI-генерируемый). Objs использует те же o.verify и o.specialTypes внутри.
pairs — массив [value, expectedTypes]. expectedTypes — строка или массив строк: встроенные имена typeof ("number", "string", "boolean", "object", "function", "undefined") или ключи из o.specialTypes. Пары проверяются по порядку; o.verify возвращает true при первом совпадении. При отсутствии совпадений — выбрасывает исключение (или возвращает Error при safe true); o.safeVerify возвращает false.
Разработчики могут добавлять глобальные валидаторы через расширение o.specialTypes. Присвойте функцию (value, typeofValue) => boolean в o.specialTypes.myType. Валидатор доступен везде — в приложении и внутри Objs.
// валидация аргументов функции (выбрасывает при ошибке)
const method = (index, newValues) => {
o.verify([
[index, ["number", "string"]],
[newValues, ["array", "undefined"]],
]);
// код функции...
};
// проверка без выброса
if (o.safeVerify([[id, ["number"]]])) {
// код...
}
// встроенные специальные типы: "array" (Array.isArray), "notEmptyString" (строка с длиной), "promise"
// добавление глобального валидатора
o.specialTypes.user = (val, type) => {
return type === 'object' && val !== null && typeof val.id === 'number' && typeof val.login === 'string';
};
o.verify([[user, ["user"]]]);
Тесты
Примеры для каждого теста:
tStyles: false; (вид консоли, \n заменены на br)tStyles: true; (HTML)
Тесты с перезагрузкой страницы
В v2.0 можно тестировать с перезагрузкой страницы (например, регистрация или покупки). Тест продолжается после перезагрузки и сохраняет результаты. Добавлен autorun для полных тестовых сессий.
Для теста с перезагрузкой используйте o.addTest() и разделите логику на две части. Первая — подготовка и перезагрузка. Вторая — продолжение проверки.
Используйте o.clearCookies(), o.clearLocalStorage() и o.clearSessionStorage() для чистого старта следующего теста с autorun.
// добавление теста с перезагрузкой в глобальный список
const testReloadable = o.addTest('Тесты с перезагрузкой',
['Начало теста: подготовка и перезагрузка...', (t) => {
// подготовка...
o.testUpdate(t, true);
window.location = window.location;
}],
['Проверка после перезагрузки', () => {
return Boolean(o.getCookie('authID'));
}],
(testId) => {
console.log(o.tLog[testId]);
}
);
// запуск теста
testReloadable.run();
// запуск с autorun следующих тестов
testReloadable.autorun();
// также доступен ID теста
o.runTest(testReloadable.testId);
// загрузка результатов из sessionStorage
o.updateLogs();
Результаты в sessionStorage сохраняются только для тестов, добавленных через o.addTest() после запуска. Продолжение после перезагрузки и autorun поддерживаются только для них. На file:// или в ограниченных средах cookies могут быть только в памяти.
Тесты
Запись действий пользователя
o.startRecording(observe?, events?, timeouts?) — начало записи взаимодействий пользователя и сетевых запросов. Опционально observe — CSS-селектор для MutationObserver. o.recorder.active — статус записи. o.stopRecording() возвращает { actions, mocks, initialData, assertions, observeRoot, stepDelays }. stepDelays — опциональная карта задержек на событие при воспроизведении. o.exportTest(recording) возвращает исходник в стиле o.addTest(). o.exportPlaywrightTest(recording, options) возвращает Playwright .spec.ts с локаторами и моками. o.clearRecording([id]) удаляет из sessionStorage. o.playRecording(recording) воспроизводит как тест. o.testOverlay() — для просмотра результатов автотестов и провалившихся ручных проверок.
Безопасность: o.startRecording() перехватывает window.fetch и сохраняет тела запросов/ответов (включая токены). Подходит для staging; проверяйте перед включением в production.
Recording API
Тесты
Экспорт в Objs (exportTest)
Тесты
Экспорт в Playwright
Тесты
Фикстура и воспроизведение записи
Тестовая фикстура (элементы для тестов):
Результаты тестов:
Автотесты и оверлей (testOverlay, testConfirm)
o.testOverlay() — отображает фиксированную кнопку (🧪 Тесты). Клик — просмотр pass/fail всех тестов. Для оценщиков: после воспроизведения откройте оверлей для проверки автотестов и провалившихся ручных проверок. Доступно во всех сборках. o.testConfirm(label, items?, opts?) — перетаскиваемый оверлей с опциональным чеклистом; возвращает Promise<{ ok, errors? }>. Используйте после воспроизведения для ручных проверок (например hover). o.measure(el), o.assertVisible(el), o.assertSize(el, expected) — измерение layout и проверки для o.test() (дизайн-система / UI).
Тесты
Настройки отладки
Сообщения об ошибках по умолчанию отключены, но их можно включить изменив o.showErrors на true. Или использовать o.logErrors() чтобы увидеть все скрытые сообщения в консоли.
Чтобы сделать свой обработчик ошибок – задайте o.onError нужную функцию. Чтобы увидеть скрытые ошибки – их можно взять из массива o.errors.
o.errors.forEach(o.onError)
Ограничения
Состояния НЕ должны совпадать с методами или параметрами Objs ниже
length
el
els
last
ie
initID
initedEvents
Параметры состояний НЕ должны иметь ключи o, i, self.
События должны добавляться через параметр self в состояниях, чтобы быть в массиве ie контролируемых слушателей.
Состояния перезаписываемы — могут перезаписывать или быть перезаписаны параметрами. Будьте осторожны.
Частные случаи и ограничения
- Аргументы состояний: При передаче примитива (например
comp.setState(5)) — используйтеdataв контексте состояния. При передаче объекта его ключи распространяются в контекст. - o.take(q): Возвращает инициализированные компоненты только когда число совпадающих DOM-элементов равно числу ранее инициализированных; иначе ведёт себя как
o(q). - attr / style:
attr(name, null)удаляет атрибут;attr(name, '')устанавливает пустую строку.style(null)илиcss(null)полностью удаляют атрибутstyle. - val(): Предназначено для
input,textarea,select; на других элементах поведение не определено. - add(): Работает для уже полученных (существующих) DOM-элементов; не для создания новых экземпляров компонентов.
- getSSR: В Node
getSSR()ничего не делает (нет реального DOM). В браузереgetSSR(initId)гидратирует из существующего DOM по initID. - Воспроизведение записи:
o.playRecording()зависит от селекторов и индексов списков; динамический контент с изменением порядка может сделать воспроизведение нестабильным. - o.inc: При
o.incCorsfalse (по умолчанию) cross-origin URL могут не работать. В средах безlocalStorage(например Node)o.inc()возвращается без загрузки. - add(): На инициализированном компоненте (с
initID)add()ничего не делает.add(number)с валидным индексом init возвращает этот компонент (какo(number)). При appendInside в ObjsInstance_parentвставленного экземпляра устанавливается в целевой. - Ключи хранилища: Objs использует
oInc-*(localStorage для кеша inc) иoTest-*(sessionStorage для тестов).clearLocalStorage(false)оставляет ключи сoInc-илиoTest-;clearSessionStorage(true)удаляет только ключи сoTest-. Избегайте этих префиксов в коде приложения. - Node vs браузер: В Node
o.D—DocumentMVP; нетwindow, реальногоdocument, событий. Методы, зависящие от DOM (события, focus), не работают. - Тесты с перезагрузкой: Используют sessionStorage и опционально cookies; на
file://или в ограниченных средах cookies могут быть только в памяти. - Утилиты:
o.C(obj, key)— безопасная проверка hasOwn.o.F,o.Uзарезервированы для внутреннего использования.o.W,o.Hсуществуют, но не используются; не полагайтесь на них.