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
Related
It seems that kendo's unobtrusive-javascript style event calls break this in my method context.
Say I have an object Foo, instantiated as bar = new Foo()
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
And attach the event using data-click for example
<a data-role="button" data-click="bar.doSomething">Click Me</a>
Object context to bar is replaced (not sure why, since we have the convenient element container.) and so this.name is undefined.
I've tried the old var self = this; in the object constructor, but it's not working, does anyone know what the best way to solve this is?
Update : Hacky Workaround
Since I really don't want to lose the benefits of wrapping up my modules as classes, I've created event call functions wrappers, which then call the methods on the appropriate object.
For example, connect the markup to a wrapper-function.
<a data-role="button" data-click="doSomething">Click Me</a>
and the wrapper function just calls the object.method.
function doSomething(e){ bar.doSomething(e) };
Now, this achieves the intended result, but it's quite horrible, each and every event called from markup must have a proxy function like the one above. So just imagine a scenario where you have 300 events... and you'll instantly see why this is horrible.
If there's no other solution, and I dearly hope there is. I'll post this workaround as an answer, but as far as I'm concerned, it's far from desirable.
Footnote
I'll be completely honest this seems like major architectural flaw in Kendo, since this method of calling events from markup is "the kendo way." Obviously it can't be patched out, because there's probably a fair bit of code already dealing with this as a reference to the html element.
Being able to override it, or being able to route these event calls through a generic handler which can pass the call on, essentially a generic proxy function, are possible ways this could be dealt with. It could also be a simple configurable value on the kendo. object.
Theoretical Solution
I'll post follow-up if this works, in theory it's possible to throw events at a generic proxy, and have it call the properly scoped function.
Say we use the event attribute to call the proxy and then create a separate attribute to convey the object/method call. For example.
<a data-role="button" data-click="prox" data-prox="o.eventHandler">Click Me</a>
The proxy function would pull prox from the attribute dataset:
method - using eval
Not because I'm evil, but needs must.
// sitting in global namespace
function prox(e){
var p = e.sender.element.data['prox'];
// make sure our delegate is a function.
if("function" == eval("typeof "+p)) {
eval(p + "(e)");
}
}
Obviously I'd like a better way to do this but, at least it's DRY.
(I'll cook a non-eval method in a moment...)
Begone Eval...
let's use the window context to locate the object / method.
function prox(e) {
var p = e.sender.element.data['prox'];
if(p.indexOf(".") == -1){
// global function : where *this* is window.
// check you've got the function if not ditch it.
if("function" == typeof window[p]) window[p](e);
} else {
// object/method (one level deep only.)
var s = p.split(".");
var o = s[0], m = s[1];
// check the object/method is a function before executing it.
if("function" == typeof window[o][p]) window[o][p](e);
}
}
Of course for global (window) scoped functions, this as the element is probably more useful, but in that case, you have a choice, I'd leave out the
version in use.
// dynamic proxy for retaining object context on methods called by
// data- attributes in Kendo.
//
// e.g.
//
// data-click="o.method"
//
// Would lose context with `o` - context would be set in the same
// way as JQuery handlers, which is an inconvenience.
//
// Alternatively we use the prox method
//
// data-click="prox"
//
// We'd then set `data-prox` on the same element, to the
// object.method pair.
//
// data-prox="o.method"
//
// This is read by prox, transformed into a method call, type
// checked and executed if it's a valid method.
//
// A `data-prox` value in any form other than `object.method` will
// be ignored, for example, `object.child.method` will fail. If
// you're doing that sort of thing, feel free to hack it.
//
// There's a backup eval() to locate the object if window doesn't
// own it. It should be possible to remove it under most
// circumstances, it's here for compatability with
// JSFiddle. (JSBin works without it.)
function prox(e) {
var p = this.element.data().prox;
if(p.indexOf(".") > -1){
var s = p.split("."); if(s.length > 2) return;
var o = s[0], m = s[1];
if("object" == typeof window[o]) {
o = window[o];
}
if("function" == typeof o[m]) o[m](e);
// comment this out in production:
l( "prox called " + s[0] + "::" + s[1] );
}
}
function l(s) { console.log(s); }
Caveats
If you have multiple handlers on the same element, prox() is unsuitable, for example, if you have data-init, data-show, etc. prox cannot differentiate, and will fail.
I'll probably update this, especially if this becomes a prevalent use-case for me.
I temporarily tried a third method, with a non-generic technique, which works like this.
Pseudo code:
MyObject {
method : function(e) {
if (this instanceof MyObject) {
// Do something with this
} else {
myInstance.method(e); // otherwise re-call the method to set this properly.
}
}
}
myInstance = new MyObject();
Not as flexible as the prox method, but suitable for my use case, and at least doesn't require a separate function proxy away from the method we want to use. We could make this more terse by doing the type check & re-call up front.
e.g.
MyObject = {
method : function(e) {
if (! this instanceof MyObject) myInstance.method(e); // re-call
// Method body...
}
}
myInstance = new MyObject();
It also meant I didn't need custom data- attributes in my markup.
Note: this method is problematic for objects which will have multiple instances, however, the objects I was applying to were single instances.
If you have handlers which need to be instance specific (which is the main reason I raised this question) the prox method is a much better fit than this, which is just a neater way of doing one-per-event proxy functions.
You may use jQuery Proxy (http://api.jquery.com/jQuery.proxy/).
function Foo(){};
Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
alert(this.name);
};
bar = new Foo();
$("btn").click($.proxy(bar.doSomething), bar);
or for inside using
$("btn").click($.proxy(this.doSomething), this);
I developed a proxy method using the JS Proxy Polyfill that simplify calling custom logic via parameters in a custon html data-* attribute.
Include https://raw.githubusercontent.com/GoogleChrome/proxy-polyfill/master/proxy.js
function makeGridTemplateEventProxy(o) {
return new Proxy(o, {
get(target, eventName) {
return function (options) {
return templateEventProxy(options, eventName);
}
}
});
}
templateEventProxy: function (options, attribute) {
if (!options.sender.element.attr('data-proxy-' + attribute)) {
throw new Error('Cannot find attribute data-proxy-' + attribute + ' on ' + options.sender.name + ' widget');
}
var proxyParams = JSON.parse(options.sender.element.attr('data-proxy-' + attribute));
method = $("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method);
if (method && typeof method == 'function') {
return $.proxy(method, this)(options);
}
return null;
}
var eventproxy = makeGridTemplateEventProxy({});
for example for upload component
<input type=file ...
data-success="eventproxy.customsuccesshandler"
data-proxy-customsuccesshandler='{widget:"kendoGrid",method:"<myJqueryDataDefinedMethod>",id:"<gridId>"}'
....
/>
substitute myJqueryDataDefinedMethod and gridId with your parameters
as you see you can define in data-success an eventproxy with dynamic name
data-success="eventproxy.CUSTOMKEY"
and after define a custom attribute
data-proxy-CUSTOMKEY
data-proxy-CUSTOMKEY contains parameters ( JSON encoded ) you can use to implement a custom logic,
I suggested custom logic which can retrieve JS method stored on kendo widget grid via $.data
$("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method)
You can bind method to grid for example with this
$('#my-grid-id').data("kendoGrid").element.data('methodName',function(e){
// my implementation
});
After discovering about Javascript namespaces, I tried to implement them but I run into a problem while trying to attach a namespace method to an element's onclick.
I used this method to wrap up my functions/methods/classes (a simplified concept, not my actual code):
;(function(window, undefined) {
//my namespace
var NS = {};
NS.test = {
f : function(param) {
alert(param);
}
}
NS.test.('test 2');
})(window);
Inside, everything works fine and "test 2" is prompted.
However, when I try to attach that function to a click event, by doing something like this:
<a href-"#" onclick="NS.test.f('test');">Click me!</a>
it doesn't work, just like it doesn't work when I call that function after the })(window); part.
I tried it calling it window.NS.test.f('test'); but with no effect.
How can I make an onclick event call my function?
I could attach an event listener inside my wrapper, like I do for other html elements with no difficulty, but it would be problematic in this case since I'm generating the links with javascript and I find it easier and simpler to just add onclick="doSomething" for all my links, instead of creating them, then cache them and add event listeners.
Call me lazy, but in this particular case I prefer to do
someDiv.innerHTML = my_Generated_Html_Code_With_OnClick;
instead of
//demo code, ignore the flaws and the fact it won't work on IE
someDiv.innerHTML = my_generated_Html_code;
myLink = document.getElementById(id);
myLink.addEventListener('mousedown', NS.test.f('test'));
I do not use any framework nor do I wish to, since I'm trying to get a better understanding of the so-called vanilla javascript first.
I set up a jsfiddle here.
P.S. I must admit I didn't understand namespaces completely so if I'm doing something wrong here or applying the concept in a way I am not supposed to, I would appreciate any tips or corrections
That's because NS is declared inside and hence only exists inside the function:
function(window, undefined) {
var NS = {};
// NS exists here ...
}
// ... but not here
If you want to make it available to the rest of the page, then you can do:
function(window, undefined) {
var NS = window.NS = {};
// NS and window.NS exist here ...
}
// ... and window.NS exists here.
I have an issuer where I lose the this inside this object. The output of the following piece of JavaScript gives me "some-id" and then undefined. When I use this inside a callback function, the scope goes out of the object and it cannot use this any more. How can I get the callback to use 'this' or at least have access to the object?
Since I will make multiple objects, I won't be able to create a 'static' like storage.
Here is my test code that you can use to reproduce my problem. What I would like to have is CheckBox.doSomething() to return the value of this.id which should match some-id for this test case.
function CheckBox(input_id) {
this.id = input_id;
this.doSomething();
$('#some-element').click(this.doSomething);
}
Checkbox.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox('some-id');
some_box.doSomething();
$('#some-element').click();
I can't even get this to work as I want it to:
function CheckBox2(input_id) {
this.id = input_id;
alert(this.id);
}
CheckBox2.prototype.doSomething = function() {
alert(this.input_id);
}
var some_box = new CheckBox2('some-id');
some_box.doSomething();
Your problem is with this line: $('#some-element').click(this.doSomething);
Why this is a problem
JavaScript methods don't know anything about the object that should be assigned to this, it's set when the method is called either explicitly (with myFunction.call(obj)) or implicitly (when called using obj.myFunction()).
For example:
var x = {
logThis: function () {
console.log(this);
}
};
x.logThis(); // logs x
x.logThis.call(y); // logs y
var func = x.logThis;
func(); // logs window: the fallback for when no value is given for `this`
In your case, you're passing this.doSomething to jQuery, which is then explicitly calling it with the element that was clicked as the value of this. What's happening is (a slightly more complex version of) this:
var callback = this.doSomething;
callback.call(anElement, anEvent);
The solution
You need to make sure that doSomething is called with the right value of this. You can do that by wrapping it in another function:
var cb = this;
$('#some-element').click(function() {
return cb.doSomething();
});
jQuery provides a proxy function lets you do this more simply:
$('#some-element').click(jQuery.proxy(this.doSomething, this));
function CheckBox(input_id) {
this.id = input_id;
this.doSomething = $.proxy( this.doSomething, this );
$('#some-element').click(this.doSomething);
}
The "javascript equivalent" of this is Function#bind but that is not available in every browser and since it seems you are using jQuery I am using the jQuery equivalent $.proxy
Others have already explained the causes of the problem and how to fix it with jQuery. What's left is how you fix it with standard JavaScript. Instead of ...
$('#some-element').click(this.doSomething);
... you write:
document.getElementById('some-element').addEventListener('click', this.doSomething.bind(this));
This changes the context of this inside doSomething. You can also do that with anonymous functions - instead of ...
$('#some-element').click(function(event) {
console.log(this);
});
... you write:
document.getElementById('#some-element').addEventListener('click', (function(event) {
console.log(this);
}).bind(this));
That has been very useful to me in projects with lots of callbacks, e.g. in Node.js (where you don't have to care about outdated browsers).
Edit: getElementById() and addEventListener() instead of $(...).click(...).
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') );
Where SomeMethod could have:
function SomeMethod(item)
{
item.setAttribute('name', item.id);
}
Instead of:
function SomeMethod(itemId)
{
var someItem;
someItem = document.getElementById(itemId);
someItem .setAttribute('name', someItem .id);
}
Silly example, but the idea is not to send in the id itself, but the actual control calling the method. I swear this can be done but have had no luck searching... partially because I'm not even sure what to search on.
I thought it was self, but self doesn't seem to be what I want when the script I have runs.
Use the this Keyword.
You actually don't need to pass this as an argument to your function, because you've got a click event object that you can access. So:
<script>
function clickEventHandler(event) {
if (!event) {
event = window.event; // Older versions of IE use
// a global reference
// and not an argument.
};
var el = (event.target || event.srcElement); // DOM uses 'target';
// older versions of
// IE use 'srcElement'
el.setAttribute('name', el.id);
}
</script>
I tend to use this approach in all function calls from HTML attributes:-
onclick="SomeMethod.call(this)"
Then in the javascript do:-
function SomeMethod()
{
this.setAttribute('name', this.id);
}
This has a distinct advantage when you may also assign directly to event handler properties in Javascript code:-
document.getElementById("someID").onclick = SomeMethod
If SomeMethod took the context element as a parameter it would very awkward to set up:-
function(id) {
var elem = document.getElementById(id)
elem.onclick = function() { SomeMethod(elem); }
}("someID");
Worse yet this would be memory leaking closure.
At this point: SomeMethod(this) - this returns window object so do not use it. The right way to use this keyword is making it context relevant, so use SomeMethod.call(this).