Handling DOM events in JavaScript classes - javascript

(Feel free to reword the title; I find this hard to put into words.)
I've created a JavaScript "class" (for lack of a better word; I know JS isn't class-based) that represents a textarea and a div.
Every time the textarea's value is changed, I want the div's content to update accordingly, so I thought to assign an onkeyup event handler to the textarea — however, that turned out to be more problematic than I thought.
Here's the relevant part of my HTML:
<div id="container"></div>
<script src="MyTextarea.js"></script>
<script>
var ta = new MyTextarea('container');
</script>
And here's the JS I've written so far:
function MyTextarea(id) {
this.textarea = document.createElement('textarea');
this.box = document.createElement('div');
var container = document.getElementById(id);
container.appendChild(this.textarea);
container.appendChild(this.box);
this.textarea.onkeyup = this._synchronize;
}
MyTextarea.prototype._synchronize = function () {
this.box.innerHTML = this.textarea.value;
};
Instead of working, this insists on throwing a "this.textarea" is undefined" error. It turns out that — much to my surprise — in the _synchronize function, this doesn't refer to the MyTextarea object, but instead to the textarea element itself. I'm puzzled as to why that would be.
What am I doing wring and/or not getting here? Is it because I'm doing this within a "class"? How can I achieve this instead?

You are losing context when you assign event handler as direct function reference. So in other words, instead of MyTextarea instance object this points to HTMLTextAreaElement object.
There are multiple solutions, for example you can bind context explicitly:
this.textarea.onkeyup = this._synchronize.bind(this);
Or you could do it old-school way:
var self = this;
this.textarea.onkeyup = function() {
self._synchronize();
};

Related

WinJS won't allow me to attach event handlers to dynamically created HTML elements

I first tried the easy yet disapproved way of creating elements with innerHTML:
// "Alarms" is a div that will contain the new divs
document.getElementById("Alarms").innerHTML += "<div onclick=\"watevr\">";
But Visual Studio told me I'm not allowed to insert JavaScript like that, then I tried the right/other way:
var alarms = document.getElementById("Alarms");
var div = document.createElement("div");
div.innerHTML = "I'm the new div!";
div.onclick = "watevr";
alarms.appendChild(div);
Now whenever I run my code I get this error:
0x800a138f - JavaScript runtime error: Unable to set property 'innerHTML' of undefined or null reference
and the code breaks at alarms.appendChild(div);
I've tried div.addEventListener("click", function(){watevr}, false); instead of onclick to no avail.
Interestingly, removing div.onclick = "watevr"; fixes the problem completely.
What am I doing wrong here?
First of all try to put all your code in a separate file then create a self execute fucntion like this:
(function(){
var MyApp {
init : function(){
this.renderMyDiv();
},
renderMyDiv: function(){
var alarms = document.getElementById("Alarms");
var element = document.createElement('div');
element.className = 'superDiv';
element.innerHTML = 'Im the div';
alarms.appendChild(element);
this.addDivEvent('superDiv');
}
addDivEvent: function(className){
var element = document.getElementsByClassName(className);
for(var i = 0, j=element.length; i<j; i++){
element[i].addEventListener("click", function(){
//TO-DO
}
}
}
}
MyApp.init();
})();
This is the old way to do it but you can also accomplish this vía jquery.
Problem 1 - You don't want to attach listeners via attributes anyway, so don't bother.
Problem 2 - You seem to have a bug causing getElementById to return null or undefined. From your text it looks like you're saying createElement returned null, but that shouldn't be possible so I'm assuming you just pasted the wrong code and/or error.
Problem 3 - You're using innerHTML in the second block to add non-HTML text.
Problem 4 - You're assigning a string to the onclick property (it should be a function).
Using createElement, appendChild, and either onclick or addEventListener is the right approach. Don't adding to innerHTML unless you really need to insert HTML. Since the string doesn't contain anything dynamic I don't see why it would mark the element as u trusted, but maybe it does... If that is the case, you can either:
A) Use innerText.
B) Create more elements directly or using a template or fragment.
C) Call toStaticHTML on the string before assigning to innerHTML.
4) Wrap the call that adds the element to the DOM in an MSApp.execUnsafeLocalFunction call.
But you'll need to fix your null dereference bugs first.

Class variables are not visible inside element event?

