Knockoutjs车把。单击绑定以使呈现的JSON无法按预期工作

时间:2013-12-03 10:37:26

标签: javascript knockout.js handlebars.js

我一直在努力解决这个问题,但是无法得到我期待的结果。任何帮助将非常感激。这是jsfiddle中的代码。我在这里要做的是迭代包含可以包含多个消息的JSON产品,并呈现消息并将它们中的每一个绑定到它们自己的click功能。相反,发生的是,在运行代码时,单击功能会自动触发,单击消息链接不会执行任何操作。

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

2 个答案:

答案 0 :(得分:1)

我认为使用模板引擎对于你想要做的事情来说太复杂了。

因此,我通过使用更多的淘汰赛可能性来简化您的代码。 现在的观点是:

<div>
    <div data-bind="foreach: products">
        <div data-bind="foreach: messages"> <a data-bind="click: alertMessage, text : message" href="#"></a>
        </div>
    </div>
</div>

视图模型:

//The Product object contains an observable array for messages and methods to add remove and save them
function Product(id, productMessages) {
    this.productId = id;
    this.messages = ko.observableArray();
    this.messages(ko.utils.arrayMap(productMessages, function (pm) {
        return new Message(pm);
    }));
}

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

var ViewModel = function (raw) {
    var self = this;    
    self.products = ko.utils.arrayMap(json, function (p) {
        return new Product(p.id, p.messages);
    });
};

See fiddle

答案 1 :(得分:1)

我知道这是前一段时间被问到的,但我想尝试解决它,因为我刚刚学习Knockout

在我开始之前,为了让我理解现有的代码,我认为有必要进行一些重构。我删除了许多混淆使用的this引用。此外,我列出了product_messages_to_vmrender_product_messages函数,因为这些函数未被多次调用。

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

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

这里似乎有一些混乱,关于Knockout和Handlebars正在对这个标记做什么。 Knockout需要一个字符串,它是视图模型上方法的名称,它会将此方法绑定到元素的click事件。把手将使用模板数据中的相应值替换Ha​​ndlebars表达式{{this.alertMessage}}

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

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

data-bind="click: alertMessage"

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

为了让Knockout正确绑定此方法,我们必须提供正确的路径:

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>

这要求我们更新template来电:

_.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小提琴here

尽管此代码有效,但我认为 更好的问题解决方案是使用Knockout提供的foreach binding作为suggested Damien

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