data-tier
Advanced tools
Comparing version 0.5.41 to 0.6.0
@@ -1,544 +0,862 @@ | ||
(function (options) { | ||
(function (scope) { | ||
'use strict'; | ||
const | ||
MODULES_NAMESPACE = 'modules', | ||
MODULE_NAME = 'dataTier', | ||
VIEW_UPDATE_EVENT = 'viewupdate', | ||
ERROR_LOG_MODE = 'error', | ||
DEBUG_LOG_MODE = 'debug', | ||
INFO_LOG_MODE = 'info'; | ||
var proxiesToTargetsMap = new Map(), | ||
targetsToObserved = new Map(), | ||
observedToObservable = new Map(), | ||
nonObservables = ['Date', 'Blob', 'Number', 'String', 'Boolean', 'Error', 'SyntaxError', 'TypeError', 'URIError', 'Function', 'Promise', 'RegExp']; | ||
var | ||
dataObserver, | ||
dataRoot, | ||
observersService, | ||
tiesService, | ||
viewsService, | ||
rulesService, | ||
logger; | ||
function copyShallow(target) { | ||
var result; | ||
if (Array.isArray(target)) { | ||
result = target.slice(); | ||
} else { | ||
result = Object.assign(new target.constructor(), target); | ||
} | ||
return result; | ||
} | ||
if (typeof options !== 'object') { options = {}; } | ||
function isNonObservable(target) { | ||
return nonObservables.indexOf(target.constructor.name) >= 0; | ||
} | ||
logger = new (function DTLogger() { | ||
var mode = ERROR_LOG_MODE; | ||
function mProc(args) { | ||
var tmp = []; | ||
args = Array.from(args); | ||
args.forEach(function (one) { | ||
if (typeof one === 'object') { | ||
tmp.push(JSON.stringify(one)); | ||
} else { | ||
tmp.push(one); | ||
function proxiedArrayGet(target, key) { | ||
var result, | ||
observed = targetsToObserved.get(target), | ||
observable = observedToObservable.get(observed.root); | ||
if (key === 'pop') { | ||
result = function proxiedPop() { | ||
var poppedIndex, popResult, changes; | ||
poppedIndex = target.length - 1; | ||
popResult = Reflect.apply(target[key], target, arguments); | ||
if (popResult && typeof popResult === 'object') { | ||
popResult = proxiesToTargetsMap.get(popResult); | ||
targetsToObserved.get(popResult).revoke(); | ||
} | ||
}); | ||
return tmp.join(' '); | ||
} | ||
Object.defineProperties(this, { | ||
mode: { | ||
get: function () { return mode; }, | ||
set: function (v) { | ||
if (v === ERROR_LOG_MODE || v === DEBUG_LOG_MODE || v === INFO_LOG_MODE) mode = v; | ||
else console.error('DTLogger: mode "' + v + '" is not supported'); | ||
changes = [new DeleteChange(observed.path.concat(poppedIndex), popResult)]; | ||
publishChanges(observable.callbacks, changes); | ||
return popResult; | ||
}; | ||
} else if (key === 'push') { | ||
result = function proxiedPush() { | ||
var pushContent, pushResult, changes = [], startingLength; | ||
pushContent = Array.from(arguments); | ||
startingLength = target.length; | ||
pushContent.forEach(function (item, index) { | ||
if (item && typeof item === 'object') { | ||
pushContent[index] = new Observed(item, startingLength + index, observed).proxy; | ||
} | ||
changes.push(new InsertChange(observed.path.concat(startingLength + index), item)); | ||
}); | ||
pushResult = Reflect.apply(target[key], target, pushContent); | ||
publishChanges(observable.callbacks, changes); | ||
return pushResult; | ||
}; | ||
} else if (key === 'shift') { | ||
result = function proxiedShift() { | ||
var shiftResult, changes, tmpObserved; | ||
shiftResult = Reflect.apply(target[key], target, arguments); | ||
if (shiftResult && typeof shiftResult === 'object') { | ||
shiftResult = proxiesToTargetsMap.get(shiftResult); | ||
targetsToObserved.get(shiftResult).revoke(); | ||
} | ||
}, | ||
info: { | ||
value: function () { | ||
if (mode === INFO_LOG_MODE || mode === ERROR_LOG_MODE) { | ||
console.info('DT: ' + mProc(arguments)); | ||
target.forEach(function (element, index) { | ||
if (element && typeof element === 'object') { | ||
tmpObserved = targetsToObserved.get(proxiesToTargetsMap.get(element)); | ||
if (tmpObserved) { | ||
tmpObserved.ownKey = index; | ||
} else { | ||
console.error('failed to resolve proxy -> target -> observed'); | ||
} | ||
} | ||
}); | ||
changes = [new DeleteChange(observed.path.concat(0), shiftResult)]; | ||
publishChanges(observable.callbacks, changes); | ||
return shiftResult; | ||
}; | ||
} else if (key === 'unshift') { | ||
result = function proxiedUnshift() { | ||
var unshiftContent, unshiftResult, changes = [], tmpObserved; | ||
unshiftContent = Array.from(arguments); | ||
unshiftContent.forEach(function (item, index) { | ||
if (item && typeof item === 'object') { | ||
unshiftContent[index] = new Observed(item, index, observed).proxy; | ||
} | ||
}); | ||
unshiftResult = Reflect.apply(target[key], target, unshiftContent); | ||
target.forEach(function (item, index) { | ||
if (item && typeof item === 'object') { | ||
tmpObserved = targetsToObserved.get(proxiesToTargetsMap.get(item)); | ||
if (tmpObserved) { | ||
tmpObserved.ownKey = index; | ||
} else { | ||
console.error('failed to resolve proxy -> target -> observed'); | ||
} | ||
} | ||
}); | ||
for (var i = 0; i < unshiftContent.length; i++) { | ||
changes.push(new InsertChange(observed.path.concat(i), target[i])); | ||
} | ||
}, | ||
debug: { | ||
value: function () { | ||
if (mode === INFO_LOG_MODE || mode === ERROR_LOG_MODE || mode === DEBUG_LOG_MODE) { | ||
console.debug('DT: ' + mProc(arguments)); | ||
publishChanges(observable.callbacks, changes); | ||
return unshiftResult; | ||
}; | ||
} else if (key === 'reverse') { | ||
result = function proxiedReverse() { | ||
var changes = [], tmpObserved; | ||
Reflect.apply(target[key], target, arguments); | ||
target.forEach(function (element, index) { | ||
if (element && typeof element === 'object') { | ||
tmpObserved = targetsToObserved.get(proxiesToTargetsMap.get(element)); | ||
if (tmpObserved) { | ||
tmpObserved.ownKey = index; | ||
} else { | ||
console.error('failed to resolve proxy -> target -> observed'); | ||
} | ||
} | ||
}); | ||
changes.push(new ReverseChange()); | ||
publishChanges(observable.callbacks, changes); | ||
return this; | ||
}; | ||
} else if (key === 'sort') { | ||
result = function proxiedSort() { | ||
var changes = [], tmpObserved; | ||
Reflect.apply(target[key], target, arguments); | ||
target.forEach(function (element, index) { | ||
if (element && typeof element === 'object') { | ||
tmpObserved = targetsToObserved.get(proxiesToTargetsMap.get(element)); | ||
if (tmpObserved) { | ||
tmpObserved.ownKey = index; | ||
} else { | ||
console.error('failed to resolve proxy -> target -> observed'); | ||
} | ||
} | ||
}); | ||
changes.push(new ShuffleChange()); | ||
publishChanges(observable.callbacks, changes); | ||
return this; | ||
}; | ||
} else if (key === 'fill') { | ||
result = function proxiedFill() { | ||
var start, end, changes = [], prev; | ||
start = arguments.length < 2 ? 0 : (arguments[1] < 0 ? target.length + arguments[1] : arguments[1]); | ||
end = arguments.length < 3 ? target.length : (arguments[2] < 0 ? target.length + arguments[2] : arguments[2]); | ||
prev = target.slice(); | ||
Reflect.apply(target[key], target, arguments); | ||
for (var i = start; i < end; i++) { | ||
if (target[i] && typeof target[i] === 'object') { | ||
target[i] = new Observed(target[i], i, observed).proxy; | ||
} | ||
if (prev.hasOwnProperty(i)) { | ||
changes.push(new UpdateChange(observed.path.concat(i), target[i], prev[i] && typeof prev[i] === 'object' ? proxiesToTargetsMap.get(prev[i]) : prev[i])); | ||
if (prev[i] && typeof prev[i] === 'object') { | ||
targetsToObserved.get(proxiesToTargetsMap.get(prev[i])).revoke(); | ||
} | ||
} else { | ||
changes.push(new InsertChange(observed.path.concat(i), target[i])); | ||
} | ||
} | ||
}, | ||
error: { | ||
value: function () { | ||
console.error('DT: ' + mProc(arguments)); | ||
publishChanges(observable.callbacks, changes); | ||
return this; | ||
}; | ||
} else if (key === 'splice') { | ||
result = function proxiedSplice() { | ||
var spliceContent, spliceResult, changes = [], tmpObserved, | ||
index, startIndex, removed, inserted; | ||
spliceContent = Array.from(arguments); | ||
// obserify the newcomers | ||
spliceContent.forEach(function (item, index) { | ||
if (index > 1 && item && typeof item === 'object') { | ||
spliceContent[index] = new Observed(item, index, observed).proxy; | ||
} | ||
}); | ||
// calculate pointers | ||
startIndex = spliceContent.length === 0 ? 0 : (spliceContent[0] < 0 ? target.length + spliceContent[0] : spliceContent[0]); | ||
removed = spliceContent.length < 2 ? target.length - startIndex : spliceContent[1]; | ||
inserted = Math.max(spliceContent.length - 2, 0); | ||
spliceResult = Reflect.apply(target[key], target, spliceContent); | ||
// reindex the paths | ||
target.forEach(function (element, index) { | ||
if (element && typeof element === 'object') { | ||
tmpObserved = targetsToObserved.get(proxiesToTargetsMap.get(element)); | ||
if (tmpObserved) { | ||
tmpObserved.ownKey = index; | ||
} else { | ||
console.error('failed to resolve proxy -> target -> observed'); | ||
} | ||
} | ||
}); | ||
// revoke removed Observed | ||
spliceResult.forEach(function (removed, index) { | ||
if (removed && typeof removed === 'object') { | ||
spliceResult[index] = proxiesToTargetsMap.get(removed); | ||
targetsToObserved.get(spliceResult[index]).revoke(); | ||
} | ||
}); | ||
// publish changes | ||
for (index = 0; index < removed; index++) { | ||
if (index < inserted) { | ||
changes.push(new UpdateChange(observed.path.concat(startIndex + index), target[startIndex + index], spliceResult[index])); | ||
} else { | ||
changes.push(new DeleteChange(observed.path.concat(startIndex + index), spliceResult[index])); | ||
} | ||
} | ||
} | ||
}); | ||
})(); | ||
for (; index < inserted; index++) { | ||
changes.push(new InsertChange(observed.path.concat(startIndex + index), target[startIndex + index])); | ||
} | ||
publishChanges(observable.callbacks, changes); | ||
function dataAttrToProp(v) { | ||
var i = 2, l = v.split('-'), r; | ||
r = l[1]; | ||
while (i < l.length) r += l[i][0].toUpperCase() + l[i++].substr(1); | ||
return r; | ||
return spliceResult; | ||
}; | ||
} else { | ||
result = Reflect.get(target, key); | ||
} | ||
return result; | ||
} | ||
function pathToNodes(value) { | ||
if (Array.isArray(value)) return value; | ||
function proxiedSet(target, key, value) { | ||
var oldValuePresent = target.hasOwnProperty(key), | ||
oldValue = target[key], | ||
result, | ||
observed = targetsToObserved.get(target), | ||
observable = observedToObservable.get(observed.root), | ||
changes = [], | ||
path; | ||
var c = 0, b = false, n = '', r = []; | ||
while (c < value.length) { | ||
if (value[c] === '.') { | ||
n.length && r.push(n); | ||
n = ''; | ||
} else if (value[c] === '[') { | ||
if (b) throw new Error('bad path: "' + value + '", at: ' + c); | ||
n.length && r.push(n); | ||
n = ''; | ||
b = true; | ||
} else if (value[c] === ']') { | ||
if (!b) throw new Error('bad path: "' + value + '", at: ' + c); | ||
n.length && r.push(n); | ||
n = ''; | ||
b = false; | ||
result = Reflect.set(target, key, value); | ||
if (observable.callbacks.length && result && value !== oldValue) { | ||
path = observed.path.concat(key); | ||
if (oldValue && typeof oldValue === 'object') { | ||
targetsToObserved.get(proxiesToTargetsMap.get(oldValue)).revoke(); | ||
if (proxiesToTargetsMap.has(oldValue)) { | ||
proxiesToTargetsMap.delete(oldValue); | ||
} | ||
} | ||
if (value && typeof value === 'object') { | ||
target[key] = new Observed(value, key, observed).proxy; | ||
} | ||
if (oldValuePresent) { | ||
changes.push(new UpdateChange(path, value, oldValue)); | ||
} else { | ||
n += value[c]; | ||
changes.push(new InsertChange(path, value)); | ||
} | ||
c++; | ||
if (!observed.preventCallbacks) { | ||
publishChanges(observable.callbacks, changes); | ||
} | ||
} | ||
n.length && r.push(n); | ||
return r; | ||
return result; | ||
} | ||
function copyObject(o) { | ||
var r, i; | ||
if (typeof o !== 'object') throw new Error('object parameter expected'); | ||
if (o === null) return null; | ||
if (Array.isArray(o)) { | ||
r = []; | ||
for (i = 0; i < o.length; i++) { | ||
if (typeof o[i] === 'object') { | ||
r[i] = copyObject(o[i]) | ||
} else if (typeof o[i] !== 'function') { | ||
r[i] = o[i]; | ||
function proxiedDelete(target, key) { | ||
var oldValue = target[key], | ||
result, | ||
observed = targetsToObserved.get(target), | ||
observable = observedToObservable.get(observed.root), | ||
changes = [], | ||
path; | ||
result = Reflect.deleteProperty(target, key); | ||
if (observable.callbacks.length && result) { | ||
if (typeof oldValue === 'object' && oldValue) { | ||
if (proxiesToTargetsMap.has(oldValue)) { | ||
proxiesToTargetsMap.delete(oldValue); | ||
} | ||
} | ||
} else { | ||
r = {}; | ||
Object.getOwnPropertyNames(o).forEach(function (p) { | ||
if (typeof o[p] === 'object') { | ||
r[p] = copyObject(o[p]) | ||
} else if (typeof o[p] !== 'function') { | ||
r[p] = o[p]; | ||
} | ||
}); | ||
path = observed.path.concat(key); | ||
changes.push(new DeleteChange(path, oldValue)); | ||
if (!observed.preventCallbacks) { | ||
publishChanges(observable.callbacks, changes); | ||
} | ||
} | ||
return r; | ||
return result; | ||
} | ||
function setPath(ref, path, value) { | ||
var list = pathToNodes(path), i; | ||
for (i = 0; i < list.length - 1; i++) { | ||
if (typeof ref[list[i]] === 'object') ref = ref[list[i]]; | ||
else if (!(list[i] in ref)) ref = (ref[list[i]] = {}); | ||
else throw new Error('the path is unavailable'); | ||
} | ||
ref[list[i]] = value; | ||
function processArraySubgraph(graph, parentObserved) { | ||
graph.forEach(function (element, index) { | ||
if (element && typeof element === 'object' && !isNonObservable(element)) { | ||
graph[index] = new Observed(element, index, parentObserved).proxy; | ||
} | ||
}); | ||
} | ||
function getPath(ref, path) { | ||
var list, i; | ||
if (!ref) return; | ||
list = pathToNodes(path) | ||
for (i = 0; i < list.length; i++) { | ||
ref = ref[list[i]]; | ||
if (!ref) return; | ||
} | ||
return ref; | ||
function processObjectSubgraph(graph, parentObserved) { | ||
Reflect.ownKeys(graph).forEach(function (key) { | ||
if (graph[key] && typeof graph[key] === 'object' && !isNonObservable(graph[key])) { | ||
graph[key] = new Observed(graph[key], key, parentObserved).proxy; | ||
} | ||
}); | ||
} | ||
function cutPath(ref, path) { | ||
var list = pathToNodes(path), i = 0, value; | ||
for (; i < list.length - 1; i++) { | ||
if (list[i] in ref) ref = ref[list[i]]; | ||
else return; | ||
} | ||
value = ref[list[i - 1]]; | ||
delete ref[list[i - 1]]; | ||
return value; | ||
} | ||
function Observed(origin, ownKey, parent) { | ||
var targetClone, revokableProxy; | ||
function isPathStartsWith(p1, p2) { | ||
var i, l; | ||
p1 = pathToNodes(p1); | ||
p2 = pathToNodes(p2); | ||
l = Math.min(p1.length, p2.length); | ||
for (i = 0; i < l; i++) { | ||
if (p1[i] !== p2[i]) return false; | ||
if (!origin || typeof origin !== 'object') { | ||
throw new Error('Observed MUST be created from a non null object origin'); | ||
} | ||
return true; | ||
} | ||
if (parent && (typeof ownKey === 'undefined' || ownKey === null)) { | ||
throw new Error('any non-root (parent-less) Observed MUST have an own path; now parent is ' + parent + '; key is ' + ownKey); | ||
} | ||
if (parent && !(parent instanceof Observed)) { | ||
throw new Error('parent, when supplied, MUST be an instance of Observed'); | ||
} | ||
function changeListener(ev) { | ||
var view = ev.target, p, tn, t; | ||
targetClone = copyShallow(origin); | ||
if (view.dataset.tieValue) { | ||
p = view.dataset.tieValue; | ||
if (Array.isArray(targetClone)) { | ||
processArraySubgraph(targetClone, this); | ||
revokableProxy = Proxy.revocable(targetClone, { | ||
set: proxiedSet, | ||
get: proxiedArrayGet, | ||
deleteProperty: proxiedDelete | ||
}); | ||
} else { | ||
p = view.dataset.tie; | ||
processObjectSubgraph(targetClone, this); | ||
revokableProxy = Proxy.revocable(targetClone, { | ||
set: proxiedSet, | ||
deleteProperty: proxiedDelete | ||
}); | ||
} | ||
// TODO: the following condition is not always error state, need to decide regarding the cardinality of the value suppliers | ||
if (!p) { logger.error('path to data not available'); return; } | ||
p = pathToNodes(p); | ||
if (!p) { logger.error('path to data is invalid'); return; } | ||
tn = p.shift(); | ||
t = tiesService.obtain(tn); | ||
if (!t) { logger.error('tie "' + tn + '" not found'); return; } | ||
t.viewToDataProcessor({ data: t.data, path: p, view: view }); | ||
targetsToObserved.set(targetClone, this); | ||
proxiesToTargetsMap.set(revokableProxy.proxy, targetClone); | ||
Reflect.defineProperty(this, 'revokable', { value: revokableProxy }); | ||
Reflect.defineProperty(this, 'proxy', { value: revokableProxy.proxy }); | ||
Reflect.defineProperty(this, 'parent', { value: parent }); | ||
Reflect.defineProperty(this, 'ownKey', { value: ownKey, writable: true }); | ||
} | ||
function addChangeListener(v) { | ||
var ow = v.ownerDocument.defaultView; | ||
if (v instanceof ow.HTMLInputElement || v instanceof ow.HTMLSelectElement) { | ||
v.addEventListener('change', changeListener); | ||
Reflect.defineProperty(Observed.prototype, 'root', { | ||
get: function () { | ||
var result = this; | ||
while (result.parent) { | ||
result = result.parent; | ||
} | ||
return result; | ||
} | ||
} | ||
}); | ||
Reflect.defineProperty(Observed.prototype, 'path', { | ||
get: function () { | ||
var result = [], pointer = this; | ||
while (typeof pointer.ownKey !== 'undefined') { | ||
result.push(pointer.ownKey); | ||
pointer = pointer.parent; | ||
} | ||
return result.reverse(); | ||
} | ||
}); | ||
Reflect.defineProperty(Observed.prototype, 'revoke', { | ||
value: function () { | ||
var proxy = this.proxy; | ||
Reflect.ownKeys(proxy).forEach(function (key) { | ||
var child = proxy[key]; | ||
if (child && typeof child === 'object') { | ||
targetsToObserved.get(proxiesToTargetsMap.get(child)).revoke(); | ||
proxiesToTargetsMap.get(proxy)[key] = proxiesToTargetsMap.get(child); | ||
} | ||
}); | ||
this.revokable.revoke(); | ||
// TODO: ensure if there are any other cleanups to do here (probably remove observed?) | ||
} | ||
}) | ||
function delChangeListener(v) { | ||
v.removeEventListener('change', changeListener); | ||
} | ||
function Observable(observed) { | ||
var isRevoked = false, callbacks = []; | ||
rulesService = new (function RulesService() { | ||
var rules = {}; | ||
function observe(callback) { | ||
if (isRevoked) { throw new TypeError('revoked Observable MAY NOT be observed anymore'); } | ||
if (typeof callback !== 'function') { throw new Error('observer (callback) parameter MUST be a function'); } | ||
function dfltResolvePath(tieValue) { return pathToNodes(tieValue); } | ||
if (callbacks.indexOf(callback) < 0) { | ||
callbacks.push(callback); | ||
} else { | ||
console.info('observer (callback) may be bound to an observable only once'); | ||
} | ||
} | ||
function Rule(id, setup) { | ||
var vpr, dtv, itd; | ||
if (typeof setup === 'string') { | ||
vpr = dfltResolvePath; | ||
dtv = function (e, s) { | ||
var d; | ||
if (s) { | ||
d = s.data; | ||
d = typeof d === 'undefined' || d === null ? '' : d; | ||
setPath(e, setup, d); | ||
function unobserve() { | ||
if (isRevoked) { throw new TypeError('revoked Observable MAY NOT be unobserved amymore'); } | ||
if (arguments.length) { | ||
Array.from(arguments).forEach(function (argument) { | ||
var i = callbacks.indexOf(argument); | ||
if (i >= 0) { | ||
callbacks.splice(i, 1); | ||
} | ||
}; | ||
itd = function () { throw new Error('not yet implemented'); }; | ||
} else if (typeof setup === 'function') { | ||
vpr = dfltResolvePath; | ||
dtv = setup; | ||
itd = function () { throw new Error('no "inputToData" functionality defined in this rule'); } | ||
} else if (typeof setup === 'object') { | ||
vpr = setup.resolvePath || dfltResolvePath; | ||
dtv = setup.dataToView; | ||
itd = setup.inputToData; | ||
}); | ||
} else { | ||
callbacks.splice(0, callbacks.length); | ||
} | ||
Object.defineProperties(this, { | ||
id: { value: id }, | ||
resolvePath: { value: vpr }, | ||
dataToView: { value: dtv, writable: true }, | ||
inputToData: { value: itd } | ||
}); | ||
} | ||
Object.defineProperties(this, { | ||
add: { | ||
value: function (id, setup) { | ||
if (!id || !setup) throw new Error('bad parameters; f(string, string|function) expected'); | ||
if (id.indexOf('tie') !== 0) throw new Error('rule id MUST begin with "tie"'); | ||
if (id in rules) throw new Error('rule with id "' + id + '" already exists'); | ||
rules[id] = new Rule(id, setup); | ||
viewsService.relocateByRule(rules[id]); | ||
return rules[id]; | ||
} | ||
}, | ||
get: { | ||
value: function (id, e) { | ||
var r, p; | ||
if (id.indexOf('tie') !== 0) { | ||
logger.error('invalid tie id supplied'); | ||
} else if (id in rules) { | ||
r = rules[id]; | ||
} else { | ||
if (id === 'tie') { | ||
p = e.ownerDocument.defaultView; | ||
if (!e || !e.nodeName) throw new Error('rule "' + id + '" not found, therefore valid DOM element MUST be supplied to grasp the default rule'); | ||
if (e instanceof p.HTMLInputElement || | ||
e instanceof p.HTMLSelectElement) return rules['tieValue']; | ||
else if (e instanceof p.HTMLImageElement) return rules['tieImage']; | ||
else return rules['tieText']; | ||
} | ||
} | ||
return r; | ||
} | ||
}, | ||
del: { | ||
value: function (id) { | ||
return delete rules[id]; | ||
} | ||
function revoke() { | ||
if (!isRevoked) { | ||
isRevoked = true; | ||
observed.revoke(); | ||
} else { | ||
console.log('revokation of Observable have an effect only once'); | ||
} | ||
}); | ||
} | ||
Object.seal(this); | ||
})(); | ||
Reflect.defineProperty(observed.proxy, 'observe', { value: observe }); | ||
Reflect.defineProperty(observed.proxy, 'unobserve', { value: unobserve }); | ||
Reflect.defineProperty(observed.proxy, 'revoke', { value: revoke }); | ||
if (typeof DataObserver !== 'function') { | ||
throw new Error('no DataObserver implementation available'); | ||
} else { | ||
dataObserver = new DataObserver(); | ||
dataRoot = dataObserver.getObserved({}, function (changes) { | ||
if (changes) { | ||
changes.forEach(function (change) { | ||
var vs = viewsService.get(change.path), i, l, key, p; | ||
for (i = 0, l = vs.length; i < l; i++) { | ||
for (key in vs[i].dataset) { | ||
if (key.indexOf('tie') === 0) { | ||
p = rulesService.get(key, vs[i]).resolvePath(vs[i].dataset[key]); | ||
if (isPathStartsWith(change.path, p)) { | ||
// TODO: use the knowledge of old value and new value here | ||
// TODO: yet, myst pass via the formatters/vizualizers of Rule/Tie | ||
viewsService.update(vs[i], key); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
Reflect.defineProperty(this, 'callbacks', { value: callbacks }); | ||
} | ||
function InsertChange(path, value) { | ||
Reflect.defineProperty(this, 'type', { value: 'insert' }); | ||
Reflect.defineProperty(this, 'path', { value: path }); | ||
Reflect.defineProperty(this, 'value', { value: value }); | ||
} | ||
function UpdateChange(path, value, oldValue) { | ||
Reflect.defineProperty(this, 'type', { value: 'update' }); | ||
Reflect.defineProperty(this, 'path', { value: path }); | ||
Reflect.defineProperty(this, 'value', { value: value }); | ||
Reflect.defineProperty(this, 'oldValue', { value: oldValue }); | ||
} | ||
function DeleteChange(path, oldValue) { | ||
Reflect.defineProperty(this, 'type', { value: 'delete' }); | ||
Reflect.defineProperty(this, 'path', { value: path }); | ||
Reflect.defineProperty(this, 'oldValue', { value: oldValue }); | ||
} | ||
function ReverseChange() { | ||
Reflect.defineProperty(this, 'type', { value: 'reverse' }); | ||
} | ||
function ShuffleChange() { | ||
Reflect.defineProperty(this, 'type', { value: 'shuffle' }); | ||
} | ||
function publishChanges(callbacks, changes) { | ||
for (var i = 0; i < callbacks.length; i++) { | ||
try { | ||
callbacks[i](changes); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}); | ||
logger.info('DataObserver details: ', dataObserver.details); | ||
} | ||
} | ||
tiesService = new (function TiesManager() { | ||
var ts = {}; | ||
Reflect.defineProperty(Observable, 'from', { | ||
value: function (target) { | ||
if (!target || typeof target !== 'object') { | ||
throw new Error('observable MAY ONLY be created from non-null object only'); | ||
} else if ('observe' in target || 'unobserve' in target || 'revoke' in target) { | ||
throw new Error('target object MUST NOT have nor own neither inherited properties from the following list: "observe", "unobserve", "revoke"'); | ||
} else if (isNonObservable(target)) { | ||
throw new Error(target + ' found to be one of non-observable object types: ' + nonObservables); | ||
} | ||
var observed = new Observed(target), | ||
observable = new Observable(observed); | ||
observedToObservable.set(observed, observable); | ||
return observed.proxy; | ||
} | ||
}); | ||
function dfltVTDProcessor(input) { | ||
setPath(input.data, input.path, input.view.value); | ||
Reflect.defineProperty(scope, 'Observable', { value: Observable }); | ||
})(this); | ||
(function (scope) { | ||
'use strict'; | ||
var ties = {}; | ||
function Tie(name, observable, options) { | ||
var data; | ||
function observer(changes) { | ||
scope.DataTier.views.processChanges(name, changes); | ||
} | ||
function Tie(namespace) { | ||
var vtdProc; | ||
if (options && typeof options === 'object') { | ||
// TODO: process options | ||
} | ||
Object.defineProperties(this, { | ||
namespace: { get: function () { return namespace; } }, | ||
setModel: { | ||
value: function (model) { | ||
if (typeof model !== 'object') { | ||
throw new TypeError('model MUST be an object'); | ||
} | ||
dataRoot[namespace] = model; | ||
return dataRoot[namespace]; | ||
Reflect.defineProperty(this, 'name', { value: name }); | ||
Reflect.defineProperty(this, 'data', { | ||
get: function () { return data; }, | ||
set: function (observable) { | ||
if (observable) { | ||
validateObservable(observable); | ||
if (data) { | ||
data.revoke(); | ||
} | ||
}, | ||
getObservedModel: { value: function () { return dataRoot[namespace]; } }, | ||
viewToDataProcessor: { | ||
get: function () { return typeof vtdProc === 'function' ? vtdProc : dfltVTDProcessor; }, | ||
set: function (v) { if (typeof v === 'function') vtdProc = v; } | ||
} | ||
}); | ||
} | ||
function obtain(namespace) { | ||
if (!namespace || typeof namespace !== 'string') throw new Error('namespace (first param) MUST be a non empty string'); | ||
if (/\W/.test(namespace)) throw new Error('namespace (first param) MUST consist of alphanumeric non uppercase characters only'); | ||
if (!ts[namespace]) { | ||
ts[namespace] = new Tie(namespace); | ||
var oldData = data; | ||
data = observable; | ||
if (data) { | ||
data.observe(observer); | ||
} | ||
scope.DataTier.views.processChanges(name, [{ type: 'update', value: data, oldValue: oldData, path: [] }]); | ||
} | ||
}); | ||
return ts[namespace]; | ||
this.data = observable; | ||
} | ||
function create(name, observable, options) { | ||
validateTieName(name); | ||
validateObservable(observable); | ||
if (ties[name]) { | ||
throw new Error('existing tie (' + name + ') MAY NOT be re-created, use the tie\'s own APIs to reconfigure it'); | ||
} | ||
function remove(namespace) { | ||
if (ts[namespace]) { | ||
delete ts[namespace]; | ||
} | ||
return (ties[name] = new Tie(name, observable, options)); | ||
} | ||
function remove(name) { | ||
if (name && ties[name]) { | ||
ties[name].observable.revoke(); | ||
delete ties[name]; | ||
} | ||
} | ||
Object.defineProperties(this, { | ||
obtain: { value: obtain }, | ||
remove: { value: remove } | ||
}); | ||
function validateTieName(name) { | ||
if (!name || typeof name !== 'string') { | ||
throw new Error('tie name MUST be a non-empty string'); | ||
} | ||
if (/\W/.test(name)) { | ||
throw new Error('tie name MUST consist of alphanumerics or underlines ([a-zA-Z0-9_]) ONLY'); | ||
} | ||
} | ||
Object.seal(this); | ||
})(); | ||
function validateObservable(observable) { | ||
if (!observable || | ||
typeof observable !== 'object' || | ||
typeof observable.observe !== 'function' || | ||
typeof observable.unobserve !== 'function' || | ||
typeof observable.revoke !== 'function') { | ||
throw new Error(observable + ' is not a valid Observable'); | ||
} | ||
} | ||
viewsService = new (function ViewsService() { | ||
var vpn = '___vs___', vs = {}, nlvs = {}, vcnt = 0; | ||
Reflect.defineProperty(scope, 'DataTier', { value: {} }); | ||
Reflect.defineProperty(scope.DataTier, 'ties', { value: {} }); | ||
Reflect.defineProperty(scope.DataTier.ties, 'get', { value: function (name) { return ties[name]; } }); | ||
Reflect.defineProperty(scope.DataTier.ties, 'create', { value: create }); | ||
Reflect.defineProperty(scope.DataTier.ties, 'remove', { value: remove }); | ||
function add(view) { | ||
var key, path, va, rule; | ||
if (view.nodeName === 'IFRAME') { | ||
initDomObserver(view.contentDocument); | ||
view.addEventListener('load', function () { | ||
initDomObserver(this.contentDocument); | ||
collect(this.contentDocument); | ||
}); | ||
collect(view.contentDocument); | ||
})(this); | ||
(function (scope) { | ||
'use strict'; | ||
var rules = {}; | ||
function Rule(name, options) { | ||
Reflect.defineProperty(this, 'name', { value: name }); | ||
Reflect.defineProperty(this, 'dataToView', { value: options.dataToView }); | ||
if (typeof options.inputToData === 'function') { Reflect.defineProperty(this, 'inputToData', { value: options.inputToData }); } | ||
if (typeof options.parseParam === 'function') { Reflect.defineProperty(this, 'parseParam', { value: options.parseParam }); } | ||
} | ||
Rule.prototype.parseParam = function (ruleParam) { | ||
var dataPath, tieName; | ||
if (ruleParam) { | ||
dataPath = ruleParam.split('.'); | ||
tieName = dataPath[0].split(':')[0]; | ||
if (dataPath[0] === tieName) { | ||
dataPath = []; | ||
} else { | ||
for (key in view.dataset) { | ||
if (key.indexOf('tie') === 0) { | ||
rule = rulesService.get(key, view); | ||
if (rule) { | ||
path = rule.resolvePath(view.dataset[key]); | ||
path.push(vpn); | ||
va = getPath(vs, path); | ||
if (!va) setPath(vs, path, (va = [])); | ||
if (va.indexOf(view) < 0) { | ||
va.push(view); | ||
path.pop(); | ||
update(view, key); | ||
addChangeListener(view); | ||
vcnt++; | ||
} | ||
} else { | ||
if (!nlvs[key]) nlvs[key] = []; | ||
nlvs[key].push(view); | ||
} | ||
} | ||
} | ||
dataPath[0] = dataPath[0].replace(tieName + ':', ''); | ||
} | ||
return { | ||
tieName: tieName, | ||
dataPath: dataPath | ||
}; | ||
} else { | ||
console.error('valid rule value MUST be a non-empty string, found: ' + ruleParam); | ||
} | ||
}; | ||
function addRule(name, configuration) { | ||
if (typeof name !== 'string' || !name) { | ||
throw new Error('name MUST be a non-empty string'); | ||
} | ||
if (rules[name]) { | ||
throw new Error('rule "' + name + '" already exists; you may want to reconfigure the existing rule'); | ||
} | ||
if (typeof configuration !== 'object' || !configuration) { | ||
throw new Error('configuration MUST be a non-null object'); | ||
} | ||
if (typeof configuration.dataToView !== 'function') { | ||
throw new Error('configuration MUST have a "dataToView" function defined'); | ||
} | ||
function get(path) { | ||
var p = pathToNodes(path), r = [], tmp, key; | ||
tmp = getPath(vs, p); | ||
tmp && Object.keys(tmp).forEach(function (key) { | ||
if (key === vpn) Array.prototype.push.apply(r, tmp[key]); | ||
else Array.prototype.push.apply(r, get(path + '.' + key)); | ||
rules[name] = new Rule(name, configuration); | ||
scope.DataTier.views.applyRule(rules[name]); | ||
} | ||
function getRule(name) { | ||
return rules[name]; | ||
} | ||
function removeRule(name) { | ||
if (typeof name !== 'string' || !name) { | ||
throw new Error('rule name MUST be a non-empty string'); | ||
} | ||
return delete rules[name]; | ||
} | ||
function getApplicable(element) { | ||
var result = []; | ||
if (element && element.nodeType === Node.ELEMENT_NODE) { | ||
Reflect.ownKeys(element.dataset).forEach(function (key) { | ||
if (rules[key]) { | ||
result.push(rules[key]); | ||
} | ||
}); | ||
return r; | ||
} | ||
return result; | ||
} | ||
function dispatchViewUpdateEvent(view, detail) { | ||
var e = new view.ownerDocument.defaultView.CustomEvent(VIEW_UPDATE_EVENT, { detail: detail }); | ||
view.dispatchEvent(e); | ||
Reflect.defineProperty(scope.DataTier, 'rules', { value: {} }); | ||
Reflect.defineProperty(scope.DataTier.rules, 'get', { value: getRule }); | ||
Reflect.defineProperty(scope.DataTier.rules, 'add', { value: addRule }); | ||
Reflect.defineProperty(scope.DataTier.rules, 'remove', { value: removeRule }); | ||
Reflect.defineProperty(scope.DataTier.rules, 'getApplicable', { value: getApplicable }); | ||
})(this); | ||
(function (scope) { | ||
'use strict'; | ||
var views = {}, | ||
nlvs = {}; | ||
function getPath(ref, path) { | ||
var i; | ||
if (!ref) return; | ||
for (i = 0; i < path.length; i++) { | ||
ref = ref[path[i]]; | ||
if (!ref) return; | ||
} | ||
return ref; | ||
} | ||
function update(view, ruleId) { | ||
var r, p, t, data; | ||
r = rulesService.get(ruleId, view); | ||
p = r.resolvePath(view.dataset[ruleId]); | ||
t = tiesService.obtain(p.shift()); | ||
if (t && r) { | ||
data = getPath(t.getObservedModel(), p); | ||
r.dataToView(view, { data: data }); | ||
dispatchViewUpdateEvent(view, { ruleId: ruleId }); | ||
function changeListener(event) { | ||
scope.DataTier.rules.getApplicable(event.target).forEach(function (rule) { | ||
if (rule.name === 'tieValue') { | ||
var ruleParam = rule.parseParam(event.target.dataset[rule.name]), | ||
tie = scope.DataTier.ties.get(ruleParam.tieName); | ||
if (!ruleParam.dataPath) { console.error('path to data not available'); return; } | ||
if (!tie) { console.error('tie "' + ruleParam.tieName + '" not found'); return; } | ||
tie.viewToDataProcessor({ data: tie.data, path: ruleParam.dataPath, view: event.target }); | ||
} | ||
}); | ||
} | ||
function addChangeListener(view) { | ||
if (view.nodeName === 'INPUT' || view.nodeName === 'SELECT') { | ||
view.addEventListener('change', changeListener); | ||
} | ||
} | ||
function collect(rootElement) { | ||
var l; | ||
if (rootElement && | ||
rootElement.nodeType && | ||
(rootElement.nodeType === Element.DOCUMENT_NODE || rootElement.nodeType === Element.ELEMENT_NODE)) { | ||
l = rootElement.nodeName === 'IFRAME' ? | ||
l = Array.prototype.slice.call(rootElement.contentDocument.getElementsByTagName('*'), 0) : | ||
l = Array.prototype.slice.call(rootElement.getElementsByTagName('*'), 0); | ||
l.push(rootElement); | ||
l.forEach(add); | ||
logger.info('collected views, current total: ' + vcnt); | ||
function delChangeListener(v) { | ||
v.removeEventListener('change', changeListener); | ||
} | ||
function add(view) { | ||
if (view.nodeName === 'IFRAME') { | ||
initDocumentObserver(view.contentDocument); | ||
view.addEventListener('load', function () { | ||
initDocumentObserver(this.contentDocument); | ||
collect(this.contentDocument); | ||
}); | ||
collect(view.contentDocument); | ||
} else { | ||
scope.DataTier.rules.getApplicable(view).forEach(function (rule) { | ||
var ruleParam = rule.parseParam(view.dataset[rule.name]), | ||
pathString = ruleParam.dataPath.join('.'), | ||
tieViews, | ||
ruleViews, | ||
pathViews; | ||
// get tie views partition | ||
if (!views[ruleParam.tieName]) { | ||
views[ruleParam.tieName] = {}; | ||
} | ||
tieViews = views[ruleParam.tieName]; | ||
// get rule views partition (in tie) | ||
if (!tieViews[rule.name]) { | ||
tieViews[rule.name] = {}; | ||
} | ||
ruleViews = tieViews[rule.name]; | ||
// get path views in this context | ||
if (!ruleViews[pathString]) { | ||
ruleViews[pathString] = []; | ||
} | ||
pathViews = ruleViews[pathString]; | ||
if (pathViews.indexOf(view) < 0) { | ||
pathViews.push(view); | ||
update(view, rule.name); | ||
addChangeListener(view); | ||
} | ||
}); | ||
// collect potentially future rules element and put them into some tracking storage | ||
for (var key in view.dataset) { | ||
if (key.indexOf('tie') === 0 && !scope.DataTier.rules.get(key)) { | ||
console.warn('non-registerd rule "' + key + '" used, it may still be defined later in code and post-tied'); | ||
if (!nlvs[key]) nlvs[key] = []; | ||
nlvs[key].push(view); | ||
} | ||
} | ||
} | ||
} | ||
function relocateByRule(rule) { | ||
if (nlvs[rule.id]) { | ||
nlvs[rule.id].forEach(add); | ||
} | ||
logger.info('relocated views, current total: ' + vcnt); | ||
function update(view, ruleName) { | ||
var r, p, t, data; | ||
r = scope.DataTier.rules.get(ruleName); | ||
p = r.parseParam(view.dataset[ruleName]); | ||
t = scope.DataTier.ties.get(p.tieName); | ||
if (t) { | ||
data = getPath(t.data, p.dataPath); | ||
r.dataToView(data, view); | ||
} | ||
} | ||
function discard(rootElement) { | ||
var l, e, key, rule, path, va, i; | ||
if (!rootElement || !rootElement.getElementsByTagName) return; | ||
l = Array.prototype.slice.call(rootElement.getElementsByTagName('*'), 0); | ||
function collect(rootElement) { | ||
var l; | ||
if (rootElement && | ||
rootElement.nodeType && | ||
(rootElement.nodeType === Node.DOCUMENT_NODE || rootElement.nodeType === Node.ELEMENT_NODE)) { | ||
l = rootElement.nodeName === 'IFRAME' ? | ||
l = Array.from(rootElement.contentDocument.getElementsByTagName('*')) : | ||
l = Array.from(rootElement.getElementsByTagName('*')); | ||
l.push(rootElement); | ||
l.forEach(function (e) { | ||
for (key in e.dataset) { | ||
i = -1; | ||
if (key.indexOf('tie') === 0) { | ||
rule = rulesService.get(key, e); | ||
path = rule.resolvePath(e.dataset[key]); | ||
path.push(vpn); | ||
va = getPath(vs, path); | ||
i = va && va.indexOf(e); | ||
if (i >= 0) { | ||
va.splice(i, 1); | ||
delChangeListener(e); | ||
vcnt--; | ||
} | ||
} | ||
}; | ||
}); | ||
logger.info('discarded views, current total: ' + vcnt); | ||
l.forEach(add); | ||
} | ||
} | ||
function move(view, ruleId, oldPath, newPath) { | ||
var pathViews, i = -1, opn, npn; | ||
function discard(rootElement) { | ||
var l, param, pathViews, i; | ||
if (!rootElement || !rootElement.getElementsByTagName) return; | ||
l = Array.from(rootElement.getElementsByTagName('*')); | ||
l.push(rootElement); | ||
l.forEach(function (e) { | ||
scope.DataTier.rules.getApplicable(e).forEach(function (rule) { | ||
param = rule.parseParam(e.dataset[rule.name]); | ||
pathViews = views[param.tieName][rule.name][param.dataPath.join('.')]; | ||
i = pathViews.indexOf(e); | ||
if (i >= 0) { | ||
pathViews.splice(i, 1); | ||
delChangeListener(e); | ||
} | ||
}); | ||
}); | ||
} | ||
// delete old path | ||
if (oldPath) { | ||
opn = pathToNodes(oldPath); | ||
opn.push(vpn); | ||
pathViews = getPath(vs, opn); | ||
if (pathViews) i = pathViews.indexOf(view); | ||
if (i >= 0) pathViews.splice(i, 1); | ||
function move(view, ruleName, oldParam, newParam) { | ||
var ruleParam, pathViews, i = -1; | ||
ruleParam = scope.DataTier.rules.get(ruleName).parseParam(oldParam); | ||
// delete old path | ||
if (views[ruleParam.tieName] && views[ruleParam.tieName][ruleName]) { | ||
pathViews = views[ruleParam.tieName][ruleName][ruleParam.dataPath]; | ||
if (pathViews) i = pathViews.indexOf(view); | ||
if (i >= 0) { | ||
pathViews.splice(i, 1); | ||
} | ||
} | ||
// add new path | ||
npn = pathToNodes(newPath); | ||
npn.push(vpn); | ||
if (!getPath(vs, npn)) setPath(vs, npn, []); | ||
getPath(vs, npn).push(view); | ||
npn.pop(); | ||
update(view, ruleId) | ||
// add new path | ||
ruleParam = scope.DataTier.rules.get(ruleName).parseParam(newParam); | ||
if (!views[ruleParam.tieName]) views[ruleParam.tieName] = {}; | ||
if (!views[ruleParam.tieName][ruleName]) views[ruleParam.tieName][ruleName] = {}; | ||
if (!views[ruleParam.tieName][ruleName][ruleParam.dataPath]) views[ruleParam.tieName][ruleName][ruleParam.dataPath] = []; | ||
views[ruleParam.tieName][ruleName][ruleParam.dataPath].push(view); | ||
update(view, ruleName); | ||
} | ||
function processChanges(tieName, changes) { | ||
var tieViews = views[tieName], ruleViews, pathString; | ||
if (tieViews) { | ||
changes.forEach(function (change) { | ||
pathString = change.path.join('.'); | ||
Object.keys(tieViews).forEach(function (ruleName) { | ||
ruleViews = tieViews[ruleName]; | ||
if (ruleViews) { | ||
Object.keys(ruleViews).forEach(function (path) { | ||
if (path.indexOf(pathString) === 0 || path === '') { | ||
ruleViews[path].forEach(function (view) { | ||
update(view, ruleName); | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
} else { | ||
console.debug('views of tie "' + tieName + '" are not defined'); | ||
} | ||
} | ||
Object.defineProperties(this, { | ||
collect: { value: collect }, | ||
update: { value: update }, | ||
relocateByRule: { value: relocateByRule }, | ||
discard: { value: discard }, | ||
move: { value: move }, | ||
get: { value: get } | ||
}); | ||
function applyRule(rule) { | ||
// apply on a pending views | ||
if (nlvs[rule.name]) { | ||
nlvs[rule.name].forEach(function (view) { | ||
add(view); | ||
}); | ||
delete nlvs[rule.name]; | ||
} | ||
} | ||
Object.seal(this); | ||
})(); | ||
function dataAttrToProp(v) { | ||
var i = 2, l = v.split('-'), r; | ||
r = l[1]; | ||
while (i < l.length) r += l[i][0].toUpperCase() + l[i++].substr(1); | ||
return r; | ||
} | ||
// TODO: push the below logic into domObserversService | ||
var domObservers = []; | ||
function initDomObserver(d) { | ||
function initDocumentObserver(document) { | ||
function processDomChanges(changes) { | ||
changes.forEach(function (change) { | ||
var tp = change.type, tr = change.target, an = change.attributeName, i, l; | ||
if (tp === 'attributes' && an.indexOf('data-tie') == 0) { | ||
viewsService.move(tr, dataAttrToProp(an), change.oldValue, tr.getAttribute(an)); | ||
} else if (tp === 'attributes' && an === 'src' && tr.nodeName === 'IFRAME') { | ||
viewsService.discard(tr.contentDocument); | ||
} else if (tp === 'childList') { | ||
if (change.addedNodes.length) { | ||
for (i = 0, l = change.addedNodes.length; i < l; i++) { | ||
if (change.addedNodes[i].nodeName === 'IFRAME') { | ||
if (change.addedNodes[i].contentDocument) { | ||
initDomObserver(change.addedNodes[i].contentDocument); | ||
viewsService.collect(change.addedNodes[i].contentDocument); | ||
} | ||
change.addedNodes[i].addEventListener('load', function () { | ||
initDomObserver(this.contentDocument); | ||
viewsService.collect(this.contentDocument); | ||
}); | ||
} else { | ||
viewsService.collect(change.addedNodes[i]); | ||
var tr = change.target, an = change.attributeName; | ||
if (change.type === 'attributes' && an.indexOf('data-tie') === 0) { | ||
move(tr, dataAttrToProp(an), change.oldValue, tr.getAttribute(an)); | ||
} else if (change.type === 'attributes' && an === 'src' && tr.nodeName === 'IFRAME') { | ||
discard(tr.contentDocument); | ||
} else if (change.type === 'childList') { | ||
// process added nodes | ||
Array.from(change.addedNodes).forEach(function (addedNode) { | ||
if (addedNode.nodeName === 'IFRAME') { | ||
if (addedNode.contentDocument) { | ||
initDocumentObserver(addedNode.contentDocument); | ||
collect(addedNode.contentDocument); | ||
} | ||
addedNode.addEventListener('load', function () { | ||
initDocumentObserver(this.contentDocument); | ||
collect(this.contentDocument); | ||
}); | ||
} else { | ||
collect(addedNode); | ||
} | ||
} | ||
if (change.removedNodes.length) { | ||
for (i = 0, l = change.removedNodes.length; i < l; i++) { | ||
if (change.removedNodes[i].nodeName === 'IFRAME') { | ||
viewsService.discard(change.removedNodes[i].contentDocument); | ||
} else { | ||
viewsService.discard(change.removedNodes[i]); | ||
} | ||
}); | ||
// process removed nodes | ||
Array.from(change.removedNodes).forEach(function (removedNode) { | ||
if (removedNode.nodeName === 'IFRAME') { | ||
discard(removedNode.contentDocument); | ||
} else { | ||
discard(removedNode); | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
}; | ||
} | ||
var domObserver = new MutationObserver(processDomChanges); | ||
domObserver.observe(d, { | ||
domObserver.observe(document, { | ||
childList: true, | ||
@@ -551,38 +869,68 @@ subtree: true, | ||
}); | ||
domObservers.push(domObserver); | ||
}; | ||
initDomObserver(document); | ||
} | ||
rulesService.add('tieValue', 'value'); | ||
Reflect.defineProperty(scope.DataTier, 'views', { value: {} }); | ||
Reflect.defineProperty(scope.DataTier.views, 'processChanges', { value: processChanges }); | ||
Reflect.defineProperty(scope.DataTier.views, 'applyRule', { value: applyRule }); | ||
rulesService.add('tieText', 'textContent'); | ||
initDocumentObserver(document); | ||
collect(document); | ||
rulesService.add('tiePlaceholder', 'placeholder'); | ||
})(this); | ||
(function (scope) { | ||
'use strict'; | ||
rulesService.add('tieTooltip', 'title'); | ||
var add = scope.DataTier.rules.add; | ||
rulesService.add('tieImage', 'src'); | ||
add('tieValue', { | ||
dataToView: function (data, view) { | ||
view.value = data ? data : ''; | ||
} | ||
}); | ||
rulesService.add('tieDateValue', { | ||
dataToView: function (view, tieValue) { | ||
view.value = tieValue.data.toLocaleString(); | ||
add('tieText', { | ||
dataToView: function (data, view) { | ||
view.textContent = data ? data.toString() : ''; | ||
} | ||
}); | ||
rulesService.add('tieDateText', { | ||
dataToView: function (view, tieValue) { | ||
view.textContent = tieValue.data.toLocaleString(); | ||
add('tiePlaceholder', { | ||
dataToView: function (data, view) { | ||
view.placeholder = data ? data : ''; | ||
} | ||
}); | ||
rulesService.add('tieList', { | ||
resolvePath: function (tieValue) { | ||
var ruleData = tieValue.split(' '); | ||
return pathToNodes(ruleData[0]); | ||
add('tieTooltip', { | ||
dataToView: function (data, view) { | ||
view.title = data ? data : ''; | ||
} | ||
}); | ||
add('tieImage', { | ||
dataToView: function (data, view) { | ||
view.src = data ? data : ''; | ||
} | ||
}); | ||
add('tieDateValue', { | ||
dataToView: function (data, view) { | ||
view.value = data ? data.toLocaleString() : ''; | ||
} | ||
}); | ||
add('tieDateText', { | ||
dataToView: function (data, view) { | ||
view.textContent = data ? data.toLocaleString() : ''; | ||
} | ||
}); | ||
add('tieList', { | ||
parseParam: function (ruleValue) { | ||
return this.constructor.prototype.parseParam(ruleValue.split(/\s*=>\s*/)[0]); | ||
}, | ||
dataToView: function (template, tiedValue) { | ||
var container = template.parentNode, i, nv, ruleData, itemId, rulePath, vs, d, df; | ||
dataToView: function (tiedValue, template) { | ||
var container = template.parentNode, i, nv, ruleData, itemId, vs, d, df; | ||
function shortenListTo(cnt, aid) { | ||
var a = Array.prototype.slice.call(container.querySelectorAll('[data-list-item-aid="' + aid + '"]'), 0); | ||
var a = Array.from(container.querySelectorAll('[data-list-item-aid="' + aid + '"]')); | ||
while (a.length > cnt) { | ||
@@ -601,20 +949,18 @@ container.removeChild(a.pop()); | ||
} | ||
i = shortenListTo(tiedValue.data ? tiedValue.data.length : 0, template.dataset.listSourceAid); | ||
if (tiedValue.data && i < tiedValue.data.length) { | ||
i = shortenListTo(tiedValue ? tiedValue.length : 0, template.dataset.listSourceAid); | ||
if (tiedValue && i < tiedValue.length) { | ||
ruleData = template.dataset.tieList.trim().split(/\s+/); | ||
if (!ruleData || ruleData.length !== 3 || ruleData[1] !== '=>') { | ||
logger.error('invalid parameter for TieList rule specified'); | ||
console.error('invalid parameter for "tieList" rule specified'); | ||
} else { | ||
rulePath = ruleData[0]; | ||
itemId = ruleData[2]; | ||
d = template.ownerDocument; | ||
df = d.createDocumentFragment(); | ||
for (; i < tiedValue.data.length; i++) { | ||
for (; i < tiedValue.length; i++) { | ||
nv = d.importNode(template.content, true); | ||
vs = Array.prototype.slice.call(nv.querySelectorAll('*'), 0); | ||
vs = nv.querySelectorAll('*'); | ||
vs.forEach(function (view) { | ||
Object.keys(view.dataset).forEach(function (key) { | ||
if (view.dataset[key].indexOf(itemId + '.') === 0) { | ||
view.dataset[key] = view.dataset[key].replace(itemId, rulePath + '[' + i + ']'); | ||
viewsService.update(view, key); | ||
if (view.dataset[key].indexOf(itemId) === 0) { | ||
view.dataset[key] = view.dataset[key].replace(itemId + ':', ruleData[0] + ':' + i + '.'); | ||
} | ||
@@ -632,41 +978,2 @@ }); | ||
viewsService.collect(document); | ||
function dispose() { | ||
domObservers.forEach(function (o) { o.disconnect(); }); | ||
viewsService.discard(document); | ||
tiesService = null; | ||
rulesService = null; | ||
viewsService = null; | ||
} | ||
if (typeof window[MODULES_NAMESPACE] !== 'object') { Object.defineProperty(window, MODULES_NAMESPACE, { value: {} }); } | ||
Object.defineProperty(window[MODULES_NAMESPACE], MODULE_NAME, { value: {} }); | ||
Object.defineProperties(window[MODULES_NAMESPACE][MODULE_NAME], { | ||
dispose: { value: dispose }, | ||
Ties: { value: tiesService }, | ||
Rules: { value: rulesService }, | ||
Utils: { | ||
value: { | ||
get logger() { return logger; }, | ||
get copyObject() { return copyObject; }, | ||
get setPath() { return setPath; }, | ||
get getPath() { return getPath; }, | ||
get cutPath() { return cutPath; } | ||
} | ||
}, | ||
About: { | ||
value: { | ||
get version() { return '0.5.3'; }, | ||
get author() { | ||
return { | ||
get name() { return 'Guller Yuri'; }, | ||
get email() { return 'gullerya@gmail.com'; } | ||
}; | ||
} | ||
} | ||
} | ||
}); | ||
})((typeof arguments === 'object' ? arguments[0] : undefined)); | ||
})(this); |
@@ -1,1 +0,1 @@ | ||
!function(a){"use strict";function b(a){var b,c=2,d=a.split("-");for(b=d[1];c<d.length;)b+=d[c][0].toUpperCase()+d[c++].substr(1);return b}function c(a){if(Array.isArray(a))return a;for(var b=0,c=!1,d="",e=[];b<a.length;){if("."===a[b])d.length&&e.push(d),d="";else if("["===a[b]){if(c)throw new Error('bad path: "'+a+'", at: '+b);d.length&&e.push(d),d="",c=!0}else if("]"===a[b]){if(!c)throw new Error('bad path: "'+a+'", at: '+b);d.length&&e.push(d),d="",c=!1}else d+=a[b];b++}return d.length&&e.push(d),e}function d(a){var b,c;if("object"!=typeof a)throw new Error("object parameter expected");if(null===a)return null;if(Array.isArray(a))for(b=[],c=0;c<a.length;c++)"object"==typeof a[c]?b[c]=d(a[c]):"function"!=typeof a[c]&&(b[c]=a[c]);else b={},Object.getOwnPropertyNames(a).forEach(function(c){"object"==typeof a[c]?b[c]=d(a[c]):"function"!=typeof a[c]&&(b[c]=a[c])});return b}function e(a,b,d){var e,f=c(b);for(e=0;e<f.length-1;e++)if("object"==typeof a[f[e]])a=a[f[e]];else{if(f[e]in a)throw new Error("the path is unavailable");a=a[f[e]]={}}a[f[e]]=d}function f(a,b){var d,e;if(a){for(d=c(b),e=0;e<d.length;e++)if(a=a[d[e]],!a)return;return a}}function g(a,b){for(var d,e=c(b),f=0;f<e.length-1;f++){if(!(e[f]in a))return;a=a[e[f]]}return d=a[e[f-1]],delete a[e[f-1]],d}function h(a,b){var d,e;for(a=c(a),b=c(b),e=Math.min(a.length,b.length),d=0;e>d;d++)if(a[d]!==b[d])return!1;return!0}function i(a){var b,d,e,f=a.target;return(b=f.dataset.tieValue?f.dataset.tieValue:f.dataset.tie)?(b=c(b))?(d=b.shift(),(e=v.obtain(d))?void e.viewToDataProcessor({data:e.data,path:b,view:f}):void y.error('tie "'+d+'" not found')):void y.error("path to data is invalid"):void y.error("path to data not available")}function j(a){var b=a.ownerDocument.defaultView;(a instanceof b.HTMLInputElement||a instanceof b.HTMLSelectElement)&&a.addEventListener("change",i)}function k(a){a.removeEventListener("change",i)}function l(a){function c(a){a.forEach(function(a){var c,d,e=a.type,f=a.target,g=a.attributeName;if("attributes"===e&&0==g.indexOf("data-tie"))w.move(f,b(g),a.oldValue,f.getAttribute(g));else if("attributes"===e&&"src"===g&&"IFRAME"===f.nodeName)w.discard(f.contentDocument);else if("childList"===e){if(a.addedNodes.length)for(c=0,d=a.addedNodes.length;d>c;c++)"IFRAME"===a.addedNodes[c].nodeName?(a.addedNodes[c].contentDocument&&(l(a.addedNodes[c].contentDocument),w.collect(a.addedNodes[c].contentDocument)),a.addedNodes[c].addEventListener("load",function(){l(this.contentDocument),w.collect(this.contentDocument)})):w.collect(a.addedNodes[c]);if(a.removedNodes.length)for(c=0,d=a.removedNodes.length;d>c;c++)"IFRAME"===a.removedNodes[c].nodeName?w.discard(a.removedNodes[c].contentDocument):w.discard(a.removedNodes[c])}})}var d=new MutationObserver(c);d.observe(a,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,characterData:!1,characterDataOldValue:!1}),z.push(d)}function m(){z.forEach(function(a){a.disconnect()}),w.discard(document),v=null,x=null,w=null}const n="modules",o="dataTier",p="viewupdate",q="error",r="debug",s="info";var t,u,v,w,x,y;if("object"!=typeof a&&(a={}),y=new function(){function a(a){var b=[];return a=Array.from(a),a.forEach(function(a){"object"==typeof a?b.push(JSON.stringify(a)):b.push(a)}),b.join(" ")}var b=q;Object.defineProperties(this,{mode:{get:function(){return b},set:function(a){a===q||a===r||a===s?b=a:console.error('DTLogger: mode "'+a+'" is not supported')}},info:{value:function(){b!==s&&b!==q||console.info("DT: "+a(arguments))}},debug:{value:function(){b!==s&&b!==q&&b!==r||console.debug("DT: "+a(arguments))}},error:{value:function(){console.error("DT: "+a(arguments))}}})},x=new function(){function a(a){return c(a)}function b(b,c){var d,f,g;"string"==typeof c?(d=a,f=function(a,b){var d;b&&(d=b.data,d="undefined"==typeof d||null===d?"":d,e(a,c,d))},g=function(){throw new Error("not yet implemented")}):"function"==typeof c?(d=a,f=c,g=function(){throw new Error('no "inputToData" functionality defined in this rule')}):"object"==typeof c&&(d=c.resolvePath||a,f=c.dataToView,g=c.inputToData),Object.defineProperties(this,{id:{value:b},resolvePath:{value:d},dataToView:{value:f,writable:!0},inputToData:{value:g}})}var d={};Object.defineProperties(this,{add:{value:function(a,c){if(!a||!c)throw new Error("bad parameters; f(string, string|function) expected");if(0!==a.indexOf("tie"))throw new Error('rule id MUST begin with "tie"');if(a in d)throw new Error('rule with id "'+a+'" already exists');return d[a]=new b(a,c),w.relocateByRule(d[a]),d[a]}},get:{value:function(a,b){var c,e;if(0!==a.indexOf("tie"))y.error("invalid tie id supplied");else if(a in d)c=d[a];else if("tie"===a){if(e=b.ownerDocument.defaultView,!b||!b.nodeName)throw new Error('rule "'+a+'" not found, therefore valid DOM element MUST be supplied to grasp the default rule');return b instanceof e.HTMLInputElement||b instanceof e.HTMLSelectElement?d.tieValue:b instanceof e.HTMLImageElement?d.tieImage:d.tieText}return c}},del:{value:function(a){return delete d[a]}}}),Object.seal(this)},"function"!=typeof DataObserver)throw new Error("no DataObserver implementation available");t=new DataObserver,u=t.getObserved({},function(a){a&&a.forEach(function(a){var b,c,d,e,f=w.get(a.path);for(b=0,c=f.length;c>b;b++)for(d in f[b].dataset)0===d.indexOf("tie")&&(e=x.get(d,f[b]).resolvePath(f[b].dataset[d]),h(a.path,e)&&w.update(f[b],d))})}),y.info("DataObserver details: ",t.details),v=new function(){function a(a){e(a.data,a.path,a.view.value)}function b(b){var c;Object.defineProperties(this,{namespace:{get:function(){return b}},setModel:{value:function(a){if("object"!=typeof a)throw new TypeError("model MUST be an object");return u[b]=a,u[b]}},getObservedModel:{value:function(){return u[b]}},viewToDataProcessor:{get:function(){return"function"==typeof c?c:a},set:function(a){"function"==typeof a&&(c=a)}}})}function c(a){if(!a||"string"!=typeof a)throw new Error("namespace (first param) MUST be a non empty string");if(/\W/.test(a))throw new Error("namespace (first param) MUST consist of alphanumeric non uppercase characters only");return f[a]||(f[a]=new b(a)),f[a]}function d(a){f[a]&&delete f[a]}var f={};Object.defineProperties(this,{obtain:{value:c},remove:{value:d}}),Object.seal(this)},w=new function(){function a(a){var b,c,d,i;if("IFRAME"===a.nodeName)l(a.contentDocument),a.addEventListener("load",function(){l(this.contentDocument),h(this.contentDocument)}),h(a.contentDocument);else for(b in a.dataset)0===b.indexOf("tie")&&(i=x.get(b,a),i?(c=i.resolvePath(a.dataset[b]),c.push(o),d=f(q,c),d||e(q,c,d=[]),d.indexOf(a)<0&&(d.push(a),c.pop(),g(a,b),j(a),s++)):(r[b]||(r[b]=[]),r[b].push(a)))}function b(a){var d,e=c(a),g=[];return d=f(q,e),d&&Object.keys(d).forEach(function(c){c===o?Array.prototype.push.apply(g,d[c]):Array.prototype.push.apply(g,b(a+"."+c))}),g}function d(a,b){var c=new a.ownerDocument.defaultView.CustomEvent(p,{detail:b});a.dispatchEvent(c)}function g(a,b){var c,e,g,h;c=x.get(b,a),e=c.resolvePath(a.dataset[b]),g=v.obtain(e.shift()),g&&c&&(h=f(g.getObservedModel(),e),c.dataToView(a,{data:h}),d(a,{ruleId:b}))}function h(b){var c;b&&b.nodeType&&(b.nodeType===Element.DOCUMENT_NODE||b.nodeType===Element.ELEMENT_NODE)&&(c=c="IFRAME"===b.nodeName?Array.prototype.slice.call(b.contentDocument.getElementsByTagName("*"),0):Array.prototype.slice.call(b.getElementsByTagName("*"),0),c.push(b),c.forEach(a),y.info("collected views, current total: "+s))}function i(b){r[b.id]&&r[b.id].forEach(a),y.info("relocated views, current total: "+s)}function m(a){var b,c,d,e,g,h;a&&a.getElementsByTagName&&(b=Array.prototype.slice.call(a.getElementsByTagName("*"),0),b.push(a),b.forEach(function(a){for(c in a.dataset)h=-1,0===c.indexOf("tie")&&(d=x.get(c,a),e=d.resolvePath(a.dataset[c]),e.push(o),g=f(q,e),h=g&&g.indexOf(a),h>=0&&(g.splice(h,1),k(a),s--))}),y.info("discarded views, current total: "+s))}function n(a,b,d,h){var i,j,k,l=-1;d&&(j=c(d),j.push(o),i=f(q,j),i&&(l=i.indexOf(a)),l>=0&&i.splice(l,1)),k=c(h),k.push(o),f(q,k)||e(q,k,[]),f(q,k).push(a),k.pop(),g(a,b)}var o="___vs___",q={},r={},s=0;Object.defineProperties(this,{collect:{value:h},update:{value:g},relocateByRule:{value:i},discard:{value:m},move:{value:n},get:{value:b}}),Object.seal(this)};var z=[];l(document),x.add("tieValue","value"),x.add("tieText","textContent"),x.add("tiePlaceholder","placeholder"),x.add("tieTooltip","title"),x.add("tieImage","src"),x.add("tieDateValue",{dataToView:function(a,b){a.value=b.data.toLocaleString()}}),x.add("tieDateText",{dataToView:function(a,b){a.textContent=b.data.toLocaleString()}}),x.add("tieList",{resolvePath:function(a){var b=a.split(" ");return c(b[0])},dataToView:function(a,b){function c(a,b){for(var c=Array.prototype.slice.call(l.querySelectorAll('[data-list-item-aid="'+b+'"]'),0);c.length>a;)l.removeChild(c.pop());return c.length}var d,e,f,g,h,i,j,k,l=a.parentNode;if("TEMPLATE"!==a.nodeName)throw new Error("tieList may be defined on template elements only");if(a.dataset.listSourceAid||(a.dataset.listSourceAid=(new Date).getTime()),d=c(b.data?b.data.length:0,a.dataset.listSourceAid),b.data&&d<b.data.length)if(f=a.dataset.tieList.trim().split(/\s+/),f&&3===f.length&&"=>"===f[1]){for(h=f[0],g=f[2],j=a.ownerDocument,k=j.createDocumentFragment();d<b.data.length;d++)e=j.importNode(a.content,!0),i=Array.prototype.slice.call(e.querySelectorAll("*"),0),i.forEach(function(a){Object.keys(a.dataset).forEach(function(b){0===a.dataset[b].indexOf(g+".")&&(a.dataset[b]=a.dataset[b].replace(g,h+"["+d+"]"),w.update(a,b))})}),k.appendChild(e),k.lastElementChild.dataset.listItemAid=a.dataset.listSourceAid;l.appendChild(k)}else y.error("invalid parameter for TieList rule specified")}}),w.collect(document),"object"!=typeof window[n]&&Object.defineProperty(window,n,{value:{}}),Object.defineProperty(window[n],o,{value:{}}),Object.defineProperties(window[n][o],{dispose:{value:m},Ties:{value:v},Rules:{value:x},Utils:{value:{get logger(){return y},get copyObject(){return d},get setPath(){return e},get getPath(){return f},get cutPath(){return g}}},About:{value:{get version(){return"0.5.3"},get author(){return{get name(){return"Guller Yuri"},get email(){return"gullerya@gmail.com"}}}}}})}("object"==typeof arguments?arguments[0]:void 0); | ||
!function(a){"use strict";function b(a){var b;return b=Array.isArray(a)?a.slice():Object.assign(new a.constructor,a)}function c(a){return t.indexOf(a.constructor.name)>=0}function d(a,b){var c,d=r.get(a),e=s.get(d.root);return c="pop"===b?function(){var c,f,g;return c=a.length-1,f=Reflect.apply(a[b],a,arguments),f&&"object"==typeof f&&(f=q.get(f),r.get(f).revoke()),g=[new m(d.path.concat(c),f)],p(e.callbacks,g),f}:"push"===b?function(){var c,f,g,h=[];return c=Array.from(arguments),g=a.length,c.forEach(function(a,b){a&&"object"==typeof a&&(c[b]=new i(a,g+b,d).proxy),h.push(new k(d.path.concat(g+b),a))}),f=Reflect.apply(a[b],a,c),p(e.callbacks,h),f}:"shift"===b?function(){var c,f,g;return c=Reflect.apply(a[b],a,arguments),c&&"object"==typeof c&&(c=q.get(c),r.get(c).revoke()),a.forEach(function(a,b){a&&"object"==typeof a&&(g=r.get(q.get(a)),g?g.ownKey=b:console.error("failed to resolve proxy -> target -> observed"))}),f=[new m(d.path.concat(0),c)],p(e.callbacks,f),c}:"unshift"===b?function(){var c,f,g,h=[];c=Array.from(arguments),c.forEach(function(a,b){a&&"object"==typeof a&&(c[b]=new i(a,b,d).proxy)}),f=Reflect.apply(a[b],a,c),a.forEach(function(a,b){a&&"object"==typeof a&&(g=r.get(q.get(a)),g?g.ownKey=b:console.error("failed to resolve proxy -> target -> observed"))});for(var j=0;j<c.length;j++)h.push(new k(d.path.concat(j),a[j]));return p(e.callbacks,h),f}:"reverse"===b?function(){var c,d=[];return Reflect.apply(a[b],a,arguments),a.forEach(function(a,b){a&&"object"==typeof a&&(c=r.get(q.get(a)),c?c.ownKey=b:console.error("failed to resolve proxy -> target -> observed"))}),d.push(new n),p(e.callbacks,d),this}:"sort"===b?function(){var c,d=[];return Reflect.apply(a[b],a,arguments),a.forEach(function(a,b){a&&"object"==typeof a&&(c=r.get(q.get(a)),c?c.ownKey=b:console.error("failed to resolve proxy -> target -> observed"))}),d.push(new o),p(e.callbacks,d),this}:"fill"===b?function(){var c,f,g,h=[];c=arguments.length<2?0:arguments[1]<0?a.length+arguments[1]:arguments[1],f=arguments.length<3?a.length:arguments[2]<0?a.length+arguments[2]:arguments[2],g=a.slice(),Reflect.apply(a[b],a,arguments);for(var j=c;j<f;j++)a[j]&&"object"==typeof a[j]&&(a[j]=new i(a[j],j,d).proxy),g.hasOwnProperty(j)?(h.push(new l(d.path.concat(j),a[j],g[j]&&"object"==typeof g[j]?q.get(g[j]):g[j])),g[j]&&"object"==typeof g[j]&&r.get(q.get(g[j])).revoke()):h.push(new k(d.path.concat(j),a[j]));return p(e.callbacks,h),this}:"splice"===b?function(){var c,f,g,h,j,n,o,s=[];for(c=Array.from(arguments),c.forEach(function(a,b){b>1&&a&&"object"==typeof a&&(c[b]=new i(a,b,d).proxy)}),j=0===c.length?0:c[0]<0?a.length+c[0]:c[0],n=c.length<2?a.length-j:c[1],o=Math.max(c.length-2,0),f=Reflect.apply(a[b],a,c),a.forEach(function(a,b){a&&"object"==typeof a&&(g=r.get(q.get(a)),g?g.ownKey=b:console.error("failed to resolve proxy -> target -> observed"))}),f.forEach(function(a,b){a&&"object"==typeof a&&(f[b]=q.get(a),r.get(f[b]).revoke())}),h=0;h<n;h++)h<o?s.push(new l(d.path.concat(j+h),a[j+h],f[h])):s.push(new m(d.path.concat(j+h),f[h]));for(;h<o;h++)s.push(new k(d.path.concat(j+h),a[j+h]));return p(e.callbacks,s),f}:Reflect.get(a,b)}function e(a,b,c){var d,e,f=a.hasOwnProperty(b),g=a[b],h=r.get(a),j=s.get(h.root),m=[];return d=Reflect.set(a,b,c),j.callbacks.length&&d&&c!==g&&(e=h.path.concat(b),g&&"object"==typeof g&&(r.get(q.get(g)).revoke(),q.has(g)&&q.delete(g)),c&&"object"==typeof c&&(a[b]=new i(c,b,h).proxy),f?m.push(new l(e,c,g)):m.push(new k(e,c)),h.preventCallbacks||p(j.callbacks,m)),d}function f(a,b){var c,d,e=a[b],f=r.get(a),g=s.get(f.root),h=[];return c=Reflect.deleteProperty(a,b),g.callbacks.length&&c&&("object"==typeof e&&e&&q.has(e)&&q.delete(e),d=f.path.concat(b),h.push(new m(d,e)),f.preventCallbacks||p(g.callbacks,h)),c}function g(a,b){a.forEach(function(d,e){d&&"object"==typeof d&&!c(d)&&(a[e]=new i(d,e,b).proxy)})}function h(a,b){Reflect.ownKeys(a).forEach(function(d){a[d]&&"object"==typeof a[d]&&!c(a[d])&&(a[d]=new i(a[d],d,b).proxy)})}function i(a,c,j){var k,l;if(!a||"object"!=typeof a)throw new Error("Observed MUST be created from a non null object origin");if(j&&("undefined"==typeof c||null===c))throw new Error("any non-root (parent-less) Observed MUST have an own path; now parent is "+j+"; key is "+c);if(j&&!(j instanceof i))throw new Error("parent, when supplied, MUST be an instance of Observed");k=b(a),Array.isArray(k)?(g(k,this),l=Proxy.revocable(k,{set:e,get:d,deleteProperty:f})):(h(k,this),l=Proxy.revocable(k,{set:e,deleteProperty:f})),r.set(k,this),q.set(l.proxy,k),Reflect.defineProperty(this,"revokable",{value:l}),Reflect.defineProperty(this,"proxy",{value:l.proxy}),Reflect.defineProperty(this,"parent",{value:j}),Reflect.defineProperty(this,"ownKey",{value:c,writable:!0})}function j(a){function b(a){if(e)throw new TypeError("revoked Observable MAY NOT be observed anymore");if("function"!=typeof a)throw new Error("observer (callback) parameter MUST be a function");f.indexOf(a)<0?f.push(a):console.info("observer (callback) may be bound to an observable only once")}function c(){if(e)throw new TypeError("revoked Observable MAY NOT be unobserved amymore");arguments.length?Array.from(arguments).forEach(function(a){var b=f.indexOf(a);b>=0&&f.splice(b,1)}):f.splice(0,f.length)}function d(){e?console.log("revokation of Observable have an effect only once"):(e=!0,a.revoke())}var e=!1,f=[];Reflect.defineProperty(a.proxy,"observe",{value:b}),Reflect.defineProperty(a.proxy,"unobserve",{value:c}),Reflect.defineProperty(a.proxy,"revoke",{value:d}),Reflect.defineProperty(this,"callbacks",{value:f})}function k(a,b){Reflect.defineProperty(this,"type",{value:"insert"}),Reflect.defineProperty(this,"path",{value:a}),Reflect.defineProperty(this,"value",{value:b})}function l(a,b,c){Reflect.defineProperty(this,"type",{value:"update"}),Reflect.defineProperty(this,"path",{value:a}),Reflect.defineProperty(this,"value",{value:b}),Reflect.defineProperty(this,"oldValue",{value:c})}function m(a,b){Reflect.defineProperty(this,"type",{value:"delete"}),Reflect.defineProperty(this,"path",{value:a}),Reflect.defineProperty(this,"oldValue",{value:b})}function n(){Reflect.defineProperty(this,"type",{value:"reverse"})}function o(){Reflect.defineProperty(this,"type",{value:"shuffle"})}function p(a,b){for(var c=0;c<a.length;c++)try{a[c](b)}catch(a){console.error(a)}}var q=new Map,r=new Map,s=new Map,t=["Date","Blob","Number","String","Boolean","Error","SyntaxError","TypeError","URIError","Function","Promise","RegExp"];Reflect.defineProperty(i.prototype,"root",{get:function(){for(var a=this;a.parent;)a=a.parent;return a}}),Reflect.defineProperty(i.prototype,"path",{get:function(){for(var a=[],b=this;"undefined"!=typeof b.ownKey;)a.push(b.ownKey),b=b.parent;return a.reverse()}}),Reflect.defineProperty(i.prototype,"revoke",{value:function(){var a=this.proxy;Reflect.ownKeys(a).forEach(function(b){var c=a[b];c&&"object"==typeof c&&(r.get(q.get(c)).revoke(),q.get(a)[b]=q.get(c))}),this.revokable.revoke()}}),Reflect.defineProperty(j,"from",{value:function(a){if(!a||"object"!=typeof a)throw new Error("observable MAY ONLY be created from non-null object only");if("observe"in a||"unobserve"in a||"revoke"in a)throw new Error('target object MUST NOT have nor own neither inherited properties from the following list: "observe", "unobserve", "revoke"');if(c(a))throw new Error(a+" found to be one of non-observable object types: "+t);var b=new i(a),d=new j(b);return s.set(b,d),b.proxy}}),Reflect.defineProperty(a,"Observable",{value:j})}(this),function(a){"use strict";function b(b,c,d){function e(c){a.DataTier.views.processChanges(b,c)}var g;Reflect.defineProperty(this,"name",{value:b}),Reflect.defineProperty(this,"data",{get:function(){return g},set:function(c){c&&(f(c),g&&g.revoke());var d=g;g=c,g&&g.observe(e),a.DataTier.views.processChanges(b,[{type:"update",value:g,oldValue:d,path:[]}])}}),this.data=c}function c(a,c,d){if(e(a),f(c),g[a])throw new Error("existing tie ("+a+") MAY NOT be re-created, use the tie's own APIs to reconfigure it");return g[a]=new b(a,c,d)}function d(a){a&&g[a]&&(g[a].observable.revoke(),delete g[a])}function e(a){if(!a||"string"!=typeof a)throw new Error("tie name MUST be a non-empty string");if(/\W/.test(a))throw new Error("tie name MUST consist of alphanumerics or underlines ([a-zA-Z0-9_]) ONLY")}function f(a){if(!a||"object"!=typeof a||"function"!=typeof a.observe||"function"!=typeof a.unobserve||"function"!=typeof a.revoke)throw new Error(a+" is not a valid Observable")}var g={};Reflect.defineProperty(a,"DataTier",{value:{}}),Reflect.defineProperty(a.DataTier,"ties",{value:{}}),Reflect.defineProperty(a.DataTier.ties,"get",{value:function(a){return g[a]}}),Reflect.defineProperty(a.DataTier.ties,"create",{value:c}),Reflect.defineProperty(a.DataTier.ties,"remove",{value:d})}(this),function(a){"use strict";function b(a,b){Reflect.defineProperty(this,"name",{value:a}),Reflect.defineProperty(this,"dataToView",{value:b.dataToView}),"function"==typeof b.inputToData&&Reflect.defineProperty(this,"inputToData",{value:b.inputToData}),"function"==typeof b.parseParam&&Reflect.defineProperty(this,"parseParam",{value:b.parseParam})}function c(c,d){if("string"!=typeof c||!c)throw new Error("name MUST be a non-empty string");if(g[c])throw new Error('rule "'+c+'" already exists; you may want to reconfigure the existing rule');if("object"!=typeof d||!d)throw new Error("configuration MUST be a non-null object");if("function"!=typeof d.dataToView)throw new Error('configuration MUST have a "dataToView" function defined');g[c]=new b(c,d),a.DataTier.views.applyRule(g[c])}function d(a){return g[a]}function e(a){if("string"!=typeof a||!a)throw new Error("rule name MUST be a non-empty string");return delete g[a]}function f(a){var b=[];return a&&a.nodeType===Node.ELEMENT_NODE&&Reflect.ownKeys(a.dataset).forEach(function(a){g[a]&&b.push(g[a])}),b}var g={};b.prototype.parseParam=function(a){var b,c;return a?(b=a.split("."),c=b[0].split(":")[0],b[0]===c?b=[]:b[0]=b[0].replace(c+":",""),{tieName:c,dataPath:b}):void console.error("valid rule value MUST be a non-empty string, found: "+a)},Reflect.defineProperty(a.DataTier,"rules",{value:{}}),Reflect.defineProperty(a.DataTier.rules,"get",{value:d}),Reflect.defineProperty(a.DataTier.rules,"add",{value:c}),Reflect.defineProperty(a.DataTier.rules,"remove",{value:e}),Reflect.defineProperty(a.DataTier.rules,"getApplicable",{value:f})}(this),function(a){"use strict";function b(a,b){var c;if(a){for(c=0;c<b.length;c++)if(a=a[b[c]],!a)return;return a}}function c(b){a.DataTier.rules.getApplicable(b.target).forEach(function(c){if("tieValue"===c.name){var d=c.parseParam(b.target.dataset[c.name]),e=a.DataTier.ties.get(d.tieName);if(!d.dataPath)return void console.error("path to data not available");if(!e)return void console.error('tie "'+d.tieName+'" not found');e.viewToDataProcessor({data:e.data,path:d.dataPath,view:b.target})}})}function d(a){"INPUT"!==a.nodeName&&"SELECT"!==a.nodeName||a.addEventListener("change",c)}function e(a){a.removeEventListener("change",c)}function f(b){if("IFRAME"===b.nodeName)n(b.contentDocument),b.addEventListener("load",function(){n(this.contentDocument),h(this.contentDocument)}),h(b.contentDocument);else{a.DataTier.rules.getApplicable(b).forEach(function(a){var c,e,f,h=a.parseParam(b.dataset[a.name]),i=h.dataPath.join(".");o[h.tieName]||(o[h.tieName]={}),c=o[h.tieName],c[a.name]||(c[a.name]={}),e=c[a.name],e[i]||(e[i]=[]),f=e[i],f.indexOf(b)<0&&(f.push(b),g(b,a.name),d(b))});for(var c in b.dataset)0!==c.indexOf("tie")||a.DataTier.rules.get(c)||(console.warn('non-registerd rule "'+c+'" used, it may still be defined later in code and post-tied'),p[c]||(p[c]=[]),p[c].push(b))}}function g(c,d){var e,f,g,h;e=a.DataTier.rules.get(d),f=e.parseParam(c.dataset[d]),g=a.DataTier.ties.get(f.tieName),g&&(h=b(g.data,f.dataPath),e.dataToView(h,c))}function h(a){var b;a&&a.nodeType&&(a.nodeType===Node.DOCUMENT_NODE||a.nodeType===Node.ELEMENT_NODE)&&(b=b="IFRAME"===a.nodeName?Array.from(a.contentDocument.getElementsByTagName("*")):Array.from(a.getElementsByTagName("*")),b.push(a),b.forEach(f))}function i(b){var c,d,f,g;b&&b.getElementsByTagName&&(c=Array.from(b.getElementsByTagName("*")),c.push(b),c.forEach(function(b){a.DataTier.rules.getApplicable(b).forEach(function(a){d=a.parseParam(b.dataset[a.name]),f=o[d.tieName][a.name][d.dataPath.join(".")],g=f.indexOf(b),g>=0&&(f.splice(g,1),e(b))})}))}function j(b,c,d,e){var f,h,i=-1;f=a.DataTier.rules.get(c).parseParam(d),o[f.tieName]&&o[f.tieName][c]&&(h=o[f.tieName][c][f.dataPath],h&&(i=h.indexOf(b)),i>=0&&h.splice(i,1)),f=a.DataTier.rules.get(c).parseParam(e),o[f.tieName]||(o[f.tieName]={}),o[f.tieName][c]||(o[f.tieName][c]={}),o[f.tieName][c][f.dataPath]||(o[f.tieName][c][f.dataPath]=[]),o[f.tieName][c][f.dataPath].push(b),g(b,c)}function k(a,b){var c,d,e=o[a];e?b.forEach(function(a){d=a.path.join("."),Object.keys(e).forEach(function(a){c=e[a],c&&Object.keys(c).forEach(function(b){0!==b.indexOf(d)&&""!==b||c[b].forEach(function(b){g(b,a)})})})}):console.debug('views of tie "'+a+'" are not defined')}function l(a){p[a.name]&&(p[a.name].forEach(function(a){f(a)}),delete p[a.name])}function m(a){var b,c=2,d=a.split("-");for(b=d[1];c<d.length;)b+=d[c][0].toUpperCase()+d[c++].substr(1);return b}function n(a){function b(a){a.forEach(function(a){var b=a.target,c=a.attributeName;"attributes"===a.type&&0===c.indexOf("data-tie")?j(b,m(c),a.oldValue,b.getAttribute(c)):"attributes"===a.type&&"src"===c&&"IFRAME"===b.nodeName?i(b.contentDocument):"childList"===a.type&&(Array.from(a.addedNodes).forEach(function(a){"IFRAME"===a.nodeName?(a.contentDocument&&(n(a.contentDocument),h(a.contentDocument)),a.addEventListener("load",function(){n(this.contentDocument),h(this.contentDocument)})):h(a)}),Array.from(a.removedNodes).forEach(function(a){i("IFRAME"===a.nodeName?a.contentDocument:a)}))})}var c=new MutationObserver(b);c.observe(a,{childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,characterData:!1,characterDataOldValue:!1})}var o={},p={};Reflect.defineProperty(a.DataTier,"views",{value:{}}),Reflect.defineProperty(a.DataTier.views,"processChanges",{value:k}),Reflect.defineProperty(a.DataTier.views,"applyRule",{value:l}),n(document),h(document)}(this),function(a){"use strict";var b=a.DataTier.rules.add;b("tieValue",{dataToView:function(a,b){b.value=a?a:""}}),b("tieText",{dataToView:function(a,b){b.textContent=a?a.toString():""}}),b("tiePlaceholder",{dataToView:function(a,b){b.placeholder=a?a:""}}),b("tieTooltip",{dataToView:function(a,b){b.title=a?a:""}}),b("tieImage",{dataToView:function(a,b){b.src=a?a:""}}),b("tieDateValue",{dataToView:function(a,b){b.value=a?a.toLocaleString():""}}),b("tieDateText",{dataToView:function(a,b){b.textContent=a?a.toLocaleString():""}}),b("tieList",{parseParam:function(a){return this.constructor.prototype.parseParam(a.split(/\s*=>\s*/)[0])},dataToView:function(a,b){function c(a,b){for(var c=Array.from(k.querySelectorAll('[data-list-item-aid="'+b+'"]'));c.length>a;)k.removeChild(c.pop());return c.length}var d,e,f,g,h,i,j,k=b.parentNode;if("TEMPLATE"!==b.nodeName)throw new Error("tieList may be defined on template elements only");if(b.dataset.listSourceAid||(b.dataset.listSourceAid=(new Date).getTime()),d=c(a?a.length:0,b.dataset.listSourceAid),a&&d<a.length)if(f=b.dataset.tieList.trim().split(/\s+/),f&&3===f.length&&"=>"===f[1]){for(g=f[2],i=b.ownerDocument,j=i.createDocumentFragment();d<a.length;d++)e=i.importNode(b.content,!0),h=e.querySelectorAll("*"),h.forEach(function(a){Object.keys(a.dataset).forEach(function(b){0===a.dataset[b].indexOf(g)&&(a.dataset[b]=a.dataset[b].replace(g+":",f[0]+":"+d+"."))})}),j.appendChild(e),j.lastElementChild.dataset.listItemAid=b.dataset.listSourceAid;k.appendChild(j)}else console.error('invalid parameter for "tieList" rule specified')}})}(this); |
{ | ||
"name": "data-tier", | ||
"version": "0.5.41", | ||
"version": "0.6.0", | ||
"main": "dist/data-tier.js", | ||
@@ -17,5 +17,6 @@ "files": [ | ||
"view", | ||
"native", | ||
"javascript", | ||
"proxy", | ||
"object", | ||
"observer", | ||
"mutation", | ||
@@ -33,8 +34,13 @@ "observe" | ||
"homepage": "https://github.com/gullerya/data-tier/README.md", | ||
"dependencies": { | ||
"object-observer": "^0.2.2" | ||
}, | ||
"devDependencies": { | ||
"object-observer": "~0.1.1", | ||
"grunt": "~0.4.5", | ||
"grunt-cli": "~1.2.0", | ||
"grunt-contrib-jshint": "~1.0.0", | ||
"grunt-contrib-uglify": "~1.0.1" | ||
"adm-zip": "^0.4.7", | ||
"grunt": "^1.0.1", | ||
"grunt-cli": "^1.2.0", | ||
"grunt-contrib-uglify": "^2.0.0", | ||
"babel-eslint": "^7.1.1", | ||
"eslint-plugin-react": "^6.9.0", | ||
"gruntify-eslint": "^3.1.0" | ||
}, | ||
@@ -41,0 +47,0 @@ "license": "MIT", |
138
README.md
@@ -1,27 +0,82 @@ | ||
Temporary Suspension notice | ||
================= | ||
The current implementation is fully based on Object.observe functionality that was part a a proposed spec and available in Chrome environment up till v. 50. | ||
The proposal was recently removed and from Crome v. 50 this feature is not supported amymore. | ||
I have a very strong opinion, that Object.observe is a MUST HAVE funtionality to be available on the native level, and i truly believe that it will make it's way back in a future. | ||
Meanwhile, I'm completely refactoring the observing part of this module implementing it via Proxy facilities (observation module will be published as a separate module in npm soon) and once this is availalbe - DataTier module will become functional again. | ||
ETA for the DataTier is end of June 2016 (ObjectObserver https://github.com/gullerya/object-observer-js is considered to be good enough to start re-writing this library on top of it now). | ||
APIs - current APIs will mostly not be changed, except the observing syntax. | ||
Feedback - any early feedback from anyone who had a chance to try this out is welcome. | ||
<img width="90px" src="data-tier-logo.png"/> | ||
[![npm version](https://badge.fury.io/js/data-tier.svg)](https://badge.fury.io/js/data-tier) | ||
[![Build Status](https://travis-ci.org/gullerya/data-tier.svg?branch=master)](https://travis-ci.org/gullerya/data-tier) | ||
DataTier - Intro | ||
================ | ||
# Summary | ||
DataTier is a framework, or better utility, that provides two way binding (Model-View - View-Model, aka MVVM) in the domain of client HTML/Javascript applications. | ||
The most important principals that guided the author were simplicity of usage, shortest learning curve and yet an ability to give all of the essentials expected from such a utility. | ||
I tried to design this library in such a way that the more simpe the usecase - less needs to be done to get it working. | ||
So i believe that in it's current architecture, DataTier covers most of the usecases in quite a simple and straight forward way, yet the more complicated the things you'd need to achieve with it - you can, you'll just need to get more into the internals. | ||
`DataTier` ('tier' from 'to tie') is a service oriented framework, that provides two way binding (Model-View - View-Model, aka MVVM) in the domain of client HTML/Javascript applications. | ||
This library works tightly with [`Observable`](https://github.com/gullerya/object-observer-js#observable-static-properties)-driven event cycle, therefore it comes with an embedded [`object-observer.js`](https://github.com/gullerya/object-observer-js) library. | ||
Yet, it is possible to provide any other `Observable` implementation, if it provides the same functionality. In this case you may want to use lighter `data-tier` distribution (bundled within the same npm module) without `object-observer.js`. | ||
Basic examples | ||
============== | ||
#### Support matrix: ![CHROME](tools/browser_icons/chrome.png) <sub>49+</sub>, ![FIREFOX](tools/browser_icons/firefox.png) <sub>42+</sub>, ![EDGE](tools/browser_icons/explorer.png) <sub>13+</sub> | ||
Support matrix is currently as wide as that of [`object-observer.js`](https://github.com/gullerya/object-observer-js), assuming that in most of the cases consumers will not provide their own object-observer, but will use an embedded one. | ||
`DataTier` supports custom elements as well, obviously this functionality is available only on supporting environments. | ||
Let's assume you have and object that holds user info and you want to bind it to it's view in HTML. This splits into the <b>declaration in the HTML</b> and <b>functional part in the Javascript</b>.<br> | ||
In Javascript you'll need to tell to DataTier that the object 'user' is to be tied to it's views and watched for changes. This is done using API as in the following example (let's assume that you've got the reference to the library in 'dataTier' variable; see full description in [API Reference](api_reference.md)): | ||
#### Backlog: | ||
<pre><code>var user = { | ||
- Support custom pre-processors for both data-to-view and view-to-data flows | ||
- Add OOTB rule for HTML classes management | ||
- API reference | ||
#### Versions | ||
- __0.6.0__ | ||
- Moved to `object-observer.js` library as an observation engine, were impacted both the API and the implementation. | ||
- __0.5.41__ | ||
- First version, based on native `Object.observe` technology. | ||
# Loading the Library | ||
You have 2 ways to load the library: into a `window` global scope, or a custom scope provided by you. | ||
* Simple reference (script tag) to the `data-tier.js` in your HTML will load it into the __global scope__: | ||
```html | ||
<script src="data-tier.js"></script> | ||
<script> | ||
var person = { name: 'Uriya', age: 8 }, | ||
observablePerson = window.Observable.from(person); | ||
window.DataTier.ties.create('person', observablePerson); | ||
</script> | ||
``` | ||
* The snippet below exemplifies how to load the library into a __custom scope__ (add error handling as appropriate): | ||
```javascript | ||
var customNamespace = {}, | ||
person = { name: 'Nava', age: 6 }, | ||
observablePerson; | ||
fetch('data-tier.js').then(function (response) { | ||
if (response.status === 200) { | ||
response.text().then(function (code) { | ||
Function(code).call(customNamespace); | ||
// the below code is an example of consumption, locate it in your app lifecycle/flow as appropriate | ||
observablePerson = customNamespace.Observable.from(person); | ||
customNamespace.DataTier.ties.create('person', observablePerson); | ||
}); | ||
} | ||
}); | ||
``` | ||
- Note the usage of an embedded `Observable` along the way. As it has been mentioned before, you may provide your own `Observable` implementation and in this case more lightweigth `data-tier-wo-oo.js` will suite you more. | ||
- Minified version is also available for both distributions, with and without `object-observer.js`. | ||
# Basic example | ||
In essence, the purpose of the `DataTier` service is to tie model and view and sync between them automatically once changes detected in either one or another. | ||
In order to let this happen, two actions need to be done: | ||
1. any model to be shown should be registered in the `DataTier` service | ||
2. DOM elements intended to visualize the model need to be decorated with an appropriate declaration | ||
The above two may happen in any order, in any phase in the application lifecycle. `DataTier` supports lazy binding, watching for DOM changes as well as for a data changes and will pick up any new linking information relevant and tie the things up. | ||
Let's review the actual example, where we have some `user` object which is our model and we want to bind it to some interactive view of it. | ||
### Functional part | ||
```javascript | ||
var user = { | ||
name: 'User Name', | ||
@@ -34,31 +89,22 @@ age: 7, | ||
} | ||
}; | ||
}, | ||
observableUser = Observable.from(user); | ||
dataTier.Ties.create('userInfo', user);</code></pre> | ||
DataTier.ties.create('userInfo', observableUser); | ||
``` | ||
The API to register an object/graph in the DataTier is Ties.<b>create</b> function which accepts 2 parameters: namespace as a string and initial data as an object.<br> | ||
In order to have a views bound to this data we need to declare the ties in HTML also, it will go as following: | ||
### Declarative part | ||
```html | ||
<div> | ||
<span data-tie-text="userInfo:name"></span> | ||
<span data-tie-text="userInfo:age"></span> | ||
<input type="checkbox" data-tie-value="userInfo:active"> | ||
<div> | ||
<input type="text" data-tie-value="userInfo:address.street"> | ||
<input type="text" data-tie-value="userInfo:address.apartment"> | ||
</div> | ||
</div> | ||
``` | ||
<pre><code><div> | ||
<span data-tie:"userInfo.name"></span> | ||
<span data-tie-text:"userInfo.age"></span> | ||
<input type="checkbox" data-tie-value="userInfo.active"/> | ||
<div> | ||
<input type="text" data-tie:"userInfo.address.street"/> | ||
<input type="text" data-tie:"userInfo.address.apartment"/> | ||
</div> | ||
</div></code></pre> | ||
Don't be confused with the diversity of variants of 'data' attributes - this is something to do with a concept of <b>rules</b> and will be covered later in docs, the main point here is that DOM elements are being tied to the data registered in DataTier by means of 'data' attributes with <b>path</b> values, which i also expand on in the documentation below. | ||
There is no importance of the order of things, Javascript part may run before the HTML declarations and an opposite. HTML chunks with binding attributes may be injected/built later on, say on dynamic rendering of the pages. | ||
Similarly, Javascript tying may be done as part of any later async flow, as well as untying in the case of need, to be sure. | ||
Once two contracts exemplified above are done, you have the following: | ||
<ul> | ||
<li>Any change in the data itself (model) will be reflected in all tied views</li> | ||
<li>Any change of values in input elements which invoke 'onchange' event (user driven changes, usually, but may be simulated from Javascript code as well, of course) will be reflected in the model, and propagate to all the relevant views</li> | ||
<li>Adding new HTML chunks having data ties setup will automatically pick up and reflect the model state</li> | ||
<li>Adding new data to the namespace or registering new namespaces with data will invode update of all the view waiting for that data</li> | ||
</ul> | ||
For a more thorough API documentation see [API Reference](api_reference.md) page. | ||
For a more thorough API documentation see [API Reference](api-reference.md) page. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
77508
6
1322
110
1
7
+ Addedobject-observer@^0.2.2
+ Addedobject-observer@0.2.6(transitive)