KendoUI数据属性事件处理程序和'this'范围

时间:2012-12-29 13:14:33

标签: javascript this kendo-ui unobtrusive-javascript

似乎kendo的不显眼的javascript风格事件在我的方法上下文中调用了this

假设我有一个对象Foo,实例化为bar = new Foo()

function Foo(){};

Foo.prototype.name = "Herring";
Foo.prototype.doSomething = function(e) {
   alert(this.name);
};

bar = new Foo();

使用数据点击附加事件,例如

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

替换bar的对象上下文(不确定原因,因为我们有方便的element容器。)所以this.name未定义。

我在对象构造函数中尝试了旧的var self = this;,但是它没有用,有没有人知道解决这个问题的最佳方法是什么?

更新:Hacky解决方法

因为我真的不想失去将模块包装成类的好处,所以我创建了事件调用函数包装器,然后在适当的对象上调用方法。

例如,将标记连接到包装函数。

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

并且包装函数只调用object.method。

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

现在,这实现了预期的结果,但它非常可怕,从标记调用的每个事件必须具有代理功能,如上所述。所以想象一下你有300个活动的场景......你会立刻明白为什么这很糟糕。

如果没有其他解决方案,我非常希望有。我将这个解决方法作为答案发布,但就我而言,这远远不可取。

脚注

我会完全诚实,这似乎是剑道中的主要架构缺陷,因为这种从标记调用事件的方法是“剑道方式”。显然它无法修补,因为可能有一些代码已经处理this作为对html元素的引用。

能够覆盖它,或能够通过可以传递调用的泛型处理程序(实质上是通用代理函数)来路由这些事件调用,是可以处理它的可能方法。它也可以是kendo.对象上的简单可配置值。

理论解决方案

如果有效,我会发布后续内容,理论上可以在通用代理中抛出事件,并让它调用正确的范围函数。

假设我们使用event属性来调用代理,然后创建一个单独的属性来传达对象/方法调用。例如。

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

代理功能会从属性数据集中提取prox

方法 - 使用eval

不是因为我是邪恶的,而是需要的。

// 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)");
    }
}

显然我想要一个更好的方法来做到这一点,但至少它是干的。

(我马上会做一个非eval方法......)

放弃评估...

让我们使用窗口上下文来定位对象/方法。

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

当然对于全局(窗口)范围的函数,这个元素可能更有用,但在这种情况下,你有一个选择,我会忽略

4 个答案:

答案 0 :(得分:0)

正在使用的版本。

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

注意事项

如果您在同一元素上有多个处理程序,prox()不适合,例如,如果您有data-initdata-show等,prox无法区分,会失败。

我可能会更新这个,特别是如果这对我来说是一个普遍的用例。

答案 1 :(得分:0)

我暂时尝试了第三种方法,使用非通用技术,就像这样。

伪代码:

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

不像prox方法那样灵活,但适合我的用例,并且至少不需要与我们想要使用的方法分开的函数代理。我们可以通过进行类型检查来简化这个问题。重新打电话。

e.g。

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

      // Method body... 

   }
}
myInstance = new MyObject();

这也意味着我的标记中不需要自定义data-属性。

注意:此方法对于具有多个实例的对象是有问题的,但是,我申请的对象是单个实例。

如果你有需要特定于实例的处理程序(这是我提出这个问题的主要原因),prox方法比这更合适,这只是一种更简洁的方法每个事件一次代理功能。

答案 2 :(得分:0)

您可以使用jQuery代理(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);

或内部使用

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

答案 3 :(得分:0)

我开发了一个使用JS Proxy Polyfill的代理方法,它简化了通过custon html data- *属性中的参数调用自定义逻辑。

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

例如上传组件

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

用您的参数替换 myJqueryDataDefinedMethod gridId

如您所见,您可以在数据成功中定义具有动态名称

的eventproxy
data-success="eventproxy.CUSTOMKEY"

并在定义自定义属性

之后
data-proxy-CUSTOMKEY

data-proxy-CUSTOMKEY包含可用于实现自定义逻辑的参数(JSON编码),

我建议自定义逻辑可以通过$ .data

检索存储在kendo小部件网格上的JS方法
$("#" + proxyParams.id).data(proxyParams.widget).element.data(proxyParams.method)

您可以将方法绑定到网格,例如使用此

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