KnockoutJS - Multi select using select 2 showing only 1st select - javascript

I'm trying to use the select 2 Multi-select boxes with Knockout JS.
The box displays a list of countries and the user can select multiple countries.
The box is displaying multiple countries correctly as expected, however the observable array is only showing the first entry.
My intention is to get all the selected countries and not the first one.
At first i thought i cant use a select 2 multi select with knockout, however if if i add two for example (MT,NL) the observable shows MT, however if i remove MT it updates to NL (so i dont think thats the issue)
Logic below:
// Class to represent a row in the dpos grid
function DpoItem(address, preferredLanguage, countries)
{
var self = this;
self.address = address;
self.preferredLanguage = preferredLanguage;
self.countries = ko.observableArray(countries);
}
// Class to represent a language
function LanguageItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Class to represent a country
function CountryItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Overall viewmodel for this screen, along with initial state
function DposViewModel()
{
var self = this;
// Populate countries
var countriesObject = JSON.parse(countriesJSON);
var countries = [];
for (var cKey in countriesObject)
{
countries.push(new CountryItem(cKey, countriesObject[cKey]));
}
self.countriesList = ko.observableArray(countries);
// Populate languages
var languagesObject = JSON.parse(languagesJSON);
var languages = [];
for (var lKey in languagesObject)
{
languages.push(new LanguageItem(lKey, languagesObject[lKey]));
}
self.languagesList = ko.observableArray(languages);
// parse JSON DTOs and put them in the viewmodel
var dposObject = JSON.parse('[{"countries":[],"type":"dpo","address":"dpo #avis.com","preferredLanguage":"en - GB"},{"countries":["GB", "MT"],"type":"dpo","address":"dpo #avis.co.uk","preferredLanguage":"en - GB"},{"countries":["MT"],"type":"dpo","address":"dpo #avis.com.mt","preferredLanguage":"mt - MT"}]');
var dpos = [];
dposObject.forEach(dpo =>
{
dpos.push(new DpoItem(dpo.address, dpo.preferredLanguage, dpo.countries));
});
self.dpos = ko.observableArray(dpos);
self.addDpo = function ()
{
self.dpos.push(new DpoItem("", "", ""));
};
self.removeDpo = function ()
{
self.dpos.remove(this);
};
self.checkDpos = function ()
{
for (i = 0; i < self.dpos().length; i++)
{
var dpo = self.dpos()[i];
var dpoCountries = dpo.countries();
}
};
}
ko.applyBindings(new DposViewModel());
$(document).ready(function ()
{
$('.js-example-basic-multiple').select2();
});
UI below:
<div id="table" class="table-editable">
<table class="table">
<thead>
<tr>
<th>Email</th>
<th>Preferred Language</th>
<th>Countries</th>
<th><span id="table-add" class="table-add glyphicon glyphicon-plus" data-bind="click: addDpo"></span></th>
</tr>
</thead>
<tbody data-bind="foreach: dpos">
<tr>
<td contenteditable="true" data-bind="text: $data.address"></td>
<td>
<select class="js-example-basic-single" data-bind="options: $parent.languagesList, optionsText: 'name', optionsValue: 'code', value: $data.preferredLanguage"></select>
</td>
<td>
<select class="js-example-basic-multiple" multiple="multiple" data-bind="options: $parent.countriesList, optionsText: 'name', optionsValue: 'code', value: $data.countries"></select>
</td>
<td>
<span class="table-remove glyphicon glyphicon-remove" data-bind="click: $parent.removeDpo"></span>
</td>
</tr>
</tbody>
</table>
</div>
<button data-bind="click: checkDpos">Click me</button>
<div>
<h1>Summary</h1>
<div data-bind="foreach: dpos">
<p>Address: <strong data-bind="text: address"></strong></p>
<p>Preferred Language: <strong data-bind="text: preferredLanguage"></strong></p>
<p>Countries: <strong data-bind="text: countries"></strong></p>
</div>
</div>
Any ideas why this is happening?

Related

UI focus bug, focus jumps back to last edited textbox after tab

