I have found a few answer regarding this but has not been able to put things together. First day of using Knockout so please let me know if I am thinking about it completely wrong!
I want to represent a list of comments that I have. The comments contain multiple attributes such as {text: ..., score: ..., ...}
I understand that I have a view model
var MatchupViewModel = function(comments) {
this.comments = ko.observableArray(comments);
}
ko.applyBindings(new MatchupViewModel(comments), document.getElementById("leftchat"));
And I understand that the forEach looks somewhat like this:
in jade (which I am using:
#leftchat.chat(data-bind="forEach: comments")
.fullMessage
.content
p.textcontent(data-bind="text: text, visible: text")
img.imagecontent(data-bind="visible: isImage, attr={src: url}")
.scorecontainer
.buttonContainer
p.likeButtonMessage(bind-data="click=voteComment(id, true)") ▲
p.dislikeButtonMessage(bind-data="click=voteComment(id, false)") ▼
p.messageScore(data-bind="text: score")
translated to html:
<div id="leftchat" data-bind="forEach: comments" class="chat">
<div class="fullMessage">
<div class="content">
<p data-bind="text: text, visible: text" class="textcontent"></p><img data-bind="visible: isImage, attr={src: url}" class="imagecontent"/>
</div>
<div class="scorecontainer">
<div class="buttonContainer">
<p bind-data="click=voteComment(id, true)" class="likeButtonMessage">▲</p>
<p bind-data="click=voteComment(id, false)" class="dislikeButtonMessage">▼</p>
</div>
<p data-bind="text: score" class="messageScore"></p>
</div>
</div>
</div>
It complains that text is not a function, which is the attribute I had hoped it would have been able to find. How do I work around this?
If you fix the typo's you code should work. forEach should be foreach, and bind-data should be data-bind (unless you made it a custom binding).
Have a look at the example below. When text is a falsy value (null, empty string), the node will be hidden, else its value will be visible. This holds true for observable properties too.
var m = {
comments: [
{text: 'a'},
{text: ko.observable('') },
{text: null},
{text: ko.observable('c')}
]
};
ko.applyBindings(m);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="leftchat" data-bind="foreach: comments" class="chat">
<div data-bind="text: text, visible: text" class="textcontent"></div>
</div>
Try changing this
data-bind="text: text, visible: text"
to this
data-bind="text: $data"
Also maybe change this
data-bind="forEach: comments"
to this
data-bind="forEach: comments()"
that should evaluate properly the underlying array (allthough this should be working just fine without the ())
Related
I am prototyping a project and have a bunch of messy code. The particular part of this prototype I'm stuck on is being unable to iterate and render nested array from a JSON file using knockout. Here are some facts:
The value of a specific key that is the issue is a list.
Each item I'm referring to in the JSON "response" is "questions" and "answers"
Questions are just a string
Answers are the list
Im breaking each item in "answer" down and insert into a radio button in html
When knockout does its thing, each of the items is being returned all together through each iter of the radio button rendering.
qna obj is from a function that is filtering an input by calling the json file based on "sub", removing the top node an leaving only
Here is what this case is looking like [Sorry, not enough rep points to embed images, tinypic will have to do!]
http://i64.tinypic.com/25i26xl.png
I'm brand new to using knockout js, I have read the official docs but admittedly I'm having a bit of trouble understanding it
Heres the code lines question, any suggestions would be appreciated.
//questions.json
[
{
"question": "Are you a buyer, seller or renter?",
"answers": ["Buyer", "Seller", "Renter"]
},
{
"question": "Second question",
"answers": ["yes", "no"]
}
]
1
// functions.js
function questionDisplay() {
var self = this;
self.data = ko.observableArray(qna);
}
function initQuestionDisplay() {
var qnaDataModel = new questionDisplay();
$("#questionsDisplay").modal("show");
ko.applyBindings(qnaDataModel);
}
2
<!-- test.html --!>
<div
class="modal-body"
data-bind="template: {name: 'template', data: $data}"
></div>
<script type="text/html" id="template">
<div class="diplay-frame" data-bind="foreach: {data: data, as: '_data'}">
<div class="question-box">
<h2 class="question" data-bind="text: _data['question']"/>
<p class="answers" data-bind="text: _data['answers']"/>
<div data-bind="foreach: _data['answers']">
<input type="radio" name="optionsGroup" data-bind="checked: _data['answers']" />
<span data-bind="text: _data['answers']"></span>
</div>
You were so close. A couple of things to note though.
Use a closing tag where possible like <p data-bind="text: item"></p> over self closing tags like this <p data-bind="text: item" /> when you want to bind things with knockoutjs. The exception to the rule are those tags that are self closing like <input data-bind="value: somevalue" />. This will eliminate a whole series of bugs. I have been caught out with this many times.
Radio buttons need to be grouped by the name so that the selected value only applies to the named group of radio buttons. this will limit the selected answer to just the question the answers relate to and not all answers rendered on the page.
var vm = {arrayOfItems : [ 'item1','item2']}
<ul data-bind="foreach: arrayOfItems">
<li data-bind="text: $data"></li>
</ul>
When you are inside the foreach bound tags you can use the current iteration data-context called $data to reference the current item.
A better explanation can be found here. - Knockout Binding Context
I have included a working example using what you have provided.
There is a "learn" section where it takes you through a pretty comprehensive tutorial on using Knockoutjs.
Knockoutjs Tutorial
var qna = [
{
"questionId": "1",
"question": "Are you a buyer, seller or renter?",
"answers": ["Buyer", "Seller", "Renter"]
},
{
"questionId": "2",
"question": "Second question",
"answers": ["yes", "no"]
}
];
// functions.js
function questionDisplay() {
var self = this;
var mappedData = qna.map(function(item){
item.optionGroupName = "optionGroup_" + item.questionId;
item.selectedAnswer = ko.observable();
return item;
});
self.data = ko.observableArray(mappedData);
}
function initQuestionDisplay() {
var qnaDataModel = new questionDisplay();
//$("#questionsDisplay").modal("show");
ko.applyBindings(qnaDataModel);
}
initQuestionDisplay();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="modal-body" data-bind="template: {name: 'template', data: $data}"></div>
<script type="text/html" id="template">
<div class="diplay-frame" data-bind="foreach: {data: data, as: '_data'}">
<div class="question-box">
<h2 class="question" data-bind="text: _data['question']"></h2>
<p class="answers" data-bind="text: _data['answers']"></p>
<div data-bind="foreach: {data: _data['answers'], as: 'answer'}">
<input type="radio" data-bind="checked: $parent.selectedAnswer, attr:{name: $parent.optionGroupName, value: $data}" />
<span data-bind="text: answer"></span>
</div>
</div>
</div>
</script>
<h2>Results</h2>
<ul data-bind="foreach: data">
<li>
Question: <span data-bind="text: question"></span>
Answer: <span data-bind="text: selectedAnswer"></span>
</li>
</ul>
<div class="tbody" data-bind="foreach: displayItems">
<div class="t-row">
<div class="t-cell">
<div class="manage-location-buttons">
<a href="javascript:void(0)">
<i class="fa fa-pencil" aria-hidden="true" data-bind="toggleClick: $component.openEditPopup"></i> Edit
</a>
<div class="edit-table-popup" data-bind="visible: $component.openEditPopup">
<ul>
<li><a data-hash="#locationmanagement/managelocations/locationediting" data-bind="click: goToTab">Locations</a></li>
<li><a data-hash="#locationmanagement/managelocations/events" data-bind="click: goToTab">Events</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
It is my sample of custom table.
On Link click I will show edit-table-popup div like popup. Cause I use only one observable openEditPopup for all items, onclick I see popup for each row.
openEditPopup = ko.observable<boolean>(false);
toggleClick - is custom dirrective, which changes boolean value to opposite
Is it possible to use only one observable but to show popup only for clicked row?
Yes, it's possible.
The click binding sends two arguments to an event handler:
The clicked $data,
The click-event.
If your click handler is an observable, this means it calls the observable like so: yourObservable(data, event)
Knowing that an observable is set by calling it with an argument, you can imagine what happens. Note that knockout ignores the second argument.
The solution would therefore be to change the openEditPopup from containing a bool, to containing a displayItem, and changing the visible binding to:
visible: $component.openEditPopup() === $data
An example:
var vm = {
selected: ko.observable("A"),
items: ["A", "B", "C", "D"]
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Tabs</p>
<div data-bind="foreach: items">
<span data-bind="text: $data, click: $parent.selected"></span>
</div>
<p>Content</p>
<div data-bind="foreach: items">
<div data-bind="visible: $parent.selected() === $data">
<h1 data-bind="text:$data"></h1>
</div>
</div>
If I understand correctly, all your rows are bound to one observable and so when you click the row it is setting it to true and all pop ups are showing up?
If so, this would suggest you have a popup per row? I'd recommend changing this and having one popup that is toggled per row and then set it's data to the selected row. Something like this can be achieved with the code below.
var viewModel = function() {
var rows = ko.observableArray([
{id: 1, name: "gimble"}, {id: 2, name: "bob"}, {id: 3, name: "jess"}
]);
var selectedRow = ko.observable();
function showRowPopup(row)
{
//console.log(row.id);
if(selectedRow() == row)
selectedRow(null);
else
selectedRow(row);
}
return {
rows: rows,
showRowPopup: showRowPopup,
selectedRow: selectedRow
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: rows">
<div>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
<button data-bind="click: $parent.showRowPopup">Toggle Modal</button>
</div>
</div>
<div data-bind="with: selectedRow">
<h3>My Fake Modal</h3>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
</div>
I just started using knockout and having some problems using foreach.
I need to dynamically populate a list with "collapsing" div.
I cant figuer out how to set the
"data-target=""" with the according div id.Is there a kind of $index as in Angular.How do i declare it inside of the data-target?
Thank for the help.
<ul class="nav navbar-nav side-nav">
<li data-bind="foreach: { data: categories, as: 'category' }">
<div id="??" class="collapse">
<h1>Some text</h1>
</div>
</li>
</ul>
Do it within the data-bind:
<a href="javascript:;" data-toggle="collapse" data-bind="attr: { 'data-target': ... }">
<div class="collapse" data-bind="attr: { id: ... }">
Knockout does have a $index context property, too:
<div data-bind="attr: { id: 'foo' + $index() }">
What's data-target being used for? I don't think you not need that in the first place.
If you want to toggle a section, I recommend using a more natural way to solve this. In the context of knockout this means: write a binding handler that encapsulates the behavior you want.
Expressed in simplest terms: You want to click on something and it should toggle something else. For example the visibility of the following <h1>
The minimal thing to do would be: Toggle a CSS class and use that to influence visibility.
Here is a binding handler that switches a CSS class on and off. Together with the simplest CSS you get collapse/expand behavior.
ko.bindingHandlers.toggleClass = {
init: function (element, valueAccessor) {
ko.utils.registerEventHandler(element, 'click', function () {
var cssClass = valueAccessor(),
shouldHaveClass = element.className.indexOf(cssClass) < 0;
ko.utils.toggleDomNodeCssClass(element, cssClass, shouldHaveClass);
});
}
}
ko.applyBindings({
categories: [
{title: 'Category A'},
{title: 'Category B'},
{title: 'Category C'},
{title: 'Category D'}
]
});
.collapsed+.collapse {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: categories">
<li>
<div class="collapse">
<h1>Some text</h1>
</div>
</li>
</ul>
Of course you can use all kinds of more advanced techniques, but the principle would stay the same. Define a behavior, attach that behavior to an element via a binding handler.
I am using knockoutjs and in my Template i have a foreach loop that will display the data it grabs from the JSON data i want to set the focus to the first input on the screen it works perfectly when there is just on input on the screen but when it gets to the second screen there are two in puts it only sets focus to the last input on the screen
here is my HTML:
<div id="section">
<span data-bind="template: { name: 'screenTemplate', foreach: screens, as: 'screen'}"></span>
<script type="text/html" id="screenTemplate">
<!-- ko if: showFlds -->
<!-- ko if: showNote -->
<span data-bind="template: { name: 'fldTemplate', foreach: flds}"></span>
<!--/ko-->
<!--/ko-->
</script>
<script type="text/html" id="fldTemplate">
<form>
<span class="text" data-bind="text: fieldName"></span>
<br/>
<input type="text" class="inputVal" data-bind="valueUpdate: 'afterkeydown', value: inputValue, disable: (inputValue() == expectedValue()), visible:(inBool() != false)" />
<br/>
</form>
</script>
<div data-bind="visible:screens()[cScreen()].rdtNote() === true">
<h2 style="color: red"><span data-bind="text: rdtNotification()[0].description()"></span></h2>
<button data-bind="click: makeHidden">Acknowledged</button>
</div>
As shown the hasFocus is on the input in the field Template
I want to know if there is a way I can make it set focus on the first input and then move to the next input once that input is correct
If any more information is needed please ask I will do my best to provide
P.s
Any answer to this must also work in IE6 as it will eventually be running on a IE6 scanning gun
Current Progress:
I have used jQuery
$(document).ready(function(){
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
})
But when using this it does not automatically select the next input when the screen changes is there a way to get that to work ?
SOLVED MY PROBLEM :
$(document).ready(function() {
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
});
$(document).keyup(function(e){
$('form').find('input[type=text],textarea,select').filter(':input:enabled:visible:first').focus();
});
var e = $.Event("keyup", { keyCode: 13}); //"keydown" if that's what you're doing
$("body").trigger(e);
Using this I am able to set the focus to the first visible enabled input and then using jQuery to trigger the Enter Key key up I was able to make sure that the next input was always focused
Thanks #neps for the push in the right direction
Working Plunker
Below is my HTML binding to display the records. I, have apply knockout js to perform the condition check as you can see the IF statement.
I want to use count++ as a knockout variable and perform the condition check.
The above code also not working for me.
Please can anyone let me know how to check the condition in knockout.
<div data-bind="foreach: GuidelinesQuestionList" id="problemcollapse">
<div data-bind="foreach: $data.SectionsSet">
<div class="primaryCaseContainer">
<div class="questionHeader" data-bind="text: $data.Heading , attr:{onClick: 'variableName.CollapseExpandCustom.ToggleSection(\''+$data.Uid.replace(/[^\w\s]/gi, '')+'\')'}"></div>
<div data-bind="attr: {id: $data.Uid.replace(/[^\w\s]/gi, '')}">
#{int count = 0;}
<div class="questionContainer" data-bind="foreach: $data.ProblemsSet">
<div data-bind="if: $data.Identified">
#{count++;}
<div>
<br><br>
<div data-bind="attr: {id: 'goalsReplaceDiv'+$data.Uid.replace(/[^\w\s]/gi, '')}"></div>
</div>
</div>
</div>
#if (count == 0)
{
<div id="divNoRecordsMessage">
<div>No Records !! </div>
</div>
}
</div>
</div>
</div>
</div>
You are mixing C# razor code with Knockout bindings. The count variable will not increment in your loop because it's evaluated before being returned to the client. Your loop is being rendered on the client.
Instead of doing that, make your divNoRecordsMessage show/hide based on a KO binding.
Something like this:
<div data-bind="visible: conditionForNoRecords">
No Records
</div>
But you should really make a custom filter for the ProblemSet array, something like this:
self.filteredProblemsSets = ko.computed(function() {
return ko.utils.arrayFilter(this.ProblemsSet(), function(item) {
return item.Identified;
});
}, viewModel);
You could then skip your if condition in the view and you would be able to easily display "No messages" when the array is empty.