RU
FAST          
          and SIMPLE

Objs

  JavaScript library to  
  develop, test, cache and  
  optimize  

Features

You can develop new features with Objs without rewriting anything

Develop

Test

Optimize

With Objs you finally can separate logic and HTML and have full access to cache abilities, async module loading and auto tests for the whole web app. For developers – faster and simplier architecture, for business – cheaper development by using layout designers and module project structure if needed.

*Async – asynchrone, in parallel to normal page functionality.

Get started

objs-core v2.4.x — use npm or a global script (same role as objs.global.min.js in the package):

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

Then use o functions in your project.

import o from 'objs-core';

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

Every function has documentation there but the main principles and examples are below.

Main principles

Logic structure to create any dynamic module and controll it

To control element Objs uses states. State - it's an information how to create or change element. To create element use render state with html (inner HTML) and tag attributes.

// timer create state called render
const timerStates = {
	render: {
		class: 'timer',
		html: 'Seconds: <span>0</span>',
	}
}

Default tag is div. Attributes dataset and style can be object type. Also, render could be a string like: '<div class="timer">Seconds: <span>0</span></div>'

Then add a new state that will start counting. Number will be stored in the object itself - self object. So the state will be a function that gets self, creates a variable, increments it by interval and shows as innerHTML of span.

// new timer states object
const timerStates = {
	render: {
		class: 'timer',
		html: 'Seconds: <span>0</span> ',
	},
	start: ({self}) => {
		// save number or create
		self.n = self.n || 0;
		// start interval
		setInterval(() => {
			self.n++;
			o(self).first('span').html(self.n);
		}, 1000);
	}
}

States are done and the last thing is to create and append element on the page. To do this - init states, render object and start timer... And also - append it:

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

Somewhere here:

There are more complex examples below but with the same logic: create states, some functions that changes element then init and append elements on page. States after initialization are self methods in addition to standart on, first, remove and etc.

State examples

First part of classical usage examples but with some features

Timer with start / stop

Timer that counts seconds on page but can be stopped. The main structure: state to render, to set events, to change state of the element and to start/stop. When the timer is stopped we append new element to show status. Data saved in the self object.

Important: change by o() in states only children elements to keep pure logic.

o.init({
	render: {
		class: 'exampleTimer',
		html: 'Seconds: <span>0</span>',
	},
	events: ({self}) => {
		self.on('click', () => {
			// check status
			if (self.interval) {
				// use other states as methods
				self.stop();
			} else {
				self.start();
			}
		});
	},
	tick: ({self, o}) => {
		// saving data in self object
		self.n = self.n || 0;
		self.n++;
		// getting child and change
		o(self).first('span').html(self.n);
	},
	// timer control
	start: ({self}) => {
		// setting interval to control
		self.interval = setInterval(() => {
			self.tick();
		}, 1000);
		// removing status
		o(self).first('.status').remove();
	},
	stop: ({self, o}) => {
		// stop interval
		clearInterval(self.interval);
		delete self.interval;
		// add status title
		o.initState(
			'<div class="status">(stopped)</div>'
		).appendInside(self.el);
	}
}).render()
	.events()
	.start()
	.appendInside('#exampleTimer');

Todo list / task manager

Dynamic list of tasks to add, check and delete them. Here you see the same principles of controll and using data for creation start elements.

Here .render(listData) creates one element per item. Each row uses index i in id="task${i}" so label and checkbox stay paired. On add, formObj.nextTaskId supplies the next index after the initial listData length.

New tasks are created with a fresh o.init(rowState), then .events() and .appendInside('.tm__list'). Each row keeps its own handlers (including delete). The text field and add control sit in a small .tm__form block mounted above the list.

const listData = [
	{
		text: 'Task number one',
		checked: true,
	},
	{
		text: 'Task number two',
		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="New task">
			<input class="tm__add" type="button" value="" aria-label="Add task">
		</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');
	

Important moments

To insert elements and save their events use append in state, not html.

You can create and render elements from objects, strings, functions and for each data set. To control children elements use o() to create separate object. To load inited elements use o.take(query).

In objs-core v2, .add() on an instance that already has initID does nothing (by design). To mount a second root next to the list, call appendInside on each piece (form and ul) as in the example. New rows are appended with o.init(rowState).render(…).events().appendInside('.tm__list').

.add() on a fresh selection still merges elements into els and updates data-o-init where applicable; for large modules you can reuse o.inits[] instead of full re-init.

State cache

To optimize some big components loaded from server render Objs can make state object and control it e.g. save HTML string from .html() in localStorage and set cookie for server to return sample and then change it to real element and change if something changed.

It is possible to control history object and cache all HTML samples to switch pages just by AJAX for data.

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

// Keys scoped to this page (avoid clashing with documentation tests / sessionStorage)
const MENU_LS = 'objsMainPage_menuHtml';
const MENU_CK = 'objsMainPage_menuCached';

// creating states object from DOM element
const menuStates = {
	render: () => {
		// check if server knows about cache
		if (!getCookie(MENU_CK)) {
			localStorage.setItem(MENU_LS, o('#exampleMenuServerPart .exampleMenu').html());
			setCookie(MENU_CK, true);
		}
		return localStorage.getItem(MENU_LS) || '';
	},
	// add dynamic element
	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: 'New Message!'
	});

Simulated server-rendered fragment (static HTML). The script snapshots it, then appends a cached clone and a clickable message below.

Site menu

Add events state like in examples above to handle menu events. Changes also can be cached and inited by AJAX for special data, instead of full element rendering.

Include and cache example

Second part to show the power of async preloading and cache

Preloading

Here is an example of async module loading - see o.inc() function. Success function runs after everything is loaded. As default JS and CSS are cached for 24 hours in localStorage. It is possible to change time or off.

Reload the page or press button to see loading from cache. Clear button removes cards to prevent scaling image without styles. Limitation: 3 cards at one time.

// 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);
};

// example
o.initState(
	'<button 
		class="example__btn">
		Press to load
	</button>'
).on('click', () => {
	// PRELOADING
	o.inc({
		'cardJS': 'card.js',
		'cardCSS': 'card.css',
		'cardJPG': 'card.jpg',
	}, () => {
		// RUN
		initCard('#examplePreloading');
	});
}).appendInside('#examplePreloading');

o.initState(
	'<button 
		class="example__btn">
		Clear cache
	</button>'
).on('click', () => {
	o('#examplePreloading div').remove();
	o('.card').remove();
	o.incCacheClear(true);
}).appendInside('#examplePreloading');

Test example

The last example - test features

Unit tests

Test function gets test title, then test arrays [title, test] and the last one can be a function to run after tests are complete. Standart timeout for async tests is 2s.

Switch checkboxes to see results. The last two are async tests and if the last one is false - test will finish after 2s timeout. Off HTML styled to see console view with /n changed to <br>.

// turn on showing success result
o.tShowOk = true;

// Avoid inheriting documentation test runner state from sessionStorage
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(
		'Test title',
		['First checkbox', b[0].checked],
		['Second checkbox', b[1].checked || 'Not checked'],
		['Third checkbox', () => b[2].checked],
		[
			'Fourth checkbox',
			() =>
				new Promise((resolve) => {
					setTimeout(() => resolve(b[3].checked), 100);
				}),
		],
		[
			'Fifth 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]));
		}
	);
});

Results:

For auto tests - get n in the last function parameter and check o.tRes[n]. If true - all tests in the set are correct. If not - show o.tLog[n] or check o.tStatus[testN] - an array of each test status. Function o.test() also returns n.

To top