EN
БЫСТРАЯ          
          и ПРОСТАЯ

Objs

  JavaScript библиотека для  
  разработки, тестов, кеша и  
  оптимизации  

Возможности

Вы можете разрабатывать новые фичи с Objs без переписывания кода

Разработка

Тестирование

Оптимизация

С Objs вы, наконец, можете разделить логику и HTML, получить полный доступ к возможностям кэширования, асинхронной загрузке модулей и автоматическим тестам для всего веб-приложения. Для разработчиков — более быстрая и простая архитектура, для бизнеса — более дешевая разработка за счет использования верстальщиков и модульной структуры проекта, если это необходимо.

Как начать

objs-core v2.4.x — npm или глобальный скрипт (аналог objs.global.min.js в пакете):

<script src="../objs.2.4.js" type="text/javascript"></script>

Теперь можно использовать o функции в проекте.

import o from 'objs-core';

o.init(states)
	.render()
	.appendInside('#root');

Для каждой функции есть документация, основные принципы и примеры ниже.

Основные принципы

Логическая структура для создания любого динамического модуля и управления им

Для управления элементом Objs использует состояния. Состояние - это информация о том, как создать или изменить элемент. Для создания элемента используйте состояние render с атрибутами тега и html (внутренний HTML).

// состояние для создания элемента - render
const timerStates = {
	render: {
		tag: 'div',
		class: 'timer',
		html: 'Секунды: <span>0</span>',
	}
}

Тег по умолчанию - div, поэтому можно не указывать. Аттрибуты dataset и style могут быть типа object. Также, render может быть строкой вида: '<div class="timer">Секунды: <span>0</span></div>'.

Затем добавим новое состояние, которое начнет отсчёт. Число будет храниться в самом объекте - объекте self. Таким образом, состояние будет функцией, которая получает self, создает переменную, увеличивает её на интервал и отображает как innerHTML из span.

// новый объект состояний для таймера
const timerStates = {
	render: {
		class: 'timer',
		html: 'Секунды: <span>0</span> ',
	},
	start: ({self}) => {
		// сохранить число или установить
		self.n = self.n || 0;
		// запустить интервал
		setInterval(() => {
			self.n++;
			o(self).first('span').html(self.n);
		}, 1000);
	}
}

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

o.init(timerStates).render().start().appendInside('#simpleTimer');

Например, здесь:

Ниже приведены более сложные примеры, но с той же логикой: создание состояний, некоторые функции, которые изменяют элемент, затем инициализируют и добавляют элементы на странице. Состояния после инициализации являются методами self в дополнение к стандартным on, first, remove и т.д.

Примеры управления состояниями

Первая часть классических примеров использования, но с некоторыми особенностями

Таймер с старт / стоп

Таймер, который считает секунды на странице, но может быть остановлен. Основная структура: состояние для рендеринга, для установки событий, для изменения состояния элемента и для запуска/остановки. Когда таймер остановлен, мы добавляем новый элемент, чтобы показать статус. Данные и состояния сохраняются в объекте self.

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

o.init({
	render: {
		class: 'exampleTimer',
		html: 'Секунды: <span>0</span>',
	},
	events: ({self}) => {
		self.on('click', () => {
			// проверка статуса
			if (self.interval) {
				// использование другого состояния как метода
				self.stop();
			} else {
				self.start();
			}
		});
	},
	tick: ({self, o}) => {
		// сохранение информации в объекте
		self.n = self.n || 0;
		self.n++;
		// получение дочернего элемента и изменение
		o(self).first('span').html(self.n);
	},
	// управление таймером
	start: ({self}) => {
		// задание интервала для управления
		self.interval = setInterval(() => {
			self.tick();
		}, 1000);
		// удаление вывода статуса
		o(self).first('.status').remove();
	},
	stop: ({self, o}) => {
		// остановка интервала
		clearInterval(self.interval);
		delete self.interval;
		// вывод статуса
		o.initState(
			'<div class="status">(stopped)</div>'
		).appendInside(self.el);
	}
}).render()
	.events()
	.start()
	.appendInside('#exampleTimer');

Todo список / менеджер задач

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