I am hoping to create a web application, this means that the HTML elements will have to be pretty dynamic as they will be being created and moved around being handled by different other elements a lot.
I therefore decided to use classes - with a lot of success, until the point of handling events..This is the class in suspicion that Chrome tells me confuses it with a variable not being defined.
function SpanInputPair(element) {
span = document.createElement("span");
this.Span = getNextInputID();
span.style.display = "none";
span.id = this.Span;
element.appendChild(span);
input = document.createElement("input");
input.type = "text";
this.Input = getNextInputID(); // request a new ID for this element and save into this class
input.id = this.Input; // Attach it onto the element for future finding :/ (slow)
element.appendChild(input);
input.focus();
span.onclick = function() {
var input = getInput(this.Input); // this.Input cannot be seen?
var span = getInput(this.Span);
input.value = span.innerHTML;
toggleDisplay(input);
toggleDisplay(span);
input.focus();
}
input.onblur...
The line "var input = getInput(this.Input);" is the issue as Chrome is telling me it doesn't see "this.Input" anymore. I know this because I can run this line and replace "this.Input" with the real value and it returns the element fine.
As you can see I have it creating a text box and span dynamically and setting their IDs for future use, this is because I previously tried to save the element itself in the class and use that inside the event - same issue, so I tried to find it in the document each event instead by using document.getElementByID().
Weirdly also it does not seem to focus the text box it just created (if that may be part of the issue too I don't know).
This project was created entirely from nothing and uses no libraries.
So my question is, can you please explain why the element's event cannot see any variables in the class that created it? And how could I fix this?
I have not yet seen this posted as many people are using a library such as JQuery and/or not using classes, is question is specific to event handling with help from internal variables of a class.
Although it may not make too much sense, it is normal in JavaScript because the value of this has changed within the local function you have declared. Therefore, you must understand properly how to declare and use functions in JavaScript:
Functions in JavaScript has something called context, which just means that every function is bounded to a specific object. If we declare a function in your script, it will be bounded to the global object (which is window in browsers).
However, if we try to declare a method (a function that belongs to an object),
this will be a magic variable that represents the object itself. Everytime you invoke this.my_property, you would get the value of the property of your object, either an "attribute" or a "method".
Therefore, the function's context can be changed, as it happens with event handlers. In your scenario, event handlers are functions whose context has changed, so everytime you access to this, its value will be the HTMLElement object that receives the event when you click on it.
To solve this problem, you can declare an auxiliary variable and use it inside of the handler. The common practice followed by the community is:
var that = this; // Also, the people use 'self' instead of 'that'
span.onclick = function() {
var input = that.input;
// ...
};
Using this workaround, you will not have any problem.
The value of 'this' inside the click function is the element clicked. Here that would be the <span> you just created.
You are wanting the value of the this back when you defined the click function. (The question might also be: What is the value of 'this' when the SpanInputPair function is called? You may want to consider that.)
You can do that by something like this which adds it to the closure:
var thisInput = this.Input;
...
span.onclick = function() {
var input = getInput(thisInput);
You are going to have the same problem with this.Span on the next line.

getElementById of something inside a function, but from outside

I keep learning very simple things about functions, returns, id and everything, so I ran into another problem that looks simple, but I cannot understand why it happens. Check this code:
function test() {
var text = document.createTextNode("Hello");
text.id = "t";
}
var whatIjustwrote = window.document.getElementById("t");
alert(whatIjustwrote);​
Does the getElementById has restrictions to look only for global items? What would be the way to make that alert output the text node inside the function?
Thank you for any comment. The last few days asking things here I have been learning quite a lot!
JSFiddle
Firstly, getElementById will only return an element, and you're creating a text node.
Secondly, it will only return an element that has been added to the DOM. The node you create doesn't get added to the DOM, so it wouldn't be found even if it could be.
Finally, you don't actually call the test function, so the text node isn't even created in memory.
Here's an updated fiddle that demonstrates getElementById actually working:
function test() {
var text = document.createElement("span"); //Create an element
text.innerHTML = "Hello";
text.id = "t";
document.body.appendChild(text); //Add it to the DOM
}
test(); //Invoke the function (so the element actually gets created)
var yourElement = document.getElementById("t"); //Get reference to element
getElementById does only search for element nodes. You did create a text node, which has neither attributes nor an id - you just added a custom property to the JS object. Also, you did not append your node to the document, so it couldn't have been found in the DOM tree.
You might want to read an introduction to the DOM (at MDN), the introduction at quirksmode.org or even the W3 standard itself (especially the introduction section)
function test() {
var elem = document.createElement("span"); // Create an element
var text = document.createTextNode("Hello"); // Create a textnode
elem.appendChild(text); // add text to the element
elem.id = "t"; // assign id to the element
document.body.appendChild(elem); // Add it to the DOM
}
test();
var yourElement = document.getElementById("t"); // Get the element from the DOM
alert(yourElement.textContent); // alerts "Hello"
// you also could have alerted yourElement.firstChild.data - the content of the
// textnode, but only if you had known that yourelement really has a firstchild
(Demo at jsfiddle.net)
A couple of points that come to mind..
1) You cant give a textNode an id attribute (you're actually giving it a new member variable named id)
2) To find an element it must exist in the document's DOM
Do this instead:
var mSpan = document.createElement('span');
mSpan.id = 't';
mSpan.appendChild( document.createTextNode('Hello') );
document.body.appendChild(mSpan);
var whatIjustwrote = window.document.getElementById("t");
alert(whatIjustwrote.innerText);
Does the getElementById has restrictions to look only for global
items?
The answer is no. First you have to define global items anyways. Anything that is attached to the DOM is in fact global, and in terms of global javascript objects there is only one, window, in the case of a browser. You are creating a function but you're never executing it.
In addition the text node cannot actually have an id or any other attribute. You need an element for this, so even if you execute the function you would still get null. Also creating a node does not attach is to the DOM, so you won't be able to access it even if this isn't a text node.
I have updated your fiddle.

getElementById Where Element is dynamically created at runtime

I have created an object at runtime by using innerHTML tag, now I want to access this element by using getElementById, when I accessed the element its return NULL value. Kindly suggest me any direction so that I can acheive this,
Here is the following code hint which I am using
In HTML
<div id="web">
<object id="test"></object>
</div>
In JS
document.getElementById("web").innerHTML="<object id='test2'></object>";
.
.
var obj = document.getElementById("test2");
Here obj return null value.
Did you assign an id to the freshly created element? Did you insert the element into the document tree (using appendChild or insertBefore)? As long as the element is not inserted into the DOM, you can't retrieve it using document.getElementById.
Example of element creation:
var myDiv = document.createElement('div');
myDiv.id = 'myDiv';
document.body.appendChild(myDiv);
document.getElementById('myDiv').innerHTML = 'this should have worked...';
[edit] Given the later supplied code, a third question emerges: is your script located at the bottom of your html page (right before the closing </body> tag)? If it's in the header of the document, your scripting may be running before the document tree is completely rendered. If your script has to be in the header of the document, you could use a load handler to run it after rendering of the document:
window.onload = function(){
document.getElementById("web").innerHTML='<object id="test2"></object>';
// [...]
var obj = document.getElementById('test2');
};
To add an element using JavaScript, you need to do 2 things.
Create the element
var element = document.createElement(tagName);
Add it to the dom
document.body.insertBefore(selector, element);
or
document.getElementByID(selector).appendChild(element);
More info here: MDN
If a DOM node is dynamically created, then there's no need EVER to find it again with document.getElementById().
By creating the node in the right way, you can keep a reference to it as a javascript variable, which can be used anywhere within scope.
For example:
window.onload = function(){
var myDiv = document.createElement('div');
document.body.appendChild(myDiv);
//and now a function that's called in response to some future event
function whatever(){
...
myDiv.style.color = 'red';
...
}
};
Note: The inner function (called at some point(s) future) has access to the myDiv variable because the outer function forms a "closure", within which variables are kept alive and accessible, even though the outer function has completed and returned.

JavaScript mechanism for holding onto a value from a user action

I've created a JavaScript object to hold onto a value set by a user checking a checbox in a ColorBox.
I am relatively new to jQuery and programming JavaScript "the right way" and wanted to be sure that the below mechanism for capturing the users check action was a best practice for JavaScript in general. Further, since I am employing jQuery is there a simpler method to hold onto their action that I should be utilizing?
function Check() {
this.Checked = false;
}
obj = new Check;
$(document).ready(function() {
$('.cboxelement').colorbox({ html: '<input id="inactivate" type="checkbox" name="inactivatemachine"> <label for="inactivate">Inactivate Machine</label>' });
$(document).bind('cbox_cleanup', function() {
obj.Checked = $.fn.colorbox.getContent().children('#inactivate').is(':checked');
});
$(document).bind('cbox_closed', function() {
if ($($.fn.colorbox.element()).attr('id').match('Remove') && obj.Checked) {
var row = $($.fn.colorbox.element()).parents('tr');
row.fadeOut(1000, function() {
row.remove();
});
}
});
});
Personally, I would attach the value(s) to an object directly using jQuery's built-in data() method. I'm not really entirely sure what you are trying to do but, you can, for instance, attach values to a "namespace" in the DOM for use later one.
$('body').data('colorbox.checked',true);
Then you would retrieve the value later by:
var isChecked = $('body').data('colorbox.checked');
You run the data() method on any jquery object. I would say this is best-practice as far as jQuery goes.
You could capture the reference in a closure, which avoids global data and makes it easier to have multiple Checks. However, in this case it appears to be binding to the single colorbox, so I don't know that you could usefully have multiple instances.
function Check() {
this.Checked = false;
var obj = this; // 'this' doesn't get preserved in closures
$(document).ready(function() {
... as before
)};
}
var check = new Check; // Still need to store a reference somewhere.
$($.fn.colorbox.element()) is redundant. $.fn.colorbox.element() is already a jquery element.
It's common use (in the examples i watched, at least) to prepend a $ to variables referencing jquery elements.
So, var $rows = $.fn.colorbox.element().parents('tr'); gives instantly the idea that it is referencing jquery element(s).
I am afraid fadeOut won't work on rows in IE6 (if i recall correctly). You should be able to hide all the content inside the <tr> before removing it.
Can't help on the "simplify" thing because i don't know the colorbox's best uses.

Categories