KendoUI data attribute event handlers and the 'this' scope

It seems that the kendo unobtrusive-javascript style event is causing break this in my method context.

Say I have a Foo object created 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 a data click, for example

 <a data-role="button" data-click="bar.doSomething">Click Me</a> 

Changed the context of the object to bar (not sure why, since we have a convenient container element .), And therefore this.name is undefined.

I tried the old var self = this; in the constructor of the object, but it doesn’t work, does anyone know what is the best way to solve this?

Update: Hacky Workaround

Since I really do not want to lose the benefits of packing my modules as classes, I created wrappers for event-calling functions that then call methods on the corresponding object.

For example, connect markup to a wrapper function.

 <a data-role="button" data-click="doSomething">Click Me</a> 

and the wrapper function calls the object.method object.

 function doSomething(e){ bar.doSomething(e) }; 

Now this achieves the intended result, but it's pretty terrible, every event called from the markup should have a proxy function , as above. So imagine a scenario in which you have 300 events ... and you will immediately understand why this is terrible.

If there is no other solution, and I really hope that there is. I will send this workaround as an answer, but as far as I know, this is far from desirable.

Footnote

I will be completely honest, this seems like a major architectural flaw in Kendo, since this method of invoking events from markup is the "Kendo path." Obviously, it cannot be fixed, because probably a fair bit of code is already dealing with this as a reference to the html element.

The ability to override it or the ability to route these event calls using a common handler that can pass a call, in essence a common proxy function, are possible solutions to this problem. It can also be a simple custom value for a kendo. object kendo. .

Theoretical solution

I will talk about the next steps, if this works, it is theoretically possible to throw events into a common proxy server and call a function with the correct scope.

Suppose we use the event attribute to call the proxy server, and then create a separate attribute to pass the call to the object / method. For example.

 <a data-role="button" data-click="prox" data-prox="o.eventHandler">Click Me</a> 

A proxy function would prox from the attribute dataset:

- using eval

Not because I'm evil, but necessary.

 // 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 need a better way to do this, but at least it's DRY.

(I prepared a non-emergency method in an instant ...)

Start Eval ...

use the window context to search for an 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) functions with a scope, this as an element is probably more useful, but in this case you have a choice, I would not notice

+7
source share
4 answers

version used.

 // dynamic proxy for retaining object context on methods called by // data- attributes in Kendo. // // eg // // 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 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 a backup eval() to locate the object if window doesn't // own it. It should be possible to remove it under most // circumstances, it 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); } 

Warnings

If you have multiple handlers for one element, prox() unsuitable, for example, if you have data-init , data-show , etc. prox cannot be distinguished and will fail.

I will probably update this, especially if it becomes a common use case for me.

0
source

I temporarily tried a third method with a non-generic technique that works as follows.

Pseudocode:

 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, does not require a separate proxy server from the method we want to use. We could make it more concise by doing type checking and re-calling ahead.

eg.

 MyObject = { method : function(e) { if (! this instanceof MyObject) myInstance.method(e); // re-call // Method body... } } myInstance = new MyObject(); 

It also meant that I didn't need the user attributes data- in my markup.

Note. this method is problematic for objects that will have multiple instances, however the objects that I accessed were separate instances.

If you have handlers that need to be specific instances (which is the main reason I raised this question), the prox method prox much better than this, which is just a more neat way to do per-event .

0
source

You can 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 internal use

 $("btn").click($.proxy(this.doSomething), this); 
0
source

I developed a proxy method using JS Proxy Polyfill, which simplifies calling custom logic through the parameters in the custon attribute html data- *.

Enable 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 a download component

 <input type=file ... data-success="eventproxy.customsuccesshandler" data-proxy-customsuccesshandler='{widget:"kendoGrid",method:"<myJqueryDataDefinedMethod>",id:"<gridId>"}' .... /> 

replace myJqueryDataDefinedMethod and gridId with your options

as you can see, you can define an eventproxy event with a dynamic name in data-success

 data-success="eventproxy.CUSTOMKEY" 

and after defining a custom attribute

 data-proxy-CUSTOMKEY 

data-proxy-CUSTOMKEY contains parameters (JSON encoded) that you can use to implement custom logic,

I suggested custom logic that can restore the JS method stored in the grid of the kendo widget via $ .data p>

 $("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method) 

You can bind a method to a grid, for example using this

 $('#my-grid-id').data("kendoGrid").element.data('methodName',function(e){ // my implementation }); 
0
source

All Articles