problems binding to observable array in knockoutJS - javascript

I am currently having problems binding data to an observable array in knockoutJS. What I am trying to do is display new values based on the user's selection from a select box.
The fiddle is available at http://jsfiddle.net/jwayne2978/k0coh1fz/3/
My HTML looks like the following.
<select data-bind="options: categories,
optionsText: 'name',
optionsValue: 'id',
value: selectedCategory,
optionsCaption: 'Choose...',
event: { change: categoryChanged }
">
<div data-bind="foreach: values">
<div data-bind="text: name"></div>
</div>
<div data-bind="foreach: categories">
<div data-bind="text: name"></div>
</div>
My JavaScript looks like the following.
var categories = [
{ "name" : "color", "id": "1" },
{ "name" : "names", "id": "2" }
];
var values0 = [ { "name" : "empty1" }, { "name" : "empty2" } ];
var values1 = [ { "name" : "white" }, { "name" : "black" } ];
var values2 = [ { "name" : "john" }, { "name" : "name" } ];
var Category = function(data) {
this.name = ko.observable(data.name);
this.id = ko.observable(data.id);
};
var Value = function(data) {
this.name = ko.observable(data.name);
}
var ViewModel = function(categories, values) {
var self = this;
self.categories = ko.observableArray(ko.utils.arrayMap(categories, function(category) {
return new Category(category);
}));
self.selectedCategory = ko.observable();
self.values = ko.observableArray(ko.utils.arrayMap(values, function(value) {
return new Value(value);
}));
self.categoryChanged = function(obj, event) {
if(self.selectedCategory()) {
console.log(self.selectedCategory());
if("1" == self.selectedCategory()) {
//console.log(values1);
self.values.push(new Value({"name":"test1"}));
} else if("2" == self.selectedCategory()) {
//console.log(values2);
self.values.push(new Value({"name":"test2"}));
}
}
};
};
var viewModel;
$(document).ready(function() {
viewModel = new ViewModel(categories, values0);
ko.applyBindings(viewModel);
});
When a category is changed, what I really want to do is something like this.
self.values.removeAll();
for(var v in values1) {
self.values.push(new Value(v));
}
But that doesn't work and so I simply have the line to push a new value into the observable array.
Also, my iterations on the div for the values and categories are not showing and I am unsure why.
Any idea on what I am doing wrong?

your <select> element is missing a closing tag and causing issues further down in the view.
<select data-bind="options: categories,
optionsText: 'name',
optionsValue: 'id',
value: selectedCategory,
optionsCaption: 'Choose...',
event: { change: categoryChanged }"></select>
updated fiddle: http://jsfiddle.net/ragnarok56/69q8xmrp/

Related

Modifying observableArray does not instantly update select UI

