data-tier
Advanced tools
Comparing version 2.10.1 to 2.11.0
@@ -0,280 +1,38 @@ | ||
import { DOMProcessor } from './dom-processor.js'; | ||
import { Ties } from './ties.js'; | ||
import { Views } from './views.js'; | ||
import { | ||
DEFAULT_TIE_TARGET_PROVIDER as dttp, | ||
CHANGE_EVENT_NAME_PROVIDER as cenp, | ||
getTargetProperty, | ||
extractViewParams, | ||
addChangeListener, | ||
delChangeListener, | ||
getPath, | ||
setPath, | ||
callViewFunction | ||
export { | ||
DEFAULT_TIE_TARGET_PROVIDER, | ||
CHANGE_EVENT_NAME_PROVIDER | ||
} from './utils.js'; | ||
export const version = '2.11.0'; | ||
console.info('DT: starting initialization...'); | ||
const initStartTime = performance.now(); | ||
console.info(`DT (${version}): starting initialization...`); | ||
const | ||
MUTATION_OBSERVER_OPTIONS = { | ||
childList: true, | ||
subtree: true, | ||
attributes: true, | ||
attributeOldValue: true, | ||
attributeFilter: ['data-tie'], | ||
characterData: false, | ||
characterDataOldValue: false | ||
}, | ||
roots = new WeakSet(), | ||
PARAMS_KEY = Symbol('view.params.key'), | ||
SCOPE_ROOT_TIE_KEY = Symbol('scope.root.tie.key'); | ||
class Instance { | ||
constructor() { | ||
this.params = Object.freeze(Array.from(new URL(import.meta.url).searchParams).reduce((a, c) => { a[c[0]] = c[1]; return a; }, {})); | ||
this.paramsKey = PARAMS_KEY; | ||
this.scopeRootTieKey = SCOPE_ROOT_TIE_KEY; | ||
this.paramsKey = Symbol('view.params.key'); | ||
this.domProcessor = new DOMProcessor(this); | ||
this.ties = new Ties(this); | ||
this.views = new Views(this); | ||
this.addTree = addTree; | ||
} | ||
} | ||
const instance = new Instance(); | ||
export const ties = instance.ties; | ||
export const DEFAULT_TIE_TARGET_PROVIDER = dttp; | ||
export const CHANGE_EVENT_NAME_PROVIDER = cenp; | ||
export const addRootDocument = function addRootDocument(rootDocument) { | ||
if (!rootDocument || (Node.DOCUMENT_NODE !== rootDocument.nodeType && Node.DOCUMENT_FRAGMENT_NODE !== rootDocument.nodeType)) { | ||
throw new Error('invalid argument, NULL or not one of: DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE'); | ||
} | ||
if (roots.has(rootDocument)) { | ||
console.warn('any root document may be added only once'); | ||
return false; | ||
} | ||
new MutationObserver(processDomChanges).observe(rootDocument, MUTATION_OBSERVER_OPTIONS); | ||
addTree(rootDocument); | ||
roots.add(rootDocument); | ||
return true; | ||
}; | ||
export const removeRootDocument = function removeRootDocument(rootDocument) { | ||
if (roots.has(rootDocument)) { | ||
dropTree(rootDocument); | ||
roots.delete(rootDocument); | ||
return true; | ||
} else { | ||
console.warn('no root document ' + rootDocument + ' known'); | ||
return false; | ||
} | ||
}; | ||
function changeListener(event) { | ||
const | ||
element = event.currentTarget, | ||
targetProperty = getTargetProperty(element), | ||
viewParams = element[PARAMS_KEY]; | ||
let tieParam, tie, newValue; | ||
if (!viewParams) { | ||
return; | ||
} | ||
let i = viewParams.length; | ||
while (i--) { | ||
tieParam = viewParams[i]; | ||
if (tieParam.targetProperty !== targetProperty) { | ||
continue; | ||
if (this.params.autostart !== 'false' && this.params.autostart !== false) { | ||
this.domProcessor.addDocument(document); | ||
} | ||
tie = ties.get(tieParam.tieKey); | ||
if (tie) { | ||
newValue = element[targetProperty]; | ||
setPath(tie, tieParam.path, newValue); | ||
} | ||
} | ||
} | ||
function add(element) { | ||
if (element.matches(':defined')) { | ||
const viewParams = extractViewParams(element, SCOPE_ROOT_TIE_KEY); | ||
if (viewParams) { | ||
instance.views.addView(element, viewParams); | ||
updateFromView(element, viewParams); | ||
addChangeListener(element, changeListener); | ||
} | ||
const instance = new Instance(); | ||
if (element.shadowRoot && !element.hasAttribute('data-tie-blackbox')) { | ||
addRootDocument(element.shadowRoot); | ||
} | ||
} else { | ||
waitUndefined(element); | ||
} | ||
} | ||
export const ties = instance.ties; | ||
export const addDocument = instance.domProcessor.addDocument.bind(instance.domProcessor); | ||
export const removeDocument = instance.domProcessor.removeDocument.bind(instance.domProcessor); | ||
function waitUndefined(element) { | ||
let tag; | ||
if (element.localName.indexOf('-') > 0) { | ||
tag = element.localName; | ||
} else { | ||
const matches = /.*is\s*=\s*"([^"]+)"\s*.*/.exec(element.outerHTML); | ||
if (matches && matches.length > 1) { | ||
tag = matches[1]; | ||
} | ||
} | ||
if (tag) { | ||
customElements.whenDefined(tag).then(() => add(element)); | ||
} else { | ||
console.warn('failed to determine tag of yet undefined custom element ' + element + ', abandoning'); | ||
} | ||
} | ||
// deprecated APIs | ||
export const addRootDocument = addDocument; | ||
export const removeRootDocument = removeDocument; | ||
function updateFromView(element, viewParams) { | ||
let i = viewParams.length; | ||
while (i--) { | ||
const param = viewParams[i]; | ||
if (param.isFunctional) { | ||
let someData = false; | ||
const args = []; | ||
param.fParams.forEach(fp => { | ||
let arg; | ||
const tie = ties.get(fp.tieKey); | ||
if (tie) { | ||
arg = getPath(tie, fp.path); | ||
someData = true; | ||
} | ||
args.push(arg); | ||
}); | ||
if (someData) { | ||
args.push(null); | ||
callViewFunction(element, param.targetProperty, args); | ||
} | ||
} else { | ||
const tie = ties.get(param.tieKey); | ||
if (!tie) { | ||
continue; | ||
} | ||
let value = getPath(tie, param.path); | ||
if (typeof value === 'undefined') { | ||
value = ''; | ||
} | ||
instance.views.setViewProperty(element, param, value); | ||
} | ||
} | ||
} | ||
export function addTree(root) { | ||
let list = [root], next; | ||
if (root.childElementCount) { | ||
list = Array.from(root.querySelectorAll('*')); | ||
list.unshift(root); | ||
} | ||
const l = list.length; | ||
for (let i = 0; i < l; i++) { | ||
try { | ||
next = list[i]; | ||
if (Node.ELEMENT_NODE === next.nodeType && !next[PARAMS_KEY]) { | ||
add(next); | ||
} | ||
} catch (e) { | ||
console.error('failed to process/add element', e); | ||
} | ||
} | ||
} | ||
function dropTree(root) { | ||
let list = [root], i, next, viewParams; | ||
if (root.childElementCount) { | ||
list = Array.from(root.querySelectorAll('*')); | ||
list.unshift(root); | ||
} | ||
i = list.length; | ||
while (i--) { | ||
next = list[i]; | ||
viewParams = next[PARAMS_KEY]; | ||
// untie | ||
if (viewParams) { | ||
instance.views.delView(next, viewParams); | ||
delChangeListener(next, changeListener); | ||
} | ||
// remove as root | ||
if (next.shadowRoot && !next.hasAttribute('data-tie-blackbox')) { | ||
removeRootDocument(next.shadowRoot); | ||
} | ||
} | ||
} | ||
function onTieParamChange(element, oldParam, newParam) { | ||
let viewParams; | ||
if (oldParam) { | ||
viewParams = element[PARAMS_KEY]; | ||
if (viewParams) { | ||
instance.views.delView(element, viewParams); | ||
delChangeListener(element, changeListener); | ||
} | ||
} | ||
if (newParam) { | ||
viewParams = extractViewParams(element, SCOPE_ROOT_TIE_KEY); | ||
if (viewParams) { | ||
instance.views.addView(element, viewParams); | ||
updateFromView(element, viewParams); | ||
addChangeListener(element, changeListener); | ||
} | ||
} | ||
} | ||
function processDomChanges(changes) { | ||
const l = changes.length; | ||
let i = 0, node, nodeType, change, changeType, added, i2, removed, i3; | ||
// collect all the changes to process and then do the processing | ||
for (; i < l; i++) { | ||
change = changes[i]; | ||
changeType = change.type; | ||
if (changeType === 'attributes') { | ||
const | ||
attributeName = change.attributeName, | ||
node = change.target, | ||
oldValue = change.oldValue, | ||
newValue = node.getAttribute(attributeName); | ||
if (oldValue !== newValue) { | ||
onTieParamChange(node, oldValue, newValue); | ||
} | ||
} else if (changeType === 'childList') { | ||
// process added nodes | ||
added = change.addedNodes; | ||
i2 = added.length; | ||
while (i2--) { | ||
node = added[i2]; | ||
nodeType = node.nodeType; | ||
if (Node.ELEMENT_NODE === nodeType) { | ||
addTree(node); | ||
} | ||
} | ||
// process removed nodes | ||
removed = change.removedNodes; | ||
i3 = removed.length; | ||
while (i3--) { | ||
node = removed[i3]; | ||
nodeType = node.nodeType; | ||
if (Node.ELEMENT_NODE === nodeType) { | ||
dropTree(node); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (instance.params.autostart !== 'false' && instance.params.autostart !== false) { | ||
addRootDocument(document); | ||
} | ||
console.info('DT: ... initialization DONE (took ' + Math.floor((performance.now() - initStartTime) * 100) / 100 + 'ms)'); | ||
console.info(`DT (${version}): ... initialization DONE (took ${(performance.now() - initStartTime).toFixed(2)}ms)`); |
@@ -1,1 +0,1 @@ | ||
import{Ties}from"./ties.min.js";import{Views}from"./views.min.js";import{DEFAULT_TIE_TARGET_PROVIDER as dttp,CHANGE_EVENT_NAME_PROVIDER as cenp,getTargetProperty,extractViewParams,addChangeListener,delChangeListener,getPath,setPath,callViewFunction}from"./utils.min.js";console.info("DT: starting initialization...");const initStartTime=performance.now(),MUTATION_OBSERVER_OPTIONS={childList:!0,subtree:!0,attributes:!0,attributeOldValue:!0,attributeFilter:["data-tie"],characterData:!1,characterDataOldValue:!1},roots=new WeakSet,PARAMS_KEY=Symbol("view.params.key"),SCOPE_ROOT_TIE_KEY=Symbol("scope.root.tie.key");class Instance{constructor(){this.params=Object.freeze(Array.from(new URL(import.meta.url).searchParams).reduce((e,t)=>(e[t[0]]=t[1],e),{})),this.paramsKey=PARAMS_KEY,this.scopeRootTieKey=SCOPE_ROOT_TIE_KEY,this.ties=new Ties(this),this.views=new Views(this),this.addTree=addTree}}const instance=new Instance;export const ties=instance.ties;export const DEFAULT_TIE_TARGET_PROVIDER=dttp;export const CHANGE_EVENT_NAME_PROVIDER=cenp;export const addRootDocument=function(e){if(!e||Node.DOCUMENT_NODE!==e.nodeType&&Node.DOCUMENT_FRAGMENT_NODE!==e.nodeType)throw new Error("invalid argument, NULL or not one of: DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE");return roots.has(e)?(console.warn("any root document may be added only once"),!1):(new MutationObserver(processDomChanges).observe(e,MUTATION_OBSERVER_OPTIONS),addTree(e),roots.add(e),!0)};export const removeRootDocument=function(e){return roots.has(e)?(dropTree(e),roots.delete(e),!0):(console.warn("no root document "+e+" known"),!1)};function changeListener(e){const t=e.currentTarget,o=getTargetProperty(t),n=t[PARAMS_KEY];let a,r,i;if(!n)return;let s=n.length;for(;s--;)(a=n[s]).targetProperty===o&&(r=ties.get(a.tieKey))&&(i=t[o],setPath(r,a.path,i))}function add(e){if(e.matches(":defined")){const t=extractViewParams(e,SCOPE_ROOT_TIE_KEY);t&&(instance.views.addView(e,t),updateFromView(e,t),addChangeListener(e,changeListener)),e.shadowRoot&&!e.hasAttribute("data-tie-blackbox")&&addRootDocument(e.shadowRoot)}else waitUndefined(e)}function waitUndefined(e){let t;if(e.localName.indexOf("-")>0)t=e.localName;else{const o=/.*is\s*=\s*"([^"]+)"\s*.*/.exec(e.outerHTML);o&&o.length>1&&(t=o[1])}t?customElements.whenDefined(t).then(()=>add(e)):console.warn("failed to determine tag of yet undefined custom element "+e+", abandoning")}function updateFromView(e,t){let o=t.length;for(;o--;){const n=t[o];if(n.isFunctional){let t=!1;const o=[];n.fParams.forEach(e=>{let n;const a=ties.get(e.tieKey);a&&(n=getPath(a,e.path),t=!0),o.push(n)}),t&&(o.push(null),callViewFunction(e,n.targetProperty,o))}else{const t=ties.get(n.tieKey);if(!t)continue;let o=getPath(t,n.path);void 0===o&&(o=""),instance.views.setViewProperty(e,n,o)}}}export function addTree(e){let t,o=[e];e.childElementCount&&(o=Array.from(e.querySelectorAll("*"))).unshift(e);const n=o.length;for(let e=0;e<n;e++)try{t=o[e],Node.ELEMENT_NODE!==t.nodeType||t[PARAMS_KEY]||add(t)}catch(e){console.error("failed to process/add element",e)}};function dropTree(e){let t,o,n,a=[e];for(e.childElementCount&&(a=Array.from(e.querySelectorAll("*"))).unshift(e),t=a.length;t--;)(n=(o=a[t])[PARAMS_KEY])&&(instance.views.delView(o,n),delChangeListener(o,changeListener)),o.shadowRoot&&!o.hasAttribute("data-tie-blackbox")&&removeRootDocument(o.shadowRoot)}function onTieParamChange(e,t,o){let n;t&&(n=e[PARAMS_KEY])&&(instance.views.delView(e,n),delChangeListener(e,changeListener)),o&&(n=extractViewParams(e,SCOPE_ROOT_TIE_KEY))&&(instance.views.addView(e,n),updateFromView(e,n),addChangeListener(e,changeListener))}function processDomChanges(e){const t=e.length;let o,n,a,r,i,s,c,d,l=0;for(;l<t;l++)if("attributes"===(r=(a=e[l]).type)){const e=a.attributeName,t=a.target,o=a.oldValue,n=t.getAttribute(e);o!==n&&onTieParamChange(t,o,n)}else if("childList"===r){for(s=(i=a.addedNodes).length;s--;)n=(o=i[s]).nodeType,Node.ELEMENT_NODE===n&&addTree(o);for(d=(c=a.removedNodes).length;d--;)n=(o=c[d]).nodeType,Node.ELEMENT_NODE===n&&dropTree(o)}}"false"!==instance.params.autostart&&!1!==instance.params.autostart&&addRootDocument(document),console.info("DT: ... initialization DONE (took "+Math.floor(100*(performance.now()-initStartTime))/100+"ms)"); | ||
import{DOMProcessor as o}from"./dom-processor.min.js";import{Ties as t}from"./ties.min.js";import{Views as e}from"./views.min.js";export{DEFAULT_TIE_TARGET_PROVIDER,CHANGE_EVENT_NAME_PROVIDER}from"./utils.min.js";export const version="2.11.0";const s=performance.now();console.info("DT (2.11.0): starting initialization...");const r=new class{constructor(){this.params=Object.freeze(Array.from(new URL(import.meta.url).searchParams).reduce((o,t)=>(o[t[0]]=t[1],o),{})),this.paramsKey=Symbol("view.params.key"),this.domProcessor=new o(this),this.ties=new t(this),this.views=new e(this),"false"!==this.params.autostart&&!1!==this.params.autostart&&this.domProcessor.addDocument(document)}};export const ties=r.ties;export const addDocument=r.domProcessor.addDocument.bind(r.domProcessor);export const removeDocument=r.domProcessor.removeDocument.bind(r.domProcessor);export const addRootDocument=addDocument;export const removeRootDocument=removeDocument;console.info(`DT (2.11.0): ... initialization DONE (took ${(performance.now()-s).toFixed(2)}ms)`); |
@@ -68,5 +68,5 @@ import { | ||
pl = tiedPathsLength; | ||
updateSet = new Map(); | ||
while (pl) { | ||
tiedPath = tiedPaths[--pl]; | ||
updateSet = []; | ||
while (pl--) { | ||
tiedPath = tiedPaths[pl]; | ||
if (cplen > tiedPath.length) { | ||
@@ -83,10 +83,5 @@ sst = tiedPath; | ||
pvl = pathViews.length; | ||
while (pvl) { | ||
view = pathViews[--pvl]; | ||
let tmp = updateSet.get(view); | ||
if (!tmp) { | ||
tmp = {}; | ||
updateSet.set(view, tmp); | ||
} | ||
tmp[tiedPath] = same; | ||
while (pvl--) { | ||
view = pathViews[pvl]; | ||
updateSet.push([view, tiedPath, same]); | ||
} | ||
@@ -101,9 +96,9 @@ } | ||
let viewParams, i; | ||
updateSet.forEach((paths, element) => { | ||
for (const [element, tiedPath, useChangeValue] of updateSet) { | ||
viewParams = element[this.ties.dti.paramsKey]; | ||
i = viewParams.length; | ||
while (i) { | ||
const param = viewParams[--i]; | ||
while (i--) { | ||
const param = viewParams[i]; | ||
if (param.isFunctional) { | ||
if (param.fParams.some(fp => fp.tieKey === this.key && fp.rawPath in paths)) { | ||
if (param.fParams.some(fp => fp.tieKey === this.key && fp.rawPath === tiedPath)) { | ||
let someData = false; | ||
@@ -126,11 +121,8 @@ const args = []; | ||
} else { | ||
if (param.tieKey !== this.key) { | ||
if (param.tieKey !== this.key || param.rawPath !== tiedPath) { | ||
continue; | ||
} | ||
if (!(param.rawPath in paths)) { | ||
continue; | ||
} | ||
let newValue; | ||
if (change && typeof change.value !== 'undefined' && paths[param.rawPath]) { | ||
if (change && typeof change.value !== 'undefined' && useChangeValue) { | ||
newValue = change.value; | ||
@@ -146,3 +138,3 @@ } else { | ||
} | ||
}); | ||
} | ||
} | ||
@@ -152,4 +144,4 @@ } | ||
export class Ties { | ||
constructor(dtInstance) { | ||
this.dti = dtInstance; | ||
constructor(dataTierInstance) { | ||
this.dti = dataTierInstance; | ||
this.ties = {}; | ||
@@ -159,3 +151,5 @@ } | ||
get(key) { | ||
const k = typeof key === 'string' ? key : (key ? key[this.dti.scopeRootTieKey] : undefined); | ||
const k = typeof key === 'string' | ||
? key | ||
: (key && key.getAttribute ? key.getAttribute('data-tie-scope') : null); | ||
const t = this.ties[k]; | ||
@@ -173,6 +167,9 @@ return t ? t.model : null; | ||
k = key; | ||
} else if (key && key.nodeType && key.nodeType === Node.ELEMENT_NODE) { | ||
k = key[this.dti.scopeRootTieKey]; | ||
} else if (key && key.nodeType === Node.ELEMENT_NODE) { | ||
k = key.getAttribute('data-tie-scope'); | ||
if (!k) { | ||
k = key[this.dti.scopeRootTieKey] = getRandomKey(16); | ||
k = getRandomKey(16); | ||
key.setAttribute('data-tie-scope', k); | ||
} else { | ||
console.log('inspect this'); | ||
} | ||
@@ -185,8 +182,7 @@ } | ||
} | ||
if (key.nodeType) { | ||
this.dti.addTree(key); | ||
this.dti.views.addScope(key); | ||
} | ||
const tieViews = this.dti.views.obtainTieViews(k); | ||
const tie = new Tie(k, model, this, tieViews); | ||
const tie = new Tie(k, model, this); | ||
this.ties[k] = tie; | ||
@@ -203,3 +199,5 @@ tie.processDataChanges([{ path: [] }]); | ||
const k = typeof key === 'string' ? key : (key ? key[this.dti.scopeRootTieKey] : undefined); | ||
const k = typeof key === 'string' | ||
? key | ||
: (key && key.getAttribute ? key.getAttribute('data-tie-scope') : null); | ||
const tie = this.ties[k]; | ||
@@ -221,3 +219,3 @@ if (tie) { | ||
if (tieToRemove.nodeType === Node.ELEMENT_NODE) { | ||
finalTieKeyToRemove = tieToRemove[this.dti.scopeRootTieKey]; | ||
finalTieKeyToRemove = tieToRemove.getAttribute('data-tie-scope'); | ||
} else { | ||
@@ -224,0 +222,0 @@ finalTieKeyToRemove = Object.keys(this.ties).find(key => this.ties[key].model === tieToRemove); |
@@ -1,1 +0,1 @@ | ||
import{ensureObservable,getPath,callViewFunction,getRandomKey}from"./utils.min.js";const MODEL_KEY=Symbol("model.key"),tieNameValidator=/^[a-zA-Z0-9]+$/,reservedTieNames=["scope"];class Tie{constructor(e,t,i){this.key=e,this.ties=i,this.model=t}set model(e){this[MODEL_KEY]=ensureObservable(e),this[MODEL_KEY].observe(e=>this.processDataChanges(e))}get model(){return this[MODEL_KEY]}processDataChanges(e){const t=this.ties.dti.views.obtainTieViews(this.key),i=t._pathsCache,s=i.length;let o,r,a,n,h,l,d,c,p,y,f,m,w,T,g,E,u,v="";if(s)for(o=0,r=e.length;o<r;o++)if(l=(h=(a=e[o]).path).length,!h.some(e=>"symbol"==typeof e)){for(T=!1,n=a.object,!Array.isArray(n)||"insert"!==a.type&&"delete"!==a.type||isNaN(h[h.length-1])?1===l?v=h[0]:l&&(v=2===l?h[0]+"."+h[1]:h.join(".")):(v=h.slice(0,-1).join("."),T=!0),f=v.length,d=s,u=new Map;d;)if(f>(c=i[--d]).length?(m=c,w=v):(m=v,w=c),g=m===w&&!T,0===w.indexOf(m))for(y=(p=t[c]).length;y;){E=p[--y];let e=u.get(E);e||(e={},u.set(E,e)),e[c]=g}this.updateViews(u,a)}}updateViews(e,t){let i,s;e.forEach((e,o)=>{for(i=o[this.ties.dti.paramsKey],s=i.length;s;){const r=i[--s];if(r.isFunctional){if(r.fParams.some(t=>t.tieKey===this.key&&t.rawPath in e)){let e=!1;const i=[];r.fParams.forEach(t=>{let s;const o=this.ties.get(t.tieKey);o&&(s=getPath(o,t.path),e=!0),i.push(s)}),e&&(i.push([t]),callViewFunction(o,r.targetProperty,i))}}else{if(r.tieKey!==this.key)continue;if(!(r.rawPath in e))continue;let i;void 0===(i=t&&void 0!==t.value&&e[r.rawPath]?t.value:getPath(this[MODEL_KEY],r.path))&&(i=""),this.ties.dti.views.setViewProperty(o,r,i)}}})}}export class Ties{constructor(e){this.dti=e,this.ties={}}get(e){const t="string"==typeof e?e:e?e[this.dti.scopeRootTieKey]:void 0,i=this.ties[t];return i?i.model:null}create(e,t){if(null===t)throw new Error("initial model, when provided, MUST NOT be null");let i;if("string"==typeof e?i=e:e&&e.nodeType&&e.nodeType===Node.ELEMENT_NODE&&((i=e[this.dti.scopeRootTieKey])||(i=e[this.dti.scopeRootTieKey]=getRandomKey(16))),Ties.validateTieKey(i),this.ties[i])throw new Error(`tie '${i}' already exists`);e.nodeType&&this.dti.addTree(e);const s=this.dti.views.obtainTieViews(i),o=new Tie(i,t,this,s);return this.ties[i]=o,o.processDataChanges([{path:[]}]),o.model}update(e,t){if(!t||"object"!=typeof t)throw new Error("model MUST be a non-null object");const i="string"==typeof e?e:e?e[this.dti.scopeRootTieKey]:void 0,s=this.ties[i];return s?(s.model!==t&&(s.model=t,s.processDataChanges([{path:[]}])),s.model):this.create(e,t)}remove(e){let t=e;if("object"==typeof e)t=e.nodeType===Node.ELEMENT_NODE?e[this.dti.scopeRootTieKey]:Object.keys(this.ties).find(t=>this.ties[t].model===e);else if("string"!=typeof e)throw new Error(`invalid tieToRemove parameter ${e}`);this.ties[t]&&(delete this.ties[t],this.dti.views.deleteTieViews(t))}static validateTieKey(e){if(!e||"string"!=typeof e)throw new Error(`invalid key '${e}'`);if(!tieNameValidator.test(e))throw new Error(`tie key MUST match ${tieNameValidator}; '${e}' doesn't`);if(reservedTieNames.indexOf(e)>=0)throw new Error(`tie key MUST NOT be one of those: ${reservedTieNames.join(", ")}`)}}; | ||
import{ensureObservable as e,getPath as t,callViewFunction as i,getRandomKey as s}from"./utils.min.js";const o=Symbol("model.key"),r=/^[a-zA-Z0-9]+$/,n=["scope"];class a{constructor(e,t,i){this.key=e,this.ties=i,this.model=t}set model(t){this[o]=e(t),this[o].observe(e=>this.processDataChanges(e))}get model(){return this[o]}processDataChanges(e){const t=this.ties.dti.views.obtainTieViews(this.key),i=t._pathsCache,s=i.length;let o,r,n,a,h,l,d,p,c,f,y,u,g,w,m,b,T,v="";if(s)for(o=0,r=e.length;o<r;o++)if(l=(h=(n=e[o]).path).length,!h.some(e=>"symbol"==typeof e)){for(w=!1,a=n.object,!Array.isArray(a)||"insert"!==n.type&&"delete"!==n.type||isNaN(h[h.length-1])?1===l?v=h[0]:l&&(v=2===l?h[0]+"."+h[1]:h.join(".")):(v=h.slice(0,-1).join("."),w=!0),y=v.length,d=s,T=[];d--;)if(y>(p=i[d]).length?(u=p,g=v):(u=v,g=p),m=u===g&&!w,0===g.indexOf(u))for(f=(c=t[p]).length;f--;)b=c[f],T.push([b,p,m]);this.updateViews(T,n)}}updateViews(e,s){let r,n;for(const[a,h,l]of e)for(n=(r=a[this.ties.dti.paramsKey]).length;n--;){const e=r[n];if(e.isFunctional){if(e.fParams.some(e=>e.tieKey===this.key&&e.rawPath===h)){let o=!1;const r=[];e.fParams.forEach(e=>{let i;const s=this.ties.get(e.tieKey);s&&(i=t(s,e.path),o=!0),r.push(i)}),o&&(r.push([s]),i(a,e.targetProperty,r))}}else{if(e.tieKey!==this.key||e.rawPath!==h)continue;let i;void 0===(i=s&&void 0!==s.value&&l?s.value:t(this[o],e.path))&&(i=""),this.ties.dti.views.setViewProperty(a,e,i)}}}}export class Ties{constructor(e){this.dti=e,this.ties={}}get(e){const t="string"==typeof e?e:e&&e.getAttribute?e.getAttribute("data-tie-scope"):null,i=this.ties[t];return i?i.model:null}create(e,t){if(null===t)throw new Error("initial model, when provided, MUST NOT be null");let i;if("string"==typeof e?i=e:e&&e.nodeType===Node.ELEMENT_NODE&&((i=e.getAttribute("data-tie-scope"))?console.log("inspect this"):(i=s(16),e.setAttribute("data-tie-scope",i))),Ties.validateTieKey(i),this.ties[i])throw new Error(`tie '${i}' already exists`);e.nodeType&&this.dti.views.addScope(e);const o=new a(i,t,this);return this.ties[i]=o,o.processDataChanges([{path:[]}]),o.model}update(e,t){if(!t||"object"!=typeof t)throw new Error("model MUST be a non-null object");const i="string"==typeof e?e:e&&e.getAttribute?e.getAttribute("data-tie-scope"):null,s=this.ties[i];return s?(s.model!==t&&(s.model=t,s.processDataChanges([{path:[]}])),s.model):this.create(e,t)}remove(e){let t=e;if("object"==typeof e)t=e.nodeType===Node.ELEMENT_NODE?e.getAttribute("data-tie-scope"):Object.keys(this.ties).find(t=>this.ties[t].model===e);else if("string"!=typeof e)throw new Error(`invalid tieToRemove parameter ${e}`);this.ties[t]&&(delete this.ties[t],this.dti.views.deleteTieViews(t))}static validateTieKey(e){if(!e||"string"!=typeof e)throw new Error(`invalid key '${e}'`);if(!r.test(e))throw new Error(`tie key MUST match ${r}; '${e}' doesn't`);if(n.indexOf(e)>=0)throw new Error(`tie key MUST NOT be one of those: ${n.join(", ")}`)}}; |
@@ -20,3 +20,3 @@ import { Observable } from './object-observer.min.js'; | ||
}, | ||
randomKeySource = 'abcdefghijklmnopqrstuvwxyz0123456789', | ||
randomKeySource = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', | ||
randomKeySourceLen = randomKeySource.length; | ||
@@ -79,6 +79,7 @@ | ||
function extractViewParams(element, scopeRootTieKey) { | ||
function extractViewParams(element) { | ||
const rawParam = element.getAttribute('data-tie'); | ||
if (rawParam) { | ||
return parseViewParams(rawParam, element, scopeRootTieKey); | ||
const parsed = parseViewParams(rawParam, element); | ||
return parsed.length ? parsed : null; | ||
} else { | ||
@@ -89,3 +90,3 @@ return null; | ||
function parseViewParams(multiParam, element, scopeRootTieKey) { | ||
function parseViewParams(multiParam, element) { | ||
const | ||
@@ -116,3 +117,3 @@ result = [], | ||
} else { | ||
parsedParam = parsePropertyParam(next, element, scopeRootTieKey); | ||
parsedParam = parsePropertyParam(next, element); | ||
} | ||
@@ -135,9 +136,9 @@ if (parsedParam.targetProperty in keysTest) { | ||
const fParams = parts[1].split(/\s*,\s*/).map(fp => { | ||
const origin = fp.split(':'); | ||
if (!origin.length || origin.length > 2 || !origin[0]) { | ||
const source = fp.split(':'); | ||
if (!source.length || source.length > 2 || !source[0]) { | ||
throw new Error('invalid function tied value: ' + fp); | ||
} | ||
const rawPath = origin.length > 1 ? origin[1] : ''; | ||
const rawPath = source.length > 1 ? source[1] : ''; | ||
return { | ||
tieKey: origin[0], | ||
tieKey: source[0], | ||
rawPath: rawPath, | ||
@@ -154,3 +155,3 @@ path: rawPath.split('.').filter(node => node) | ||
function parsePropertyParam(rawParam, element, scopeRootTieKey) { | ||
function parsePropertyParam(rawParam, element) { | ||
const parts = rawParam.split(PARAM_SPLITTER); | ||
@@ -164,14 +165,10 @@ | ||
// process 'from' part | ||
const origin = parts[0].split(':'); | ||
if (!origin.length || origin.length > 2 || !origin[0]) { | ||
const source = parts[0].split(':'); | ||
if (!source.length || source.length > 2 || !source[0]) { | ||
throw new Error(`invalid tie parameter '${rawParam}'; expected (example): "orders:0.address.street, orders:0.address.apt => title"`); | ||
} | ||
let tieKey = origin[0]; | ||
if (origin[0] === 'scope') { | ||
tieKey = getScopeTieKey(element, scopeRootTieKey); | ||
} | ||
let tieKey = source[0]; | ||
const rawPath = source.length > 1 ? source[1] : ''; | ||
const rawPath = origin.length > 1 ? origin[1] : ''; | ||
const result = new Parameter(tieKey, rawPath, rawPath.split('.').filter(node => node), parts[1], false, null); | ||
@@ -185,15 +182,2 @@ if (result.targetProperty === 'classList') { | ||
function getScopeTieKey(element, scopeRootTieKey) { | ||
let next = element, | ||
result = next[scopeRootTieKey]; | ||
while (!result && next.parentNode) { | ||
next = next.parentNode; | ||
if (next.host) { | ||
next = next.host; | ||
} | ||
result = next[scopeRootTieKey]; | ||
} | ||
return result || null; | ||
} | ||
function addChangeListener(element, changeListener) { | ||
@@ -264,7 +248,6 @@ const cen = obtainChangeEventName(element); | ||
function getRandomKey(length) { | ||
let result = '', i = length; | ||
const random = crypto.getRandomValues(new Uint8Array(length)); | ||
while (i) { | ||
i--; | ||
function getRandomKey(keyLength) { | ||
let result = '', i = keyLength; | ||
const random = crypto.getRandomValues(new Uint8Array(keyLength)); | ||
while (i--) { | ||
result += randomKeySource.charAt(randomKeySourceLen * random[i] / 256); | ||
@@ -271,0 +254,0 @@ } |
@@ -1,1 +0,1 @@ | ||
import{Observable}from"./object-observer.min.js";const DEFAULT_TIE_TARGET_PROVIDER="defaultTieTarget",CHANGE_EVENT_NAME_PROVIDER="changeEventName",PARAM_SPLITTER=/\s*=>\s*/,MULTI_PARAMS_SPLITTER=/\s*[,;]\s*/,DEFAULT_VALUE_ELEMENTS={INPUT:1,SELECT:1,TEXTAREA:1},DEFAULT_SRC_ELEMENTS={IMG:1,IFRAME:1,SOURCE:1},DEFAULT_HREF_ELEMENTS={A:1,ANIMATE:1,AREA:1,BASE:1,DISCARD:1,IMAGE:1,LINK:1,PATTERN:1,use:1},DEFAULT_CHANGE_ELEMENTS={INPUT:1,SELECT:1,TEXTAREA:1},randomKeySource="abcdefghijklmnopqrstuvwxyz0123456789",randomKeySourceLen=randomKeySource.length;export{DEFAULT_TIE_TARGET_PROVIDER,ensureObservable,getTargetProperty,extractViewParams,CHANGE_EVENT_NAME_PROVIDER,addChangeListener,delChangeListener,getPath,setPath,callViewFunction,getRandomKey};class Parameter{constructor(e,t,r,n,a,o){this.tieKey=e,this.rawPath=t,this.path=r,this.targetProperty=n,this.isFunctional=a,this.fParams=o,this.iClasses=null}}function ensureObservable(e){return e?Observable.isObservable(e)?e:Observable.from(e):Observable.from({})}function getTargetProperty(e){let t=e[DEFAULT_TIE_TARGET_PROVIDER];if(!t){const r=e.nodeName;t="INPUT"===r&&"checkbox"===e.type?"checked":r in DEFAULT_VALUE_ELEMENTS?"value":r in DEFAULT_SRC_ELEMENTS?"src":r in DEFAULT_HREF_ELEMENTS?"href":"textContent"}return t}function extractViewParams(e,t){const r=e.getAttribute("data-tie");return r?parseViewParams(r,e,t):null}function parseViewParams(e,t,r){const n=[],a={},o=e.split(MULTI_PARAMS_SPLITTER),s=o.length;let i,l,E,c=0;for(;c<s;c++)if((i=o[c])&&(l&&(l+=","+i),!(i.indexOf("(")>0&&(l=i).indexOf(")")<0)))try{l?(E=parseFunctionParam(l),l=null):E=parsePropertyParam(i,t,r),E.targetProperty in a?console.error(`elements's property '${E.targetProperty}' tied more than once; all but first dismissed`):(n.push(E),a[E.targetProperty]=!0)}catch(e){console.error(`failed to parse one of a multi param parts (${i}), skipping it`,e)}return n}function parseFunctionParam(e){const t=e.split(/[()]/),r=t[1].split(/\s*,\s*/).map(e=>{const t=e.split(":");if(!t.length||t.length>2||!t[0])throw new Error("invalid function tied value: "+e);const r=t.length>1?t[1]:"";return{tieKey:t[0],rawPath:r,path:r.split(".").filter(e=>e)}});if(!r.length)throw new Error(`functional tie parameter MUST have at least one tied argument, '${e}' doesn't`);return new Parameter(null,null,null,t[0],!0,r)}function parsePropertyParam(e,t,r){const n=e.split(PARAM_SPLITTER);1===n.length&&n.push(getTargetProperty(t));const a=n[0].split(":");if(!a.length||a.length>2||!a[0])throw new Error(`invalid tie parameter '${e}'; expected (example): "orders:0.address.street, orders:0.address.apt => title"`);let o=a[0];"scope"===a[0]&&(o=getScopeTieKey(t,r));const s=a.length>1?a[1]:"",i=new Parameter(o,s,s.split(".").filter(e=>e),n[1],!1,null);return"classList"===i.targetProperty&&(i.iClasses=Array.from(t.classList)),i}function getScopeTieKey(e,t){let r=e,n=r[t];for(;!n&&r.parentNode;)(r=r.parentNode).host&&(r=r.host),n=r[t];return n||null}function addChangeListener(e,t){const r=obtainChangeEventName(e);r&&e.addEventListener(r,t)}function delChangeListener(e,t){const r=obtainChangeEventName(e);r&&e.removeEventListener(r,t)}function obtainChangeEventName(e){let t=e[CHANGE_EVENT_NAME_PROVIDER];return t||e.nodeName in DEFAULT_CHANGE_ELEMENTS&&(t="change"),t}function getPath(e,t){if(!e)return null;const r=t,n=r.length;if(!n)return e;let a,o=e,s=0;for(;s<n-1;s++)if(null===(o=o[a=r[s]])||void 0===o)return o;return o[r[s]]}function setPath(e,t,r){if(!e)return;const n=t.length;let a,o,s=0;for(;s<n-1;s++)if((o=e[a=t[s]])&&"object"==typeof o)e=o;else if(void 0===o||null===o)e[a]={},e=e[a];else if("object"!=typeof o)return void console.error("setting deep path MAY NOT override primitives along the way");e[t[s]]=r}function callViewFunction(e,t,r){try{e[t].apply(e,r)}catch(n){console.error(`failed to call '${t}' of '${e}' with '${r}'`,n)}}function getRandomKey(e){let t="",r=e;const n=crypto.getRandomValues(new Uint8Array(e));for(;r;)r--,t+=randomKeySource.charAt(randomKeySourceLen*n[r]/256);return t} | ||
import{Observable as t}from"./object-observer.min.js";const e="defaultTieTarget",n="changeEventName",r=/\s*=>\s*/,i=/\s*[,;]\s*/,o={INPUT:1,SELECT:1,TEXTAREA:1},s={IMG:1,IFRAME:1,SOURCE:1},l={A:1,ANIMATE:1,AREA:1,BASE:1,DISCARD:1,IMAGE:1,LINK:1,PATTERN:1,use:1},a={INPUT:1,SELECT:1,TEXTAREA:1},c="abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",u=c.length;export{e as DEFAULT_TIE_TARGET_PROVIDER,h as ensureObservable,p as getTargetProperty,d as extractViewParams,n as CHANGE_EVENT_NAME_PROVIDER,E as addChangeListener,A as delChangeListener,v as getPath,T as setPath,w as callViewFunction,P as getRandomKey};class f{constructor(t,e,n,r,i,o){this.tieKey=t,this.rawPath=e,this.path=n,this.targetProperty=r,this.isFunctional=i,this.fParams=o,this.iClasses=null}}function h(e){return e?t.isObservable(e)?e:t.from(e):t.from({})}function p(t){let n=t[e];if(!n){const e=t.nodeName;n="INPUT"===e&&"checkbox"===t.type?"checked":e in o?"value":e in s?"src":e in l?"href":"textContent"}return n}function d(t){const e=t.getAttribute("data-tie");if(e){const n=function(t,e){const n=[],r={},o=t.split(i),s=o.length;let l,a,c,u=0;for(;u<s;u++)if((l=o[u])&&(a&&(a+=","+l),!(l.indexOf("(")>0&&(a=l).indexOf(")")<0)))try{a?(c=g(a),a=null):c=m(l,e),c.targetProperty in r?console.error(`elements's property '${c.targetProperty}' tied more than once; all but first dismissed`):(n.push(c),r[c.targetProperty]=!0)}catch(t){console.error(`failed to parse one of a multi param parts (${l}), skipping it`,t)}return n}(e,t);return n.length?n:null}return null}function g(t){const e=t.split(/[()]/),n=e[1].split(/\s*,\s*/).map(t=>{const e=t.split(":");if(!e.length||e.length>2||!e[0])throw new Error("invalid function tied value: "+t);const n=e.length>1?e[1]:"";return{tieKey:e[0],rawPath:n,path:n.split(".").filter(t=>t)}});if(!n.length)throw new Error(`functional tie parameter MUST have at least one tied argument, '${t}' doesn't`);return new f(null,null,null,e[0],!0,n)}function m(t,e){const n=t.split(r);1===n.length&&n.push(p(e));const i=n[0].split(":");if(!i.length||i.length>2||!i[0])throw new Error(`invalid tie parameter '${t}'; expected (example): "orders:0.address.street, orders:0.address.apt => title"`);let o=i[0];const s=i.length>1?i[1]:"",l=new f(o,s,s.split(".").filter(t=>t),n[1],!1,null);return"classList"===l.targetProperty&&(l.iClasses=Array.from(e.classList)),l}function E(t,e){const n=y(t);n&&t.addEventListener(n,e)}function A(t,e){const n=y(t);n&&t.removeEventListener(n,e)}function y(t){let e=t[n];return e||t.nodeName in a&&(e="change"),e}function v(t,e){if(!t)return null;const n=e,r=n.length;if(!r)return t;let i,o=t,s=0;for(;s<r-1;s++)if(null===(o=o[i=n[s]])||void 0===o)return o;return o[n[s]]}function T(t,e,n){if(!t)return;const r=e.length;let i,o,s=0;for(;s<r-1;s++)if((o=t[i=e[s]])&&"object"==typeof o)t=o;else if(void 0===o||null===o)t[i]={},t=t[i];else if("object"!=typeof o)return void console.error("setting deep path MAY NOT override primitives along the way");t[e[s]]=n}function w(t,e,n){try{t[e].apply(t,n)}catch(r){console.error(`failed to call '${e}' of '${t}' with '${n}'`,r)}}function P(t){let e="",n=t;const r=crypto.getRandomValues(new Uint8Array(t));for(;n--;)e+=c.charAt(u*r[n]/256);return e} |
@@ -1,3 +0,13 @@ | ||
import { getRandomKey } from './utils.js'; | ||
/** | ||
* lifecycle of the DOM elements: | ||
* - scan upon: init | add | remove | attribute changed | ||
* - add | del as view | ||
* | ||
* lifecycle as View of DT: | ||
* - when model change: update - lookup by tie + path | ||
* - when change event - update model | ||
*/ | ||
import { extractViewParams, getRandomKey } from './utils.js'; | ||
export class Views { | ||
@@ -7,11 +17,8 @@ constructor(dtInstance) { | ||
this.views = {}; | ||
this.scopes = {}; | ||
this.unscoped = []; | ||
} | ||
obtainTieViews(tieKey) { | ||
let tieViews = this.views[tieKey]; | ||
if (!tieViews) { | ||
tieViews = { _pathsCache: [] }; | ||
this.views[tieKey] = tieViews; | ||
} | ||
return tieViews; | ||
return this.views[tieKey] || (this.views[tieKey] = { _pathsCache: [] }); | ||
} | ||
@@ -23,41 +30,25 @@ | ||
addView(element, tieParams) { | ||
addView(element) { | ||
const tieParams = extractViewParams(element); | ||
if (!tieParams) { | ||
return null; | ||
} | ||
let tieParam, fParams, fp; | ||
let i = tieParams.length, l; | ||
let scopeRoot = false; | ||
const isRootScope = typeof element[this.dti.scopeRootTieKey] !== 'undefined'; | ||
let added = false; | ||
while (i) { | ||
tieParam = tieParams[--i]; | ||
while (i--) { | ||
tieParam = tieParams[i]; | ||
if (tieParam.isFunctional) { | ||
fParams = tieParam.fParams; | ||
l = fParams.length; | ||
while (l) { | ||
fp = fParams[--l]; | ||
if (!fp.tieKey) { | ||
continue; | ||
} | ||
this.seekAndInsertView(fp, element); | ||
added = true; | ||
if (!isRootScope && fp.targetProperty === 'scope') { | ||
scopeRoot = true; | ||
} | ||
while (l--) { | ||
fp = fParams[l]; | ||
this._seekAndInsertView(fp, element); | ||
} | ||
} else { | ||
if (!tieParam.tieKey) { | ||
continue; | ||
} | ||
this.seekAndInsertView(tieParam, element); | ||
added = true; | ||
if (!isRootScope && tieParam.targetProperty === 'scope') { | ||
scopeRoot = true; | ||
} | ||
this._seekAndInsertView(tieParam, element); | ||
} | ||
} | ||
if (added) { | ||
element[this.dti.paramsKey] = tieParams; | ||
} | ||
if (scopeRoot) { | ||
element[this.dti.scopeRootTieKey] = getRandomKey(16); | ||
} | ||
element[this.dti.paramsKey] = tieParams; | ||
return tieParams; | ||
} | ||
@@ -68,13 +59,13 @@ | ||
let i = tieParams.length, l; | ||
while (i) { | ||
tieParam = tieParams[--i]; | ||
while (i--) { | ||
tieParam = tieParams[i]; | ||
if (tieParam.isFunctional) { | ||
fParams = tieParam.fParams; | ||
l = fParams.length; | ||
while (l) { | ||
fp = fParams[--l]; | ||
this.seekAndRemoveView(fp, element); | ||
while (l--) { | ||
fp = fParams[l]; | ||
this._seekAndRemoveView(fp, element); | ||
} | ||
} else { | ||
this.seekAndRemoveView(tieParam, element); | ||
this._seekAndRemoveView(tieParam, element); | ||
} | ||
@@ -85,4 +76,40 @@ } | ||
seekAndInsertView(tieParam, element) { | ||
const tieKey = tieParam.tieKey; | ||
addScope(element) { | ||
let scopeKey = element.getAttribute('data-tie-scope'); | ||
if (!scopeKey) { | ||
// TODO: review this one | ||
element.setAttribute('data-tie-scope', getRandomKey(16)); | ||
return; | ||
} | ||
if (scopeKey in this.scopes && this.scopes[scopeKey] !== element) { | ||
throw new Error(`scope key '${scopeKey} already claimed by another element`); | ||
} | ||
if (this.scopes[scopeKey] === element) { | ||
return; | ||
} | ||
this.scopes[scopeKey] = element; | ||
for (const unscoped of this.unscoped) { | ||
if (element.contains(unscoped)) { | ||
this.addView(unscoped); | ||
this.unscoped.splice(this.unscoped.indexOf(unscoped), 1); | ||
} | ||
} | ||
} | ||
delScope() { | ||
throw new Error('not implemented'); | ||
} | ||
_seekAndInsertView(tieParam, element) { | ||
let tieKey = tieParam.tieKey; | ||
if (tieKey === 'scope') { | ||
tieKey = this._lookupClosestScopeKey(element); | ||
if (!tieKey) { | ||
this.unscoped.push(element); | ||
return; | ||
} else { | ||
tieParam.tieKey = tieKey; | ||
} | ||
} | ||
const rawPath = tieParam.rawPath; | ||
@@ -101,3 +128,3 @@ const tieViews = this.obtainTieViews(tieKey); | ||
seekAndRemoveView(tieParam, element) { | ||
_seekAndRemoveView(tieParam, element) { | ||
const tieKey = tieParam.tieKey; | ||
@@ -117,2 +144,3 @@ const rawPath = tieParam.rawPath; | ||
// TOOD: this function may become stateless, see remark below | ||
setViewProperty(elem, param, value) { | ||
@@ -127,2 +155,3 @@ const targetProperty = param.targetProperty; | ||
// TOOD: this function may become stateless, see remark below | ||
_unsafeSetProperty(elem, param, value, targetProperty) { | ||
@@ -132,2 +161,3 @@ if (targetProperty === 'href' && typeof elem.href === 'object') { | ||
} else if (targetProperty === 'scope') { | ||
// TODO: this is the ONLY line that refers to a state | ||
this.dti.ties.update(elem, value); | ||
@@ -165,2 +195,16 @@ } else if (targetProperty === 'classList') { | ||
} | ||
_lookupClosestScopeKey(element) { | ||
let tmp = element, result; | ||
do { | ||
result = tmp.getAttribute('data-tie-scope'); | ||
if (!result) { | ||
tmp = tmp.parentNode; | ||
if (tmp.host) { | ||
tmp = tmp.host; | ||
} | ||
} | ||
} while (!result && tmp && tmp.nodeType !== Node.DOCUMENT_NODE); | ||
return result; | ||
} | ||
} |
@@ -1,1 +0,1 @@ | ||
import{getRandomKey}from"./utils.min.js";export class Views{constructor(e){this.dti=e,this.views={}}obtainTieViews(e){let t=this.views[e];return t||(t={_pathsCache:[]},this.views[e]=t),t}deleteTieViews(e){delete this.views[e]}addView(e,t){let s,i,o,r,n=t.length,a=!1;const h=void 0!==e[this.dti.scopeRootTieKey];let c=!1;for(;n;)if((s=t[--n]).isFunctional)for(r=(i=s.fParams).length;r;)(o=i[--r]).tieKey&&(this.seekAndInsertView(o,e),c=!0,h||"scope"!==o.targetProperty||(a=!0));else{if(!s.tieKey)continue;this.seekAndInsertView(s,e),c=!0,h||"scope"!==s.targetProperty||(a=!0)}c&&(e[this.dti.paramsKey]=t),a&&(e[this.dti.scopeRootTieKey]=getRandomKey(16))}delView(e,t){let s,i,o,r,n=t.length;for(;n;)if((s=t[--n]).isFunctional)for(r=(i=s.fParams).length;r;)o=i[--r],this.seekAndRemoveView(o,e);else this.seekAndRemoveView(s,e);delete e[this.dti.paramsKey]}seekAndInsertView(e,t){const s=e.tieKey,i=e.rawPath,o=this.obtainTieViews(s);let r=o[i];r||(r=[],o[i]=r,o._pathsCache.push(i)),r.indexOf(t)<0&&r.push(t)}seekAndRemoveView(e,t){const s=e.tieKey,i=e.rawPath,o=this.views[s];if(o){const e=o[i];if(e){const s=e.indexOf(t);s>=0&&e.splice(s,1)}}}setViewProperty(e,t,s){const i=t.targetProperty;try{this._unsafeSetProperty(e,t,s,i)}catch(t){console.error(`failed to set '${i}' of '${e}' to '${s}'`,t)}}_unsafeSetProperty(e,t,s,i){if("href"===i&&"object"==typeof e.href)e.href.baseVal=s;else if("scope"===i)this.dti.ties.update(e,s);else if("classList"===i){const i=t.iClasses.slice(0);s&&(Array.isArray(s)&&s.length?s.forEach(e=>{i.indexOf(e)<0&&i.push(e)}):"object"==typeof s?Object.keys(s).forEach(e=>{const t=i.indexOf(e);s[e]?t<0&&i.push(e):t>=0&&i.splice(t,1)}):"string"==typeof s&&i.indexOf(s)<0&&i.push(s)),e.className=i.join(" ")}else e[i]=s}}; | ||
import{extractViewParams as e,getRandomKey as t}from"./utils.min.js";export class Views{constructor(e){this.dti=e,this.views={},this.scopes={},this.unscoped=[]}obtainTieViews(e){return this.views[e]||(this.views[e]={_pathsCache:[]})}deleteTieViews(e){delete this.views[e]}addView(t){const s=e(t);if(!s)return null;let i,o,r,n,h=s.length;for(;h--;)if((i=s[h]).isFunctional)for(n=(o=i.fParams).length;n--;)r=o[n],this._seekAndInsertView(r,t);else this._seekAndInsertView(i,t);return t[this.dti.paramsKey]=s,s}delView(e,t){let s,i,o,r,n=t.length;for(;n--;)if((s=t[n]).isFunctional)for(r=(i=s.fParams).length;r--;)o=i[r],this._seekAndRemoveView(o,e);else this._seekAndRemoveView(s,e);delete e[this.dti.paramsKey]}addScope(e){let s=e.getAttribute("data-tie-scope");if(s){if(s in this.scopes&&this.scopes[s]!==e)throw new Error(`scope key '${s} already claimed by another element`);if(this.scopes[s]!==e){this.scopes[s]=e;for(const t of this.unscoped)e.contains(t)&&(this.addView(t),this.unscoped.splice(this.unscoped.indexOf(t),1))}}else e.setAttribute("data-tie-scope",t(16))}delScope(){throw new Error("not implemented")}_seekAndInsertView(e,t){let s=e.tieKey;if("scope"===s){if(!(s=this._lookupClosestScopeKey(t)))return void this.unscoped.push(t);e.tieKey=s}const i=e.rawPath,o=this.obtainTieViews(s);let r=o[i];r||(r=[],o[i]=r,o._pathsCache.push(i)),r.indexOf(t)<0&&r.push(t)}_seekAndRemoveView(e,t){const s=e.tieKey,i=e.rawPath,o=this.views[s];if(o){const e=o[i];if(e){const s=e.indexOf(t);s>=0&&e.splice(s,1)}}}setViewProperty(e,t,s){const i=t.targetProperty;try{this._unsafeSetProperty(e,t,s,i)}catch(t){console.error(`failed to set '${i}' of '${e}' to '${s}'`,t)}}_unsafeSetProperty(e,t,s,i){if("href"===i&&"object"==typeof e.href)e.href.baseVal=s;else if("scope"===i)this.dti.ties.update(e,s);else if("classList"===i){const i=t.iClasses.slice(0);s&&(Array.isArray(s)&&s.length?s.forEach(e=>{i.indexOf(e)<0&&i.push(e)}):"object"==typeof s?Object.keys(s).forEach(e=>{const t=i.indexOf(e);s[e]?t<0&&i.push(e):t>=0&&i.splice(t,1)}):"string"==typeof s&&i.indexOf(s)<0&&i.push(s)),e.className=i.join(" ")}else e[i]=s}_lookupClosestScopeKey(e){let t,s=e;do{(t=s.getAttribute("data-tie-scope"))||(s=s.parentNode).host&&(s=s.host)}while(!t&&s&&s.nodeType!==Node.DOCUMENT_NODE);return t}}; |
{ | ||
"name": "data-tier", | ||
"version": "2.10.1", | ||
"version": "2.11.0", | ||
"description": "Tiny and fast two way (MV-VM) data binding framework for browser environments.", | ||
"type": "module", | ||
"main": "dist/data-tier.min.js", | ||
"browser": "dist/data-tier.min.js", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "node ./ci/tools/build-dist.js", | ||
"lint": "eslint -c ./ci/.eslintrc.json ./src/*.js ./tests/*.js ./ci/**/*.js", | ||
"test": "node ./node_modules/just-test/dist/tests-runner/run-tests.js --config=./tests/tests-run-config.json" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/gullerya/data-tier" | ||
}, | ||
"keywords": [ | ||
@@ -20,3 +35,7 @@ "two", | ||
], | ||
"homepage": "https://github.com/gullerya/data-tier", | ||
"author": { | ||
"name": "Guller Yuri", | ||
"email": "gullerya@gmail.com" | ||
}, | ||
"license": "ISC", | ||
"bugs": { | ||
@@ -26,13 +45,3 @@ "url": "https://github.com/gullerya/data-tier/issues", | ||
}, | ||
"license": "MIT", | ||
"files": [ | ||
"dist" | ||
], | ||
"main": "dist/data-tier.min.js", | ||
"browser": "dist/data-tier.min.js", | ||
"type": "module", | ||
"author": { | ||
"name": "Guller Yuri", | ||
"email": "gullerya@gmail.com" | ||
}, | ||
"homepage": "https://github.com/gullerya/data-tier", | ||
"funding": [ | ||
@@ -46,11 +55,2 @@ { | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/gullerya/data-tier" | ||
}, | ||
"scripts": { | ||
"build": "node ./ci/tools/build-dist.js", | ||
"lint": "eslint -c ./ci/.eslintrc.json ./src/*.js ./tests/*.js ./ci/**/*.js", | ||
"test": "node ./node_modules/just-test/dist/tests-runner/run-tests.js --config=./tests/tests-run-config.json" | ||
}, | ||
"dependencies": { | ||
@@ -60,3 +60,3 @@ "object-observer": "^4.0.2" | ||
"devDependencies": { | ||
"eslint": "^7.12.0", | ||
"eslint": "^7.13.0", | ||
"just-test": "2.3.2", | ||
@@ -63,0 +63,0 @@ "uglify-es": "^3.3.9", |
@@ -1,2 +0,3 @@ | ||
[![NPM](https://img.shields.io/npm/v/data-tier.svg?label=npm%20data-tier)](https://www.npmjs.com/package/data-tier)[![GitHub](https://img.shields.io/github/license/gullerya/data-tier.svg)](https://github.com/gullerya/data-tier) | ||
[![NPM](https://img.shields.io/npm/v/data-tier.svg?label=npm%20data-tier)](https://www.npmjs.com/package/data-tier) | ||
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](./license.md) | ||
@@ -19,2 +20,9 @@ [![Quality pipeline](https://github.com/gullerya/data-tier/workflows/Quality%20pipeline/badge.svg?branch=master)](https://github.com/gullerya/data-tier/actions?query=workflow%3A%22Quality+pipeline%22) | ||
* __2.11.0__ | ||
* implemented [Issue #53](https://github.com/gullerya/data-tier/issues/53) - version to the runtime | ||
* implemented [Issue #41](https://github.com/gullerya/data-tier/issues/41) - thoroughtly revised DOM processing mechanics | ||
* deprecated `addRootDocument` API, (use `addDocument` instead) | ||
* deprecated `removeRootDocument` API, (use `removeDocument` instead) | ||
* updated dependencies | ||
* __2.10.1__ | ||
@@ -29,6 +37,2 @@ * updated dependencies | ||
* __2.9.5__ | ||
* implemented [issue #49](https://github.com/gullerya/data-tier/issues/49) - fixed a new API: `DataTier.ties.update(key, newModel);` which misbehaved when non-existing **scoped** (element) key provided | ||
* upgraded dependencies | ||
## Loading the library | ||
@@ -35,0 +39,0 @@ |
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
54298
14
868
116