I have an annoying, somewhat odd bug. I have a grid in knockout that has several columns, of which one is editable. This column generates a textbox input for each row.
When the user edits one row and then tabs onto the next one, the focus will jump back to the row that was just edited. This will only happen once, so if you tab again, you can tab on to the next boxes.
If you do not edit a textbox, the jump back behaviour will not occur. I am having a hard time seeing what exactly is causing this behaviour.
Code for the knockout grid in the view:
<table class="table table-responsive table-striped center table-hover" style="clear: both; margin-bottom: 10px;" id="resultsTable">
<thead>
<tr>
<th class="col-md-2"><b>Culture</b></th>
<th class="col-md-2"><b>Section</b></th>
<th class="col-md-2"><b>Name</b></th>
<th class="col-md-2"><b>Value</b></th>
<th class="col-md-2"><b>LastChangeOn</b></th>
<th class="col-md-2"></th>
</tr>
</thead>
<tbody data-bind='foreach: Items'>
<tr>
<td class="col-md-2">
<span data-bind="text: Culture"></span>
</td>
<td class="col-md-2">
<span data-bind="text: Section"></span>
</td>
<td class="col-md-2">
<span data-bind="text: Name"></span>
</td>
<td class="col-md-2">
<input type="text" data-bind="value: Value" />
</td>
<td class="col-md-2">
<span data-bind="text: LastChangeOn"></span>
</td>
<td class="col-md-2">
<span data-bind="text: Id, visible: false"></span>
</td>
</tr>
</tbody>
</table>
Code for the javascript:
<script type="text/javascript">
var _VM;
var initialLoad = true;
$(function () {
LoadKnockoutContent(true);
});
$("#SearchButton").on("click", function (e) {
_VM.moveToFirstPage();
});
IndexModel = function (initialData) {
var _self = this;
PagedViewModel.call(_self);
_self.Items = ko.observableArray();
_self.CurrentPage.subscribe(function (value) {
$("#SearchCriteria_HiddenPage").val(value);
LoadKnockoutContent(false, _self.Release);
});
_self.loadModelData = function (data) {
_self.CurrentPage(data.CurrentPage);
_self.PageSize = data.PageSize;
_self.MaxPageIndex(data.PageCount);
_self.Items(ToResourcesArray(data.Resources, _self));
}
_self.loadModelData(initialData);
};
ResourceModel = function (item, parent) {
var _self = this;
_self.Parent = parent;
_self.Id = item.Id;
_self.Culture = ko.observable(item.Culture);
_self.Section = ko.observable(item.Section);
_self.Name = ko.observable(item.Name);
_self.Value = ko.observable(item.Value);
_self.Value.subscribe(function (newValue) {
// Send the new value to the backend
SaveResource(newValue, item.Id);
});
if (!item.LastChangeOn == "") {
_self.LastChangeOn = ko.observable(parseJsonDate(item.LastChangeOn).toPaddedDateTimeString());
}
else {
_self.LastChangeOn = ko.observable(item.LastChangeOn);
}
}
function ToResourcesArray(data, parent) {
var items = ko.utils.arrayMap(data, function (item) {
return new ResourceModel(item, parent);
});
return items;
}
function LoadKnockoutContent(initialLoad, callback, callback2) {
// Call to the back end, passing along the search criteria
}
function SaveResource(newValue, Id) {
$.ajax({
url: '#Url.Action("UpdateResource", "Resource")',
data: JSON.stringify({ id: Id, newValue: newValue }),
type: 'POST',
cache: false,
contentType: 'application/json;',
dataType: 'json',
success: function (data) {
if (data.isSuccess) {
// Success, we need to reload here as well else the last changed on field is not updated in the grid overview
LoadKnockoutContent(false);
} else {
alertify.error(data.message);
// Refresh the view else the value field will stay empty while it is not saved.
// A reload will show the grid again with the previous value.
LoadKnockoutContent(false);
}
},
error: function (request, status, error) {
alert(request.responseText);
}
});
}
</script>
dd
I have solved the issue.
The issue was that the Grid was reloaded after ever call to the save function. This should only occur when the save failed. If the grid is reloaded after every save, then when you tab, you will start at the first row of the Grid again.

