我已经为此苦苦挣扎了一段时间,但无法得到我期望的结果。任何帮助将非常感激。这是 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_vm
和 render_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/