i have a model like this
function ViewModel(){
var self = this
self.Choices = ko.observableArray([])
self.AcceptedChoices = ko.observableArray([])
self.LoadData = function(){
self.ViewAnswered()
}
self.ViewAnswered = function(){
var url = 'QuestionsApi/ViewAnswered'
var type = 'GET'
ajax(url , null , self.OnViewAnsweredComplete, type )
}
self.OnViewAnsweredComplete = function(data){
var currentAnswer = data.Answer
self.Choices(currentAnswer.Choices)
self.AcceptedChoices(currentAnswer.AcceptedChoices)
}
self.LoadData()
}
Here is my object. I have removed extra things
{
"AcceptedChoices": [94, 95],
"Choices": [{
"ChoiceId": 93,
"ChoiceText": "Never"
}, {
"ChoiceId": 94,
"ChoiceText": "Sometimes"
}, {
"ChoiceId": 95,
"ChoiceText": "Always"
}]
}
And here is binding
<u data-bind="foreach:Choices">
<li>
<input type="checkbox" name="choice[]" data-bind="value:ChoiceId,checked:$root.AcceptedChoices">
<span data-bind="text:ChoiceText">Never</span>
</li>
</u>
Now the problem is that checkboxes are not being checked due to the choices being array of objects. How can i resolve this issue? Although the same thing works for radio where there is only one selection.
Never mind i have found a solution here
checked binding does not properly compare primatives
Also it tells two ways for this. The Solution provided in fiddle is creepy so i will use the one using knockout version 3.0.0.
All i need to do is attach knockout-3.0.0.js instead of any other and then use checkedValue instead of value.
<input type="checkbox" name="choice[]"
data-bind="
checkedValue:ChoiceId,
checked:$root.AcceptedChoices"
>
And that's done. Hope it helps someone.
EDITS :
I noticed it is not working on the Chrome. So i found an alternative. I created these two functions.
self.ConvertToString = function(accepted){
var AcceptedChoices = []
ko.utils.arrayForEach(accepted, function(item) {
AcceptedChoices.push(item.toString())
})
return AcceptedChoices
}
self.ConvertToInteger = function(accepted){
var AcceptedChoices = []
ko.utils.arrayForEach(accepted, function(item) {
AcceptedChoices.push(parseInt(item))
})
return AcceptedChoices
}
And use them
self.AcceptedChoices(self.ConvertToString(currentAnswer.AcceptedChoices))
To get the value
AcceptedChoices: self.ConvertToInteger(self.AcceptedChoices()),
You need to be checking to see if the Id of a choice is in the AcceptedChoices array. Use the ko.utils array function to help do that:
checked: function() { return ko.utils.arrayFirst($root.acceptedChoices(), function(item){
return item == ChoiceId();
} !== null }
You could put this into a function on your root object:
self.isChoiceAccepted = function(choiceId){
return ko.utils.arrayFirst($root.acceptedChoices(), function(item){
return item == choiceId;
} !== null
};
then call it in your data-bind as:
checked: function() { return $root.isChoiceAccepted(ChoiceId()); }
This isn't tested, I'm not 100% sure that the arrayFirst method returns null if it doesn't find a matching item in the array, so chack that.
Related
I have below KnockoutObservableArray. I want to search and retrieve the only first 5 results from this array.
var people: KnockoutObservableArray<string>;
this.people.push("abc");
this.people.push("aaa");
this.people.push("xyz");
...
I tried multiple options using people.filter method but could not find exactly what i required.
Can someone please help me here.
Not totally sure what you mean by searching the array. If you want to apply some sort of test to the array items and only retrieve the items that pass then ko.utils.arrayFilter is the way to go.
An example that filters an array and only returns values that match the value held in another observable:
self.KeyWorkerFilter = ko.observable('');
self.KeyWorkersFiltered = ko.computed(function () {
// if the filter is blank then just return the whole collection
if (!self.KeyWorkerFilter || self.KeyWorkerFilter() === '') {
return self.TpwKeyWorkers();
}
var filteredKeyWorkers = ko.utils.arrayFilter(self.TpwKeyWorkers(), function (item) {
var name = item.name.toLowerCase();
return name.includes(self.KeyWorkerFilter().toLowerCase());
});
return filteredKeyWorkers;
});
so in your case you might be doing something like
var.peopleFilter = ko.computed(function() {
return ko.utils.arrayFilter(this.people, function(item) {
// whatever your test is
// example: only return array items that are equal to "aaa"
item === "aaa";
});
});
You can create a computed property like this which will filter the array based on the searchText. Then use slice to get the first 5 results:
Search for "a" in the input and you'll only be able to see the results till "ae".
let viewModel = function() {
let self = this;
self.people = ko.observableArray(["aa", "ab", "ac", "ad", "ae", "af", "ag"]);
self.searchText = ko.observable('');
// When "searchText" or "people" changes, this gets computed again
self.filteredPeople = ko.computed(function() {
if (!self.searchText() || !self.searchText().trim()) {
return [];
} else {
return self.people()
.filter(p => p.toLowerCase().indexOf(self.searchText().toLowerCase()) > -1)
.slice(0, 5);
}
});
};
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<input type="text" data-bind="value: searchText, valueUpdate: 'keyup'" placeholder="Search...">
<div data-bind="foreach:filteredPeople">
<span data-bind="text:$data"></span><br>
</div>
I have data array like this :
$scope.data = [{
name: 'joseph',
statarray: [{
status: 'Online',
status: 'Offline',
}],
active: 'yes'
},
{
name: 'arnold',
statarray: [{
status: 'Offline'
}],
active: 'no'
},
{
name: 'john',
statarray: [{
status: 'Online'
}],
active: 'yes'
}
];
$scope.findObjectByKey = function(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
};
$scope.Online = function(array){
var obj = $scope.findObjectByKey(array, 'status', 'Online');
return obj;
}
$scope.Offline = function(array){
var obj = $scope.findObjectByKey(array, 'status', 'Offline');
return obj;
}
The functions $scope.Online and $scope.Offline sorts the data according to the status Online and Offline.
Here's my view :
I have these two checkboxes as filters :
<input ng-true-value='Online' ng-false-value='' type="checkbox" ng-model="online" type="checkbox">Online
<input ng-true-value='Offline' ng-false-value='' type="checkbox" ng-model="offline" type="checkbox">Offline
<div ng-repeat="user in data|filter:online|filter:offline">
<p>{{user.name}}</p>
</div>
Currently when I click the checkbox corresponding to Online it displays the user joseph and john whose status is Online and when I click the checkbox corresponding to Offline it displays the users joseph and arnold whose status are Offline. This much is working perfectly. But when I click both the filter buttons it only displays joseph as joseph has both Online and Offline status. So an AND operation is being applied here. But I want an OR operation here. So when I click both the filter buttons I should get the output as joseph,arnold and john in the view. Any clue on how can I do it?
First, your statarray seems wrong, considering you declared one object with two properties with the same name, first we should move it to something like an array only containing the status strings ex. ['Online', 'Offline'].
You are executing the filter function only using the latest filter selected.
You need to think in a different approach to aggregate your selected filters,
something like create an filter obj.
filter = {
online: true,
offline: false
}
and iterate over then to display your data
$scope.filterArray = function(array, key, value) {
var filtered = [];
for (var i = 0; i < array.length; i++) {
var shouldInclude = false;
shouldInclude |= ($scope.filter.online && array[i].statarray.indexOf('Online') >= 0);
shouldInclude |= ($scope.filter.offline && array[i].statarray.indexOf('Offline') >= 0);
if (shouldInclude) {
filtered.push(array[i]);
}
}
return filtered;
};
This is just one possible approach, if you are able to use ES6 functions this become even simpler.
# pravin navle-
Are you sure its working same as you described below code? Because when I tried to replicated same functionality it works only for Offline and not for Online as well as Both Checked.
I have the following spans inside a DIV
<div class="itens">
<span data-type="car" data-value="1"></span>
<span data-type="car" data-value="2"></span>
<span data-type="car" data-value="3"></span>
<span data-type="bus" data-value="1"></span>
<span data-type="bus" data-value="2"></span>
<span data-type="airplane" data-value="1"></span>
</div>
And I need to iterate through the spans and group by type. In the end, I need to have a hash like this
{
car: [1,2,3],
bus: [1, 2],
airplane: [1]
}
I've tried some snippets but I can't solve this.
Thanks.
You can use the jQuery [attribute=value] selector for each data type, then iterate each one with .each
const store = {
car: [],
bus: [],
airplane: [],
};
$("[data-type='car']").each(function() {
store.car.push(this.getAttribute('data-value'));
});
$("[data-type='bus']").each(function() {
store.bus.push(this.getAttribute('data-value'));
});
$("[data-type='airplane']").each(function() {
store.airplane.push(this.getAttribute('data-value'));
});
For a more general case, we want to initialize the arrays as we discover types that don't have one yet.
const store = {};
$(".items span").each(function() {
let dataType = this.getAttribute('data-type');
//see if the array for the type has been defined yet
if(!store.hasOwnProperty(dataType)) {
store[dataType] = [];
}
store[dataType].push(this.getAttribute('data-value'));
});
Try using this Javascript Code -
var resultArr = new Object();
$('div.itens span').each(function(key, val){
var typeVal = $(val).attr('data-type');
var value = $(val).attr('data-value');
if(resultArr.hasOwnProperty(typeVal)){
resultArr[typeVal].push(value);
}
else{
resultArr[typeVal] = [value];
}
});
I am having a situation like in this post here only that I not only need to fetch the element, but change its e.g. name value.
I already found out that one could do it like that:
dataList.splice(index, 1);
dataList.splice(index, 0, newItem);
But there are several problems. I know the id but if I am manipulating the array from time to time I will loose track of the index <=> id correlation because with this method i will take out items, change them and push them as a "new" one, right? But that is kind of not really elegant and could cause problems I think.
Basically I just want to toggle a visible attribute which then should change in the array. Here is the array:
$scope.cLines = [{ id: 1, cColor: 'red', cName: 'Entryline right', visible: true }];
Of course there are usually more elements inside, but I left one for simplicity reasons.
The visible toggler should be working like that (naiv "pseudocode" which would be really awesome if it would work like that simple :) )
$scope.cLines[id === id].visible = !$scope.cLines[id === id].visible;
Second best thing would be if I could access the element directly with the filter, is that possible?
Thank you in advance.
There are several ways to go about it. One is to use filter().
var id = 1;
var visibility = true;
var items = $scope.cLines.filter(function(item) {
return item.id === id;
});
if (items.length >= 1) items[0].visible = visibility;
You can wrap that into a function:
function setVisibility(arr, id, visibility) {
var items = arr.filter(function(item) {
return item.id === id;
});
if (items.length >= 1) items[0].visible = visibility;
}
Then use it like this:
setVisibility($scope.cLines, 1, true);
You could also update $scope.cLines into a more complex object, instead of just an array:
$scope.cLines = {
"item" : function (id) {
var items = this.lines.filter(function(item) {
return item.id === id;
});
if (items.length >= 1)
return items[0];
else
return new Object(); //or throw an error
},
"lines" : [
{ id: 1, cColor: 'red', cName: 'Entryline right', visible: true }
//....and more
]
};
Then use it like this:
$scope.cLines.item(1).visible = true;
With this, make sure to use $scope.cLines.lines if you have to loop through it.
I'm not sure I fully understand the question, so if I'm off here, perhaps you can clarify what you're trying to do.
If you have an ng-repeat and you're trying to toggle some value in the current object, just pass that object to the ng-click function:
<button ng-click="changeVisible(line);" ng-repeat="line in cLines">Visible = {{line.visible}}</button>
and then in your controller, you'd have something like this:
$scope.changeVisible = function(obj) {
obj.visible = !obj.visible;
}
var app = angular.module('demo', []);
app.controller('DemoCtrl', function($scope) {
$scope.cLines = [{
id: 1,
cColor: 'red',
cName: 'Entryline right',
visible: true
}, {
id: 2,
cColor: 'blue',
cName: 'Entryline right',
visible: false
}];
$scope.changeVisible = function(obj) {
obj.visible = !obj.visible;
}
});
<script src="https://code.angularjs.org/1.3.16/angular.js"></script>
<div ng-app="demo">
<div ng-controller="DemoCtrl">
<button ng-click="changeVisible(line);" ng-repeat="line in cLines">Visible = {{line.visible}}</button>
</div>
</div>
so I'm trying to push the object stored in an array when a checkbox is called with knockout to an observable array.
HTML:
<input type="checkbox" data-bind="checked: checked, click: $root.saveSelected"/>
JS:
var definition = [
{title: 'some text', checked: ko.observable(false), definition: '<p>Some HTML</p>'}
],
var viewModel = {
selectedItems: ko.observableArray([]),
saveSelected: function() {
for (var i = 0; i < definition.length; ++i) {
if (viewModel.definition[i].checked().value === true) {
viewModel.selectedItems.push(definition[i]);
}
}
}
So I'm pretty sure that my if statement is what's causing the issue here, but I'm not sure what I did wrong. But the outcome should be that for every checkbox that is selected, that object (now with a value of true for 'checked') should get pushed to the selectedItems array so that (with this example) the blank selectedItems array should have the object
{title: 'some text', checked: ko.observable(false), definition: '<p>Some HTML</p>'}
in it after the saveSelection function runs.
--EDIT--
The fiddle for this code: http://jsfiddle.net/imagitron/mMc6k/6/
Remove the ".value" when accessing the checked observable:
if (viewModel.definition[i].checked() === true) {
...
Knockout manages setting the value of the HTML <input> control. All you need to know is that it's an observable with either true or false value.
First off, click: $root.saveSelected doesn't match saveSelection: function() {
Second issue
for (var i = 0; i < definition.length; ++i) {
if (viewModel.definition[i].checked() === true) {
definition and viewModel.definition are two seperate things here.
viewModel.definition is actually with the first save an empty array.
so, viewModel.definition[i] won't work ...