I have a multi-select dropdown. If the user selects the option all, I want all the other options to be deselected and only select all. I have this almost working, but my issue is that the select does not show the updated value until minimise the dropdown. The state of the observableArray appears to be correct.
Here is the HTML:
<select data-bind="options: games, selectedOptions: selectedGame, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>
And the javascript:
this.games= [
{
name: 'All',
id: 'all'
},
{
name: 'Game1',
id: 'game1'
},
{
name: 'Game2',
id: 'game2'
},
]
this.selectedGame = ko.observableArray(['all']);
this.selectedGameBeforeChange = ko.observableArray([]);
this.selectedGame.subscribe((oldValue) =>
{
this.selectedGameBeforeChange(oldValue);
}, null, 'beforeChange');
this.selectedGame.subscribe((newValue) =>
{
const newValueAdded = newValue.filter(x => !this.selectedGameBeforeChange().includes(x));
if (newValueAdded.length > 0 && newValueAdded[0] === 'all'){
this.selectedGame.removeAll();
this.selectedGame.push('allCombined');
}
this.updateTable();
});
The code above works, but the change is only reflected in the UI once I have 'minimised' the select and reopen it. Is there a way to force the UI to update as soon my observableArray is updated?
You've got 2 bugs:
Instead of push('allCombined'), it should be push('all').
It works when all is selected last, but not when it's selected as the first option. To fix that, we need to modify the condition a bit.
Here's the final code (with few more minor modifications, e.g using self instead of this):
var vm = function () {
var self = this;
self.games = [
{ name: 'All', id: 'all' },
{ name: 'Game1', id: 'game1' },
{ name: 'Game2', id: 'game2' }
];
self.selectedGames = ko.observableArray(['all']);
self.selectedGamesBeforeChange = ko.observableArray([]);
self.selectedGames.subscribe((oldValue) =>
{
self.selectedGamesBeforeChange(oldValue);
}, null, 'beforeChange');
self.selectedGames.subscribe((newValue) =>
{
if (newValue.length > 1 &&
newValue.includes('all')){
self.selectedGames.removeAll();
self.selectedGamesBeforeChange.removeAll();
self.selectedGames.push('all');
}
});
};
ko.applyBindings(new vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: games, selectedOptions: selectedGames, optionsText: 'name', optionsValue: 'id'" multiple="true"></select>

how to set selected option in asp.net mvc using knockout.js?

I'm trying to set an option selected by default based on value recieved from Model.
I'm using asp.net mvc and knockout js for data binding.
//Model.TestValue="DEF"
script section.
<script>
var model = {
MyData: ko.mapping.fromJS(#Html.Raw(Json.Serialize(Model)))
};
ko.applyBindings(model);
</script>
View Section: Razor
#{
var mydropdownlist = new SelectList(
new List<SelectListItem>
{
new SelectListItem {Text = "ABC", Value = "1"},
new SelectListItem {Text = "DEF", Value = "3"},
new SelectListItem {Text = "GHI", Value = "5"}
}, "Value", "Text");
}
View Section HTML.
<select data-bind="options: mydropdownlist, optionsText:'text', value:MyData.testValue "></select>
Now mydropdownlist is populationg fine but I couldn't set "DEF" selected by default.
Set the observable property MyData.testValue, with the required option reference from the mydropdownlist.
MyData.testValue(mydropdownlist.Items()[1]); //DEF
Example:
$(function() {
var VmClass = function() {
self.MyOptions = ko.observableArray([
{ Name: 'Jhon', Age: 45 }, { Name: "Peter", Age: 67 }, { Name: 'Oliver', Age: 90}
]);
self.SelectedOption = ko.observable();
// can select after binding initialized as well.
self.ClickMeToSelect = function() {
self.SelectedOption(self.MyOptions()[2]);
};
self.ClickMeToSelect();
};
var vmInstance = new VmClass();
ko.applyBindings(vmInstance, document.getElementById('[element-id]'));
});
Is this what you are trying to achieve?
var data = [
{"text": "ABC","value": 1},
{"text": "DEF","value": 2},
{"text": "GHI","value": 3},
{"text": "JKL","value": 4},
];
var model = {
mydropdownlist: ko.observableArray(data),
selectedOption: ko.observable(data[1])
};
ko.applyBindings(model);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: mydropdownlist, optionsText:'text', value:selectedOption "></select>
<pre data-bind="text: ko.toJSON($root.selectedOption)"> </pre>

Change background color after add an item to a list with Knockout

I'm trying to create a table where new items will be showed with a different background color. The exemple bellow shows what I'm doing.
var data = [{
name: "Gibson",
id: "1"
}, {
name: "Fender",
id: "2"
}, {
name: "Godin",
id: "3"
}, {
name: "Tagima",
id: "4"
}, {
name: "Giannini",
id: "5"
}];
var list = [{
name: "Gibson",
id: "1"
}, {
name: "Fender",
id: "2"
}];
var ViewModel = function() {
var self = this;
self.mylist = ko.mapping.fromJS(list);
self.data = ko.mapping.fromJS(data);
self.selectedItem = ko.observable(undefined);
self.addItem = function() {
if (self.selectedItem == undefined) return;
self.mylist.push(self.selectedItem());
$("#" + self.selectedItem().id()).addClass("newItem");
self.selectedItem(undefined);
}
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
.newItem {
background-color: #DCEDC1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<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.mapping/2.4.1/knockout.mapping.min.js"></script>
<select data-bind="options: $root.data, optionsText: 'name', optionsCaption: 'Select yout Guitar', value: selectedItem, event:{change: $root.addItem}"></select>
<table>
<thead>
<tr>
<th>
<p>
Guitars
</p>
</th>
</tr>
</thead>
<tbody data-bind="foreach:mylist">
<tr data-bind="attr:{id: $data.id}">
<td>
<p data-bind="text: $data.name"></p>
</td>
</tr>
</tbody>
</table>
But in in some views, the new row in the table is added after the function addItem finish so the background color do not change. Is there another way to do it or to workaround this issue?
Using jQuery to keep track of the classes will be difficult if you're also using knockout. If your guitar objects remain as simple as they are, you can implement your feature with very little code (you don't have to create new viewmodels if you don't want to). This is what you'll need to do:
Keep track of all the new items in an array
For each item, determine if they need the .isNew class by checking if they're in this array
Toggle a class using the css binding
Step 1:
Inside ViewModel you can create a third observableArray. myList still stores all selected items, the new array newItems stores just the id properties of the items added through the UI.
Step 2:
In the addItem method, instead of selecting an element via jQuery and adding a class, we push the new item to the newItems array.
Step 3:
Replace the attr binding with the css binding that creates a computed boolean to indicate if the row is new or not:
<tr data-bind="css:{'newItem' : guitarIsInArray($data, $parent.newItems())}">
Additional notes:
You were using selectedValue to store the <select> element's changes: it's better to subscribe to this value's changes than to create another event listener via event: { change: fn }
Since your guitars are just plain objects, the Gibson in the data array will not equal the Gibson in myList. I've created a helper method to make sure you don't get duplicate values (guitarIsInArray).
Eventually, even if you're not creating a Guitar viewmodel, I'd try to make sure there's only one object reference per guitar in your code.
Here's an updated example:
var data = [{
name: "Gibson",
id: "1"
}, {
name: "Fender",
id: "2"
}, {
name: "Godin",
id: "3"
}, {
name: "Tagima",
id: "4"
}, {
name: "Giannini",
id: "5"
}];
var list = [{
name: "Gibson",
id: "1"
}, {
name: "Fender",
id: "2"
}];
var ViewModel = function() {
var self = this;
self.data = ko.mapping.fromJS(data);
self.mylist = ko.mapping.fromJS(list);
self.newItems = ko.observableArray([]);
// This excludes any guitar in mylist from data
self.unusedData = ko.computed(function() {
return self.data().filter(function(guitar) {
return !guitarIsInArray(guitar, self.mylist());
});
});
self.selectedItem = ko.observable();
// Called whenever your select changes
self.selectedItem.subscribe(function(newItem) {
if (!newItem || guitarIsInArray(newItem, self.mylist())) {
return;
}
self.mylist.push(newItem);
self.newItems.push(newItem);
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
function guitarIsInArray(guitar, array) {
for (var g = 0; g < array.length; g += 1) {
if (array[g].id() === guitar.id()) {
return true;
}
}
return false;
};
.newItem {
background-color: #DCEDC1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<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.mapping/2.4.1/knockout.mapping.min.js"></script>
<select data-bind="options: $root.unusedData, optionsText: 'name', optionsCaption: 'Select your Guitar', value: selectedItem"></select>
<table>
<thead>
<tr>
<th>
<p>
Guitars
</p>
</th>
</tr>
</thead>
<tbody data-bind="foreach:mylist">
<tr data-bind="css:{'newItem' : guitarIsInArray($data, $parent.newItems())}">
<td>
<p data-bind="text: $data.name"></p>
</td>
</tr>
</tbody>
</table>
If you add a duplicate item with the same id then in order to add a class jquery will select the first element with that id from DOM tree (id is a unique selector). Here is what you can do by using knockout
Example :https://jsfiddle.net/9aLvd3uw/213/
HTML :
<select data-bind="options: $root.data, optionsText: 'name', optionsCaption: 'Select yout Guitar', value: selectedItem, event:{change: $root.addItem}"></select>
<table>
<thead>
<tr>
<th>
<p>
Guitars
</p>
</th>
</tr>
</thead>
<tbody data-bind="foreach:mylist">
<tr>
<td>
<p data-bind="text:name, css:CSS"></p>
</td>
</tr>
</tbody>
</table>
VM:
var data = [{name: "Gibson",id: "1"}, {name: "Fender",id: "2"},
{name: "Godin",id: "3"},{name: "Tagima",id: "4"},
{name: "Giannini",id: "5"}];
var list = [{name: "Gibson",id: "1"}, {name: "Fender",id: "2"}];
var ViewModel = function() {
var self = this;
self.mylist = ko.observableArray([]);
self.data = ko.observableArray([]);
self.mylist($.map(list, function (element) {
return new ItemViewModel(element);
}));
self.data($.map(data, function (element) {
return new DataViewModel(element);
}));
self.selectedItem = ko.observable(undefined);
self.addItem = function() {
if (self.selectedItem == undefined) return;
//uncomment below if you want only last added item has that class
//ko.utils.arrayForEach(self.mylist(), function (item) {
// if (item) {
// item.CSS('');
// }
//});
self.mylist.push(new ItemViewModel({name:self.selectedItem().name() , id:self.selectedItem().id() , css:'newItem'}));
self.selectedItem(undefined);
}
}
var ItemViewModel = function (data){
var self = this;
self.name = ko.observable(data.name);
self.id = ko.observable(data.id);
self.CSS = ko.observable(data.css ? data.css :'');
}
var DataViewModel = function (data){
var self = this;
self.name = ko.observable(data.name);
self.id = ko.observable(data.id);
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);

KendoUI grouped filters

I'm trying to make grouped filters for KendoUI grid. I have to create a text field that filters the grid by name and a kendo numeric field that filters grid by Units in stock.
How could I make grouped filters?
I tried the following but it's not working - bad request 404 error:
$('body').bind('keyup mouseup', '#UnitsInStock', function () {
var value = $('#UnitsInStock').val();
var val = $('#ProductName').val();
if (value) {
grid.data("kendoGrid").dataSource.filter(myFilters(val, value));
} else {
grid.data("kendoGrid").dataSource.filter();
}
});
function myFilters(name='', price='') {
var filters = {
logic: "or",
filters: [
{ field: "ProductName", operator: "startswith", value: name},
{ field: "UnitsInStock", operator: "gte", value: price}
]
}
return filters;
}
<div id="grid"></div>
<script type="text/x-kendo-template" id="template">
<div class="toolbar">
<label for="category">Search by Product Name:</label>
<input type="search" id="ProductName" />
<input id="UnitsInStock" type="number" />
</div>
</script>
Since you have fields to be filtered on of multiple types, make sure the types are preserved when creating the filter object. for example, you could predefined your filter fields as such..
var filterFields = [{ field: "Units", type: "number" }, { field: "Name", type: "string" }]
and get the user input
var searchString = // user input
and a method to generate the filters similar to this
var getFilter = function (filterFields, searchString) {
var filterInt = function (value) {
if (/^(\-|\+)?([0-9]+|Infinity)$/.test(value))
return true;
return false;
}
var filters = [];
var i = 0;
for (var i = 0; i < filterFields.length; i++) {
if (filterFields[i].type === "string") {
filters.push({
field: filterFields[i].field,
operator: "startswith",
value: searchString.toString()
});
}
if (filterFields[i].type === "number") {
if (filterInt(searchString)) {
filters.push({
field: filterFields[i].field,
operator: "gte",
value: parseInt(searchString)
});
}
}
}
return {
logic: "or",
filters: filters
};
}
finally, filter your grid
grid.data("kendoGrid").dataSource.filter(getFilter(filterFields, searchString))
also, to be certain that your endpoint works, use a tool such as postman and do a GET (http://............./Units?$filter=Id eq 1 and Name eq 'name').

Setting the initial value in a Knockout select

I'm having problems getting the initial value of a select to be equal to the value in my knockout model.
http://jsfiddle.net/npearson99/bjwAT/2/
In that fiddle, the group should be "Group 2", but it's not selecting any group.
If I change value: 'SelectedGroupId' to value: 2, it works.
<div data-bind="with: selectedWorkout">
<h3>Current Workout</h3>
Workout Id:
<label data-bind="text: Id"></label>
<br/>Workout Name:
<label data-bind="text: Name"></label>
<br/>Group:
<select data-bind="options: $root.groupList,
optionsText: 'GroupName',
optionsValue: 'Id',
optionsCaption: 'No Group',
value: 'SelectedGroupId'"></select>
function Group(Id, GroupName) {
var self = this;
self.Id = Id;
self.GroupName = GroupName;
}
function Workout(id, name, selectedGroupId) {
var self = this;
self.Id = id;
self.Name = name
self.SelectedGroupId = ko.observable(selectedGroupId);
}
function viewModel() {
var self = this;
self.groupList = ko.observableArray([
new Group(1, 'Group One'),
new Group(2, 'Group Two'),
new Group(3, 'Group Three')]);
self.selectedWorkout = ko.observable(new Workout(4, 'Test Workout', 2));
}
ko.applyBindings(new viewModel());
The value binding takes a reference to the property as a parameter and not a string (so not the property name like the optionsValue or optionsValue).
So the correct usage is:
<select data-bind="options: $root.groupList,
optionsText: 'GroupName',
optionsValue: 'Id',
optionsCaption: 'No Group',
value: SelectedGroupId"></select>
Demo JSFiddle.

Categories

Resources