.render(listData) создаёт элемент для каждой записи. Индекс i в id="task${i}" связывает метку и чекбокс. При добавлении следующий индекс берётся из formObj.nextTaskId (начальное значение — длина listData).

Новые задачи создаются через o.init(rowState), затем .events() и .appendInside('.tm__list'). У каждой строки свои обработчики (включая удаление). Поле ввода и кнопка добавления находятся в блоке .tm__form над списком.

const listData = [
	{
		text: 'Задача номер 1',
		checked: true,
	},
	{
		text: 'Задача номер 2',
		checked: false,
	},
];

const rowState = {
	render: ({checked,text,i}) => `
		<li class="tm__row" >
			<input type="checkbox" 
				${checked ? 'checked="1"':''} 
				id="task${i}">
			<label for="task${i}">
				${text}
			</label>
			<input 
				class="tm__delete" 
				type="button">
		</li>
	`,
	events: ({self, o, i}) => {
		o(self.els[i])
			.first('.tm__delete')
			.on('click', () => {
				self.remove(i);
			});
	},
};

const formState = {
	render: `
		<div class="tm__form">
			<input class="tm__field" type="text" maxlength="120" placeholder="Новая задача">
			<input class="tm__add" type="button" value="" aria-label="Добавить">
		</div>
	`,
	events: ({self, o}) => {
		o(self.el).first('.tm__add').on('click', () => {
			const text = o(self.el).first('.tm__field').el.value.trim();
			if (!text) return;
			const i = self.nextTaskId++;
			o.init(rowState)
				.render({ i, text, checked: false })
				.events()
				.appendInside('.tm__list');
			o(self.el).first('.tm__field').el.value = '';
		});
	}
};

const listObj = o.initState({
	tag: 'ul',
	class: 'tm__list',
	append: o.init(rowState).render(listData).events().els,
});
const formObj = o.initState(formState).events();
formObj.nextTaskId = listData.length;

formObj.appendInside('#taskManager');
listObj.appendInside('#taskManager');
	

Важные моменты

Для вставки элементов и сохранения их событий используйте append в состоянии, а не html.

Вы можете создавать и отображать элементы из объектов, строк, функций и для каждого набора данных. Для управления дочерними элементами используйте o() для создания отдельного объекта. Для загрузки ранее инициированных элементов используйте o.take(query).

В objs-core v2 вызов .add() у экземпляра с уже заданным initID не выполняется (так задумано). Форму и список монтируйте отдельными appendInside, как в примере. Новые строки добавляйте через o.init(rowState).render(…).events().appendInside('.tm__list').

.add() для «свежего» набора элементов по-прежнему объединяет els и при необходимости обновляет data-o-init; для крупных модулей можно переиспользовать o.inits[].

Кеширование состояний

Чтобы оптимизировать некоторые большие компоненты, загруженные с сервера, Objs может создавать объект состояния элемента страницы и управлять им. В примере HTML сохранится в localStorage и будет cookie для сервера, чтобы он возвращал заглушку.

Можно управлять объектом истории и кэшировать все образцы HTML для переключения страниц, актуализируя данные с помощью AJAX.

const noticeState = ({title}) => {
	return `<div 
		class="exampleMenu__message">
			${title}
		</div>
	`;
};

const MENU_LS = 'objsMainPage_menuHtml_ru';
const MENU_CK = 'objsMainPage_menuCached_ru';

// создание объекта состояний из элемента страницы
const menuStates = {
	render: () => {
		// проверка, в курсе ли сервер о кеше
		if (!getCookie(MENU_CK)) {
			localStorage.setItem(MENU_LS, o('#exampleMenuServerPart .exampleMenu').html());
			setCookie(MENU_CK, true);
		}
		return localStorage.getItem(MENU_LS) || '';
	},
	// добавление динамического элемента
	newMessage: ({self, title}) => {
		o.init(noticeState)
			.render({title})
			.on('click', (e) => {
				o(e.target).remove();
			})
			.appendInside(self.el);
	}
};

o.init(menuStates)
	.render()
	.appendInside('#exampleMenu')
	.newMessage({
		title: 'Новое сообщение!'
	});

