Reputation: 1164
I am trying to build a table as follows, but I get "Error: Cannot find closing comment tag to match: ko if: cellIsStartOfRow".
Am I doing it wrong?
<table>
<tbody data-bind="foreach: MyDocs">
<!-- ko if: cellIsStartOfRow -->
<tr class="docsRow">
<!-- /ko -->
<td>
<!-- There is more databinding in here - a div containing a textarea and also containing a hyperlink surrounding an image. I think the contents are irrelevant to my question, but I can post if someone disagrees.-->
</td>
<!-- ko if: cellIsEndOfRow -->
</ tr>
<!-- /ko -->
</tbody>
</table>
Here is the JS for the viewmodel. The contents of the td above are somewhat simplified, because I thought it was fairly irrelevant what was in there. I call the functions from other js on my page. The viewmodel itself is assigned to a variable that is declared on the page.
Type.registerNamespace("HpDocs");
HpDocs.DocsVM = function (data) {
ko.mapping.fromJS(data, {}, this);
// add additional properties to each document for presentation
// purposes
for (i = 0; i < this.MyDocs().length; i++) {
var myDoc = this.MyDocs()[i];
myDoc.docObjectId = "docObject" + myDoc.Id();
myDoc.textareaId = "ucHpDocs" + "_txta";
if (i % 5 == 0) {
myDoc.cellIsStartOfRow = true;
myDoc.cellIsEndOfRow = false;
} else if (i % 5 == 5) {
myDoc.cellIsStartOfRow = false;
myDoc.cellIsEndOfRow = true;
} else {
myDoc.cellIsStartOfRow = false;
myDoc.cellIsEndOfRow = false;
}
}
};
HpDocs.DocsVM.prototype = {
// cellIsStartOfRow: function(){
// return true;
// },
getDocs: function (filter) {
var self = this;
$.ajax({
url: getMethodUrl("GetDocs"),
data: "{'filter': " + filter + "}",
success: function (response) {
ko.mapping.fromJS(response.d, {}, self.MyDocs);
}
})
}
};
HpDocs.dbGetDocs = function (filter) {
$.ajax({
url: getMethodUrl("DbGetDocs"),
data: "{'filter': " + filter + "}",
success: function (response) {
myDocsViewModel = new HpDocs.DocsVM({
MyDocs: ko.mapping.fromJS(response.d)
});
var bindingScope = $("#divMyDocs")[0];
ko.applyBindings(myDocsViewModel, bindingScope);
$(".DocsUpdateProgress").addClass("invisible");
}
})
};
HpDocs.getPreferredTab = function () {
var tabPref = $("[id$='hidDocTabPreference']").html();
return tabPref;
};
HpDocs.showProgress = function () {
$(".DocsUpdateProgress").removeClass("invisible");
};
HpDocs.hideProgress = function () {
$(".DocsUpdateProgress").addClass("invisible");
};
//register the class
HpDocs.DocsVM.registerClass('HpDocs.DocsVM', null, Sys.IDisposable);
// notify ajax that the script is now loaded.
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
I refactored my model: instead of having MyDocs contain a list of objects, I now have it containing a property called Rows which in turn contains a property called Documents. Then, I can just do the following:
<table id="tblMyDocs">
<tbody data-bind="foreach: MyDocs.Rows">
<tr data-bind="foreach: Documents">
<td>
<!-- in here i present each document by databinding to the Model's properties -->
<td>
</tr>
</tbody>
</table>
and of course the viewmodel is much easier, since the model is now organized in rows:
HpDocs.DocsVM = function (data) {
ko.mapping.fromJS(data, {}, this);
};
HpDocs.DocsVM.prototype = {
getDocs: function (filter) {
var self = this;
$.ajax({
url: getMethodUrl("GetDocs"),
data: "{'filter': " + filter + "}",
success: function (response) {
ko.mapping.fromJS(response.d, {}, self.MyDocs);
}
})
}
};
HpDocs.dbGetDocs = function (filter) {
$.ajax({
url: getMethodUrl("DbGetDocs"),
data: "{'filter': " + filter + "}",
success: function (response) {
myDocsViewModel = new HpDocs.DocsVM({
MyDocs: ko.mapping.fromJS(response.d)
});
var bindingScope = $("#divMyDocs")[0];
ko.applyBindings(myDocsViewModel, bindingScope);
HpDocs.hideProgress();
}
})
};
Upvotes: 2
Views: 1827
Reputation: 21
The problem was here:
<!-- ko if: cellIsStartOfRow -->
<tr class="docsRow">
<!-- /ko -->
you have to have both open and close tags inside the if, so..
<!-- ko if: cellIsStartOfRow -->
<tr class="docsRow">
...
</tr>
<!-- /ko -->
Upvotes: 2
Reputation: 7073
Instead of adding formatting logic to your js like below:
// add additional properties to each document for presentation
// purposes
for (i = 0; i < this.MyDocs().length; i++) {
var myDoc = this.MyDocs()[i];
myDoc.docObjectId = "docObject" + myDoc.Id();
myDoc.textareaId = "ucHpDocs" + "_txta";
if (i % 5 == 0) {
myDoc.cellIsStartOfRow = true;
myDoc.cellIsEndOfRow = false;
} else if (i % 5 == 5) {
myDoc.cellIsStartOfRow = false;
myDoc.cellIsEndOfRow = true;
} else {
myDoc.cellIsStartOfRow = false;
myDoc.cellIsEndOfRow = false;
}
I would suggest creating a separate viewmodel for row of data. Since you didn't provide any json data, I wasn't able to solve it 100%, but hopefully this gets you in the right direction. Here is the jsfiddle I was working off of: http://jsfiddle.net/JasonMore/GcSAn/2/
View
<table>
<tbody data-bind="foreach: someArray">
<tr class="docsRow" data-bind="foreach:docRows">
<td>
<div data-bind="attr: {id: objectId}">
<a data-bind="attr: {href: someUrl}">
<img data-bind="attr: {src: IconPath, alt: Tooltip}"/>
</a>
<br/>
<textarea runat="server" readonly="readonly" data-bind="html: DisplayName"></textarea>
</div>
</td>
</ tr>
</tbody>
</table>
Javascript
HpDocs.RowVM = function(data) {
ko.mapping.fromJS(data, {}, this);
}
HpDocs.DocsVM = function(data) {
ko.mapping.fromJS(data, {}, this);
this.docRows = ko.observableArray();
// add additional properties to each document for presentation
// purposes
for (i = 0; i < this.MyDocs().length; i++) {
var myDoc = this.MyDocs()[i];
myDoc.docObjectId = "docObject" + myDoc.Id();
myDoc.textareaId = "ucHpDocs" + "_txta";
if (i % 5 == 0) {
// create new RowVM and start adding DocsVM to it
} else if (i % 5 == 5) {
// push the RowVM to this.docRows populated with cells
} else {
// add the myDoc to the current RowVM you are working with
}
}
};
UPDATED - Links to knockout talk on how to create proper mvvm
Slides and examples: http://bit.ly/FamilyFeudSlides
Code: https://github.com/jasonmore/familyfeud
Upvotes: 2