The Problem
Given a generic array of strings, create an unordered list of items where each item contains the text of the relative array index without creating a singe leak or reference during the whole procedure.As plus, make each item in the list clickable so that an alert with current text content occur once clicked.
The assumption is that we are in a standard W3C environment with ES5+ support.
The Reason
I think is one of the most common tasks in Ajax world. We receive an array with some info, we want to display this info to the user and we want to react once the user interact with the list.If we manage to avoid references we are safer about leaks. If we manage to optimize the procedure, we are also safe about memory consumption over a simplified DOM logic ...
How would you solve this ? Give it a try, then come back for my solution.
The Solution
Here mine:
document.body.appendChild(
/* input */["a","b","c"].map(
function (s, i) {
this.appendChild(
document.createElement("li")
).textContent = s;
return this;
},
document.createElement("ul")
)[0]
).addEventListener(
"click",
function (e) {
if (e.target.nodeName === "LI") {
alert(e.target.textContent);
}
},
false
);
Did you solve it the same way ? :)
9 comments:
Nice post Andrea :)
I think that your code has a "leak": you are wasting an array when you use map. It's better with reduce:
document.body.appendChild(
['a','b','c'].reduce(
function (container,str) {
container.appendChild(document.createElement('LI'))
.textContent = str;
return container;
},
document.createElement('UL')
)
).addEventListener('click',
function (e) {
if (e.target.nodeName === 'LI') {
alert(e.target.textContent);
}
},
false
);
not a leak, ul is not referenced and the array will be an array of references with zero references count and discarded immediately as soon as accessed in index 0 ;)
First, did you see the quotation marks around "leak"? :)
Second, with map you create a new transient array, a new task for the garbage collector. Let's replace map by reduce and reduce the work to do by that poor engine ;)
not sure reduce makes GC life any easier ... is the created array that goes in GC, not the DOM node referenced itself, since the array references counter is zero. It does not matter for this specific task though.
It's a map anti-pattern.
You could argue that saving is infinitesimal, but the saving exists.
/* imho its cleanest way from point of view FP */
/* i know that is not super optimal :) */
document.body.appendChild(
document.createElement("ul").appendChild(
/* classic map-reduce */
/* input */['a', 'b', 'c'].map(
function(s, i) {
const li = document.createElement("li")
li.textContent = s
return li
}
)
/* linear, not homogeneous */
.reduce(
function(df, li){ df.appendChild(li); return df },
document.createDocumentFragment()
)
)
).addEventListener(
"click",
function(e) {
if (e.target.nodeName === "LI") {
alert(e.target.textContent);
}
},
false
)
Very interesting bga ;) but I have a question: is it really necessary to create a DocumentFragment? Note that the UL element hasn't yet been hooked on his father.
On the other hand, if f and g has no free variables and they don't use the this keyword, we can apply "classical" deforestation to that classic map-reduce construction and then we obtain the version above:
array.map(f).reduce(g,z)
is equivalent to
array.reduce(g.compose(f),z)
Here's another solution, using innerHTML to build the list items.
(/*convert array into clickable set of LI*/
function(parent, element, arr, eventHdlr){
element.addEventListener('click',eventHdlr,false);
element.innerHTML = '<li>' + arr.join('</li><li>') + '</li>';
parent.appendChild(element);
}
)(
document.body,
document.createElement('ul'),
['a','b','c'],
function(e){ if( e.target.nodeName==='LI' ){ alert(e.target.textContent); } }
);
Hi...Thanks for the solution. I think including a library and changing its properties. I don't think that's a good idea. If the library changes, your augmenting will probably fail or cause trouble; the defined interfaces of that library on the other hand probably won't change.
Thanks,
Jackie
Post a Comment