Knockout or other JavaScript to add/remove properties from an object

I want to build a HTML page that allows the user to construct an object that can then be posted as JSON to an internally hosted service (similar to the Chrome Advanced Rest Client). The user must be able to add and remove properties.
My model is not correct because each property is treated like an object with the properties 'name' and 'value'. I end up with an array of objects instead of an object with properties.
Here is a snippet of the HTML:
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="value: name" /></td>
<td><input data-bind="value: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: ko.toJSON(myFieldList)"></textarea>
Here is the JS:
<script type="text/javascript">
function dynamicProperty(name, value) {
var self = this;
this.name = name;
this.value = value;
}
function fieldModel() {
var self = this;
//start with 2 empty properties
self.myFieldList = ko.observableArray([
new dynamicProperty("", ""),
new dynamicProperty("","")
]);
var noTracker = self.myFieldList.length;
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty(this.name,this.value));
}
}
ko.applyBindings(fieldModel);
</script>
What I get in the textarea is output like this:
[{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}]
What I want is output like this:
{"test name 1":"test value 1","test name 2":"test value 2"}
I fear this is rather trivial but in my defense I am very new to JS and Knockout, so any help you can offer would be hugely appreciated. Thanks.
You may want to do something like this to get it done .
Example : https://jsfiddle.net/9aLvd3uw/79/
HTML
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="textInput: name" /></td>
<td><input data-bind="textInput: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: myFieldList2"></textarea>
JS
function dynamicProperty(name, value) {
var self = this;
self.name = ko.observable(name || '');
self.value = ko.observable(value || '');
}
function fieldModel() {
var self = this;
self.name = ko.observable();
self.value = ko.observable();
self.myFieldList = ko.observableArray([
new dynamicProperty("test_name_1", "test value 1"),
new dynamicProperty("test_name_2","test value 2")
]);
var noTracker = self.myFieldList.length;
self.myFieldList2 = ko.computed(function () {
var string = '{';
ko.utils.arrayForEach(self.myFieldList(), function (item) {
string += item.name() + ': ' + item.value() + ',';
});
string = string.replace(/,\s*$/, "");
string+='}';
return string;
});
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty('',''));
}
}
ko.applyBindings(fieldModel);
What you need is a "reducer".
A simple (naive too) implementation would be this:
function reduce(input, step, init) {
for(var i = 0; i < input.length; i++) {
init = step(init, input[i]);
}
return init;
}
Then you call it like this:
var in = [{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}];
var out = reduce(in, function(result, item) {
result[item.name] = item.value;
return result;
}, {});
console.log(out);
What it does is that it iterates through your array and "accumulates" the result of each step in a single item. Could be the sum of numbers in an array where the "accumulator" would be a number instead of an object.
I advise you don't write your own but instead use lodash, it comes with a _.reduce function that's optimized.

Knockout JS Computed Observable on edit record

