Reputation: 15872
I'm trying to render a different section of a page and apply the appropriate bindings for different items contained within a single array. Each item in the array could have a different structure / properties.
As an example we could have 3 different question types, the data associated with that question could be in a different format.
JSON Data
var QuestionTypes = { Textbox: 0, Checkbox: 1, Something: 2 }
var QuestionData = [
{
Title: "Textbox",
Type: QuestionTypes.Textbox,
Value: "A"
},
{
Title: "Checkbox",
Type: QuestionTypes.Checkbox,
Checked: "true"
},
{
Title: "Custom",
Type: QuestionTypes.Something,
Something: { SubTitle : "Something...", Description : "...." }
}
];
JavaScript
$(document).ready(function(){
ko.applyBindings(new Model(QuestionData), $("#container")[0]);
})
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.isTextbox = ko.computed(function(){
return self.type() === QuestionTypes.Textbox;
});
self.isCheckbox = ko.computed(function(){
return self.type() === QuestionTypes.Checkbox;
});
self.isSomething = ko.computed(function(){
return self.type() === QuestionTypes.Something;
});
}
function Model(data){
var self = this;
self.questionData = ko.observableArray(ko.utils.arrayMap(data, function(question){
return new QuestionModel(question);
}));
}
HTML
<div id="container">
<div data-bind="foreach: questionData">
<h1 data-bind="text: title"></h1>
<!-- ko:if isTextbox() -->
<div data-bind="text: Value"></div>
<!-- /ko -->
<!-- ko:if isCheckbox() -->
<div data-bind="text: Checked"></div>
<!-- /ko -->
<!-- ko:if isSomething() -->
<div data-bind="text: Something">
<h1 data-text: SubTitle></h1>
<div data-text: Description></div>
</div>
<!-- /ko -->
</div>
</div>
The bindings within the if conditions get applied whether the condition if true / false. Which causes JavaScript errors... as not all of the objects within the collection have a 'Value' property etc.
Uncaught ReferenceError: Unable to process binding "foreach: function (){return questionData }"
Message: Unable to process binding "text: function (){return Value }"
Message: Value is not defined
Is there any way to prevent the bindings from being applied to the wrong objects?
Conceptual JSFiddle: https://jsfiddle.net/n2fucrwh/
Upvotes: 0
Views: 667
Reputation: 7641
First of all your "QuestionModel" has no corresponding properties: you create "type" and "title" fields only from incoming data.
Proposed solution:
You can use different templates for different data types. I've updated your fiddle:
var QuestionTypes = { Textbox: 0, Checkbox: 1, Something: 2 }
var QuestionData = [
{
Title: "Textbox",
Type: QuestionTypes.Textbox,
templateName: "template1",
Value: "A"
},
{
Title: "Checkbox",
Type: QuestionTypes.Checkbox,
templateName: "template2",
Checked: "true"
},
{
Title: "Custom",
Type: QuestionTypes.Something,
templateName: "template3",
Something: "Something"
}
];
$(document).ready(function(){
ko.applyBindings(new Model(QuestionData), $("#container")[0]);
})
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.data = data;
}
function Model(data){
var self = this;
self.questionData = ko.observableArray(ko.utils.arrayMap(data, function(question){
return new QuestionModel(question);
}));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="template1">
<div data-bind="text: Value"></div>
</script>
<script type="text/html" id="template2">
<div data-bind="text: Checked"></div>
</script>
<script type="text/html" id="template3">
<div data-bind="text: Something"></div>
</script>
<div id="container">
<div data-bind="foreach: questionData">
<h1 data-bind="text: title"></h1>
<!-- ko with: data -->
<!-- ko template: templateName -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>
In the above edition you can get rid of "QuestionTypes".
Of course, you can calculate template name from the question type.
Explanation of the cause of errors. If you check original view model:
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.isTextbox = ko.computed(function(){
return self.type() === QuestionTypes.Textbox;
});
self.isCheckbox = ko.computed(function(){
return self.type() === QuestionTypes.Checkbox;
});
self.isSomething = ko.computed(function(){
return self.type() === QuestionTypes.Something;
});
}
You can see, that "QuestionModel" has following properties: "title", "type", "isTextbox", "isCheckbox" and "isSomething".
So, if you will try bind template to "Value", "Checked" or "Something" you will get an error because view model does not contain such a property.
Changing binding syntax to the
<div data-bind="text: $data.Value"></div>
or something similar eliminates the error, but always will display nothing in this case.
Upvotes: 2
Reputation: 1956
Please check out the Updated Fiddler without changing your code.Only added $data in side the loop
https://jsfiddle.net/n2fucrwh/3/
<!-- ko:if isTextbox() -->
<div data-bind="text: $data.Value"></div>
<!-- /ko -->
<!-- ko:if isCheckbox() -->
<div data-bind="text: $data.Checked"></div>
<!-- /ko -->
<!-- ko:if isSomething() -->
<div data-bind="text: $data.Something"></div>
<!-- /ko -->
Inside the loop you need provide $data.Value.It seems to Value is the key word in knockout conflicting with the binding.
Upvotes: 4