using javascript closures to create singletons to ensure the survival of a reference to an HTML bloc

J

Jake Barnes

Using javascript closures to create singletons to ensure the survival
of a reference to an HTML block when removeChild() may remove the last
reference to the block and thus destory the block is what I'm hoping to
achieve.

I've never before had to use Javascript closures, but now I do, so I'm
making an effort to understand them. I've been giving this essay a
re-read:

http://jibbering.com/faq/faq_notes/closures.html

Let me state the problem I face and the solution as I understand it. I
wanted to create an AJAX interface to some online software. As an
experiment, I built this page:

http://www.publicdomainsoftware.org/ajaxExperiment.htm

Click anywhere to get the controls and then click "Add paragraph" to
add some text. The communication box that appears is a bit like a modal
dialog box (except I don't yet freeze the use of everything else, so it
isn't really modal). I want to add blocks of HTML to this communication
box so that the user can add an appropriate input. When this box is
called again, I'll want to remove some of the HTML, and then add other
blocks of HTML.

Let's assume that all the HTML that I'll use starts off on the page.
The user arrives at the page and clicks somewhere to bring up the
controls, then clicks "Change Background color". The HTML block in the
div with the id of "backgroundColorPicker" is now placed in the
communication box. The user then picks a color and the background color
changes.

Let's suppose the user then clicks "Add paragraph". If I have a
reference to the communication box and I have a reference to the
"backgroundColorPicker" div, then I could do this:

refToCommunicationBox.removeChild(refToBackgroundColorPicker);

But now, as I understand it, the div "backgroundColorPicker" is gone
for good. It can not be brought back except by refreshing the page and
losing all of one's work. For the div "backgroundColorPicker" to remain
as a thing that my script can reference, then at least one reference to
it must survive. The easiest way for me to achieve this would be, at
the top of the page, to declare a reference to it in global space, like
this:

var refToBackgroundColorPicker =
document.getElementById("refToBackgroundColorPicker");

then I could create a function like this:

function getRefToBackgroundColorPicker() {
var newRefToBgColorPicker = refToBackgroundColorPicker.cloneNode();
return newRefToBgColorPicker;
}


Then I just have to use this function whenver I want a reference to the
div "backgroundColorPicker". This is basically a workable solution. The
only problem with it is that I need a global variable for every HTML
block that I'm going to have on my page. If I hope to someday build
sophisticated software, this might lead to a very large number of
global variables. Also, there is still the risk that I might
accidentally use removeChild() on the global variable, destroying the
only reference to that block of HTML, and thus removing it from the
page for good.

I realize, though, that there is another way to do this, and, I think,
it involves Javascript closures. But I'm not sure how to quite do it.
I'd like to create a reference that is pretty much indestructible. A
singleton. But, with my limited understanding, it seems like somehow
one must always end up with a global variable for the HTML to survive
after removeChild() has been used. This seems like a bit of a hack. I
suspect that my conceptual thinking here is fuzzy. Can anyone give me
a clue about what I've got wrong?


function storeReferencesToHTMLSafely() {
var arrayOfReferencesToHtmlBlocks = new Object();
var setAndGetReferenceToHtml = new Object();

setAndGetReferenceToHtml.get = function (idOfHtmlBlock) {
var htmlBlockRef = arrayOfReferencesToHtmlBlocks[idOfHtmlBlock];
if (!htmlBlockRef || htmlBlockRef == "" || htmlBlockRef == undefined)
{
arrayOfReferencesToHtmlBlocks[idOfHtmlBlock] =
document.getElementById(idOfHtmlBlock);
var htmlBlockRef = arrayOfReferencesToHtmlBlocks[idOfHtmlBlock];
}
var newHtmlBlockRef = htmlBlockRef.cloneNode(true);
return newHtmlBlockRef;
}

return setAndGetReferenceToHtml;
}

then I could have this line in global space:

var allHtmlReferencesObject = storeReferencesToHTMLSafely();


But this is the wrong way to do things? Is there a cleaner approach? I
admit my conceptual grasp of closures is weak, and what I've done here
seems like it could be done without closures. Somehow, I've failed to
achieve my goal.

Is there a way for arrayOfReferencesToHtmlBlocks to be a singleton and
for no variable to be needed in global space?

I suppose what I've done here does take me toward my goal. I'd never
use removeChild() on allHtmlReferencesObject, so that would at least
take me part way toward where I want to go - the risk of accidentally
removing the last reference to a block of HTML would be greatly
reduced.

Any thoughts, feedback, comments?
 
J

Jambalaya

Jake said:
Using javascript closures to create singletons to ensure the survival
of a reference to an HTML block when removeChild() may remove the last
reference to the block and thus destory the block is what I'm hoping to
achieve.

Then you have been misinformed. removeChild does not destroy the node.
In fact, it return a reference to it.
I've never before had to use Javascript closures, but now I do,

No you don't. Not for this scenario.
[snipped remainder of perceived problems]
But this is the wrong way to do things? Is there a cleaner approach?

Yes and yes. One possible solution would be to have one global variable
where you store your references to your various nodes.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title></title>
<style type="text/css">
*{padding:0;margin:0;}
html,body{height:100%;}
body{font-size:100.01%;}
..tool{border:1px solid gray;margin:10px;padding:10px;background:#eee;}
</style>
<script type="text/javascript">
function $(id){return document.getElementById(id)}

var registry = {
toggle: function(id){
if (id in registry){
registry[id].visible = !registry[id].visible;
if (registry[id].visible){
$('container').appendChild(registry[id]);
} else {
$('container').removeChild(registry[id]);
}
} else {
registry[id] = $('container').removeChild($(id));
registry[id].visible = false;
}
return false;
}
}
</script>
</head>
<body>
<div id="container">
<div id="toolbox">
<a href="jsreq.htm" onclick="return registry.toggle('tool1')">
Tool 1</a>
<a href="jsreq.htm" onclick="return registry.toggle('tool2')">
Tool 2</a>
<a href="jsreq.htm" onclick="return registry.toggle('tool3')">
Tool 3</a>
</div>
<div id="tool1" class="tool">This is tool one.</div>
<div id="tool2" class="tool">This is tool two.</div>
<div id="tool3" class="tool">This is tool three.</div>
</div>
</body>
</html>
 
R

Richard Cornford

Jake said:
Using javascript closures to create singletons to ensure the survival
of a reference to an HTML block when removeChild() may remove the last
reference to the block and thus destory the block is what I'm hoping
to achieve.

I've never before had to use Javascript closures, but now I do, so I'm
making an effort to understand them. I've been giving this essay a
re-read:

http://jibbering.com/faq/faq_notes/closures.html

Let me state the problem I face and the solution as I
understand it. I wanted to create an AJAX interface to

'AJAX interface' can mean all things to all men.
some online software. As an experiment, I built this page:

http://www.publicdomainsoftware.org/ajaxExperiment.htm

That is nice for you, and irrelevant to anyone reading news off-line.
Click anywhere to get the controls and then click
"Add paragraph" to add some text.

No thanks, re-connecting to the Internet just to look through reams of
unrelated javascript buried in masses of bloated HTML to find a tiny
pertinent fragment has not proved a worthwhile activity in the past.
The communication box that appears is a bit like a
modal dialog box ... . When this box is called again,
I'll want to remove some of the HTML, and then add
other blocks of HTML.

Let's assume that all the HTML that I'll use starts off
on the page.

Seems reasonable.
The user arrives at the page and clicks somewhere to
bring up the controls, then clicks "Change Background
color". The HTML block in the div with the id of
"backgroundColorPicker" is now placed in the communication
box. The user then picks a color and the background color
changes.

Let's suppose the user then clicks "Add paragraph". If I
have a reference to the communication box and I have a
reference to the "backgroundColorPicker" div, then I
could do this:

refToCommunicationBox.removeChild(refToBackgroundColorPicker);

But now, as I understand it, the div "backgroundColorPicker"
is gone for good.

So why do that? Why not re-append/insert the block of HTML to wherever
you got it from in the first place?
It can not be brought back except by refreshing the page
and losing all of one's work. For the div
"backgroundColorPicker" to remain as a thing that my script
can reference, then at least one reference to it must survive.
The easiest way for me to achieve this would be, at the top of
the page, to declare a reference to it in global space, like
this:

var refToBackgroundColorPicker =
document.getElementById("refToBackgroundColorPicker");

The odds of successfully retrieving a reference to a DOM node with -
getElementById - at the "top of the page" (prior to the parsing of the
HTML BODY) are low.
then I could create a function like this:

function getRefToBackgroundColorPicker() {
var newRefToBgColorPicker =
refToBackgroundColorPicker.cloneNode();
^
Without a true boolean argument the - cloneNode - method only clones the
Node upon which it is called, rather than the DOM branch it contains.
The likelihood is that the DIV node alone does not define the required
GUI interface.
return newRefToBgColorPicker;
}

If your dialog is model why would you need more than one instance of the
DOM branch that defines it (which is the implication of cloning it,
assuming you actually were cloning the whole branch)?

Incidentally, the group's FAQ references instruction on formatting
posted code in a way that is suitable for this context; preserving
indentation, avoiding automated line wrapping and so on. If you want
people to read your code you should present it in a form that is as
readable as possible.
Then I just have to use this function whenver I want
a reference to the div "backgroundColorPicker".

It actually returns a clone of the DOM Node not a reference to the
original.
This is basically a workable solution. The only problem with
it is that I need a global variable for every HTML block that
I'm going to have on my page. If I hope to someday build
sophisticated software, this might lead to a very large
number of global variables.

Oh yes, any system based upon the extensive use of global variables will
have become chaotic and un-maintainable by the time you get to 4000-odd
lines of code. It is a trap for the amateur, and you know enough not to
even start off down that path.
Also, there is still the risk that I might
accidentally use removeChild() on the global variable,

Ah, do I perceive a misconception about the relationship between the
DOM, and DOM methods, and javascript (from this, combined with the
unexpected cloning above)? The act of removing a Node from the DOM
with - removeChild - is not going to impact the value of a global
variable. If the global variable refers to the Node before it is removed
it will refer to it after it is removed from the DOM, and the existence
of the reference will prevent the garbage collection of the Node. Having
said that, DOMs don't seem to like Nodes to be hanging around in a freed
state for long. They are much happier if you move Nodes from place to
place in a DOM or from one document to a related document fragment and
back.
destroying the only reference to that block of HTML,

The DOM structure refers to the Node, and the global variable refers to
the Node. Using - removeChild - will remove the Node from the DOM only.
and thus removing it from the
page for good.

I realize, though, that there is another way to do this,

Probably half a dozen other ways.
and, I think, it involves Javascript closures.

For a couple of them, yes. Though you want to watch out for the circular
references including DOM Nodes problem on IE browsers as if you hide
references to DOM Nodes away in closures they may become difficult to
explicitly free later, if the need arises.
But I'm not sure how to quite do it.
I'd like to create a reference that is pretty much
indestructible.

Closures are the nearest you will get to that.
A singleton.

I am not user that is a useful label here.
But, with my limited understanding, it seems like somehow
one must always end up with a global variable for the HTML
to survive after removeChild() has been used.

As - removeChild - only acts upon references to a Node within the DOM
structure any references to the same node in javascript structures will
not be altered.
This seems like a bit of a hack. I suspect that my
conceptual thinking here is fuzzy. Can anyone give me
a clue about what I've got wrong?


function storeReferencesToHTMLSafely() {
var arrayOfReferencesToHtmlBlocks = new Object();
var setAndGetReferenceToHtml = new Object();

setAndGetReferenceToHtml.get = function (idOfHtmlBlock) {
var htmlBlockRef = arrayOfReferencesToHtmlBlocks[idOfHtmlBlock];
if (!htmlBlockRef || htmlBlockRef == "" ||
htmlBlockRef == undefined)

The NOT (- ! -) operator in the expression - !htmlBlockRef -
type-converts its operand to boolean and then evaluates as true for
false and false for true. The empty string type-converts to boolean
false, so the expression - !htmlBlockRef - will be true when -
htmlBlockRef == "" - is true. So this expression is worthless in your
logical OR expression.

Similarly - undefined - type-converts to boolean false so the
expression - !htmlBlockRef - is also true when - htmlBlockRef ==
undefined - is true. Making the third expression in your logical OR
worthless, and so the entire logical OR is redundant (The first
expression will always be true whenever either of the other two is true)
and can be replaced with:-

if(!htmlBlockRef){
// if - htmlBlockRef - is either an empty string or undefined we
come in here anyway.
// we also come in here is - htmlBlockRef - is null and numeric zero
or NaN.
}

And references to the global - undefined - variable were one of the more
recent additions to the language (Netscape 4+, IE 5+ and error producing
in preceding implementations), and have proved the source of much
confusion, with - if(typeof x != undefined){ ... - being a commonly
recurring mistake while learning the language (it is always true even
if - x - is undeclared and/or undefined as typeof always evaluates as a
non-empty string).
{
arrayOfReferencesToHtmlBlocks[idOfHtmlBlock] =
document.getElementById(idOfHtmlBlock);
var htmlBlockRef = arrayOfReferencesToHtmlBlocks[idOfHtmlBlock];
^^^
Multiple declarations of the same variable are pointless (though
harmless).
}
var newHtmlBlockRef = htmlBlockRef.cloneNode(true);
^^^ ^^^^
Dito. ||||
////
Deep cloning this time, but why clone at all?

However, if you always clone the original DOM Node, and never call -
removeChild - or move on the original (it always stays at its original
location in the DOM) then why it is a problem to be throwing away
references to the clones, as the original can then be re-cloned? (It
would not be very efficient to do that (with the inefficient browser
garbage collection you would probably be looking at quite steep and
one-way increases in memory consumption)).
return newHtmlBlockRef;
}

return setAndGetReferenceToHtml;
}

then I could have this line in global space:

var allHtmlReferencesObject = storeReferencesToHTMLSafely();


But this is the wrong way to do things? Is there a
cleaner approach?

There is a great deal here that needs questioning/clarifying,
particularly all the cloning. There probably is a better approach to the
bigger problem, but that may not necessarily remove this particular
issue.
I admit my conceptual grasp of closures is weak, and what
I've done here seems like it could be done without closures.

If you look at the cited description of closures you will see that the
mechanism in javascript is based entirely around a structure of
inter-referring objects (the scope chains and the function object that
refer to them). Any language that has references to objects (or
pointers) can implement a parallel structure and do in another way what
can be done with closures, and this is true of javascript as well. The
advantage of using closures in a language that has closures is that the
mechanism is built-in, available and relatively cheep to employ (rather
than programming your own).
Somehow, I've failed to
achieve my goal.

Is there a way for arrayOfReferencesToHtmlBlocks to be
a singleton and for no variable to be needed in global space?

You have already done that, so long as - storeReferencesToHTMLSafely -
is only called once. You can guarantee that the equivalent is only
called once by instead using the execution of a function expression to
create the closure:-

var safeStoreForHTMLReferences = (function(){
var refToHtmlBlocks = {};
return ({
get:function(id){
var htmlBlockRef;
if(
(
(htmlBlockRef = refToHtmlBlocks[id])||
(
(htmlBlockRef =
(refToHtmlBlocks[id] = document.getElementById(id))
)
)
)&&
(htmlBlockRef.cloneNode)
){
return htmlBlockRef.cloneNode(true);
}
return null;
}
});
})();

called as:-

var x = safeStoreForHTMLReferences.get('someId');
I suppose what I've done here does take me toward my goal.
I'd never use removeChild() on allHtmlReferencesObject,

It would error if you did at that object does not implement the method,
and cannot be used as an argument for the method of objects that do.
so that would at least take me part way toward where I
want to go - the risk of accidentally removing the last
reference to a block of HTML would be greatly reduced.

Any thoughts, feedback, comments?

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top