How do I get the correct context set when I want to apply focus()?
What I try to do is basically this:
elemnt = document.getElementById('someFormField');
elemnt.focus('none');
Only the call to focus is generated somewhere else in the script, queued and applied when the application requests it.
function testIt() {
var queued = {
elementId: 'someFormField'
, func: focus
, args: ['none']};
elemnt = document.getElementById(queued.elementId);
queued.func.apply(elemnt, queued.args);
}
The above method works for other functions but for the focus method I get an error:
Opera: WRONG_THIS_ERR
Firefox: uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" (...)]
How can get this to work?
(I'm trying to understand the issue, so I'm not interested in 'use framework X' answers)
Update:
There seems to be some confusion about why I pass an argument to focus(): I only included the argument to show that passing an argument to focus() will not cause an error.
I might just as well have typed:
document.getElementById('someFormField').focus();
Update (2):
Because Anonymous's answer does not work in IE6, I'm now using an intermediate function to solve the problem:
function testIt() {
var queued = {
elementId: 'someFormField'
, func: setFocus
, args: ['someFormField']};
elemnt = document.getElementById(queued.elementId);
queued.func.apply(elemnt, queued.args);
}
function setFocus(elemntId) {
document.getElementById(elemntId).focus();
}
If IE6 compatibility is not on your requirement sheet, Anonymous's solution is the answer
In the interests of avoiding confusing myself with what function does what, I would always start with a closure, until I needed to reduce memory usage:
var dofocus = function(){element.focus()};
dofocus();
If you're in a loop and element is changing, you can use a function that returns another function: (function(e,m){return function(){e[m]()}})(element, method)
Edit: On a reread of the question, I'm thinking the reason your code doesn't work is because you're passing in the value of some variable named 'focus' and not the string method name 'focus', or rather the focus function (is it defined elsewhere?)
Edit: This works:
<html><title>test</title><script>
function foo (id, method, args) {
var d = {"id": id, "method": method, "args": args};
// ... time passes
var e = document.getElementById(d.id); // test exists
var f = e[d.method]; // test exists
var a = d.args; // test is an array
f.apply(e, a);
}
</script><body id="thebody">
<p>
<button onclick="foo('bar', 'setSelectionRange', [4,6]);">select</button>
<button onclick="foo('bar', 'focus', []);">focus</button>
<input type="text" id="bar" value="foo bar baz">
<p>
<button onclick="foo('thebody', 'appendChild', [document.createTextNode(new Date())]);">body</button>
</body></html>
Edit: And if passing functions was the issue, you can always pass an optional method name and test whether the method is a string or a function, then be able to use 'focus' or a custom function without having to place it on the element.
Read the thread here.
From that thread, we learn that using just "focus" doesn't work, like so:
focus.apply(document.getElementById('someElement'));
(a simple case of what you're attempting) because "focus" has to have an element to which it's bound. Otherwise, it's not a bound function, but rather just a native call with no real reference. My guess is this'll be the case for any element-specific functions.
What you need to do is grab the focus element from either the target element, or another DOM element that has a focus function. For example, this will work:
var fn = document.createElement('input').focus;
fn.apply( document.getElementById('someElement') );
Related
Coming from a C++ background, trying to work with an OO language that doesn't have explicit typing is a little more than a headache.
So I have dynamic elements for a webpage that are "controlled" by objects since there are tons of stuff I need to manage on each for it to work. The element is just the visual output of the data inside of the object itself, that's all I really need it for.
Except that I need the object to perform an internal function when it's clicked. That seems to be the biggest source of my headache thus far.
Javascript:
function onClick(file) //The external onClick function I use to try to get it to call from.
{
file.state = INUSE;
file.checkState();
}
function fileObject () { //The file object itself
this.element;
this.newElement();
//initialize stuff for the object
}
fileObject.prototype.newElement = function() { //creates a new element and sets its event listener
this.element.click(function() {onClick(this)});
}
fileObject.prototype.checkState = function() {/*does stuff*/} //apparently this is "not a function"
The error I get exactly is "file.checkState is not a function" from Firefox's console panel.
I'm still new to javascript, but after doing some debugging, I've come to find out that it's explicitly the onClick(this) function that is causing all of the errors. When used with something else, the onClick function works perfectly, but for some reason, the this keyword doesn't appear to actually be sending the reference to the fileObject since all checks show file being undefined when inside of the onClick scope.
Is there something fundamentally wrong about the way I'm trying to do this or am I just missing a step (or adding something that I don't need) that will help get this snippet working.
So you know, your initial problem isn't actually handling the action, but listening to it. click will trigger a synthetic click event, rather than liste for one.
You want ... .element.addEventListener("click", callback); that said, you face a second problem, immediately thereafter.
I will leave my example code as you've written it to not confuse the matter...
But when you see click( ) know that I mean subscribing with addEventListener, if element really does mean a browser DOM element. If it's not a standard browser element, and your own API, then ignore the previous portion, and carry on.
this is dynamically bound at the invocation time of the function (not at definition time).
The nearest function, scoped above, is your callback function that you are passing into .click( ... ).
Which is entirely different than the this which you mean outside of the callback.
Whatever is on the left-hand side of the dot is the this context for the duration of that particular invocation.
Needless to say, click() doesn't know enough to bind the this you mean, to the left-hand side of your callback.
The solution (or one of many) is to use lexical scoping and/or closure to retain the value of the object you mean.
// easy but messier
var fileObject = this;
... .click(function () { onClick(fileObject); });
// Cleaner with thunks:
function clickHandler (onClick, obj) {
return function () { onClick(obj); };
}
... .click(clickHandler(this));
Coming from c++ the way Javascript handles this will seem a little crazy, it looks like here you need to tell the function you've defined what this is - like so:
this.element.click(function() {onClick(this)}.bind(this));
I am using a namespace for my javascript code, and I think I have hit a brick wall with a onchange attribute for a select element. When I attempt to call a function with (or without) my namespace the error console is reporting that the function is not found.
var MYNS = {}; //namespace
MYNS.modifySearchPage = function () {
....
var eSelect = document.createElement("select")
.....
eSelect.setAttribute('onchange', 'MYNS.handleChange(this)');
.....
//set up the options (value, textcontent, eSelect.appendChild(theOption)
...
// add the eSelect to the DOM
}
MYNS.handleChange = function (select) {
//parse the select options
}
The result I get in the console when I select an item from the dropdown list is:
Uncaught ReferenceError: MYNS is not defined
I have attempted to add the namespace to the windows but that does not seem to help (and I'm not convinced that is a safe thing to do).
I have tried adding a onclick handler to the select element but obviously that is a bad idea as select does not handle onclicks.
Stripping the MYNS from both the call and function definition also didn't help.
Any ideas?
Thanks,
mwolfe
Don't use attributes to attach handlers - use properties:
eSelect.onchange = function() {
MYNS.handleChange(this);
};
More generically you could also use the standard and more recommended addEventListener:
function changeHandler() {
MYNS.handleChange(this);
}
if (eSelect.addEventListener) {
eSelect.addEventListener('change', changeHandler, false);
} else if (eSelect.attachEvent) {
eSelect.attachEvent('onchange', changeHandler); // fallback for IE
}
It's also worth noting that you can call
eSelect.addEventListener('change', MYNS.handleChange, false);
You will need to modify your callback though - the argument passed will be an event object and this inside the function will refer to the element that triggered the event.
You just code a different word of wrong case MyNS.handleChange, it should be MYNS.handleChange. In JavaScript variables are case sensitive.
I am creating a plugin using jQuery library.
Here i am storing String.prototype in a variable then i am using this variable to extend my Sting object. And this is working fine.
// String Prototyping store in a variable
// Save bytes in the minified version of js
var StrProto = String.prototype;
String.prototype.toProperCase = function () {
return this.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
// working fine
alert("yogesh kumar".toProperCase());
In the next case i am creating m function xyz which stored in
abc variable and this is also working fine.
function xyz(x){
alert(x)
}
var abc = xyz;
// working fine
abc("yogesh kumar");
In the last case i am storing document.createElement in a variable
tag and using tag to create a button. but this is not working.
var tag=document.createElement;
$(document.createElement("button")).html("document.Element").appendTo("#myDiv");
// not working
$(tag("button")).html("tag").appendTo("#myDiv");
Please check the link on jsFiddle:
click here
Error:
In Chrome
Uncaught TypeError: Illegal invocation
in Firefox
Error: NS_ERROR_XPC_BAD_CONVERT_JS: Could not convert JavaScript
argument
Why this error?
What is the solution?
You are getting a reference to a function that is a member of the document. When you call that reference directly, it's context is now the window rather than the document. Here's an example:
http://jsfiddle.net/DeCNx/
var foo = {
createElement: function(tagname) {
if (this._secretvarthatisneeded) {
console.log(tagname + " Element Created!");
}
},
_secretvarthatisneeded: true
}
foo.createElement("FOOBAR"); // works
var bar = foo.createElement;
bar("BARFOO"); // doesn't work
bar.call(foo,"BARBAR") // works
Since the context was lost, the bar() call didn't result in a console.log();
obviously this is just a simplification to demonstrate.
Update: For the use you are making, i'd suggest doing this:
$.createElement = function(tagName,attributes){
return $(
document.createElement(tagName),
attributes ? attributes : {}
)
}
Now you can simply do $.createElement("button").html("tag").appendTo("#myDiv"); It is fast and still easy to read. Note however IE has problems with inputs, if you're creating input elements, i suggest using $("<input type='text' />") rather than this.
Use the bind() method for "assigning" the native JS method to a variable:
var ce = document.createElement.bind(document);
var elem = ce('div');
alert(elem.nodeName);
Works in modern browsers including IE9+. For older browsers, use a wrapper function.
jQuery can create new elements for you as simple as:
$("<button />").html("document.Element").appendTo("#myDiv");
To have a reason why your approach is not working, read #Kevin's comment below.
That is happening because document.createElement uses this inside itself. When you call it like document.createElement() then this is set to document. But, when you save it as a variable, then this is no longer document, it's window.
You need to call it with the context.
var tag = document.createElement; // you are saving the function, not its context
var btn = tag.call(document, 'button'); // you need to set the context
If your browser supports it, you can also use .bind:
var tag = document.createElement.bind(document);
var btn = tag('button');
The reason for this error is that the method lost its context. The method createElement() must be called in the context of a document object.
Try this in a console:
var tag = document.createElement;
tag.call(document, "div"); // no error
tag("div"); // error
The specific details of why createElement() must be called in the context of document are implementation specific, but can easily be guessed at.
So, to maintain context, create a function wrapper for document.createElement():
function tag(tagName) {
return document.createElement(tagName);
}
Of course, jQuery will also create elements for you:
$("<div>"); // new div element
The question
In Javascript, how can an event handler function refer to members of its parent? i.e. can you define an event handler function part of a larger object and have that function "know about" its parent?
(Note that someone else posted a nearly identical question Accessing variables of parent function in Javascript event handlers . It hadn't been answered. Hence the repost )
Erroneous Presuppositions
I had thought that at "function definition" time you could capture a copy of "this" for later re-use, (e.g. copy to "self"). Evidently I was wrong: after I bind the function to the event ("click()" below), "self" subsequently refers to the html anchor tag ('');
The general context :
trying to use encapsuation/object-orientedness for code-re-use in javascript.
Example
Here's a simple example (cribbed from elsewhere and modified).
The function succeeds when called during page load, but fails when the user preses "click"
MY LINK
<script type="text/javascript">
var Construct = function() {
var self = this;
this.attr1 = 3;
this.attr2 = 2;
this.childObj = {
method1: function () {
// this function fails if called from an event handler
// edited this function to "do something", i.e. provide a visual cue upon execute
var foo = self.attr1 * self.attr2;
alert ('value is ' + foo);
return foo;
}
}
}
var obj = new Construct();
// this call succeeds
alert (obj.childObj.method1());
//this call fails as soon as the event handler refers to "self"
$("#myLink").click(obj.childObj.method1);
</script>
</body>
</html>
Update/Edit
-Updated the example to give a 'visual cue' when it runs
-added this section.
My error. As pointed out below, the example works fine. My original, non-working code used this:
this.self = this
instead of
var self = this
I didn't realize the distinction (in Java they would be identical) and missed the fact that my example actually worked, (whereas my code failed).
Your code works fine. self refers to the object as it is supposed to. That's how the lexical scoping of javascript is defined.
The problem is your handler does nothing. method1 simply returns 6 but you never tell it do anything with that value. If you want to prove it to yourself, the line before the return, add an alert: alert(self.attr1 * self.attr2);
Working Example
I'm using hoverIntent which, as part of its settings, will call a function. I think this is called 'referencing a function' (correct?):
var HIconfig = {
interval: 250,
sensitivity: 8,
over: myFunction,
timeout: 100,
out: myOtherFunction
};
However, I'd like to reuse said function at times and explicitly pass in a jQuery object. So, I added that to the function.
myFunction($myObject){
}
The challenge now is to figure out when the function is being referenced by hoverIntent or being explicitly called. My thought was that I'd check to see if $(this) contained a particular DOM element:
myFunction($myObject){
if($(this).is('li')){
$myObject = $(this)
};
$myObject.doSomething...
}
But...I'm having issues. If I log out both $(this) and $myObject these are the results:
Called via hoverIntent:
$(this) = [li#Trigger-0.nav-main-tab]
$myObject = Object { originalEvent=, more...}
Called via explicitely passing an object
$(this) = [Window PT02-home-page.php#]
$myObject = [li#Trigger-0.nav-main-tab]
I can test for $(this).is('li') in the first scenario, as it's true.
I can't in the second, though, as when I try to perform the test, Firefox doesn't like it:
g.nodeName is undefined
One suggestion was to switch to 1.4.1 and try to test for the opposite via .isPlayObject:
if (jQuery.isPlainObject($myObject))...
This works just fine in Firefox. However, IE8 always returns true.
My questions:
Is my logic simply way off in terms of how my function gets called from hoverIntent vs. directly?
If not, is there a way to consistently test to see if I have explicitly passed in an object to my variable in the function?
I would do this totally differently. First, it's weird to have a function take a jQuery object as a parameter. Go the jQuery way and make your function into a jQuery plugin. For use in your hoverIntent configuration, you can either wrap your function in another little function, or do that with the new (1.4) jQuery.proxy() function.
Instead of passing an object, why not pass a simple boolean to indicate where it has been called from, for instance:
myFunction(asOption){
if(asOption) {
alert("called from hoverIntent");
} else {
alert("called from somewhere else");
}
}
or am I completely missing the point?
You're making this unnecessarily complex. Just use a wrapper for the callback that passes the argument the function expects:
var HIconfig = {
interval: 250,
sensitivity: 8,
// myFunction expects a jQuery object, so create one from the context
over: function() { myFunction($(this)) },
timeout: 100,
out: myOtherFunction
};
...then you can skip the check inside your function altogether:
myFunction($myObject)
{
$myObject.doSomething...
}