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

Objs

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

Возможности

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

Разработка

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

Оптимизация

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

Как начать

Подключить актуальную версию: v1.0 02/2023

<script src="objs.1.0.min.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) создает элемент для каждого набора данных в массиве listData. Также существует идентификатор для каждой задачи, чтобы синхронизировать метку и флажок, хранящийся как rowID в объекте формы.

Новая задача добавляет в качестве элемента в строки инициированный объект, выбирает последний с помощью .select() и получает инициированные события. Рекомендуется снять выбор с помощью .all(), чтобы общий объект не изменился и далее изменения применялись ко всем элементам.

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: `
		<input 
			class="tm__field" 
			type="text" 
			maxlength="27" 
			placeholer="Input text">
		<input 
			class="tm__add" 
			type="button">
	`,
	events: ({self, o}) => {
		o(self.el)
			.first('.tm__add')
			.on('click', () => {
				const rowsObjs = 
					o.take('.tm__row') 
					|| o.init(rowState);
				const i = self.rowID 
					|| rowsObjs.length;
				self.rowID = i + 1;

				rowsObjs.add(
					o.init(rowState).render({
						i: i,
						text: o(self.el)
							.first('.tm__field')
							.el
							.value
					}).el
				)
					.select()
					.events()
					.appendInside('.tm__list')
					.all();

				o(self.el)
					.first('.tm__field')
					.el
					.value = '';
			});
	}
};

o.initState({
	tag: 'ul',
	class: 'tm__list',
	append: o.init(rowState)
		.render(listData)
		.events()
		.els,
}).add(
	o.initState(formState)
		.events()
		.el
).appendInside('#taskManager');
	

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

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

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

Элемент .add() меняет атрибут oInit набора данных на родительский, но после удаления из DOM объект сохраняется в o.inits[] и self.Сохраните initID для больших модулей, чтобы взять их из o.inits[] и просто добавить вместо новой полной инициализации.

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

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

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

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

// создание объекта состояний из элемента страницы
const menuStates = {
	render: () => {
		// проверка, в курсе ли сервер о кеше
		if (!getCookie('exampleMenuCache')) {
			localStorage
				.setItem(
					'exampleMenuCache', 
					o('.exampleMenu').html()
				);
			setCookie('exampleMenuCache', true);
		}
		return localStorage.getItem('exampleMenuCache');
	},
	// добавление динамического элемента
	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: 'Новое сообщение!'
	});
Новый элемент:

Для инициализации всех событий меню можно добавить состояние 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;

const results = o('.testResult').on('change', () => {
	// HTML стиль
	o.tStyled = o('#testStyled').el.checked;
	// функция тестирования
	o.test(
		'Название теста',
		// просто логический результат
		[
			'Первый checkbox', 
			results.els[0].checked
		],
		// возврат ошибки текстом
		[
			'Второй checkbox', 
			results.els[1].checked || 'Не отмечен'
		],
		// тестирование функцией
		[
			'Третий checkbox', 
			() => {
				return results.els[2].checked;
			}
		],
		// асинхронный тест
		[
			'Четвертый checkbox', 
			(info) => {
				setTimeout(() => o
					.testUpdate(
						info, 
						results.els[3].checked), 
				100);
		}],
		// асинхронный тест с ошибкой таймаута
		['Пятый checkbox', (info) => {
			if (results.els[4].checked) {
				setTimeout(() => o.testUpdate(
					info, 
					true), 
				100);
			}
		}],
		// функция вывода после всех тестов или таймаута
		(n) => {
			o('#exampleTests')
				.html(
					o.tLog[n].replaceAll('\n', '<br>') + 
					'<br>' + 
					JSON.stringify(o.tStatus[n])
				);
		}
	);
});

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

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

Наверх