javascript - Knockoutjs Handlebars 。单击绑定(bind)到渲染的 JSON 无法按预期工作

标签 javascript knockout.js handlebars.js

我已经为此苦苦挣扎了一段时间,但无法得到我期望的结果。任何帮助将非常感激。这是 jsfiddle 中的代码。我在这里尝试做的是迭代包含可包含多个消息的产品的 JSON,并呈现消息并将每个消息绑定(bind)到自己的单击函数。相反,当代码运行时,单击函数会自行触发,并且单击消息链接不会执行任何操作。

<a href="#" data-bind="click: doTest">This binding works</a>
<!--The json is appended to these 2 ULs by class name-->
<ul class="product1"></ul>
<ul class="product2"></ul>
<script id="product-message-template" type="text/x-handlebars-template">
    {{#each this}}
    <!-- code below should be binding to alertMessage(), but fails...-->
    <a href="#" data-bind="click:{{this.alertMessage}}">{{this.message}}</a>
    {{/each}}
</script>

这是 JS:

function init(){
    var self = this;
    self.view_model.doTest = function(){alert('this works')};
    self.product_messages_to_vm(json, view_model);
    self.render_product_messages(view_model);
}
var view_model= {};
var json = [{
        "id": "product1",
        "messages": [
            "p1 message1",
            "p1 message2"
            ]},{
        "id": "product2",
        "messages": [
            "p2 message1",
            "p2 message2"
        ]}];
//The Product object contains an observable array for messages and methods to add remove and save them
function Product(id, productMessages){
    this.productId = id;
    //make observableArray so when messages are removed, other messages can be notified
    this.messages = ko.observableArray([]);
    //iterate through messages and make them observable and add them to this product's messages 
    for(var i=0; i<productMessages.length; i++){                
        //this.messages().push(productMessages[i]);
        this.messages().push(new Message(productMessages[i])); 
    }
}
//message object 
function Message(msg){
    this.message = msg;
    this.alertMessage = function(){
        alert(this.message);
    }.bind(this);
}
//gets json messages and add them to the view model object
function product_messages_to_vm(json){
    var self = this;
    view_model.products = ko.observableArray([]);
    _.each(json, function(product,index){
        view_model.products().push( new self.Product(product.id, product.messages) );
    }); 
}
function render_product_messages(view_model){
    var self = this;
    _.each(view_model.products(), function(product){
        var template = Handlebars.compile($("#product-message-template").html());
        var messages = product.messages();
        var $message_html = $(template( messages ));
        //find element with the product class name, and append the compiled template into UI
        $( "." + product.productId ).append( $message_html );
    });
    ko.applyBindings(view_model);
}
init();

最佳答案

我知道不久前有人问过这个问题,但我想尝试解决它,因为我刚刚学习 Knockout .

在开始之前,我认为需要进行一些重构才能理解现有代码。我删除了很多被混淆使用的 this 引用。此外,我还内联了 product_messages_to_vmrender_product_messages 函数,因为这些函数没有被多次调用。

您的具体问题与 Handlebars 中的以下代码片段有关模板:

data-bind="click:{{this.alertMessage}}"

这里关于 Knockout 和 Handlebars 对此标记所做的事情似乎有些困惑。 Knockout 需要一个字符串,该字符串是 View 模型上的方法名称,并且它将将此方法绑定(bind)到元素的单击事件。 Handlebars 会将 Handlebars 表达式 {{this.alertMessage}} 替换为模板数据中的相应值。

当 Handlebars 尝试查找每个 message 对象上的 alertMessage 值时,它发现该值是一个函数; Handlebars 执行该函数以获取返回值(更多详细信息请参阅 this answer)。这就是您在渲染模板时收到警报的原因。由于每条消息的 alertMessage 方法不返回字符串,因此 Knockout 无法将单击事件绑定(bind)到方法。

我们需要做的是提供一个字符串,它是我们希望绑定(bind)到点击事件的处理函数的名称:

data-bind="click: alertMessage"

但是,我们有一个问题。仅当我们的 view_model 对象上有 alertMessage 属性时,此功能才有效。我们希望调用的alertMessage方法深深地嵌套在我们的view_model中;表示为路径,它看起来像:/products/product[0]/messages/message[0]/alertMessage。

为了让Knockout正确绑定(bind)这个方法,我们必须提供正确的路径:

data-bind="click: products()[0].messages()[0].alertMessage"

这更好,但无论我们单击哪个链接,它都只会在第一个产品的第一条消息上调用alertMessage。我们需要一种在模板中动态分配产品和消息索引的方法。

为此,我们必须更改模板,使其采用 product 对象而不是 messages 数组,并且我们必须提供产品索引:

<script id="product-message-template" type="text/x-handlebars-template">
    {{#each product.messages}}
        <li><a href="#" data-bind="click: products()[{{../index}}].messages()[{{@index}}].alertMessage">{{this.message}}</a></li>
    {{/each}}
</script>

这需要我们更新我们的模板调用:

_.each(view_model.products(), function (product, index) {
    // TODO: Template ID should be renamed to 'product-template.
    var template = Handlebars.compile($("#product-message-template").html());
    var $product_html = $(template({
        index: index,
        product: product
    }));
    $("." + product.productId).append($product_html);
});

而且它有效!

我的重构后的最终代码如下所示:

var json = [
    {
        "id": "product1",
        "messages": [
            "p1 message1",
            "p1 message2"
        ]
    },
    {
        "id": "product2",
        "messages": [
            "p2 message1",
            "p2 message2"
        ]
    }
];

function Product (id, productMessages) {
    this.productId = id;
    this.messages = ko.observableArray([]);

    for (var i = 0; i < productMessages.length; i++) {
        this.messages().push(new Message(productMessages[i])); 
    }
}

function Message (msg) {
    this.message = msg;
    this.alertMessage = function () {
        alert(this.message);
    }.bind(this);
}

(function init () {
    var template = Handlebars.compile($("#product-template").html());
    var view_model = {
        products: ko.observableArray([]),
        doTest: function () {
            alert('this works');
        }
    };

    _.each(json, function (product, index) {
        var next_product = new Product(product.id, product.messages);
        var $product_html = $(template({
            index: index,
            product: next_product
        }));

        view_model.products().push(next_product);
        $('#Products').append($product_html);
    });

    ko.applyBindings(view_model);
})();

更新后的 HTML:

<a href="#" data-bind="click: doTest">This binding works</a>
<div id="Products"></div>

<script id="product-template" type="text/x-handlebars-template">
    <ul class="{{product.productId}}">
        {{#each product.messages}}
            <li><a href="#" data-bind="click: products()[{{../index}}].messages()[{{@index}}].alertMessage">{{this.message}}</a></li>
        {{/each}}
    </ul>
</script>

可以找到一个正常运行的 JS Fiddle here .

尽管此代码有效,但我认为解决该问题的更好方法是使用 foreach binding由 Knockout 提供,如 suggested通过 Damien .

我不会在这里提供此类解决方案的代码,但我链接到working Fiddle .

关于javascript - Knockoutjs Handlebars 。单击绑定(bind)到渲染的 JSON 无法按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20348775/

相关文章:

javascript - Ajax 进度无法正常工作

javascript - 在 Firebase 注册中上传图片

javascript - 在可拖动对象内显示文本

javascript - 如何大写并删除句子/字符串中的空格?

javascript - 限制 {{#each a in b}} 的结果

javascript - iPad : when using swipe event the entire page is moving

javascript - 如何一次运行多个 Protractor 测试套件?

javascript - HTML 多选更改 'None selected' 文本

javascript - knockout : Get value of object from array

javascript - 如何在 Htmlunit 中抓取 Javascript 模板?