Class variables are not visible inside element event? - javascript

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.

Related

Handling DOM events in JavaScript classes

(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();
};

What's the scope of parameters of a function set by onclick?

I've a function which accept only html string, it will show a info window (a popup) with the html as its content:
function createInfoWindow(info_html){
// show a popup with info_html as its content
}
Now I want to create a info window which will have a button:
function createMyInfoWindow(o){
var info_html = "<input type='button' value='click me' onclick='foo(o)'>"
createInfoWindow(info_html);
}
function foo(o){
console.log(o);
}
createMyInfoWindow({ name: "test", age: 21);
However, when I click the button, it says that o can't be found.
Try following code
var info_html = "<input type='button' value='click me' onclick='foo(\""+o+"\")'>"
UPDATE
If o is object it becomes more complicated.
You can store passed objects in store-object. Then you can pass corresponding index in foo:
var storage = [];
function createMyInfoWindow(o){
var index = storage.length;
storage[index] = o;
var info_html = "<input type='button' value='click me' onclick='foo(\""+index+"\")'>"
createInfoWindow(info_html);
}
function foo(i){
console.log(storage[i]);
}
createMyInfoWindow({ name: "test", age: 21);
In the HTML assigned to the innerHTML of the input, handlers are wrapped in functions so the scope is the handler, then global (there may be other objects on the scope chain).
In your code, name is local to createMyInfoWindow, the handler (nor any other function) has access to that variable. See Molecule's answer for how to use it.
The way you are doing it now is a form of eval, and is generally frowned upon. Read up on Unobtrusive Javascript if you'd like to know all the reasons why.
However, there is a really excellent way to accomplish the same task without the scope problems your facing (let alone trying to get that object passed to the function in string form -- yikes!) Doing this properly will require some restructuring of your functions, but I'm sure you'll find it to be worth it.
function createMyInfoWindow(o){
// creating window first so we can access it from the DOM
createInfoWindow(info_html);
// we can select the window from the DOM now, but it would be even better if
// createInfoWindow returned that object so we could just pick up where we left off
var myInfoWindow = document.getElementById("myInfoWindow");
// The button you are putting into the window
var myButton = document.createElement("input");
myButton.type = "button";
myButton.value = "click me";
// because of javascript closures, we can call foo(o) from within an anonymous function
myButton.onclick = function () { foo(o) };
}
I prefer creating HTML elements this way for many reasons: 1) Avoid the implicit use of eval, 2) much easier to debug the HTML when it is generated by the javascript for you and 3) no more scope issues for event functions.
You just have to create the window in the reverse order now, because the window element must exist first in order to add a button to it, and the button element must exist before you can add the onclick handler to it.

$(this) OR event.target OR var input = $(this)

