I have three elements in my HTML, the Question, The Score, The Comment. The Score is an HTML SELECT, with "Poor", "Good" and "Excellent" as it's options.
I only want the Comment field to be visbile if the Score is not = "Good".
<!-- ko foreach: questions -->
<tr>
<td data-bind="text: question"></td>
<td><select data-bind="options: availableScores"></select></td>
<td>
<!-- ko if: availableScores() != 'Good' -->
<input data-bind="textInput: comment" />
<!-- /ko -->
</td>
</tr>
<!-- /ko -->
Any advice appreciated, thanks.
I assume that the comment textbox must only be visible if the selected score differs from the value 'Good'.
For doing so, the selected value must be tracked and bound to the listbox,
here below via the property selectedScore.
A minimal MCVE shows this behaviour.
var Question = function() {
_self = this;
_self.question = "";
_self.comment = ko.observable("");
_self.availableScores = ["Good", "Poor", "Excellent"];
_self.selectedScore = ko.observable("Good");
}
var vm = new Question();
ko.applyBindings(vm);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: availableScores, value: selectedScore"></select>
<!-- ko if: selectedScore() != 'Good' -->
Comment: <input data-bind="textInput: comment" />
<!-- /ko -->
how about you modify this line:
<!-- ko if: availableScores() != 'Good' -->
to something like:
<!-- ko if: displayComments() -->
and in your code add something like:
this.displayComments = ko.computed(function(){ return this.availableScores() !== 'Good'; });
Related
Disclaimer: The environment I'm working in has to be completely inline. The HTML calls to a JS file I am not allowed to edit. That being said, I'm trying to select/deselect all checkboxes in a column when I click the header row. I want to be able to select/deselect any individual row below the header as usual but when I check the header I want EVERY row underneath to select/deselect.
Right now my problem is that selecting the header row only selects or deselects once. So clicking it once unchecks every row but then the functionality stops. For what it's worth the check box doesn't appear in the header row either. That's a different problem though.
The problem resides in the first table row --> label class tag. Any suggestions?
<tbody>
<tr>
<td class="feature-name">All</td>
<!-- ko foreach: $parent.featureHeadings[$index()] -->
<td data-bind="css:{alt: !($index() % 2)}">
<label class="multiple-checkboxes">
<input type="checkbox" data-bind="checked: $parent.dataItems.every(function (acct) {
return !acct.features[$index()].isVisible() ||
acct.features[$index()].value(); }),
click: function (data, index, model, event)
{
var newValue = event.srcElement.checked;
data.forEach(function (acct) {
if (acct.features[index].isVisible())
acct.features[index].value(newValue);
}
);
}.bind($data, $parent.dataItems, $index())" />
</label>
</td>
<!-- /ko -->
</tr>
<!-- ko foreach: dataItems -->
<tr>
<td class="feature-name" data-bind="text: name"></td>
<!-- ko foreach: features -->
<td class="setting" data-bind="highlightOverride: isOverridden(), css: { alt: !($index() % 2) }, highlightHelpHint: isHintActive">
<!-- ko if: type === 'Boolean' -->
<label class="checkbox" data-bind="css: { checked: value }, fadeVisible: isVisible()"><input type="checkbox" data-bind="checked: value" /></label>
<!-- /ko -->
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
Edit: Thank you all for reading. Sorry I could not offer more details to allow you to help me. Here is the final solution.
<label class="multiple-checkboxes" data-bind="css: { checked: $parent.dataItems.every(function (acct) { return !acct.features[$index()].isVisible() || acct.features[$index()].value(); }) }, click: function (data, index, model, event) { var newValue = !event.srcElement.classList.contains('checked'); data.forEach(function (acct) { if (acct.features[index].isVisible()) acct.features[index].value(newValue); }); }.bind($data, $parent.dataItems, $index())">
This works:
<label class="multiple-checkboxes" data-bind="css: { checked: $parent.dataItems.every(function (acct) { return !acct.features[$index()].isVisible() || acct.features[$index()].value(); }) }, click: function (data, index, model, event) { var newValue = !event.srcElement.classList.contains('checked'); data.forEach(function (acct) { if (acct.features[index].isVisible()) acct.features[index].value(newValue); }); }.bind($data, $parent.dataItems, $index())">
I'd like to mark the question as answered but you can see I am new here.
I am new to knockout, currently I met a weird problem. What I want is to use ko if-else bind, please see the code below:
<table>
<thead>
something......
</thead>
<tbody>
<tr>
Problem comes here...
<!-- ko ifnot: editing -->
<td><span data-bind="text: Value" /></td>
<td><button data-bind="click: Edit">Edit</button></td>
<!-- /ko -->
<!-- ko if: editing -->
<td><input data-bind="value: Value"></td>
<td><button data-bind="click: Save">Save</button></td>
<!-- /ko -->
</tr>
</tbody>
</table>
In ViewModel:
function SettingData(){
var self = this;
self.editing = ko.observable(false);
self.Edit = function () {
self.editing(true);
};
self.Save = function () {
self.editing(false);
}
}
When start the page, content in if and ifnot statement all come out (it should be wrong). When click "Edit", only content in if statement show up and content in ifnot statement disappear, which is good, but when click "Save", connent in if and ifnot statement all show up.
I don't know which part of my code is incorrect, can anyone give me hand? Thanks.
You must add Value observable in the viewmodel. Add below line to function SettingData()
self.Value = ko.observable("");
I'm in a bit of trouble from good 20 hours now. I am using knockout.js and dynamically add/remove rows from html table. I am having trouble in displaying an extra column for the remove button dynamically, my template is:
<table class="tg">
<tbody data-bind="template: {name: 'LineItemsBodyScript', foreach: LineItemFields, afterRender: addRowRemoveButton}"></tbody>
</table>
//template that gets called from HTML table.
<script id="LineItemsBodyScript" type="text/html">
<!-- ko ifnot: isFirsElement($index) -->
<tr data-bind="template: {name: 'LineItemDataTemplate', foreach: $data }"></tr>
<!-- /ko -->
</script>
//template called inside the template
<script id="LineItemDataTemplate" type="text/html">
<td><input type="text" data-bind="value: FieldValue, visible: IsVisible, enable: IsUpdatable" class="table-column" /></td>
</script>
If i add remove button in 'LineItemDataTemplate' template, it renders the remove button after every column (makes sense). And if i add remove button in 'LineItemsBodyScript', it gets overwritten by the child template. My model is, List>.
How and where could i add the remove button?
<td><input type='button' value="Remove" /></td>
I looked around and found afterRender afterAdd methods but they are not going to solve the issue.
Note: No. of columns are unknown (therefore i made a generic class for Column-Name & Column-Value)
You can add an extra <td> in the LineItemDataTemplate template when it's being rendered for the last field (grootboek) for each row that is not the header row:
Last field when: $index() == $parentContext.$data.length - 1
Not header row (first row): $parentContext.$index() > 0
Which results in:
<script id="LineItemDataTemplate" type="text/html">
<td><input type="text"
data-bind="value: FieldValue, visible: IsVisible,
enable: IsUpdatable"
class="table-column" /></td>
<!-- ko if: $parentContext.$index() > 0
&& $index() == $parentContext.$data.length - 1 -->
<td>
<button data-bind="click: removeLineItem">Remove</button>
</td>
<!-- /ko -->
</script>
I have a table bound to knockoutjs using foreach.
<table>
<thead>
<tr>
<th>ID</th>
<th>BALANCE</th>
<th>GENDER</th>
<th>AGE</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: data -->
<tr>
<!-- ko foreach: Object.keys($data) -->
<td>
<label data-bind="text: $parent[$data]" />
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
Table rows iterate an observableArray (about 2000 items).
After table is rendered, I need to edit a row but table do not render the row changed.
How can I do this whithout clear observableArray and reload it again?
Here JSFIDDLE
Thank you
You need to make your data array properties observable so knockout would observe the changes. I'd suggest you to use knockout mapping to do the job of creating observables for you, like this:
var foo = new MyVM();
var mapping = {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
};
ko.mapping.fromJS(myJS, mapping, foo.data);
But you need to change your markup so it won't just iterate through object properties, but explicitly specify which property should be used:
<tbody>
<!-- ko foreach: data -->
<tr>
<td>
<label data-bind="text: _id" />
</td>
<td>
<label data-bind="text: balance" />
</td>
<td>
<label data-bind="text: gender" />
</td>
<td>
<label data-bind="text: age" />
</td>
</tr>
<!-- /ko -->
</tbody>
Here is working demo. Of course you can make observables yourself, then just rewrite your code, so every property of each item in data array would be an observable.
Edit:
Well, since we don't know the actual properties, I'd suggest the following code to make observables:
var foo = new MyVM();
for (var i=0, n = myJSON.length; i < n; i++) {
for (var prop in myJSON[i])
if (myJSON[i].hasOwnProperty(prop))
myJSON[i][prop] = ko.observable(myJSON[i][prop]);
}
foo.data(myJSON);
And in your view model function:
self.changeRow = function (){
if(typeof self.data() != "undefined"){
self.data()[0]["gender"]("male");
}
};
See updated demo.
I'm building a simple web app using sinatra and knockout.js. My basic structure is a model on the backend that only returns json, and rendering everything client side using knockout. But my data isn't rendering, even when I pre-populate the array.
Here's part of app.rb:
get "/" do
content_type 'html'
erb :index
end
get "/topics" do
#topics = Topic.all
#topics.to_json
end
get "/topics/:id" do
#topic = Topic.find(params[:id])
#topic.to_json
end
post "/topics/new" do
#topic = Topic.new(name: params[:name],
description: params[:description])
#topic.created_at = DateTime.now
end
And my database:
APP_ROOT = File.expand_path(File.dirname(__FILE__))
DataMapper.setup(:default, "sqlite:///#{APP_ROOT}/db.sqlite3")
class Topic
include DataMapper::Resource
property :id, Serial
property :name, Text
property :description, Text
property :created_at, DateTime
property :updated_at, DateTime
end
DataMapper.auto_upgrade!
My Javascript file:
function Topic(data){
this.name = ko.observable(data.name);
this.description = ko.observable(data.description);
this.created_at = ko.observable(data.created_at);
this.updated_at = ko.observable(data.updated_at);
this.id = ko.observable(data.id);
}
function TopicViewModel(){
var that = this;
that.topics = ko.observableArray([{name: "hello",description: "hi"}]);
$.getJSON("/topics",function(raw){
var topics = $.map(raw, function(item){return new Topic(item)});
that.topics(topics);
});
that.newTopic = ko.observable();
that.addTopic = function(){
var newTopic = new Topic({name: "", description: ""});
$.getJSON("date", function(data){
newTopic.created_at(data.date);
newTopic.updated_at(data.date);
that.topics.push(newTopic);
that.saveTopic(newTopic);
});
};
that.saveTopic = function(topic){
var t= ko.toJS(topic);
$.ajax({
url: "http://localhost:4567/topics/new",
type: "POST",
data: t
}).done(function(data){
topic.id(data.topic.id);
});
}
}
ko.applyBindings(new TopicViewModel());
Finally, my html:
<!DOCTYPE html>
<html>
<head>
<link href="style.css">
<title>Topics</title>
</head>
<body>
<div id="container">
<section id="create">
<h2>New Topic</h2>
<form id="addTopic" data-bind="submit: addTopic">
<input data-bind="value: name"/>
<input data-bind="value: description"/>
<button type="submit">Create Topic</button>
</form>
</section>
<section id="topics">
<!-- ko foreach: topics -->
<td data-bind="text: name"></td>
<td data-bind="text: description"></td>
<td data-bind="text: created_at"></td>
<td data-bind="text: updated_at"></td>
<!-- /ko -->
</section>
<script src="scripts/jquery.js"></script>
<script src="scripts/knockout.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
Why isn't this rendering?
Your problem is that your html is not valid. You are trying to render tds inside a section...
Change your td to span (or div ) and it should work fine:
<section id="topics">
<!-- ko foreach: topics -->
<span data-bind="text: name"></span>
<span data-bind="text: description"></span>
<span data-bind="text: created_at"></span>
<span data-bind="text: updated_at"></span>
<!-- /ko -->
</section>
Or build a correct table (Knockout cannot figure it out that you want a table and magically render it for you)
<table>
<thead>
<tr>
<th>name</th>
<th>description</th>
<th>created_at</th>
<th>updated_at</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: topics -->
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: description"></td>
<td data-bind="text: created_at"></td>
<td data-bind="text: updated_at"></td>
</tr>
<!-- /ko -->
</tbody>
</table>