Bug 982322 - Part 1: Use a resolver object to only descend once. r=ochameau

This commit is contained in:
"J. Ryan Stinnett" 2014-03-17 11:39:00 +01:00
parent 380a642bcc
commit e9e8df3ffd
2 changed files with 111 additions and 118 deletions

View File

@ -57,6 +57,7 @@ const NOT_FOUND_STRING = "n/a";
*/
function Template(root, store, l10nResolver) {
this._store = store;
this._rootResolver = new Resolver(this._store.object);
this._l10n = l10nResolver;
// Listeners are stored in Maps.
@ -84,39 +85,6 @@ Template.prototype = {
this._doc = null;
},
_resolvePath: function(path, defaultValue=null) {
// From the store, get the value of an object located
// at @path.
//
// For example, if the store is designed as:
//
// {
// foo: {
// bar: [
// {},
// {},
// {a: 2}
// }
// }
//
// _resolvePath("foo.bar.2.a") will return "2".
//
// Array indexes are not surrounded by brackets.
let chunks = path.split(".");
let obj = this._store.object;
for (let word of chunks) {
if ((typeof obj) == "object" &&
(word in obj)) {
obj = obj[word];
} else {
return defaultValue;
}
}
return obj;
},
_storeChanged: function(event, path, value) {
// The store has changed (a "set" event has been emitted).
@ -200,22 +168,18 @@ Template.prototype = {
set.add(element);
},
_processNode: function(element, rootPath="") {
_processNode: function(element, resolver=this._rootResolver) {
// The actual magic.
// The element has a template attribute.
// The value is supposed to be a JSON string.
// rootPath is the prefex to the path used by
// these elements (if children of template-loop);
// resolver is a helper object that is used to retrieve data
// from the template's data store, give the current path into
// the data store, or descend down another level of the store.
// See the Resolver object below.
let e = element;
let str = e.getAttribute("template");
if (rootPath) {
// We will prefix paths with this rootPath.
// It needs to end with a dot.
rootPath = rootPath + ".";
}
try {
let json = JSON.parse(str);
// Sanity check
@ -225,7 +189,7 @@ Template.prototype = {
if (json.rootPath) {
// If node has been generated through a loop, we stored
// previously its rootPath.
rootPath = json.rootPath;
resolver = this._rootResolver.descend(json.rootPath);
}
// paths is an array that will store all the paths we needed
@ -240,16 +204,16 @@ Template.prototype = {
!("path" in json)) {
throw new Error("missing property");
}
e.setAttribute(json.name, this._resolvePath(rootPath + json.path, NOT_FOUND_STRING));
paths.push(rootPath + json.path);
e.setAttribute(json.name, resolver.get(json.path, NOT_FOUND_STRING));
paths.push(resolver.rootPathTo(json.path));
break;
}
case "textContent": {
if (!("path" in json)) {
throw new Error("missing property");
}
e.textContent = this._resolvePath(rootPath + json.path, NOT_FOUND_STRING);
paths.push(rootPath + json.path);
e.textContent = resolver.get(json.path, NOT_FOUND_STRING);
paths.push(resolver.rootPathTo(json.path));
break;
}
case "localizedContent": {
@ -258,17 +222,17 @@ Template.prototype = {
throw new Error("missing property");
}
let params = json.paths.map((p) => {
paths.push(rootPath + p);
let str = this._resolvePath(rootPath + p, NOT_FOUND_STRING);
paths.push(resolver.rootPathTo(p));
let str = resolver.get(p, NOT_FOUND_STRING);
return str;
});
e.textContent = this._l10n(json.property, params);
break;
}
}
if (rootPath) {
if (resolver !== this._rootResolver) {
// We save the rootPath if any.
json.rootPath = rootPath;
json.rootPath = resolver.path;
e.setAttribute("template", JSON.stringify(json));
}
if (paths.length > 0) {
@ -281,7 +245,7 @@ Template.prototype = {
}
},
_processLoop: function(element, rootPath="") {
_processLoop: function(element, resolver=this._rootResolver) {
// The element has a template-loop attribute.
// The related path must be an array. We go
// through the array, and build one child per
@ -296,9 +260,7 @@ Template.prototype = {
!("childSelector" in json)) {
throw new Error("missing property");
}
if (rootPath) {
json.arrayPath = rootPath + "." + json.arrayPath;
}
let descendedResolver = resolver.descend(json.arrayPath);
let templateParent = this._doc.querySelector(json.childSelector);
if (!templateParent) {
throw new Error("can't find child");
@ -306,7 +268,7 @@ Template.prototype = {
template = this._doc.createElement("div");
template.innerHTML = templateParent.innerHTML;
template = template.firstElementChild;
let array = this._resolvePath(json.arrayPath, []);
let array = descendedResolver.get("", []);
if (!Array.isArray(array)) {
console.error("referenced array is not an array");
}
@ -315,11 +277,11 @@ Template.prototype = {
let fragment = this._doc.createDocumentFragment();
for (let i = 0; i < count; i++) {
let node = template.cloneNode(true);
this._processTree(node, json.arrayPath + "." + i);
this._processTree(node, descendedResolver.descend(i));
fragment.appendChild(node);
}
this._registerLoop(json.arrayPath, e);
this._registerLoop(json.arrayPath + ".length", e);
this._registerLoop(descendedResolver.path, e);
this._registerLoop(descendedResolver.rootPathTo("length"), e);
this._unregisterNodes(e.querySelectorAll("[template]"));
e.innerHTML = "";
e.appendChild(fragment);
@ -328,7 +290,7 @@ Template.prototype = {
}
},
_processFor: function(element, rootPath="") {
_processFor: function(element, resolver=this._rootResolver) {
let e = element;
try {
let template;
@ -339,10 +301,6 @@ Template.prototype = {
throw new Error("missing property");
}
if (rootPath) {
json.path = rootPath + "." + json.path;
}
if (!json.path) {
// Nothing to show.
this._unregisterNodes(e.querySelectorAll("[template]"));
@ -350,6 +308,7 @@ Template.prototype = {
return;
}
let descendedResolver = resolver.descend(json.path);
let templateParent = this._doc.querySelector(json.childSelector);
if (!templateParent) {
throw new Error("can't find child");
@ -358,10 +317,10 @@ Template.prototype = {
content.innerHTML = templateParent.innerHTML;
content = content.firstElementChild;
this._processTree(content, json.path);
this._processTree(content, descendedResolver);
this._unregisterNodes(e.querySelectorAll("[template]"));
this._registerFor(json.path, e);
this._registerFor(descendedResolver.path, e);
e.innerHTML = "";
e.appendChild(content);
@ -371,21 +330,55 @@ Template.prototype = {
}
},
_processTree: function(parent, rootPath="") {
_processTree: function(parent, resolver=this._rootResolver) {
let loops = parent.querySelectorAll(":not(template) [template-loop]");
let fors = parent.querySelectorAll(":not(template) [template-for]");
let nodes = parent.querySelectorAll(":not(template) [template]");
for (let e of loops) {
this._processLoop(e, rootPath);
this._processLoop(e, resolver);
}
for (let e of fors) {
this._processFor(e, rootPath);
this._processFor(e, resolver);
}
for (let e of nodes) {
this._processNode(e, rootPath);
this._processNode(e, resolver);
}
if (parent.hasAttribute("template")) {
this._processNode(parent, rootPath);
this._processNode(parent, resolver);
}
},
};
function Resolver(object, path = "") {
this._object = object;
this.path = path;
}
Resolver.prototype = {
get: function(path, defaultValue = null) {
let obj = this._object;
if (path === "") {
return obj;
}
let chunks = path.toString().split(".");
for (let word of chunks) {
if ((typeof obj) == "object" &&
(word in obj)) {
obj = obj[word];
} else {
return defaultValue;
}
}
return obj;
},
rootPathTo: function(path) {
return this.path ? this.path + "." + path : path;
},
descend: function(path) {
return new Resolver(this.get(path), this.rootPathTo(path));
}
};

View File

@ -22,17 +22,17 @@
<span template='{"type":"textContent","path":"title"}'>ttt</span>
<span title="ttt" template='{"type":"attribute","name":"title","path":"title"}'></span>
<span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo_l10n/bar_l10n</span>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop."}'>meh</span></div>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh</span></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -42,17 +42,17 @@
<span template='{"type":"textContent","path":"title"}'>xxx</span>
<span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
<span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo2_l10n/bar_l10n</span>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop."}'>meh2</span></div>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh2</span></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -64,14 +64,14 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -83,29 +83,29 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2."}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3."}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.4."}'>xx4</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.4."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.4."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.4"}'>xx4</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.4"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.4"}'>b</span>
</div>
</div>
</div>
@ -117,24 +117,24 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2."}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3."}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
</div>
</div>
</div>