data-context
Version:
Watch data changes in the browser and node.js
472 lines (424 loc) • 21.3 kB
HTML
<html>
<head>
<title>data-context</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css" />
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" />
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
<style>
#list-headers ul {
list-style: none;
padding-left: .5em;
}
#list-headers > ul {
padding: 0;
}
#list-headers h1, h2, h3, h4, h5 {
white-space: nowrap;
}
.markdown-body {
padding-left: 2em;
}
@media (min-width: 992px) {
.vh-lg-100{
height: 100vh ;
}
}
</style>
</head>
<body>
<div class="row w-100">
<div class="col-12 text-center">
<h1 id="data-context">data-context</h1>
<p>Watch data changes in the browser and node.js<br>
This manual is also available in <a href="https://manuel-lohmus.github.io/data-context/README.html">HTML5</a>.<br></p>
</div>
</div>
<div class="row w-100">
<div class="col-lg-3 d-lg-inline">
<div class="sticky-top overflow-auto vh-lg-100">
<div id="list-headers" class="list-group mt-2 ms-lg-2 ms-4">
<h4 id="table-of-contents">Table of contents</h4>
<ul>
<li><a href="#introduction"><strong>Introduction</strong></a></li>
<li><a href="#features"><strong>Features</strong></a></li>
<li><a href="#installation"><strong>Installation</strong></a></li>
<li><a href="#testing"><strong>Testing</strong></a></li>
<li><a href="#usage"><strong>Usage</strong></a>
<ul>
<li><a href="#nodejs-example">nodejs example</a></li>
<li><a href="#browser-example">browser example</a></li>
</ul>
</li>
<li><a href="#references"><strong>References</strong></a>
<ul>
<li><a href="#createdatacontextdata-propertyname-parent">createDataContext(data, propertyName, parent)</a></li>
<li><a href="#datacontext">DataContext</a></li>
<li><a href="#eventlistenerevent">EventListener(event)</a></li>
<li><a href="#eventobject">EventObject</a></li>
<li><a href="#propertyname">PropertyName</a></li>
<li><a href="#reviver">Reviver</a></li>
</ul>
</li>
<li><a href="#license"><strong>License</strong></a></li>
</ul>
</div>
</div>
</div>
<div class="col-lg-9 mt-2">
<div class="ps-4 markdown-body" data-bs-spy="scroll" data-bs-target="#list-headers" data-bs-offset="0" tabindex="0">
<h2 id="introduction">Introduction</h2>
<p>It is a simple and easy-to-use library that can be used in the browser or in node.js.
You can create a context from the data, then listen for change events and stringify changes.
Included event-emitter functions. Automatically detects changes in the data and emits events.
Designed for module 'data-context-binding' for binding data to the DOM and for module 'fs-broker' for working with files.
Using a single-page application (SPA) with the <a href="https://www.npmjs.com/package/data-context-binding">'data-context-binding'</a> module gives very good results.
This module is part of the <a href="https://www.npmjs.com/package/conextra">'conextra'</a> framework,
which is a simple and easy-to-use single-page application (SPA) framework.
You have to try it! A different solution than MVC (model–view–controller).</p>
<blockquote>
<p>Please note, this version is not backward compatible with version 1.x<br>
Please note that JSON string is not 100% compatible.<br>
It has been extended to allow for incremental updates of JSON files.<br>
Added the ability to include metadata and comments.<br>
Parsing of JSON files is enabled.</p>
</blockquote>
<p>The code in 'data-context' provides a robust mechanism for creating data contexts that can track changes,
emit events, and support serialization/deserialization.
It leverages JavaScript proxies to intercept and manage property operations,
making it a powerful tool for managing state in complex applications.</p>
<p>The data context library implemented in 'data-context' can be used in various scenarios where tracking changes to data,
emitting events, and managing state are essential.
Here are some potential use cases:</p>
<p><strong>1. State Management in Single Page Applications (SPAs)</strong></p>
<ul>
<li><strong>Description</strong>: Manage the state of an application by creating data contexts for different parts of the state.</li>
<li><strong>Example</strong>: Use the library to track changes in user data, application settings, or UI state, and automatically update the UI when changes occur.</li>
</ul>
<p><strong>2. Data Binding</strong></p>
<ul>
<li><strong>Description</strong>: Bind data to the DOM and automatically update the DOM when the data changes.</li>
<li><strong>Example</strong>: Use the library in conjunction with a data-binding library like data-context-binding to create dynamic and responsive web applications.</li>
</ul>
<p><strong>3. Form Handling</strong></p>
<ul>
<li><strong>Description</strong>: Track changes to form inputs and validate data in real-time.</li>
<li><strong>Example</strong>: Create a data context for form data, listen for changes to input fields, and validate the data or provide instant feedback to the user.</li>
</ul>
<p><strong>4. Real-time Collaboration</strong></p>
<ul>
<li><strong>Description</strong>: Enable real-time collaboration features by synchronizing data changes across multiple clients.</li>
<li><strong>Example</strong>: Use the library to track changes to a shared document or project, and emit events to notify other clients of the changes.</li>
</ul>
<p><strong>5. Undo/Redo Functionality</strong></p>
<ul>
<li><strong>Description</strong>: Implement undo and redo functionality by tracking changes to data and allowing users to revert to previous states.</li>
<li><strong>Example</strong>: Use the library to maintain a history of changes and provide undo/redo capabilities in applications like text editors or drawing tools.</li>
</ul>
<p><strong>6. Data Synchronization</strong></p>
<ul>
<li><strong>Description</strong>: Synchronize data between different parts of an application or between client and server.</li>
<li><strong>Example</strong>: Use the library to track changes to local data and synchronize those changes with a remote server or other clients.</li>
</ul>
<p><strong>7. Configuration Management</strong></p>
<ul>
<li><strong>Description</strong>: Manage application configuration settings and track changes to those settings.</li>
<li><strong>Example</strong>: Create a data context for configuration settings, listen for changes, and update the application behavior accordingly.</li>
</ul>
<p><strong>8. Logging and Auditing</strong></p>
<ul>
<li><strong>Description</strong>: Log changes to data for auditing purposes.</li>
<li><strong>Example</strong>: Use the library to track changes to sensitive data and log those changes for security audits or compliance purposes.</li>
</ul>
<p><strong>9. Testing and Debugging</strong></p>
<ul>
<li><strong>Description</strong>: Facilitate testing and debugging by tracking changes to data and emitting events.</li>
<li><strong>Example</strong>: Use the library to create test cases that verify the correct behavior of data changes and event emissions.</li>
</ul>
<p><strong>10. Incremental JSON Updates</strong></p>
<ul>
<li><strong>Description</strong>: Handle incremental updates to JSON data, including metadata and comments.</li>
<li><strong>Example</strong>: Use the library to parse, modify, and serialize JSON data with support for incremental updates and metadata.</li>
</ul>
<h2 id="features">Features</h2>
<ul>
<li>Create a context from the data.</li>
<li>Listen for change events.</li>
<li>Update the context.</li>
<li>Parse JSON data to the context.</li>
<li>Stringify changes to JSON.</li>
<li>Used extended JSON data format. Metadata and comments are supported.</li>
</ul>
<h2 id="installation">Installation</h2>
<h3 id="nodejs">nodejs:</h3>
<p><code>npm install data-context</code></p>
<h3 id="browser">browser:</h3>
<p><code><script src="https://cdn.jsdelivr.net/npm/data-context@2" ></script></code></p>
<h3 id="using-tiny-https-server-router">Using tiny-https-server router:</h3>
<p>or use <a href="https://www.npmjs.com/package/tiny-https-server">'tiny-https-server'</a> router:
<code><script async src="node_modules/data-context@2"></script></code></p>
<h2 id="testing">Testing</h2>
<p>You can test <code>data-context</code> on your system using this command:</p>
<p><code>node ./node_modules/data-context/index.test</code></p>
<p>or in the <code>data-context</code> project directory:</p>
<p><code>npm test</code></p>
<p>or open in your browser:</p>
<p><code>./node_modules/data-context/index.test.html</code></p>
<h2 id="usage">Usage</h2>
<h3 id="nodejs-example">nodejs example:</h3>
<pre><code class="language-javascript">'use strict';
//Import the required modules.
const { createDataContext, parse } = require('data-context');
//import { createDataContext, parse } from "data-context";
//Create a JSON string.
var strJSON = `{
"count": 0
}`;
//Interval id.
var intervalId = null;
//Create data context.
const context = parse(
//Parse the JSON string.
strJSON,
//Reviver function. Create data context.
createDataContext
);
//Listen to the count property.
context.on('count', (event) => {
console.log('event:', event);
if (event.newValue > 10) {
console.log('I am dead.');
clearInterval(intervalId);
//I am dead. Remove listener.
return false;
}
//I am live. Continue listening.
return true;
});
context.on('-change', (event) => {
//Stringify the changes.
var str = context.stringifyChanges(
//Reviver function. Default is null.
null,
//Indentation. Default is 0.
4,
//Include only modified data. Default is true.
true,
//Set data to unmodified after stringification. Default is true.
true
);
console.log('changes:', str);
//I am live. Continue listening.
return true;
});
//Start the interval.
intervalId = setInterval(() => {
//Increment the count property.
context.count++;
}, 1000);
</code></pre>
<h3 id="browser-example">browser example:</h3>
<pre><code class="language-html"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>data-context</title>
<!-- STEP 1. Import the module. Import for an HTML page hosted on the server. -->
<script type="text/javascript" src="./index.js"></script>
<!-- STEP 1. Import the module. Import for a standalone HTML page. -->
<!--<script src="https://cdn.jsdelivr.net/npm/data-context"></script>-->
<script>
'use strict';
// STEP 3. Import the module.
importModules(['data-context'], function (DC) {
var { createDataContext, parse } = DC;
//Create a JSON string.
var strJSON = `{
"count": 0
}`;
//Interval id.
var intervalId = null;
//Create data context.
const context = parse(
//Parse the JSON string.
strJSON,
//Reviver function. Create data context.
createDataContext
);
//Listen to the count property.
context.on('count', (event) => {
console.log('event:', event);
if (event.newValue > 10) {
console.log('I am dead.');
clearInterval(intervalId);
//I am dead. Remove listener.
return false;
}
//I am live. Continue listening.
return true;
});
context.on('-change', (event) => {
//Stringify the changes.
var str = context.stringifyChanges(
//Reviver function. Default is null.
null,
//Indentation. Default is 0.
4,
//Include only modified data. Default is true.
true,
//Set data to unmodified after stringification. Default is true.
true
);
console.log('changes:', str);
//I am live. Continue listening.
return true;
});
//Start the interval.
intervalId = setInterval(() => {
//Increment the count property.
context.count++;
}, 1000);
});
// STEP 2. Add module import function.
/**
* Module import function.
* @param {string[]} importIdentifierArray Modules to import.
* @param {(...importModules:any[]) => void} callback Callback function.
*/
function importModules(importIdentifierArray, callback) {
var thisScope = "undefined" != typeof globalThis
? globalThis
: "undefined" != typeof window
? window
: "undefined" != typeof global
? global : "undefined" != typeof self
? self
: {};
if (!thisScope.modules) { thisScope.modules = {}; }
waitModules();
function waitModules() {
if (importIdentifierArray.length) {
for (let i = 0; i < importIdentifierArray.length; i++) {
if (!thisScope.modules[importIdentifierArray[i]]) { return setTimeout(waitModules, 10); }
}
}
callback.call(thisScope, ...importIdentifierArray.map(function (id) { return thisScope.modules[id]; }));
}
}
</script>
</head>
<body>
<h3>Example 'data-context'</h3>
<p>Press F12. Console results.</p>
</body>
</html>
</code></pre>
<h2 id="references">References</h2>
<h3 id="createdatacontextdata-propertyname-parent"><em>createDataContext(data, propertyName, parent)</em></h3>
<p>Create a data context from the data.</p>
<p><strong>Type:</strong> <code>function</code></p>
<p><strong>Parameters:</strong></p>
<ul>
<li><code>data</code> - The data object.</li>
<li><code>propertyName</code> - The property name where the data is located. Default is null.</li>
<li><code>parent</code> {<a href="#datacontext">DataContext</a>} - The parent data context. Default is null.</li>
</ul>
<p><strong>Returns:</strong></p>
<ul>
<li>{<a href="#datacontext">DataContext</a>} - The data context.</li>
</ul>
<p><strong>Static Properties:</strong></p>
<ul>
<li><code>ignoreMetadata</code> - Global flag to ignore metadata and comments. Default is false.</li>
<li><code>createDataContext</code> {(data: any, propertyName?: string, parent?: <a href="#datacontext">DataContext</a>) => <a href="#datacontext">DataContext</a>} - Create a data context from the data.</li>
<li><code>parse</code> {(str: string, reviver?: <a href="#reviver">Reviver</a>) => <a href="#datacontext">DataContext</a>} - Parse JSON string to the data context.</li>
<li><code>stringify</code> {(data: any, replacer?: Replacer, space?: number) => string} - Stringify the data to JSON string.</li>
</ul>
<h3 id="datacontext"><em>DataContext</em></h3>
<p>The data context Proxy object.</p>
<p><strong>Type:</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"><code>Proxy</code></a></p>
<p><strong>Properties:</strong></p>
<ul>
<li><code>_isDataContext</code> - The flag that indicates that the object is a data context. Default is true.</li>
<li><code>_isModified</code> - The flag that indicates that the object is modified. Default is false.</li>
<li><code>_modified</code> {Array<<a href="#propertyname">PropertyName</a>>} - The array of modified properties.</li>
<li><code>_propertyName</code> - The property name where the data is located. Default is null.</li>
<li><code>_parent</code> {<a href="#datacontext">DataContext</a>} - The parent data context. Default is null.</li>
<li><code>_events</code> {Map<string, <a href="#eventlistenerevent">EventListener</a>>} - The map of event listeners.</li>
<li><code>isChanged</code> - The flag that indicates that the object is changed.</li>
</ul>
<p><strong>Methods:</strong></p>
<ul>
<li><code>resetChanges</code> {() => void} - Set the current and child objects to status - unmodified. Must be used to start tracking changes again.</li>
<li><code>once</code> {(propertyName: <a href="#propertyname">PropertyName</a>, listener: <a href="#eventlistenerevent">EventListener</a>) => void} - Add an event listener that will be called only once.</li>
<li><code>on</code> {(propertyName: <a href="#propertyname">PropertyName</a>, listener: <a href="#eventlistenerevent">EventListener</a>) => void} - Add an event listener.</li>
<li><code>emit</code> {(eventName: string, event: <a href="#eventobject">EventObject</a>) => void} - Emit an event.</li>
<li><code>emitToParent</code> {(eventName: string, event: <a href="#eventobject">EventObject</a>) => void} - Emit an event to the parent.</li>
<li><code>toString</code> {() => string} - Returns the string representation of the data context.</li>
<li><code>overwritingData</code> {(text: string, reviver?: <a href="#reviver">Reviver</a> ) => void} - Overwrite the data.</li>
<li><code>stringifyChanges</code> {(reviver?: <a href="#reviver">Reviver</a>, space?: number|string, onlyModified?: boolean, setUnmodified?: boolean) => string} - Stringify the changes.</li>
</ul>
<h3 id="eventlistenerevent"><em>EventListener(event)</em></h3>
<p>The event listener function.</p>
<p><strong>Type:</strong> <code>function</code></p>
<p><strong>Parameters:</strong></p>
<ul>
<li><code>event</code> - The event object.</li>
</ul>
<p><strong>Returns:</strong></p>
<ul>
<li> - The flag that indicates that the listener is alive.</li>
</ul>
<h3 id="eventobject"><em>EventObject</em></h3>
<p>The event object.</p>
<p><strong>Type:</strong> <code>object</code></p>
<p><strong>Properties:</strong></p>
<ul>
<li><code>eventName</code> - The event name. 'new' | 'set' | 'delete' | 'reposition' | '-change'
<ul>
<li>'new' It happens when a new property is added to the data context.</li>
<li>'set' It happens when an existing property is updated.</li>
<li>'delete' It happens when a property is removed from the data context.</li>
<li>'reposition' It happens when the position of an item in an array changes.</li>
<li>'change' It happens when any change occurs in the data context.</li>
</ul>
</li>
<li><code>target</code> - The target data context.</li>
<li><code>propertyPath</code> {Array<PropertyName>} - The property path in array format.</li>
<li><code>parent</code> - The parent data context.</li>
<li><code>oldValue</code> - The old value.</li>
<li><code>newValue</code> - The new value.</li>
</ul>
<h3 id="propertyname"><em>PropertyName</em></h3>
<p>The property name.</p>
<p><strong>Type:</strong> <code>string</code></p>
<h3 id="reviver"><em>Reviver</em></h3>
<p>The reviver function.</p>
<p><strong>Type:</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#reviver"><code>reviver</code></a>
or <a href="#createdatacontextdata-propertyname-parent"><code>createDataContext</code></a> or <code>null</code></p>
<h2 id="license">License</h2>
<p>This project is licensed under the MIT License.</p>
<p>Copyright © Manuel Lõhmus</p>
<p><a href="https://www.paypal.com/donate?hosted_button_id=TMZURGNWWYUBW"><img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" alt="Donate" /></a></p>
<p>Donations are welcome and will go towards further development of this project.</p>
<br>
<br>
<br>
</div>
</div>
</div>
<script>
(function () {
'use strict';
var isIE = !!document.documentMode; // Detect IE
if (!isIE) {
// list-group style for headers
document.querySelectorAll('#list-headers a')
.forEach(function (a) { a.classList.add('list-group-item', 'list-group-item-action') });
}
})();
</script>
</body>
</html>