Имитация HTML с сервера (статический фрагмент). Скрипт сохраняет его в localStorage и ниже добавляет клон из кеша и интерактивное сообщение.

Для инициализации всех событий меню можно добавить состояние events в котором их назначить. Изменения также можно кешировать, запрашивать события AJAX запросами.

Пример подгрузки и кеширования

Возможности асинхронной предварительной загрузки и кеширования

Подгрузка

Вот пример асинхронной загрузки модуля - см. функцию o.inc(). Функция успеха запускается после того, как все загружено. По умолчанию JS и CSS кэшируются на 24 часа в localStorage. Можно изменить время или выключить.

Перезагрузите страницу или нажмите кнопку, чтобы увидеть загрузку из кеша. Кнопка «Очистить» удаляет карточки, чтобы предотвратить масштабирование изображения без стилей. Ограничение примера: 3 карточки одновременно.

// card.js
let initCard = (query) => {
	return o.initState(`
		<div class="card">
			<img 
				class="card__img" 
				src="/objs/card.jpg">
			<div 
				class="card__text">
				It is a sea
			</div>
		</div>
	`).appendInside(query);
};

// пример
o.initState(
	'<button 
		class="example__btn">
		Загрузить
	</button>'
).on('click', () => {
	// ПОДГРУЗКА
	o.inc({
		'cardJS': 'card.js',
		'cardCSS': 'card.css',
		'cardJPG': 'card.jpg',
	}, () => {
		// ЗАПУСК
		initCard('#examplePreloading');
	});
}).appendInside('#examplePreloading');

o.initState(
	'<button 
		class="example__btn">
		Очистить кеш
	</button>'
).on('click', () => {
	o('#examplePreloading div').remove();
	o('.card').remove();
	o.incCacheClear(true);
}).appendInside('#examplePreloading');

Пример тестов

Возможности функции для unit тестирования и автотестов

Тесты

Тестовая функция получает заголовок теста, затем тестовые массивы [название, тест], и последний может быть функцией, запускаемой после завершения тестов. Стандартный тайм-аут для асинхронных тестов составляет 2 секунды.

Установите флажки, чтобы увидеть результаты. Последние два являются асинхронными тестами, и если последний ложный, тест завершится через 2 секунды. Выкличите HTML-стиль, чтобы увидеть представление консоли (/n, изменены на <br> для примера).

// включаем отображение успешных тестов
o.tShowOk = true;

function clearObjsTestSession() {
	try {
		sessionStorage.removeItem('oTest-Run');
		sessionStorage.removeItem('oTest-Autorun');
	} catch (e) {}
}

const boxes = () =>
	document.querySelectorAll('#mainPageTestDemo input.testResult');

o('#mainPageTestDemo').on('change', () => {
	clearObjsTestSession();
	o.tStyled = o('#testStyled').el.checked;
	const b = boxes();
	o.test(
		'Название теста',
		['Первый checkbox', b[0].checked],
		['Второй checkbox', b[1].checked || 'Не отмечен'],
		['Третий checkbox', () => b[2].checked],
		[
			'Четвертый checkbox',
			() =>
				new Promise((resolve) => {
					setTimeout(() => resolve(b[3].checked), 100);
				}),
		],
		[
			'Пятый checkbox',
			() =>
				new Promise((resolve) => {
					const delay = b[4].checked ? 100 : o.tTime;
					setTimeout(() => resolve(!!b[4].checked), delay);
				}),
		],
		{ sync: true },
		(n) => {
			const log = o.tLog[n];
			const text =
				typeof log === 'string' && o.tStyled
					? log
					: String(log).replaceAll('\n', '<br>');
			o('#exampleTests').html(text + '<br>' + JSON.stringify(o.tStatus[n]));
		}
	);
});

Успешность тестов 1-5:

Для автотестов - получить n в последнем параметре функции и проверить o.tRes[n]. Если true - все тесты в наборе верны. Если нет - покажите o.tLog[n] или проверьте o.tStatus[testN] - массив статусов каждого теста. Функция o.test() также возвращает n.

Наверх