I have three fields
1. Quantity
2. Unit Price
3. Total Cost
I am using knockout JS to calculate total cost in real time. This works fine when adding a new record. However when I edit the record, I want the quantity and unit price to be prepopulated by their value in the database when the page first loads. I have tried the code below which prepopulates the quantity and unit price but the Total Cost result does not update and appears as blank now. Here is the code
<tr>
<td>
<div class="form-group">
<label asp-for="Quantity" class="col-md-2 control-label"></label>
<div class="col-md-10">
<kendo-numerictextbox name="Quantity" min="0" enable="true" data-bind="value: Quant">
</kendo-numerictextbox>
<span asp-validation-for="Quantity" class="text-danger"/>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="form-group">
<label asp-for="UnitPrice" class="col-md-2 control-label"></label>
<div class="col-md-10">
<kendo-numerictextbox name="UnitPrice" min="0" enable="true" data-bind="value: UPrice">
</kendo-numerictextbox>
<span asp-validation-for="UnitPrice" class="text-danger"/>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="form-group">
<label asp-for="TotalCost" class="col-md-2 control-label"></label>
<div class="col-md-10">
<span data-bind="text: TotalCost"> </span>
</div>
</div>
<script>
var someJSON = $.getJSON("/Expenses/EditUSExpense", function(data) {});
var parsed = JSON.parse(someJSON);
// Update view model properties
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function(quantity, unitPrice, Quantity, UnitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function() {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(ViewModel);
</script>
</td>
</tr>
UPDATE
I have modified my code per suggestions below and appear to be cleanly pulling the Quantity and UnitPrice from the database but the computed field TotalCost is still displaying null and when I change the other two parameters the value does not change. Here is my modified code so someone can take a look. I am using the same razor code just changed the javascript.
$.getJSON("/Expenses/EditUSExpense", function (data) {
var parsed = JSON.parse(data);
// Update view model properties
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function (quantity, unitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function () {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(new ViewModel(quantity, unitprice));
});
In your code, you're creating a function that accepts four arguments:
var ViewModel = function(quantity, unitPrice, Quantity, UnitPrice) {
I don't know why four because in the code you posted you're only using the first two.
So if, you're expecting the first two values as arguments you should do this:
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function(quantity, unitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function() {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(new ViewModel(quantity, unitprice));
This:
var someJSON = $.getJSON("/Expenses/EditUSExpense", function(data) {});
var parsed = JSON.parse(someJSON);
Should be:
$.getJSON("/Expenses/EditUSExpense", function(data) {
var parsed = JSON.parse(data);
// Rest of code...
});
And this:
ko.applyBindings(ViewModel);
Should be:
ko.applyBindings(new ViewModel(quantity, unitprice));

Remove selected row in table from Knockout observable array

Alright i have working code that removes a selected row(s) via a checkbox being checked. However i am running into the issue of enforcing that only one of the radio buttons can be checked at any given moment. My first approach is to tie a click event to the each radio button and if it gets clicked, it loops through the observable array and marks all "false." Then it simply flips the flag to true for the item that fired the event. I know this isn't the best way but my lack luster knowledge of knockout is forcing me down this path..even though this method doesn't work atm. Can anyone shed light on what i am doing wrong or how to properly wire this up?
The html for the table
<table class="accountGroups information" id="tblAccountGroups">
<tr>
<td width="125px;" style="font-weight: bold;">StandardAccountNo</td>
<td width="125px;" style="font-weight: bold; text-align: center;">Primary</td>
<td style="font-weight: bold;">Effective Date</td>
<td style="font-weight: bold;">End Date</td>
<td style="font-weight: bold;">Remove</td>
</tr>
<!-- ko foreach: NewAccountGroupDetails-->
<tr id="Model.NewAccountGroupDetails[0].AccountGroupName" class="acctgrp-row">
<td>
<div>
<input style="width: 100%;" data-bind="value: StandardAccountNo, attr: {name: 'NewAccountGroupDetails[' + $index() + '].StandardAccountNo'}" />
</div>
</td>
<td>
<div style="text-align:center;">
<input style="width:100%;" type="radio" data-bind="value: IsPrimary, attr: {name: 'NewAccountGroupDetails[' + $index() + '].IsPrimary'}, click: $parent.markIsPrimary" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EffectiveDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EffectiveDate'}" readonly="readonly" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EndDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EndDate'}" readonly="readonly" />
</div>
</td>
<td>
<div style="text-align:center;">
<input type="checkbox" data-bind="checked: markedForDeletion, attr: {name: 'NewAccountGroupDetails[' + $index() + '].MarkedForDeletion'}" />
</div>
</td>
</tr>
<!-- /ko -->
</table>
The JS below powers the page
////VIEW MODEL FOR KNOCKOUT////
var Detail = function () {
this.StandardAccountNo = ko.observable('');
this.IsPrimary = ko.observable(false);
this.EffectiveDate = ko.observable(formattedDate(new Date()));
this.EndDate = ko.observable(formattedDate(new Date()));
this.markedForDeletion = ko.observable(false);
};
var ViewModel = function () {
var rawList = '#Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model.NewAccountGroupDetails))';
this.NewAccountGroupDetails = ko.observableArray(convertJSONToKoObservableObject($.parseJSON(rawList)));
this.NewAccountGroupDetails.push(new Detail());
this.deleteMarkedItems = function () {
this.NewAccountGroupDetails.remove(function (item) {
return item.markedForDeletion();
});
};
this.markIsPrimary = function () {
for (i = 0; this.NewAccountGroupDetails().length > 0; i++) {
this.NewAccountGroupDetails[i].IsPrimary(false);
}
return item.IsPrimary(true);
};
this.addNew = function () {
this.NewAccountGroupDetails.push(new Detail());
$('.datepicker').each(function (i, obj) {
$(obj).datepicker({ changeYear: true, changeMonth: true });
});
}
};
ko.applyBindings(new ViewModel());
function convertJSONToKoObservableObject(json) {
var ret = [];
$.each(json, function (i, obj) {
var newOBJ = {};
for (prop in obj) {
newOBJ[prop] = ko.observable(obj[prop]);
}
ret.push(newOBJ);
});
return ret;
}
Once i have the page working the way i want it to, i'll look into syntax improvements such as ko mapping library for the array.
In your view model, construct the remove button like this:
viewModel.remove = function (row) {
console.log(row);
viewModel.NewAccountGroupDetails.remove(row);
};
Now, the current context is passed as the first argument to any callback in knockout. Therefore, if you add a button with data-bind="click: $parent.remove", it will call the viewModel.remove function with the row context.
<tr ...>
...
<td>
<button data-bind="click: $parent.remove">Remove</button>
</td>
</tr>
I'd need some extra information, but let me show you an example, and give you a few advices:
First, the advices:
in order to convert your regular object in an object with observable properties an arrays you can use the Knockout Mapping plugin.
you can omit the step of parsing the JSON. You can simply assigng the JSON to a var, like this: var JSON=*your serialized JSON*; (Don't forget the semicolon at the end.
instead of including so many code in the data-bind, like this: NewAccountGroupDetails['+ $index() + '].EndDate, do this calculation on the viewmodel itself, an use a computed named, for example EndDateName
your viewmodel should include a selectedRow observable. When the user selects the row, put the row there, and you can use a computed observable that determines if a row is the selected row or not.
take into account that you can bind events that invoke functions in your code, and this events carry the data associated to the DOM object that originated the event. I.e. if the users clicks a row associated to a account group detail, you'll receive it in the event.
Example for 2:
// Instead of:
var viewModelJson = '[{"name": "Pepe"},{"name":"Juan"}]';
var viewModel = $.parseJSON(viewModelJson);
// Do this directly:
var people = [{"name": "Pepe"},{"name":"Juan"}];
As 4 and 5 are not clear at once, this is a simple sample of what you want to achieve.
<ul data-bind="foreach: people">
<li data-bind="text: name, click: $root.select,
css: {red: $data == $root.selectedPerson()}" >
</li>
</ul>
NOTE that the css class red is applied when the condition true. And the condition is that the value bound to the current row is the same as the value in the selectedPerson observable.
And this is the corresponding JavaScript (remember to reference knockout mapping!!)
var people = [{"name": "Pepe"},{"name":"Juan"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // event receives the current data as 1st param
self.selectedPerson(person);
}
self.delete = function(person) {
// find de index of person and remove 1 item from that index
self.people.splice(self.people.indexOf(person),1);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
You can run the jsfiddle here.
If you change the click binding to invoke $root.delete instead of $root.select, you'll see the person dissapear from the list when clicking it. Of course, you can add an extra element to do so.
NOTE: you can read the docs on click binding on knockout js site.
And a last advice: it's much better to use Web API, or a method returning a JsonResult to recover the data directly from the server, and keep the js on a separate file.
UPDATE
A little bit mode code.
You can add this HTML:
<input type="button" data-bind="click: removeSelected" value="removeSelected"/>
And this method in the view model:
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
If you do so, when clicking the button, if there is a selected item, it will be removed from the list.
UPDATE: Another, more comple example
Here you have a more complete example, in this fiddle, that includes the code below:
CSS:
body {
font-family: Arial;
}
.container {
margin: 10px 0;
border: solid 1px #ABF;
}
.container > div {
padding: 4px;
border: solid 1px #ABF;
position: relative;
}
.selected {
border: solid 1px #00A;
color: #00A;
background-color: #BCF;
}
HTML:
<div data-bind="foreach: people" class="container">
<div data-bind="click: $root.select,
css: {selected: $data == $root.selectedPerson()}" >
<!-- ko text: name --><!-- /ko -->
<input type="button" value="Remove"
style="right:3px;top:2px; position:absolute;"
data-bind="click:$root.delete"/>
</div>
</div>
<div data-bind="visible: selectedPerson()" >
<input type="button" data-bind="click: removeSelected" value="Remove Selected"/>
<input type="button" data-bind="click: unSelect" value="Deselect"/>
</div>
<div data-bind="visible: selectedPerson()" class="container">
<div>
Selected: <!-- ko text: selectedPerson().name --><!-- /ko -->
</div>
</div>
JavaScript:
var people = [{"name": "Pepe"},{"name":"Juan"},{"name":"Luis"},{"name":"Adolfo"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // The event receives the current data as parameter
self.selectedPerson(person);
};
self.delete = function(person) {
// find de index of person and remove (splice) it from the observable array
self.people.splice(self.people.indexOf(person),1);
self.selectedPerson(null);
}
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
self.unSelect = function() {
self.selectedPerson(null);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
Try to temporarily save the selected row when you select it
function AccountGroupViewModel() {
var viewModel = this;
viewModel.selectedRow = null;
// ...
viewModel.selectRow = function (data) {
// ...
viewModel.selectedRow = data;
}
viewModel.remove = function () {
// ...
if (viewModel.selectedRow != null) {
this.NewAccountGroupDetails.remove(viewModel.selectedRow);
}
}
}

knockoutjs deselect/select all checkboxes when one or more items deselected

This is similar to, but different from other questions around this topic.
I have a table with a list of records, each having a select checkbox.
In the table header I have a "Select All" checkbox.
When the user checks/unchecks "Select All" the records are selected/unselected. This works fine.
However, I need to deselect my "Select All" checkbox when one or more of the records are deselected.
My markup:
<table>
<thead>
<tr>
<th>Name</th>
<th><input type="checkbox" data-bind="checked: SelectAll" /></th>
</tr>
</thead>
<tbody data-bind="foreach: $data.People">
<tr>
<td data-bind="text: Name"></td>
<td class="center"><input type="checkbox" data-bind="checked: Selected" /></td>
</tr>
</tbody>
</table>
My script (edited):
function MasterViewModel() {
var self = this;
self.People = ko.observableArray();
self.SelectAll = ko.observable(false);
self.SelectAll.subscribe(function (newValue) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(newValue);
});
});
}
my.Person = function (name, selected) {
var self = this;
self.Name = name;
self.Selected = ko.observable(false);
}
This works
http://jsfiddle.net/AneL9/
self.SelectAll = ko.computed({
read: function() {
var item = ko.utils.arrayFirst(self.People(), function(item) {
return !item.Selected();
});
return item == null;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
});
but will give you a ordo n ^ 2 problem when selecting deselecting all, you can use a pasuable computed to get around that
http://www.knockmeout.net/2011/04/pausing-notifications-in-knockoutjs.html
edit: You can also extend the computed with a throttle, this way you avoid the ordo n^2 problem
.extend({ throttle: 1 })
http://jsfiddle.net/AneL9/44/
You should make SelectAll computed observable like this:
self.SelectAll = ko.computed({
read: function() {
var persons = self.People();
for (var i = 0, l = persons.length; i < l; i++)
if (!persons[i].Selected()) return false;
return true;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function(person){
person.Selected(value);
});
}
});
and strip SelectAll.subscribe out.
http://jsfiddle.net/Yqj59/

Categories