【问题标题】:KendoUI data- attribute event handlers and 'this' scopeKendoUI 数据属性事件处理程序和“this”范围
【发布时间】:2012-12-14 10:59:41
【问题描述】:

似乎 kendo 的 unobtrusive-javascript 样式事件在我的方法上下文中调用了 break 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 个事件的场景......你会立即明白为什么这是可怕的。

如果没有其他解决方案,我非常希望有。我将发布此解决方法作为答案,但就我而言,它远非理想。

脚注

老实说,这似乎是 Kendo 的主要架构缺陷,因为这种从标记调用事件的方法是“剑道方式”。显然它不能被修补,因为可能已经有相当多的代码已经处理this 作为对 html 元素的引用。

能够覆盖它,或者能够通过通用处理程序路由这些事件调用,该处理程序可以传递调用,本质上是通用代理函数,是可能的处理方式。它也可以是 kendo. 对象上的一个简单可配置值。

理论解

如果可行,我将发布后续跟进,理论上可以在通用代理上引发事件,并让它调用适当范围的函数。

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

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

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

【问题讨论】:

    标签: javascript this kendo-ui unobtrusive-javascript


    【解决方案1】:

    正在使用的版本。

    // 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 无法区分,会失败。

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

    【讨论】:

      【解决方案2】:

      我暂时尝试了第三种方法,使用非通用技术,它的工作原理是这样的。

      伪代码:

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

      不如代理方法灵活,但适合我的用例,并且至少不需要单独的函数代理远离我们要使用的方法。我们可以通过预先进行类型检查和重新调用来使其更简洁。

      例如

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

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

      注意:这种方法对于具有多个实例的对象是有问题的,但是,我应用的对象是单个实例。

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

      【讨论】:

        【解决方案3】:

        您可以使用 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);
        

        【讨论】:

        • 问题是如何使用剑道的数据属性来做到这一点。
        【解决方案4】:

        我使用 JS Proxy Polyfill 开发了一种代理方法,该方法通过自定义 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>"}'
               ....
        />
        

        用您的参数替换 myJqueryDataDefinedMethodgridId

        如您所见,您可以在 data-success 中定义具有动态名称的事件代理

        data-success="eventproxy.CUSTOMKEY"
        

        然后定义一个自定义属性

        data-proxy-CUSTOMKEY
        

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

        我建议自定义逻辑,它可以通过 $.data 检索存储在 kendo 小部件网格上的 JS 方法

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-11-28
          • 2018-08-18
          • 1970-01-01
          • 1970-01-01
          • 2011-07-05
          • 2011-08-09
          • 2011-10-21
          相关资源
          最近更新 更多