jQuery is currently providing me with a fun introduction to Javascript after 12 years of surviving happily without. I'm at the stage where I'm trying to learn as much as I can about optimising the code I write and, whilst I have found plenty of good reference material, there is something quite basic which is puzzling me and I have been unable to find anything about it anywhere.
When I'm attaching something to an element how should I be referring to that element within the function. For example, when attaching a function to an element's click event :
$('#a_button',$('#a_list_of_buttons')).click(function() {
// NOW WHAT'S THE BEST WAY TO REFER TO '#a_button' ?
});
I know not to keep re-selecting it like so as the browser has to search the whole DOM again from scratch to find what it's already found once :
$('#a_button').click(function() {
// I KNOW THAT THIS IS NAUGHTY
var buttonValue = $('#a_button').val();
$('#a_button').addClass('button_has_been_clicked');
});
Currently I'm using either of the following but am not entirely sure what each is actually doing :
$('#a_button').click(function() {
// USING this
var buttonValue = $(this).val();
$(this).addClass('button_has_been_clicked');
});
But is this just re-selecting like in the first "naughty" example?
$('#a_button').click(function(event) {
// USING event.target
var buttonValue = $(event.target).val();
$(event.target).addClass('button_has_been_clicked');
});
This seems like it might be better but is it efficient to refer to 'event.target' multiple times?
$('#a_button').click(function(event) {
// USING A LOCAL VARIABLE
var thisButton = $(this);
// OR SHOULD THAT BE
var thisButton = $(event.target);
var buttonValue = thisButton.val();
thisButton.addClass('button_has_been_clicked');
});
I understand the performance efficiencies of passing things to variables but I'm unsure whether or not in these situations using $(this) or $(event.target) provides me with the same efficiencies already and so by setting a new variable I'm actually doing more work that I need to.
Thank you.
this and event.target are not always the same.
this refers to the element you assigned the listener to ( in this case the '#a_button' ). event.target however is the element that actualy triggered the event, which can be a childnode of #a_button.
So $(this) is the thing you are looking for.
See reference: http://api.jquery.com/event.target/
I may be wrong, but this and event.target are both just different references to the same element.
this and event.target are not always references to the same element. But in answer to your question, var thisButton = $(this); is definitely the winner. If you were writing C# code, you would never do the following:
this.Controls[0].Controls[0].Text = "Foo";
this.Controls[0].Controls[0].Controls.Clear();
You would do this:
var control = this.Controls[0].Controls[0];
So you probably should never re-use $(this) more than once either. Althought it's trivial to convert this from a DOM element to a jQuery object, it's still an unnecessary overhead.
However, sometimes you need to gear back from optimisation to make sure your code maintains it's readability.
Another option of course is just to change what this is. This is javascript afteral:
this = $(this); // Now `this` is your jQuery object
Disclaimer: I only just tried the above and it seemed to work. Might have some issues though.
I built a little example to demonstrate how this and e.target actually work: http://jsfiddle.net/xZAVa/
In my experience i would go with the following:
$('#a_button').click(function() {
// USING this
var buttonValue = $(this).val();
$(this).addClass('button_has_been_clicked');
});
The this in the context of your click callback method is a reference to the DOM event. Since you already have a reference to the DOM object it is trival to convert it into a jQuery object since a lookup is not required.
But on a side note, if you don't need to use jQuery in your callback, then don't. You can simply get the value of the button using standard JS this.currentTarget.value.
The other examples you mentioned require a DOM lookup, and depending on the complexity of your selector can take longer. Using a id based lookup like '#a_button' will perform better than a class based looked like .myClass.

JavaScript Inline Events or Adding Events Afterwards

I have a question, which I can't seem to decide on my own so I'll ask here. The question is simple: whether to use inline JavaScript events or adding them afterwards? The theory in the background isn't that simple though:
I have a JS object that returns HTML. Whenever you create this object, the returned HTML will be used for another object's HTML. Therefore, adding events is not straight-forward. See:
secret.object = function() {
this.init = function() {
var html = '<div>and lots of other HTML content</div>';
return html;
};
}
This is a sample object that is created within this code:
for ( var i = 0; i < countObjects; i++) {
var obj = arguments[0].content[i];
generatedContent += spawnSecret(); /* The spawnSecret() is a method that initializes the object, and calls its init() method that returns the HTML.
}
and then later on I create a new object whose property "content" will be set to "generatedContent". It needs to add the events within the secret object I have, nowhere else. And since my system is built like this, I see only two ways around this: use inline events or build HTML using method calling instead of returning.
Hopefully, this wasn't too hard to understand.
If you created the elements using document.createElement() (but didn't append them to the DOM) and kept a reference to them, then you could populate them with the text content and attach event handlers to them, without having to use inline events.
When you are ready to reveal your 'secret' you could then append them to the DOM, rather than dumping in a text string of HTML tags and content.
I cant see it making much of a difference - if you just render your events using onclick etc. JavaScript event handlers they will be evaluated as soon as you append your generated HTML to the document, rather than you having to call attachEvent() or whatever.

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