data-context
Version:
Watch data changes in the browser and node.js
1,595 lines (1,052 loc) • 58.3 kB
JavaScript
/** Copyright (c) Manuel Lõhmus (MIT License). */
"use strict";
(function () {
exportModule("data-context", function factory() {
var script = this.document && Array.from(document.scripts).find(function (s) { return s.src.includes('data-context'); }),
isDebug = script?.attributes.debug || false,
ignoreMetadata = script?.attributes.ignoreMetadata || false;
/**
* Create a new proxy object with the same structure as the original object.
* With the ability to listen to changes in the object
* and the ability to restore the original object.
* @param {any} value Value.
* @param {string} propertyName Property name. Default is null.
* @param {any} parent Parent. Default is null.
* @returns {Proxy} Returns a proxy object.
*/
function createDataContext(value, propertyName = null, parent = null) {
if (createDataContext === this?.constructor) { throw new Error('This function must be used without the `new` keyword.'); }
var type = _typeof(value);
if (value?._isDataContext || type !== "object" && type !== "array") { return value; }
var proxy = null;
Object.defineProperties(value, {
_isDataContext: { value: true, writable: false, configurable: false, enumerable: false },
_isModified: { value: false, writable: true, configurable: false, enumerable: false },
_modified: { value: [], writable: false, configurable: false, enumerable: false },
_propertyName: {
configurable: false, enumerable: false,
get: function () { return propertyName; },
set: function (v) { propertyName = v; }
},
_parent: {
configurable: false, enumerable: false,
get: function () {
if (parent === null) { return null; }
if (_isMyParent()) { return parent; }
return null;
},
set: function (v) { parent = v; }
},
toString: {
writable: true, configurable: false, enumerable: false,
value: function toString(createModifiedData = false) {
if (type === "array") { return _stringifyArray(this); }
if (type === "object") { return _stringifyObject(this); }
return _stringifyValue(this);
function _stringifyValue(val) {
if (val?._isDataContext) { return val.toString(createModifiedData); }
var t = typeof val;
if (t === "string") { return '"' + val + '"'; }
if (t === "boolean") { return Boolean.prototype.toString.call(val); }
if (t === "number") { return Number.prototype.toString.call(val); }
if (val === null) { return "null"; }
if (Array.isArray(val)) { return _stringifyArray(val); }
if (t === "object") { return _stringifyObject(val); }
return "undefined";
}
function _stringifyArray(val) {
return "[".concat(
createModifiedData ? "\r\r" : "",
val.map(function (v, i) {
return "".concat(
createModifiedData ? _stringifyValue(i) : "",
createModifiedData ? ":" : "",
_stringifyValue(v));
}).join(","),
"]"
);
}
function _stringifyObject(val) {
return "{".concat(
createModifiedData ? "\r\r" : "",
Object.keys(val).map(function (k) {
return _stringifyValue(k).concat(":", _stringifyValue(val[k]));
}).join(","),
"}"
);
}
}
},
overwritingData: {
writable: true, configurable: false, enumerable: false,
value: function _overwritingData(text, reviver) { parse.call(this, text, reviver); }
},
stringifyChanges: {
writable: true, configurable: false, enumerable: false,
value: function _stringifyChanges(replacer, space, modifiedData = true, setUnmodified = true) {
return stringify(this, replacer, space, { modifiedData, setUnmodified });
}
},
isChanged: {
configurable: false, enumerable: false,
get: function () { return Boolean(this._isModified || this._modified.length); }
},
resetChanges: {
writable: true, configurable: false, enumerable: false,
value: function _resetChanges() {
for (var k of this._modified) {
if (this[k] && "undefined" !== typeof this[k].resetChanges) {
this[k].resetChanges();
}
}
if ("undefined" !== typeof this._isModified) { this._isModified = false; }
if ("undefined" !== typeof this._modified) { this._modified.length = 0; }
}
},
// EventEmitter
_events: { value: {}, writable: true, configurable: false, enumerable: false },
once: {
writable: false, configurable: false, enumerable: false,
/**
* @param {string} eventName
* @param {(...params:any)=>void} listener
* @returns {DataContext}
*/
value: function once(eventName, listener) {
return this.on(eventName, listener, false);
}
},
on: {
writable: false, configurable: false, enumerable: false,
/**
* @param {string} eventName
* @param {(...params:any)=>void} listener
* @param {boolean|()=>boolean|Node} isActive if false then adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked.
* @returns {DataContext}
*/
value: function on(eventName, listener, isActive = true) {
if (!this._events[eventName]) { this._events[eventName] = []; }
this._events[eventName].push(listener);
isActive && (listener.isActive = isActive);
return this;
}
},
emitToParent: {
writable: false, configurable: false, enumerable: false,
/**
* @param {string} eventName
* @param {...any} params
* @returns {boolean} Returns true if the event had listeners, false otherwise.
*/
value: function emitToParent(eventName, ...params) {
var ret = false;
if (eventName !== '-change') {
ret = this.emit(eventName, ...params) || ret;
}
else if (this._events['-change']?.length) {
clearTimeout(emitToParent.timeout);
emitToParent.timeout = setTimeout(function (_this) {
delete emitToParent.timeout;
_this.emit("-change", { eventName: "-change", target: _this });
}, 10, this);
}
if ((eventName === '-' || eventName === '-change') &&
parent && typeof parent.emitToParent === "function") {
//_modified
if (params[0] && params[0].propertyPath) {
params[0].propertyPath.unshift(propertyName);
}
ret = parent.emitToParent(eventName, ...params) || ret;
}
return ret;
}
},
emit: {
writable: false, configurable: false, enumerable: false,
value: function emit(eventName, ...params) {
var ret = false;
var arr = this._events[eventName] || [];
var index = 0;
while (index < arr.length) {
var listener = arr[index];
if (arr[index] === listener &&
(typeof listener.isActive === "function" && !listener.isActive()
|| listener.isActive?.isConnected === false
|| listener.isActive === undefined
|| !listener.isActive === true)) {
arr.splice(index, 1);
}
if (typeof listener.isActive === "function" && listener.isActive()
|| listener.isActive?.isConnected === true
|| listener.isActive === undefined
|| listener.isActive === true) {
ret = true;
if (!listener.call("undefined" != typeof window && listener.isActive instanceof window.Node && listener.isActive, ...params)) {
arr.splice(index, 1);
}
}
if (arr[index] === listener) { index++; }
}
if (this._events[eventName] && !this._events[eventName].length) {
delete this._events[eventName];
}
return ret;
}
}
});
proxy = new Proxy(value, handler);
Object.keys(value).forEach(function (key) {
if (key.startsWith('-metadata')) {
Object.defineProperty(value, key, {
value: Array.isArray(value[key]) ? value[key] : [value[key]],
enumerable: false
});
return;
}
if (value[key] && !value[key]._isDataContext) {
value[key] = createDataContext(value[key]);
}
if (value[key] && value[key]._isDataContext) {
value[key]._propertyName = key;
value[key]._parent = proxy;
}
});
return proxy;
function _isMyParent() { return !parent || parent && parent[propertyName] === proxy; }
}
/**
* Stringify type.
* @param {any} v Value.
* @returns {string} Returns the type of the value.
*/
function _typeof(v) { return v === null ? "null" : Array.isArray(v) ? "array" : typeof v; }
/**
* Is global object in the browser and Node.js.
* @param {any} g Check object.
* @returns {boolean} Returns true if the object is global.
*/
function isGlobal(g) { return g === (typeof globalThis !== 'undefined' ? globalThis : global || self); }
/**
* Sync data objects
* @param {any} target
* @param {any} source
* @returns {any}
*/
function syncData(target, source, removeUnusedKeys = true) {
if (!target && typeof target !== "object") { target = {}; }
if (Array.isArray(source) && !Array.isArray(target) && target !== null) { target = []; }
if (source && typeof source === "object") {
var keys = Object.keys(source);
Object.keys(source).forEach(function (k) { keys.push('-metadata-' + k); });
keys.push('-metadata');
for (var k of keys) {
if (!source.hasOwnProperty(k)) continue;
if (typeof source[k] === "function" || k === "__proto__" || k === "constructor") { continue; }
if (!source[k] || typeof source[k] !== "object") {
target[k] = source[k];
}
else {
target[k] = syncData(target[k], source[k], removeUnusedKeys);
if (k.startsWith('-metadata')) {
Object.defineProperty(target, k, { enumerable: false });
}
}
}
if (removeUnusedKeys && (_typeof(target) === 'object' || _typeof(target) === 'array')) {
Object.keys(target).forEach(function (k) {
if (!keys.includes(k)) { delete target[k]; }
});
if (Array.isArray(target)) { target.length = source.length; }
}
}
return target;
}
//#region *** Handler ***
var handler = { deleteProperty, set };
function deleteProperty(target, property) {
if (target.propertyIsEnumerable(property)) {
var oldValue = target[property],
newValue = undefined;
var ret = Reflect.deleteProperty(target, property);
target.emitToParent("-", { eventName: "delete", target, propertyPath: [property], oldValue, newValue });
if (Array.isArray(target)) {
target._isModified = true;
}
else if (!target._modified.includes(property)) {
target._modified.push(property);
}
setModified(target._parent, target._propertyName);
if (Array.isArray(target)) {
var index = target._modified.indexOf(property);
if (index > -1) {
target._modified.splice(index, 1);
}
}
target.emit(property, { eventName: "delete", target, propertyPath: [property], oldValue, newValue });
target.emitToParent("-change", { eventName: "-change", target });
return ret;
}
return Reflect.deleteProperty(target, property);
}
function set(target, property, newValue, proxy) {
if (target.propertyIsEnumerable(property)
|| target[property] === undefined) {
var oldValue = target[property],
newValue = newValue && newValue._isDataContext ? newValue : createDataContext(newValue);
if (oldValue !== newValue) {
var eventName = "";
var ret = Reflect.set(target, property, newValue, proxy);
var isDC = newValue?._isDataContext;
var isNew = oldValue === undefined;
if (isNew) {
//console.log("'-new' set: oldValue is undefined", property, event);
eventName = "new";
}
else if (isDC && newValue._propertyName !== null && newValue._propertyName !== property) {
//console.log("'-reposition' set: newValue propertyName is change ", newValue + "", ">", property, event);
eventName = "reposition";
}
else {
//console.log("'-set' set: newValue parent is change", property, event);
eventName = "set";
}
target.emitToParent("-", { eventName, target, propertyPath: [property], oldValue, newValue });
target.emit(property, { eventName, target, propertyPath: [property], oldValue, newValue });
if (isDC) {
newValue._isModified = true;
newValue._parent = proxy;
newValue._propertyName = property;
}
setModified(target, property);
target.emitToParent("-change", { eventName: "-change", target });
return ret;
}
}
if (property === 'length' && _typeof(target) === 'array' && newValue < target.length && target[newValue] !== undefined) {
for (var i = target.length - 1; newValue <= i; i--) {
var oldValue = target[i];
delete target[i + ''];
target.emitToParent("-", { eventName: "delete", target, propertyPath: [i + ''], oldValue, newValue: undefined });
target._isModified = true;
setModified(target._parent, target._propertyName);
if (Array.isArray(target)) {
var index = target._modified.indexOf(property);
if (index > -1) {
target._modified.splice(index, 1);
}
}
Reflect.set(target, 'length', i, proxy);
target.emitToParent(i + '', { eventName: "delete", target, propertyPath: [i + ''], oldValue, newValue: undefined });
}
target.emitToParent("-change", { eventName: "-change", target });
return true;
}
return Reflect.set(target, property, newValue, proxy);
}
function setModified(target, property) {
if (target?._isDataContext) {
if (!target._modified.includes(property)) {
target._modified.push(property);
}
setModified(target._parent, target._propertyName);
}
}
//#endregion
//#region *** parse / stringify ***
/**
* Parse a string to an object.
* @param {string|number} text Input value.
* @param {any} reviver Reviver.
* @returns {any} Returns an object.
*/
function parse(text, reviver) {
if (text !== null
&& typeof text !== "string"
&& text && typeof text !== "number") {
return;
}
var it = new _it(text);
var _this = isGlobal(this) || this === createDataContext ? undefined : this;
var isOverwriting = Boolean(_this);
if (typeof reviver === "object") {
_this = reviver;
if (_this?._isDataContext) {
reviver = createDataContext;
}
}
if (reviver === createDataContext) {
reviver = function _set(k, v) {
if (v?._isDataContext) {
return v;
}
return createDataContext(v, k, this);
};
}
try {
var meta = _whitespace();
var value = _value(_this);
}
catch (e) {
throw ": In parsing position: " + it.position + " '" + it.current + "' => "
+ it.text.substring(it.position < 10 ? 0 : it.position - 10, it.position + 10)
.replace(/\r/g, "\\r")
.replace(/\n/g, "\\n")
+ "\n" + e.message + e.stack;
}
_setMetadata(value, meta);
if (reviver && reviver.name === "_set") {
value = reviver.call(
undefined,
undefined,
value
);
return value;
}
if (reviver) {
value = reviver.call(
{ "": value },
"",
value
);
}
return value;
function _it(text) {
var currentPosition = 0;
this.text = removeBOM(text + '');
this.position = 1;
this.current = this.text.charAt(0);
this.following = this.text.charAt(1);
this.is = _is;
this.next = _next;
this.isInfiniteLoop = _isInfiniteLoop;
this.setPosition = _setPosition;
function _isInfiniteLoop() {
if (currentPosition !== this.position) {
currentPosition = this.position;
return false;
}
throw "Incorrect entry.";
}
function _next() {
this.position++;
this.current = this.following;
this.following = this.text.charAt(this.position);
}
function _is(str) {
if (this.text.substring(this.position - 1, this.position - 1 + str.length) === str) {
this.position = this.position + str.length;
this.current = this.text.charAt(this.position - 1);
this.following = this.text.charAt(this.position);
return true;
}
return false;
}
function _setPosition(pos) {
this.position = pos;
this.current = this.text.charAt(pos - 1);
this.following = this.text.charAt(pos);
}
function removeBOM(str) {
if (str.charCodeAt(0) === 65279) {
return str.slice(1);
}
return str;
}
}
function _get(val, def) {
if (_typeof(val) !== _typeof(def)) {
if (val?._isDataContext) {
def = createDataContext(def);
def._events = val._events;
if (val?._propertyName && val._parent) { val._parent[val._propertyName] = def; }
}
return def;
}
return val;
}
function _whitespace() {
var meta = [], metadata;
__whitespace();
while (metadata = _metadata()) {
if (metadata.trim()) { meta.push(metadata); }
__whitespace();
}
__whitespace();
return meta;
function __whitespace() {
while (it.current === '\n'
|| it.current === '\r'
|| it.current === '\t'
|| it.current === '\u0020') {
it.next();
}
}
}
function _metadata() {
if (it.current === '/' && (it.following === '*' || it.following === '/')) {
var metadata = '',
isMetadata = it.current === '/' && it.following === '*';
it.next();
it.next();
while (isMetadata && !(it.current === '*' && it.following === '/')
|| !isMetadata && !(it.current === '\r' || it.current === '\n')) {
metadata += it.current;
it.next();
}
if (it.current === '\r' || it.current === '\n') {
return ' ';
}
it.next();
it.next();
return metadata;
}
}
function _setMetadata(obj, metadata, key = '') {
if (ignoreMetadata) { return; }
if (metadata && metadata.length && obj && typeof obj === 'object') {
key = key + "";
Object.defineProperty(
obj,
'-metadata' + (key ? '-' + key : ''),
{
value: metadata,
writable: true,
configurable: true,
enumerable: false
}
);
}
}
function _value(val) {
var _val = _object(val);
if (_val !== undefined) { return _val; }
_val = _array(val);
if (_val !== undefined) { return _val; }
_val = _string();
if (_val !== undefined) { return _val; }
_val = _true();
if (_val !== undefined) { return _val; }
_val = _false();
if (_val !== undefined) { return _val; }
_val = _null();
if (_val !== undefined) { return _val; }
_val = _number(val);
return _val;
}
function _string() {
_whitespace();
if (it.current === '"') {
var str = '';
it.next();
if (!it.current) { return; }
while (it.current && it.current !== '"') {
str += it.current;
it.next();
}
if (it.current === '"') {
it.next();
}
return str;
}
}
function _number() {
_whitespace();
var str = _negative();
if (it.current === "0") {
str += it.current;
it.next();
}
else {
str += _digit();
}
if (it.current === ".") {
str += it.current;
it.next();
str += _digit();
}
if (it.current && "eE".includes(it.current)) {
str += it.current;
it.next();
if (it.current && "-+".includes(it.current)) {
str += it.current;
it.next();
}
str += _digit();
}
if (!str) { return undefined; }
str = JSON.parse(str);
return str;
}
function _negative() {
if (it.current === '-') {
it.next();
return '-';
}
return '';
}
function _digit() {
var str = '';
while (it.current && "0123456789".includes(it.current)) {
str += it.current;
it.next();
}
return str;
}
function _object(val) {
var meta = _whitespace();
if (it.current === '{') {
it.next();
var isNewObject = it.current === '\r' && it.following === '\r',
keys = [];
if (isNewObject) {
it.next();
it.next();
}
var obj = _get(val, {});
_setMetadata(obj, meta);
while (it.current !== '}' && !it.isInfiniteLoop()) {
meta = _whitespace();
if (it.current === '}') {
if (isOverwriting && _typeof(_this) === 'object') {
Object.keys(_this).forEach(function (k) {
delete _this[k];
});
}
break;
}
var k = _key();
keys.push(k);
_whitespace();
var v = _value(obj[k] || val?.[k]);
_whitespace();
if (it.current !== ',' && it.current !== '}') {
throw "Incorrect object separator.";
}
if (it.current === ',') {
it.next();
}
if (typeof k === 'string' && v !== undefined) {
if (reviver) {
Reflect.set(
obj,
k,
reviver.call(obj, k, v)
);
}
else {
obj[k] = v;
}
if (typeof obj[k] === 'object') {
_setMetadata(obj[k], meta);
}
else {
_setMetadata(obj, meta, k);
}
}
else {
delete obj[k];
}
}
if (it.current === '}') {
it.next();
}
if (isNewObject) {
for (var k of Object.keys(obj)) {
if (!keys.includes(k)) {
delete obj[k];
obj._isModified = true;
}
}
}
return obj;
}
}
function _key() {
_whitespace();
if (it.current === ":") {
throw "Invalid object key.";
}
var key = _string();
_whitespace();
if (typeof key !== "string" || it.current !== ":") {
throw "Invalid object key.";
}
else {
it.next();
}
return key;
}
function _array(val) {
var meta = _whitespace();
if (it.current === '[') {
it.next();
var isNewArray = it.current === '\r' && it.following === '\r';
if (isNewArray) {
it.next();
it.next();
}
var arr = _get(val, []);
_setMetadata(arr, meta);
var i = 0;
while (it.current !== ']' && !it.isInfiniteLoop()) {
meta = _whitespace();
if (it.current === ']') {
if (isOverwriting && _typeof(_this) === 'array') {
_this.length = 0;
}
break;
}
// for update
var index = _index();
if (isOverwriting && index === undefined) {
pWarn("Overwriting data -> array index must be.");
index = i;
}
_whitespace();
var v = _value(
arr[typeof index === "number" && index || i] ||
val?.[typeof index === "number" && index || i]
);
_whitespace();
if (it.current !== ',' && it.current !== ']') {
throw "Incorrect array separator.";
}
if (it.current === ',') {
it.next();
}
if (reviver) {
v = reviver.call(arr, String(typeof index === "number" && index || i), v);
}
if (v === undefined && typeof index === "number" && index > -1) {
arr.splice(index, 1);
}
else if (typeof index === "number" && arr[index] !== undefined) {
arr[index] = v;
}
else {
arr.push(v);
}
if (typeof arr[typeof index === "number" && index || i] === 'object') {
_setMetadata(arr[typeof index === "number" && index || i], meta);
}
else {
_setMetadata(arr, meta, typeof index === "number" && index || i);
}
i++;
}
if (it.current === ']') {
it.next();
}
if (isNewArray && val?.length > i) {
val.length = i;
arr._isModified = true;
}
return arr;
}
}
function _index() {
_whitespace();
if (it.current === ":") {
throw "Invalid array index.";
}
var pos = it.position;
var nr = _number();
_whitespace();
if (typeof nr !== "number" && it.current === ":") {
throw "Invalid array index.";
}
if (typeof nr === "number" && it.current === ":") {
it.next();
return nr;
}
it.setPosition(pos);
return;
}
function _true() {
_whitespace();
if (it.is('true')) {
return true;
}
}
function _false() {
_whitespace();
if (it.is('false')) {
return false;
}
}
function _null() {
_whitespace();
if (it.is('null')) {
return null;
}
}
}
/**
* Stringify an object.
* @param {any} value Input value.
* @param {any} replacer Replacer. Optional.
* @param {any} space Space. Optional.
* @param {Options} options Options. Optional.
* @returns {string} Returns a string.
*
* @typedef {Object} Options
* @property {boolean} modifiedData Select modified data. Optional.
* @property {boolean} setUnmodified Set unmodified. Optional.
* @property {boolean} includeBOM Add the BOM to the beginning of the string. Optional.
*/
function stringify(value, replacer, space, { modifiedData = false, setUnmodified = false, includeBOM = false } = {}) {
// Define the BOM character
var BOM = String.fromCharCode(65279),
strJSON = '',
isModified = false;
replacer = _replacer(replacer);
space = _space(space);
if (typeof replacer === "function") {
value = replacer.call(
{ "": value },
"",
value
);
}
_value(value, 0);
return includeBOM && strJSON ? BOM + strJSON : strJSON || undefined;
function _replacer(replacer) {
if (Array.isArray(replacer)) {
replacer = fn(replacer);
function fn(arr) {
var isInitial = true;
return function (k, v) {
if (isInitial) {
isInitial = false;
return v;
}
return arr?.includes(k) ? v : undefined;
};
}
}
return function (k, v) {
if (typeof replacer === "function") {
v = replacer.call(this, k, v);
}
if (typeof v?.toJSON === "function"
&& v.toJSON.name !== "_toJSON") {
v = v.toJSON(k);
}
return v;
};
}
function _space(space) {
if (typeof space === "number") {
var l = space < 1 ? 1 : space > 10 ? 10 : space;
space = "";
for (var i = 0; i < l; i++) {
space += " ";
}
}
if (typeof space === "string") {
space = space.substring(0, 10);
}
else { space = undefined; }
return space;
}
function _value(value, level) {
var type = _typeof(value);
_meta();
if (modifiedData) {
//root
if (!value?._parent) {
_getValue();
}
//selec all
else if (isModified) {
_setUnmodified();
_getValue();
}
else if (value?._isModified === true) {
var isMod = isModified;
isModified = true;
_getValue();
isModified = isMod;
_setUnmodified();
}
//selec modified
else {
_setUnmodified();
_getValue();
}
}
else { _getValue(); }
return;
function _meta() {
if (level === 0 || modifiedData && value?._parent && !isModified && value?._isModified === true) {
return _metadata(value, level, '');
}
}
function _setUnmodified() {
if (modifiedData && setUnmodified
&& value?._isDataContext) {
//set _isModified false
if (value._isModified === true) { value._isModified = false; }
//removing propertyName
if (Array.isArray(value._parent?._modified)) {
var index = value._parent._modified.indexOf(value._propertyName);
if (index > -1) {
value._parent._modified.splice(index, 1);
}
}
}
}
function _getValue() {
if (_object()) { return; }
if (_string()) { return; }
if (_bool()) { return; }
if (_null()) { return; }
if (_number()) { return; }
}
function _object(cb) {
var startChar, isKeyVal, endChar;
if (type === "object") { startChar = '{'; isKeyVal = true; endChar = "}"; }
else if (type === "array") { startChar = '['; endChar = "]"; }
if (startChar && endChar) {
// signal of updating the entire object
if (modifiedData && value._isModified) {
startChar += '\r\r';
}
var keys = modifiedData && !value._isModified && !isModified
? Object.values(value._modified).sort()
: Object.keys(value);
if (type === "array") { keys = keys.filter(function (k) { return Number.isInteger(Number(k)) }); }
// write emty object
if (!keys.length) {
if (modifiedData && !value._parent) { return true; }
strJSON += startChar + endChar;
return true;
}
// write startChar
if (space) { startChar += startChar.includes("\r\r") ? "" : _newline(); }
strJSON += startChar;
var isMod = isModified;
isModified = value._isModified;
// write keyVal
while (keys.length) {
_writeMetaKeyVal(keys.shift());
}
strJSON += _newline();
isModified = isMod;
if (modifiedData) { value._isModified = false; }
// write endChar
strJSON += _lineSpace(endChar, level);
return true;
}
return;
function _writeMetaKeyVal(k) {
var val = value[k];
if (typeof replacer === "function") {
val = replacer.call(
value,
k,
val
);
}
if (val?._isDataContext) {
_metadata(val, level + 1, "");
}
else {
_metadata(value, level + 1, k);
}
_writeKeyVal();
return;
function _writeKeyVal() {
// minified array and modified data
if (!space && !isKeyVal && modifiedData) { _writeKey(k + ':', true); }
// minified array
else if (!space && !isKeyVal) { _writeKey(''); }
// minified object
else if (!space && isKeyVal) { _writeKey('"' + k + '":'); }
// array and modified data
else if (!isKeyVal && modifiedData) { _writeKey(_lineSpace(k + ': ', level + 1), true); }
// array
else if (!isKeyVal) { _writeKey(_lineSpace('', level + 1)); }
// object
else { _writeKey(_lineSpace('"' + k + '": ', level + 1)); }
return;
function _writeKey(strKey, isStartTrim) {
// write key
strJSON += strKey;
// write value
_value(val, level + 1);
// write separator
strJSON += keys.length ? ',' + _newline() : '';
setUnmodified();
return;
function setUnmodified() {
//set unmodified private val
if (modifiedData && setUnmodified) {
//removing propertyName
var index = value._modified.indexOf(k);
if (index > -1) {
value._modified.splice(index, 1);
}
}
}
}
}
}
}
function _string() {
if (type === "string") {
strJSON += '"' + value + '"' + '';
return true;
}
}
function _bool() {
if (type === "boolean") {
strJSON += value + '';
return true;
}
}
function _null() {
if (type === "null") {
strJSON += "null";
return true;
}
}
function _number() {
if (type === "number") {
strJSON += JSON.stringify(value);
return true;
}
}
}
function _metadata(value, level, key = "") {
if (ignoreMetadata || !value) { return; }
var i = 0;
key = key + "";
key = "-metadata" + (key ? "-" + key : "");
if (value[key] && !Array.isArray(value[key])) {
Object.defineProperty(value, key, { value: [value[key]], enumerable: false });
}
if (modifiedData && !value._isModified) { return; }
while (value[key] && i < value[key].length) {
// write line
strJSON += _lineSpace(
'/*' + value[key][i] + '*/' + _newline(),
level
);
i++;
}
}
function _lineSpace(text, level) {
if (!space || !level) { return text; }
return _space_(level) + text;
function _space_(level) {
if (!space || !level) { return ""; }
return get[level] || get();
function get() {
get[level] = "";
for (var i = 0; i < level; i++) { get[level] += space; }
return get[level];
}
}
}
function _newline() {