data-tier
Advanced tools
Comparing version 0.5.2 to 0.5.3
{ | ||
"name": "data-tier", | ||
"version": "0.5.2", | ||
"version": "0.5.3", | ||
"homepage": "https://github.com/gullerya/data-tier", | ||
@@ -5,0 +5,0 @@ "author": { |
@@ -14,3 +14,3 @@ DataTier - Intro | ||
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, we'll cover this part later): | ||
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)): | ||
@@ -29,3 +29,3 @@ <pre><code>var user = { | ||
The API to register an object/graph in the DataTier is Ties.<b>creaet</b> function which accepts 2 parameters: namespace as a string and initial data as an object.<br> | ||
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: | ||
@@ -55,5 +55,2 @@ | ||
Diving deeper - Concepts | ||
======================== | ||
to be done... | ||
For a more thorough API documentation see [API Reference](api_reference.md) page. |
@@ -5,3 +5,3 @@ (function (options) { | ||
const DEFAULT_NAMESPACE = 'Modules'; | ||
var domObserver, dataRoot = {}, observers, ties, views, rules, log = {}; | ||
var domObserver, dataRoot = {}, observersService, tiesService, viewsService, rulesService, logger; | ||
@@ -14,8 +14,34 @@ if (typeof options !== 'object') { options = {}; } | ||
Object.defineProperties(log, { | ||
info: { value: function (m) { console.info('DT: ' + m); }, }, | ||
warn: { value: function (m) { console.warn('DT: ' + m); } }, | ||
error: { value: function (m) { console.error('DT: ' + m); } } | ||
}); | ||
logger = new (function DTLogger() { | ||
var mode = 'error'; | ||
Object.defineProperties(this, { | ||
mode: { | ||
get: function () { return mode; }, | ||
set: function (v) { | ||
if (/^(info|warn|error)$/.test(v)) mode = v; | ||
else console.error('DTLogger: mode "' + v + '" is not supported'); | ||
} | ||
}, | ||
info: { | ||
value: function (m) { | ||
if (mode !== 'info') return; | ||
console.info('DT: ' + m); | ||
} | ||
}, | ||
warn: { | ||
value: function (m) { | ||
if (mode !== 'info' && mode !== 'warn') return; | ||
console.warn('DT: ' + m); | ||
} | ||
}, | ||
error: { | ||
value: function (m) { | ||
console.error('DT: ' + m); | ||
} | ||
} | ||
}); | ||
})(); | ||
function dataAttrToProp(v) { | ||
@@ -132,27 +158,19 @@ var i = 2, l = v.split('-'), r; | ||
} | ||
if (!p) { log.error('path to data not available'); return; } | ||
// 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) { log.error('path to data is invalid'); return; } | ||
t = ties.obtain(p.shift()); | ||
if (!t) { log.error('tie not found'); return; } | ||
if (!p) { logger.error('path to data is invalid'); return; } | ||
t = tiesService.obtain(p.shift()); | ||
if (!t) { logger.error('tie not found'); return; } | ||
if (t.modelChangePreprocessor) { | ||
// TODO: add timing out... | ||
t.modelChangePreprocessor({ | ||
data: t.data, | ||
path: p, | ||
view: v | ||
}).then(function (result) { | ||
setPath(t.data, p, result.value); | ||
}).catch(function () { | ||
log.info('change was rejected'); | ||
log.error('rollback to the view to be done here'); | ||
}); | ||
} else { | ||
setPath(t.data, p, v.value); | ||
} | ||
t.viewToDataProcessor({ | ||
data: t.data, | ||
path: p, | ||
view: v | ||
}); | ||
} | ||
function addChangeListener(v) { | ||
if (v.nodeName === 'INPUT' || v.nodeName === 'SELECT') { | ||
var ow = v.ownerDocument.defaultView; | ||
if (v instanceof ow.HTMLInputElement || v instanceof ow.HTMLSelectElement) { | ||
v.addEventListener('change', changeListener); | ||
@@ -166,8 +184,8 @@ } | ||
// TODO: decide if encapsulate into ViewsManager and expose as API | ||
// TODO: decide if encapsulate into ViewsService and expose as API | ||
function updateView(view, ruleId, path) { | ||
var ns, t, r, data; | ||
ns = path.shift(); | ||
t = ties.obtain(ns); | ||
r = rules.get(ruleId, view); | ||
t = tiesService.obtain(ns); | ||
r = rulesService.get(ruleId, view); | ||
if (t && r) { | ||
@@ -179,4 +197,4 @@ data = getPath(t.data, path); | ||
function RulesManager() { | ||
var rs; | ||
rulesService = new (function RulesService() { | ||
var rules = {}; | ||
@@ -211,3 +229,3 @@ function dfltResolvePath(tieValue) { return pathToNodes(tieValue); } | ||
resolvePath: { value: vpr }, | ||
dataToView: { value: dtv }, | ||
dataToView: { value: dtv, writable: true }, | ||
inputToData: { value: itd } | ||
@@ -217,50 +235,2 @@ }); | ||
function RulesSet() { } | ||
rs = new RulesSet(); | ||
RulesSet.prototype.tieValue = new Rule('tieValue', 'value'); | ||
RulesSet.prototype.tieText = new Rule('tieText', 'textContent'); | ||
RulesSet.prototype.tiePlaceholder = new Rule('tiePlaceholder', 'placeholder'); | ||
RulesSet.prototype.tieTooltip = new Rule('tieTooltip', 'title'); | ||
RulesSet.prototype.tieImage = new Rule('tieImage', 'scr'); | ||
RulesSet.prototype.tieList = new Rule('tieList', { | ||
resolvePath: function (tieValue) { | ||
var ruleData = tieValue.split(' '); | ||
return pathToNodes(ruleData[0]); | ||
}, | ||
dataToView: function (view, tieValue) { | ||
var t = view.getElementsByTagName('template')[0], i, l, nv, ruleData, itemId, rulePath, vs, tmpDF; | ||
if (!tieValue.data) { | ||
while (view.childElementCount > 1) { | ||
view.removeChild(view.lastChild); | ||
} | ||
} else if (view.childElementCount - 1 < tieValue.data.length) { | ||
ruleData = view.dataset.tieList.trim().split(/\s+/); | ||
if (!ruleData || ruleData.length !== 3 || ruleData[1] !== '=>') { | ||
log.error('invalid parameter for TieList rule specified'); | ||
} else { | ||
rulePath = ruleData[0]; | ||
itemId = ruleData[2]; | ||
tmpDF = document.createDocumentFragment(); | ||
for (i = view.childElementCount - 1; i < tieValue.data.length; i++) { | ||
nv = t.content.cloneNode(true); | ||
vs = Array.prototype.splice.call(nv.querySelectorAll('*'), 0); | ||
vs.forEach(function (v) { | ||
Object.keys(v.dataset).forEach(function (key) { | ||
if (v.dataset[key].indexOf(itemId) === 0) { | ||
v.dataset[key] = v.dataset[key].replace(itemId, rulePath + '[' + i + ']'); | ||
} | ||
}); | ||
}); | ||
tmpDF.appendChild(nv); | ||
} | ||
} | ||
view.appendChild(tmpDF); | ||
} else if (view.childElementCount - 1 > tieValue.data.length) { | ||
while (view.childElementCount - 1 > tieValue.data.length) { | ||
view.removeChild(view.lastChild); | ||
} | ||
} | ||
} | ||
}); | ||
Object.defineProperties(this, { | ||
@@ -271,6 +241,6 @@ add: { | ||
if (id.indexOf('tie') !== 0) throw new Error('rule id MUST begin with "tie"'); | ||
if (rs.hasOwnProperty(id)) throw new Error('rule with id "' + id + '" already exists'); | ||
rs[id] = new Rule(id, setup); | ||
views.relocateByRule(rs[id]); | ||
return rs[id]; | ||
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]; | ||
} | ||
@@ -280,8 +250,16 @@ }, | ||
value: function (id, e) { | ||
var r = rs[id]; | ||
if (!r && id === 'tie') { | ||
if (!e) throw new Error('rule "' + id + '" not found, supply DOM element to get the default view'); | ||
if (e.nodeName === 'INPUT' || e.nodeName === 'SELECT') return rs['tieValue']; | ||
else if (e.nodeName === 'IMAGE') return rs['tieImage']; | ||
else return rs['tieText']; | ||
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']; | ||
} | ||
} | ||
@@ -293,18 +271,17 @@ return r; | ||
value: function (id) { | ||
return delete rs[id]; | ||
return delete rules[id]; | ||
} | ||
} | ||
}); | ||
}; | ||
rules = new RulesManager(); | ||
})(); | ||
function ObserversManager() { | ||
observersService = new (function ObserversManager() { | ||
var os = {}; | ||
function publishDataChange(data, path) { | ||
var vs = views.get(path), i, l, key, p; | ||
function publishDataChange(newData, oldData, path) { | ||
var vs = viewsService.get(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 = rules.get(key, vs[i]).resolvePath(vs[i].dataset[key]); | ||
p = rulesService.get(key, vs[i]).resolvePath(vs[i].dataset[key]); | ||
if (isPathStartsWith(path, p)) { | ||
@@ -324,3 +301,3 @@ updateView(vs[i], key, p); | ||
if (nv && typeof nv === 'object') { create(nv, p); } | ||
publishDataChange(nv, p); | ||
publishDataChange(nv, ov, p); | ||
} | ||
@@ -333,3 +310,3 @@ | ||
if (ov && typeof ov === 'object') { remove(ov, p + (i + change.index)); } | ||
publishDataChange(null, p); | ||
publishDataChange(null, null, p); | ||
} | ||
@@ -339,5 +316,5 @@ for (i = 0; i < change.addedCount; i++) { | ||
if (nv && typeof nv === 'object') { create(nv, p + (i + change.index)); } | ||
publishDataChange(nv, p); | ||
publishDataChange(nv, null, p); | ||
} | ||
publishDataChange(change.object, p); | ||
publishDataChange(change.object, change.oldValue, p); | ||
}; | ||
@@ -384,11 +361,16 @@ | ||
create(dataRoot, ''); | ||
} | ||
observers = new ObserversManager(); | ||
})(); | ||
// TODO: view to model change flow needs further design | ||
function TiesManager() { | ||
tiesService = new (function TiesManager() { | ||
var ts = {}; | ||
function Tie(namespace, data) { | ||
var mChangePrep; | ||
function dfltVTDProcessor(input) { | ||
setPath(input.data, input.path, input.view.value); | ||
} | ||
function Tie(namespace, data, options) { | ||
var vtdProc; | ||
options = options || {}; | ||
vtdProc = typeof options.viewToDataProcessor === 'function' ? options.viewToDataProcessor : null; | ||
Object.defineProperties(this, { | ||
@@ -400,11 +382,12 @@ namespace: { get: function () { return namespace; } }, | ||
}, | ||
modelChangePreprocessor: { | ||
get: function () { return mChangePrep; }, | ||
set: function (v) { if (typeof v === 'function') mChangePrep = v; } | ||
viewToDataProcessor: { | ||
get: function () { return typeof vtdProc === 'function' ? vtdProc : dfltVTDProcessor; }, | ||
set: function (v) { if (typeof v === 'function') vtdProc = v; } | ||
} | ||
}); | ||
dataRoot[namespace] = data; | ||
} | ||
function create(namespace, data) { | ||
function create(namespace, data, options) { | ||
if (!namespace || typeof namespace !== 'string') throw new Error('namespace (first param) MUST be a non empty string'); | ||
@@ -414,12 +397,15 @@ if (/\W/.test(namespace)) throw new Error('namespace (first param) MUST consist of alphanumeric non uppercase characters only'); | ||
if (data && typeof data !== 'object') throw new Error('data (second param) MUST be a non null object'); | ||
if (!data) data = null; | ||
return ts[namespace] = new Tie(namespace, data); | ||
return ts[namespace] = new Tie(namespace, data, options); | ||
} | ||
function remove(namespace) { | ||
console.error('to be implemented'); | ||
function obtain(path) { | ||
// TODO: add validations | ||
path = pathToNodes(path); | ||
return ts[path[0]]; | ||
} | ||
function obtain(namespace) { | ||
return ts[namespace]; | ||
function remove(path) { | ||
// TODO: add validations | ||
// TODO: add implementation | ||
path = pathToNodes(path); | ||
} | ||
@@ -432,6 +418,5 @@ | ||
}); | ||
} | ||
ties = new TiesManager(); | ||
})(); | ||
function ViewsManager() { | ||
viewsService = new (function ViewsService() { | ||
var vpn = '___vs___', vs = {}, nlvs = {}, vcnt = 0; | ||
@@ -451,3 +436,3 @@ | ||
if (key.indexOf('tie') === 0) { | ||
rule = rules.get(key, view); | ||
rule = rulesService.get(key, view); | ||
if (rule) { | ||
@@ -466,4 +451,4 @@ path = rule.resolvePath(view.dataset[key]); | ||
} else { | ||
if (!nlvs.key) nlvs[key] = []; | ||
nlvs.push(view); | ||
if (!nlvs[key]) nlvs[key] = []; | ||
nlvs[key].push(view); | ||
} | ||
@@ -490,7 +475,7 @@ } | ||
l = rootElement.nodeName === 'IFRAME' ? | ||
l = Array.prototype.splice.call(rootElement.contentDocument.getElementsByTagName('*'), 0) : | ||
l = Array.prototype.splice.call(rootElement.getElementsByTagName('*'), 0); | ||
l = Array.prototype.slice.call(rootElement.contentDocument.getElementsByTagName('*'), 0) : | ||
l = Array.prototype.slice.call(rootElement.getElementsByTagName('*'), 0); | ||
l.push(rootElement); | ||
l.forEach(add); | ||
log.info('collected views, current total: ' + vcnt); | ||
logger.info('collected views, current total: ' + vcnt); | ||
} | ||
@@ -502,3 +487,3 @@ | ||
} | ||
log.info('relocated views, current total: ' + vcnt); | ||
logger.info('relocated views, current total: ' + vcnt); | ||
} | ||
@@ -509,3 +494,3 @@ | ||
if (!rootElement || !rootElement.getElementsByTagName) return; | ||
l = Array.prototype.splice.call(rootElement.getElementsByTagName('*'), 0); | ||
l = Array.prototype.slice.call(rootElement.getElementsByTagName('*'), 0); | ||
l.push(rootElement); | ||
@@ -516,3 +501,3 @@ l.forEach(function (e) { | ||
if (key.indexOf('tie') === 0) { | ||
rule = rules.get(key, e); | ||
rule = rulesService.get(key, e); | ||
path = rule.resolvePath(e.dataset[key]); | ||
@@ -530,3 +515,3 @@ path.push(vpn); | ||
}); | ||
log.info('discarded views, current total: ' + vcnt); | ||
logger.info('discarded views, current total: ' + vcnt); | ||
} | ||
@@ -539,3 +524,3 @@ | ||
if (oldPath) { | ||
pathViews = getPath(views, oldPath); | ||
pathViews = getPath(viewsService, oldPath); | ||
if (pathViews) i = pathViews.indexOf(view); | ||
@@ -547,4 +532,4 @@ if (i >= 0) pathViews.splice(i, 1); | ||
npn = pathToNodes(newPath); | ||
if (!getPath(views, npn)) setPath(views, npn, []); | ||
getPath(views, npn).push(view); | ||
if (!getPath(viewsService, npn)) setPath(viewsService, npn, []); | ||
getPath(viewsService, npn).push(view); | ||
updateView(view, dataKey, npn) | ||
@@ -560,7 +545,4 @@ } | ||
}); | ||
})(); | ||
collect(document); | ||
} | ||
views = new ViewsManager(); | ||
function initDomObserver(d) { | ||
@@ -571,17 +553,17 @@ function processDomChanges(changes) { | ||
if (tp === 'attributes' && an.indexOf('data-tie') == 0) { | ||
views.move(tr, dataAttrToProp(an), change.oldValue, tr.getAttribute(an)); | ||
} else if (tp === 'attributes' && an === 'src' && tr.nodeName === 'IFRAME') { | ||
views.discard(tr.contentDocument); | ||
viewsService.move(tr, dataAttrToProp(an), change.oldValue, tr.getAttribute(an)); | ||
} else if (tp === 'attributes' && an === 'src' && tr instanceof tr.ownerDocument.defaultView.HTMLIFrameElement) { | ||
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] instanceof change.addedNodes[i].ownerDocument.defaultView.HTMLIFrameElement) { | ||
initDomObserver(change.addedNodes[i].contentDocument); | ||
views.collect(change.addedNodes[i].contentDocument); | ||
viewsService.collect(change.addedNodes[i].contentDocument); | ||
change.addedNodes[i].addEventListener('load', function () { | ||
initDomObserver(this.contentDocument); | ||
views.collect(this.contentDocument); | ||
viewsService.collect(this.contentDocument); | ||
}); | ||
} else { | ||
views.collect(change.addedNodes[i]); | ||
viewsService.collect(change.addedNodes[i]); | ||
} | ||
@@ -592,6 +574,6 @@ } | ||
for (i = 0, l = change.removedNodes.length; i < l; i++) { | ||
if (change.removedNodes[i].nodeName === 'IFRAME') { | ||
views.discard(change.removedNodes[i].contentDocument); | ||
if (change.removedNodes[i] instanceof change.removedNodes[i].ownerDocument.defaultView.HTMLIFrameElement) { | ||
viewsService.discard(change.removedNodes[i].contentDocument); | ||
} else { | ||
views.discard(change.removedNodes[i]); | ||
viewsService.discard(change.removedNodes[i]); | ||
} | ||
@@ -616,8 +598,71 @@ } | ||
rulesService.add('tieValue', 'value'); | ||
rulesService.add('tieText', 'textContent'); | ||
rulesService.add('tiePlaceholder', 'placeholder'); | ||
rulesService.add('tieTooltip', 'title'); | ||
rulesService.add('tieImage', 'scr'); | ||
rulesService.add('tieDateValue', { | ||
dataToView: function (view, tieValue) { | ||
view.value = tieValue.data.toLocaleString(); | ||
} | ||
}); | ||
rulesService.add('tieDateText', { | ||
dataToView: function (view, tieValue) { | ||
view.textContent = tieValue.data.toLocaleString(); | ||
} | ||
}); | ||
rulesService.add('tieList', { | ||
resolvePath: function (tieValue) { | ||
var ruleData = tieValue.split(' '); | ||
return pathToNodes(ruleData[0]); | ||
}, | ||
dataToView: function (view, tieValue) { | ||
var t = view.getElementsByTagName('template')[0], i, l, nv, ruleData, itemId, rulePath, vs, d, df; | ||
// TODO: may think of better contract to specify the template element | ||
if (!t) return; | ||
if (!tieValue.data) { | ||
while (view.childElementCount > 1) { | ||
view.removeChild(view.lastChild); | ||
} | ||
} else if (view.childElementCount - 1 < tieValue.data.length) { | ||
ruleData = view.dataset.tieList.trim().split(/\s+/); | ||
if (!ruleData || ruleData.length !== 3 || ruleData[1] !== '=>') { | ||
logger.error('invalid parameter for TieList rule specified'); | ||
} else { | ||
rulePath = ruleData[0]; | ||
itemId = ruleData[2]; | ||
d = view.ownerDocument; | ||
df = d.createDocumentFragment(); | ||
for (i = view.childElementCount - 1; i < tieValue.data.length; i++) { | ||
nv = d.importNode(t.content, true); | ||
vs = Array.prototype.slice.call(nv.querySelectorAll('*'), 0); | ||
vs.forEach(function (v) { | ||
Object.keys(v.dataset).forEach(function (key) { | ||
if (v.dataset[key].indexOf(itemId) === 0) { | ||
v.dataset[key] = v.dataset[key].replace(itemId, rulePath + '[' + i + ']'); | ||
} | ||
}); | ||
}); | ||
df.appendChild(nv); | ||
} | ||
view.appendChild(df); | ||
} | ||
} else if (view.childElementCount - 1 > tieValue.data.length) { | ||
while (view.childElementCount - 1 > tieValue.data.length) { | ||
view.removeChild(view.lastChild); | ||
} | ||
} | ||
} | ||
}); | ||
viewsService.collect(document); | ||
Object.defineProperty(options.namespace, 'DataTier', { value: {} }); | ||
Object.defineProperties(options.namespace.DataTier, { | ||
Ties: { value: ties }, | ||
Rules: { value: rules }, | ||
Ties: { value: tiesService }, | ||
Rules: { value: rulesService }, | ||
Utils: { | ||
value: { | ||
get logger() { return logger; }, | ||
get copyObject() { return copyObject; }, | ||
@@ -630,2 +675,3 @@ get setPath() { return setPath; }, | ||
}); | ||
})((typeof arguments === 'object' ? arguments[0] : undefined)); |
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
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
23810
587
54