I need some help here: https://jsfiddle.net/vhaurnpw/
I want to do a simple list, which is filterable by the input on top and updates itself..
JS/Knockout:
var viewModel = {
query: ko.observable(''),
places: ko.observable(data),
search: function(value) {
viewModel.places = [];
console.log(value)
for (var i = 0; i < data.length; i++) {
if(data[i].name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.places.push(data[i]);
}
console.log(viewModel.places);
}
}
};
viewModel.query.subscribe(viewModel.search);
ko.applyBindings(viewModel);
HTML:
<form acion="#" data-bind="submit: search">
<input placeholder="Search" type="search" name="q" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</form>
<ul data-bind="foreach: places">
<li>
<span data-bind="text: name"></span>
</li>
</ul>
List will be rendered, but it when you type something, it doesn't show you the result.
Instead when you lookup the console, you will see the console.log and it updates just fine!
so how do i refresh my HTML? :(
There are following issues in your code.
places needs to be an ObservableArray and not an Observable so that you can track the addition/removal from the Observable Array. So, change code From places: ko.observable(data) To places: ko.observableArray(data),
viewModel.places is function, so when you assign another value like viewModel.places = [], it is assigning an empty array to the viewModel.places. In order to modify the value of the viewModel.places, you need to call it as a function like viewModel.places([]);
Note: Your code doesn't add the data back in case the textbox is cleared, I hope you got the solution to the problem and you can resolve this issue as well.
Complete Working Code:
var data = [
{ name: 'Isartor'},
{ name: 'Sendlinger Tor'},
{ name: 'Marienplatz'},
{ name: 'Stachus'},
{ name: 'Bayerischer Rundfunk'},
{ name: 'Google München'},
{ name: 'Viktualienmarkt'},
{ name: 'Museumsinsel'},
{ name: 'Tierpark Hellabrunn'},
{ name: 'Allianz Arena'},
{ name: 'Olympia-Park'},
{ name: 'Flaucher-Insel'}
];
var viewModel = {
query: ko.observable(''),
places: ko.observableArray(data),
search: function(value) {
viewModel.places([]);
console.log(value)
for (var i = 0; i < data.length; i++) {
if(data[i].name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.places.push(data[i]);
}
console.log(viewModel.places);
}
}
};
viewModel.query.subscribe(viewModel.search);
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form acion="#" data-bind="submit: search">
<input placeholder="Search" type="search" name="q" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</form>
<ul data-bind="foreach: places">
<li>
<span data-bind="text: name">asd</span>
</li>
</ul>
Related
I am making an AngularJs survey application.
I want to show tasks one by one. And every task can have one or multiple questions with radio button answers. And I want to save question+answer pair in the new array.
If I want to show only one question per task than I was able to get answer values from radio buttons and push then into answer array. But as I have multiple questions and multiple radio button groups per page, I can't find a way to get the selected radio buttons values and push them into the answers array. I have read that ng-model can solve this, but I couldn't figure how.
This is what I have so far: https://jsfiddle.net/8qfom9th
<div ng-app="surveyApp" ng-controller="surveyCtrl">
<div ng-repeat="questionSet in questionSet">
<div ng-if="question_index == $index">
<div ng-repeat="onePageQuestions in questionSet.onePageQuestions">
<div ng-repeat="question in onePageQuestions.question">
{{question.description}}
<form action="">
<div ng-repeat="options in question.options">
<input type="radio" name="gender" ng-model="question.random" ng-value="options.answer"> {{options.answer}}
</div>
</form>
</div>
</div>
</div>
</div>
<hr>
<button ng-click="next(); submitAnswers()">Next</button>
It's actually pretty simple buddy.
You can get the value of the selected button by using the checked property. Since only one radio button can be selected from a group, you would be able to get the value of the selected one easily using this property in an if loop within the javascript.
Since you have given the options of the radio buttons a name already, i.e., gender. You can simply get all the options elements, using the following:
var options = document.getElementsByName('gender');
var option_value; //to get the selected value
The next step is to loop through all the buttons and check which of those is selected.. To loop through them use a for loop as follows: for (var i = 0; i < options.length; i++) {...}
To check if it is selected or not, use the checked attribute as follows:
if (options[i].checked) {
option_value = options[i].value;
}
I'm not sure what you intend to do with those values, hence I have assumed that you need to display those and to do that just create another element, like a <div> and give it an ID. And then just add the selected option value to that element. Should be something like this:
HTML: <div id="selected">The selected options are:</div>
JS:document.getElementById('selected').innerHTML += "<br>" + option_value;
Updated your fiddle.
Or if you want to check it right here,
here is the updated code:
var app = angular.module('surveyApp', []);
app.controller('surveyCtrl', function($scope) {
$scope.questionSet = [{
onePageQuestions: [{
question: [{
description: 'question#1?',
options: [{
answer: 'answer#1'
}, {
answer: 'answer#2'
}, {
answer: 'answer#3'
}]
},
{
description: 'question#2?',
options: [{
answer: 'answer#4'
}, {
answer: 'answer#5'
}, {
answer: 'answer#6'
}]
}
]
}]
},
{
onePageQuestions: [{
question: [{
description: 'question#3?',
options: [{
answer: 'answer#7'
}, {
answer: 'answer#8'
}, {
answer: 'answer#9'
}]
}]
}]
}
];
$scope.question_index = 0;
$scope.next = function() {
if ($scope.question_index >= $scope.questionSet.length - 1) {
$scope.question_index = 0;
} else {
$scope.question_index++;
}
};
$scope.submitAnswers = function() {
var options = document.getElementsByName('gender');
var option_value;
for (var i = 0; i < options.length; i++) {
if (options[i].checked) {
option_value = options[i].value;
document.getElementById('selected').innerHTML += "<br>" + option_value;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="surveyApp" ng-controller="surveyCtrl">
<div ng-repeat="questionSet in questionSet">
<div ng-if="question_index == $index">
<div ng-repeat="onePageQuestions in questionSet.onePageQuestions">
<div ng-repeat="question in onePageQuestions.question">
{{question.description}}
<form action="">
<div ng-repeat="options in question.options">
<input type="radio" name="gender" ng-model="question.random" ng-value="options.answer"> {{options.answer}}
</div>
</form>
</div>
</div>
</div>
</div>
<hr>
<button ng-click="next(); submitAnswers()">Next</button>
<hr>
<div id="selected">The selected options are:
</div>
</div>
Use parent.answer for ng-model for dynamic radio button.
And for your use case I have added one saveAnswers functions to manipulate and save users answers.
Below is the code for your use case demo, run it and see updated questionSet in console.
var app = angular.module('surveyApp', []);
app.controller('surveyCtrl', function($scope) {
$scope.answer = '';
$scope.saveAnswers = function(description, options) {
$scope.questionSet.map(function(value, key) {
value.onePageQuestions.map(function(item, index) {
item.question.map(function(question, count) {
if (question.description === description.description) {
question.answer = options;
}
})
})
})
}
$scope.questionSet = [{
onePageQuestions: [{
question: [{
description: 'question#1?',
answer: '',
options: [{
answer: 'answer#1'
}, {
answer: 'answer#2'
}, {
answer: 'answer#3'
}]
},
{
description: 'question#2?',
answer: '',
options: [{
answer: 'answer#4'
}, {
answer: 'answer#5'
}, {
answer: 'answer#6'
}]
}
]
}]
},
{
onePageQuestions: [{
question: [{
description: 'question#3?',
answer: '',
options: [{
answer: 'answer#7'
}, {
answer: 'answer#8'
}, {
answer: 'answer#9'
}]
}]
}]
}
];
$scope.question_index = 0;
$scope.next = function() {
if ($scope.question_index >= $scope.questionSet.length - 1) {
$scope.question_index = 0;
} else {
$scope.question_index++;
}
};
$scope.submitAnswers = function() {
console.log($scope.questionSet)
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="surveyApp" ng-controller="surveyCtrl">
<div ng-repeat="questionSet in questionSet">
<div ng-if="question_index == $index">
<div ng-repeat="onePageQuestions in questionSet.onePageQuestions">
<div ng-repeat="question in onePageQuestions.question">
{{question.description}}
<form action="">
<div ng-repeat="options in question.options">
<input ng-change="saveAnswers(question,options.answer)" type="radio" name="gender" ng-model="$parent.answer" ng-value="options.answer"> {{options.answer}}
</div>
</form>
</div>
</div>
</div>
</div>
<hr>
<button ng-click="next()">Next</button>
<button ng-click="submitAnswers()"> Submit</button>
Hope this will help you!
I have custom dropdown (made with using divs and list)
<div class="primary-tags-wrapper">
<div id="primaryTag" class="primary-tags-dropdown ui-dropdown fl">
<div class="fl">
<div class="primary-tag-selected-value" data-bind="text: showPrimaryTag"></div>
</div>
<div class="fr" data-primary="tag">
<div class="fa fa-caret-down"></div>
<ul class="primary-tags-list">
<li class="primary-tags-item">
<input class="primary-tags-item-radio" type="radio" name="primary-tag" id="primary-tag-default" data-bind="checkedValue: null, checked: primaryTag"/>
<label class="primary-tags-item-label" for="primary-tag-default">Set Primary Tag</label>
</li>
<!-- ko foreach: tags -->
<li class="primary-tags-item">
<input class="primary-tags-item-radio" type="radio" name="primary-tag" data-bind="attr: { 'id': 'primary-tag-' + $index() }, checkedValue: $data, checked: $parent.primaryTag"/>
<label class="primary-tags-item-label" data-bind="attr: { 'for': 'primary-tag-' + $index() }, text: $data"></label>
</li>
<!-- /ko -->
<li class="primary-tags-item">
<input type="button" class="btn green-btn" data-bind="click: savePrimaryTag" value="Save"/>
</li>
</ul>
</div>
</div>
</div>
To it I have binded knockout ViewModel
var TagsViewModel = function (inputModel) {
var vm = this;
vm.tags = ko.observableArray(inputModel.tags);
vm.allTags = ko.observableArray(inputModel.allTags);
vm.primaryTag = ko.observable(inputModel.primaryTag);
vm.refreshTags = function () {
var data = vm.tags().slice(0);
vm.tags([]);
vm.tags(data);
};
vm.savePrimaryTag = function() {
var data = {
locationId: inputModel.locationId,
reviewId: inputModel.reviewId,
tag: vm.primaryTag()
};
initializeAjaxLoader();
$.post('/data/reviews/primaryTag',
data,
function(response) {
if (!response.status) {
vm.primaryTag('');
} else {
vm.primaryTag(response.tag);
}
removeAjaxLoader();
});
}
vm.showPrimaryTag = ko.pureComputed(function() {
var primaryTagVal = vm.primaryTag();
if (primaryTagVal) {
return 'Primary Tag: ' + primaryTagVal;
}
return DEFAULT_PRIMARY_TAG;
},
vm);
vm.noPrimaryTagSelected = ko.pureComputed(function() {
var primaryTagVal = vm.primaryTag();
if (primaryTagVal) {
return false;
}
return true;
},
vm);
}
In dropdown I have default option : "Set Primary Tag" which should be selected when primaryTag is null or string.Empty. Currently it is what I can't achive.
So is it possible to set multiple checkedValue to radio button, or there are another way to support this "feature"
When knockout handles the checked binding, it compares primitives using ===. This means, as you've noticed, that a checked value of null doesn't work with "", false, undefined or 0.
If you somehow can't prevent your selected value to be initialized as an empty string, you could bind to a computed layer that "sanitizes" the output.
All of the radio inputs write their value to a computed observable.
The computed observable has a private backing field to store raw input
The read method makes sure all falsey values are returned as null
var VM = function() {
// Notice this can be initialized as any falsey value
// and the checkedValue=null binding will work.
const _selectedTag = ko.observable("");
this.selectedTag = ko.computed({
read: function() {
// Explicitly "cast" all falsey values
// to `null` so it can be handled by
// knockout's `checked` binding:
return _selectedTag() || null;
},
write: _selectedTag
});
this.tags = [
{ label: "one" },
{ label: "two" },
{ label: "three" },
{ label: "four" },
]
};
ko.applyBindings(new VM());
label { display: block }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>
<label>
<input type="radio" data-bind="checked: selectedTag, checkedValue: null">
Don't use a tag
</label>
<!-- ko foreach: tags -->
<label>
<input type="radio" data-bind="checked: $parent.selectedTag, checkedValue: $data">
<span data-bind="text: label"></span>
</label>
<!-- /ko -->
</div>
I'm a new programmer for knockout.
Here's a question about ko.observableArray while practicing.
I give an zero-based index array for initial data that will show with JSON while clicking button.
But when I try to update any value from input field but I cannot get the new JSON after clicking button.
And I think the problem is that there's no index in my array.How can I get the new JSON after clicking
function ViewModel(inputs){
this.inputs = ko.observableArray(inputs);
this.getData = function(){
this.jsonData(ko.toJSON(this.inputs));
};
this.jsonData = ko.observable('');
};
var initialData = [ 'Jan', 'Feb', 'Mar', 'etc' ];
var viewModel = new ViewModel(initialData);
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<ul data-bind="foreach: inputs">
<li>
The current item is: <input type='text' data-bind="value: $data"></b>
</li>
</ul>
<textarea rows='5' cols='60' data-bind='value: jsonData'> </textarea>
<button data-bind='click: getData'>get Data</button>
I thing due to $index you can't get this updated JSON
if you add key value pair in "initialData" you will get updated JSON data
Here blow working code :
function ViewModel(inputs) {
this.inputs = ko.observableArray(inputs);
this.getData = function () {
this.jsonData(ko.toJSON(this.inputs));
};
this.jsonData = ko.observable('');
};
var initialData = [{ name: 'Jan' }, { name: 'Feb' }, { name: 'Mar' }, { name: 'etc' }];
var viewModel = new ViewModel(initialData);
ko.applyBindings(viewModel);
<ul data-bind="foreach: inputs">
<li>
The current item is: <input type='text' data-bind="value: name"><br />
</li>
</ul>
<textarea rows='5' cols='60' data-bind='value: jsonData'> </textarea>
<button data-bind='click: getData'>get Data</button>
I have been following some tutorials and trying to follow knockout.js. I am not able to edit any data that is newly added.
My JS Code:
var data = [
new ListData("Programmer", 1),
new ListData("Testing", 2),
new ListData("DBA", 3),
new ListData("Lead", 4),
new ListData("Manager", 5)
];
function ListData(desig, id) {
return {
Desig: ko.observable(desig),
Id: ko.observable(id)
};
}
var viewModel = {
list: ko.observableArray(data),
dataToAdd: ko.observable(""),
selectedData: ko.observable(null),
addTag: function () {
this.list.push({ Desig: this.dataToAdd() });
this.tagToAdd("");
},
selecData: function () {
viewModel.selectedData(this);
}
};
$(document).on("click", ".editData", function () {
});
ko.applyBindings(viewModel);
My View Code:
<input type="text" data-bind="value: dataToAdd" />
<button data-bind="click: addData">
+ Add
</button>
<ul data-bind="foreach: list">
<li data-bind="click: $parent.selecData">
<span data-bind="text: Desig"></span>
<div>
Edit
</div>
</li>
</ul>
<div id="EditData" data-bind="with: selectedData">
Designation:
<input type="text" data-bind="value: Desig" />
</div>
I am able to edit the data which already exists like - Programmer, Testing, DBA...but if I add a new data..I am not able to edit. Please assist.
Your addData (you named it addTag in the code, but call addData in the HTML) function doesn't construct new elements the same way your initial data creation does. Note: since your ListData constructor explicitly returns an object, new is superfluous.
addData: function () {
this.list.push(ListData(this.dataToAdd(), ""));
},
I try to search by name in observable array. Here's my code:
<input class="form-control" data-bind="value: Query, valueUpdate: 'keyup'" autocomplete="off">
And my code in ViewModel
viewModel.Query = ko.observable('');
viewModel.search = function(value) {
viewModel.TestList.removeAll();
for (var x in viewModel.TestList) {
if (viewModel.TestList[x].Name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.TestList.push(viewModel.TestList[x]);
}
}
}
viewModel.Query.subscribe(viewModel.search);
First: I would like to search by name string.
Two: Is there any other sollutions to not remove all elements from the view? I mean when query string is empty, there should be all list again.
Now I have error message:
TypeError: viewModel.TestList[x].Name is undefined
Use a computed observable array to show search results, along these lines:
var viewModel = {
items: [ { Name: "Apple part" }, { Name: "Apple sauce" }, { Name: "Apple juice" }, { Name: "Pear juice" }, { Name: "Pear mush" }, { Name: "Something different" } ]
};
viewModel.Query = ko.observable('');
viewModel.searchResults = ko.computed(function() {
var q = viewModel.Query();
return viewModel.items.filter(function(i) {
return i.Name.toLowerCase().indexOf(q) >= 0;
});
});
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input class="form-control" data-bind="value: Query, valueUpdate: 'keyup'" autocomplete="off">
<h3>Search results:</h3>
<ul data-bind="foreach: searchResults">
<li data-bind="text: Name"></li>
</ul>
<h3>All items:</h3>
<ul data-bind="foreach: items">
<li data-bind="text: Name"></li>
</ul>
This also removes the need for a subscription or seperate function.
This example utilizes:
A regular observableArray for storing all items (this list is always the same, regardless of your search query);
A read-only computed observable which returns a filtered array of items that match your query;
The array filter method (you call it on the observableArray, but KO just forwards it to the underlying array);
As you can see in the example, items will always contain all objects, and searchResults is just a filtered read-only view on that array.