select obsservableArray record from table in knockout - javascript

trying to figure out how to set up the radio button bindings in a table in knockout. I would like when the radio button on the table is selected. the entire selected record is available in the model. not quite sure how to set up the binding on the radio input. I assume I need to use $parent and a function for the value binding?
here is the fiddle. (the selected record does not do anything right now I would like it to be populated when the radio button is selected)
https://jsfiddle.net/othkss9s/5/
HTML
<table>
<thead>
<tr>
<th>Select</th>
<th>First</th>
<th>Last</th>
<th>Dept</th>
</tr>
</thead>
<tbody data-bind='foreach: employees'>
<tr>
<td><input type="radio" name="employees"></td>
<td data-bind='text: first'></td>
<td data-bind='text: last'></td>
<td data-bind='text: dept'></td>
</tr>
</tbody>
</table>
<div data-bind="with: selectedRow">
<h2>
Selected Record
</h2>
<p>
First: <span data-bind="first" ></span>
</p>
<p>
Last: <span data-bind="last" ></span>
</p>
<p>
Dept: <span data-bind="dept" ></span>
</p>
</div>
JS
function employee(first, last, dept) {
this.first = ko.observable(first);
this.last = ko.observable(last);
this.dept = ko.observable(dept);
}
function model() {
var self = this;
this.employees = ko.observableArray("");
this.selectedRow = ko.observable({});
};
var myViewModel = new model();
$(document).ready(function() {
ko.applyBindings(myViewModel);
myViewModel.employees.push(
new employee("Bob","Jones", "Hr")
);
myViewModel.employees.push(
new employee("Sarah","Smith", "It")
);
myViewModel.employees.push(
new employee("John","Miller", "It")
);
});

You have to perform two steps to get your code working.
First, apply bindings to radio buttons:
<input type="radio" data-bind="checked: $root.selectedRow, checkedValue: $data" />
checkedValue should contain actual value that corresponds the current radio button. In this case we refer $data variable that is whole employee object rather than simple (scalar) value.
checked binding should refer to an observable containing currently selected employee.
Second, correct the line where the selectedRow property is defined:
this.selectedRow = ko.observable();
Do not pass empty object as default value. Otherwise with binding won't work correctly.
By fixing the syntax of bindings in your second block that displays selected employee you will get something like this.

Related

Show/Hide div within a single row in a foreach loop - KnockoutJS

I've tried several ways of doing this with no success. Would love some advice!
Goal: I have a table where each row is an order, but where within that row, if changes need to be made, a div appears underneath (in red). This needs to show/hide when a button on that row is clicked/toggled (Button is: Make Changes)
Issue: I have all the buttons working apart from the make changes toggle. Tried the visible observable, but the closest I could get was toggling the div's visibility for the whole table, not per row.
//Class to represent a row in the table
function orderDetail(order, orderChange) {
var self = this;
self.order = ko.observable(order);
self.orderChange = ko.observable(orderChange);
}
//Overall viewmodel, plus initial state
function FoodViewModel() {
var self = this;
self.foodTypes = [
{ foodType: "Please Select"},
{ foodType: "Veg"},
{ foodType: "Meat"}
];
self.orders = ko.observableArray([
new orderDetail(self.foodTypes[0], self.foodTypes[0])
]);
// Add and remove rows
self.addOrder = function() {
self.orders.push(new orderDetail(self.foodTypes[0], self.foodTypes[0]));
}
self.removeOrder = function(order) { self.orders.remove(order) }
}
ko.applyBindings(new FoodViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Orders</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td>
<div><select data-bind="options: $root.foodTypes, value: order, optionsText: 'foodType'" id="foodList"></select></div>
<div><select data-bind="options: $root.foodTypes, optionsText: 'foodType', value: orderChange" id="foodListChange" style="color: red;"></select></div>
</td>
<td>
<button class="button button2" >Make Changes</button>
</td>
<td>
<button class="button button1" href="#" data-bind="click: $root.removeOrder">Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addOrder" class="button">Add Order</button>
Thanks in advance!
If you want that the user interface reacts to something in Knockout, make an observable.
In this case you want to display part of the UI conditionally (apparently to toggle an edit mode), so let's create:
an observable editMode that is either true or false, to store the UI state
a function toggleEditMode that toggles between the two states, to bind it to the button
an if: editMode and an ifnot: editMode binding, to show different parts of the UI accordingly
function OrderDetail(params) {
var self = this;
params = params || {};
self.order = ko.observable(params.order);
self.orderChange = ko.observable(params.orderChange);
self.editMode = ko.observable(true);
self.buttonCaption = ko.pureComputed(function () {
return self.editMode() ? "Done" : "Edit";
});
self.toggleEditMode = function () {
self.editMode(!self.editMode());
}
}
function OrderList(params) {
var self = this;
params = params || {};
self.foodTypes = ko.observableArray(params.foodTypes);
self.orders = ko.observableArray();
self.addOrder = function(foodType) {
self.orders.push(new OrderDetail());
}
self.removeOrder = function(order) {
self.orders.remove(order);
}
}
var vm = new OrderList({
foodTypes: [
{foodType: "Veg"},
{foodType: "Meat"}
]
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th style="width: 150px;">Orders</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td>
<div data-bind="ifnot: editMode">
<!-- ko with: order -->
<span data-bind="text: foodType"></span>
<!-- /ko -->
</div>
<div data-bind="if: editMode">
<select data-bind="
options: $root.foodTypes,
value: order,
optionsText: 'foodType',
optionsCaption: 'Please select…'
"></select>
</div>
</td>
<td>
<button class="button button2" data-bind="
click: toggleEditMode,
text: buttonCaption,
enable: order
"></button>
<button class="button button1" href="#" data-bind="
click: $root.removeOrder
">Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addOrder" class="button">Add Order</button>
<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
Notes
Don't make "Please Select" part of your food types. That's what the optionsCaption binding is for.
I've parameterized the viewmodels (see the params object). This will work better than hard-coding values or using long argument lists, especially if you want to use a mapping plugin later.
The "Done" button is disabled as long as no order is selected, via the enable: order binding, i.e. if the order property is empty, the enable binding will keep the button disabled.
The with: order binding serves a similar purpose. It will only display its contents when there actually is an order value to display. This will prevent rendering errors with incomplete OrderDetail instances.

AngularJs: uncheck checkbox after item is removed

after spending a lot of time on this simple issue and having made a lot of research, I was wondering if someone could give me some help.
I have data which is generated inside of a table like so:
<tbody>
<tr class="odd gradeX" ng-repeat="user in ctrl.datas | orderBy:ctrl.sortType:ctrl.sortTypeReverse">
<td>
<input type="checkbox" class="checkboxes" value="{{user.id}}" ng-click="ctrl.addItem(user)"/>
</td>
<td>
{{user.given_name}}
</td>
<td>
{{user.family_name}}
</td>
<td>
{{user.email}}
</td>
<td class="center" ng-bind-html="ctrl.convertToDate(user.created_at) | date: 'dd-MMMM-yyyy hh:mm'"></td>
<td>
<span class="btn blue-hoki"> Details </span>
</td>
</tr>
</tbody>
Above is a container where I get the items selected via a checkbox, add the in an array and give the user the ability to delete the selected item:
<tr ng-repeat="user in ctrl.checkedObject track by $index" ng-show="user.id">
<td>{{user.family_name}}</td>
<td>{{user.given_name}}</td>
<td>
<button class="btn blue" ng-click="ctrl.removeItem($index)">Unselect</button>
</td>
</tr>
In my controller, here are the two functions used to do so:
this.checkedObject = [];
//Add selected user
this.addItem = function (user) {
self.checkedObject.push(user);
};
this.removeItem = function(obj){
delete self.checkedObject[obj];
};
What i'd like to achieve is to uncheck the corresponding checkbox if a user changes his selection.
The thing is, I have no idea how to target the corresponding checkbox. Does anyone have a clue?
Thanks in advance
Try ng-checked like:
<input type="checkbox" ng-checked="user !== null" class="checkboxes" value="{{user.id}}" ng-click="ctrl.addItem(user)"/>
And set the item (user) to null on button click (inside removeItem() ) or a other variable.
I set up a simple plunker to show one approach, which would be to assign a selected property to each user when his/her checkbox is checked, and set an ng-checked attribute on the checkbox corresponding to user.selected (so will be unchecked when false).
Using this approach you won't need to push and delete from the array of checkedUsers, you can just filter all the users by whether they are selected or not.
function getSelected() {
ctrl.checkedObject = _.filter(ctrl.datas, {selected: true});
}
ctrl.selectUser = function (user) {
user.selected = true;
getSelected();
};
ctrl.removeUser = function(user){
user.selected = false;
getSelected();
};

AngularJS, easy way to show a delete button when a checkbox is checked in the list of users under ng-repeat

I am trying to toggle a delete button which has already a function bound to it. The list is created with ng-repeat checking the users object. But the methds I have seen so far are either simple model show which does not work in my case. Or complex directive controller methods.
All I need is to check whether at least one checkbox is checked. Here is the snippet from the code:
<table class="table">
<tr ng-repeat="user in ctrl.commonUserService.users | filter:ctrl.commonUserService.suppressLoggedOnUserFilter()">
<td><input type="checkbox" ng-model="user.selected" value="y" /></td>
<td title="User Name"><strong>{{user.userName}}</strong></td>
How can I easily do this with Angular? I tried to create a function looping users and looking if they are selected at all but don't think I can put this function to ng-show.
You'll have to check if at least one box is selected when the state changes:
<td><input type="checkbox" ng-model="user.selected" ng-change="ctrl.userSelected()" value="y" /></td> <!-- use ng-change to notify when a checkbox is clicked -->
<button ng-click="ctrl.delete()" ng-show="ctrl.usersSelected">Delete</button> <!-- if ctrl.usersSelected is true show delete -->
In the controller:
this.userSelected = function userSelected() { // when checkbox is clicked check users to see if at least one is selected and save the state in this.userSelected
this.usersSelected = this.commonUserService.users.some(function(user) {
return user.selected;
});
};
You can do this fairly easily using the ng-change directive on your inputs. See this plunkr for the working example, but it basically goes like this:
View:
<table class="table">
<tr ng-repeat="user in users">
<td><input type="checkbox" ng-change="isSelected()" ng-model="user.selected" /></td>
<td title="User Name"><strong>{{user.userName}}</strong></td>
</tr>
</table>
<button class="delete" ng-show="showDelete">Delete</button>
Controller:
$scope.isSelected = function() {
var somethingSelected = false;
$scope.users.forEach(function(user, index) {
if (user.selected) somethingSelected = true;
});
$scope.showDelete = somethingSelected;
};

How can I append text with table tr using knockout

I have got a assignment to implement knockout js to my application. I have a table like
<table>
<tr>
<th>
Name
</th>
<th>
Category
</th>
<th>
Price
</th>
<th></th>
</tr>
<tr>
<td>
Iphone
</td>
<td>
SmartPhone
</td>
<td>
50000
</td>
</tr>
</table>
There are three textbox for creation of this field.
<div id="create">
<input data-bind="value: Name" id="name"/>
<input data-bind="value: Category" id="category"/>
<input data-bind="value: Prize" id="prize"/>
</div>
When I am typing on this textboxes i want to show this on the table as a new tr.. How can I do this? DEMO
Reference Link
What you want to do is define a viewmodel that contains the data for an individual item, and another viewmodel that contains the rest of the interactions (list of items, how to add new ones, etc).
var Item = function (Name, Category, Price) {
var self = this;
self.Name = ko.observable(Name);
self.Category = ko.observable(Category);
self.Price = ko.observable(Price);
}
var ViewModel = function () {
var self = this;
self.ItemToAdd = ko.observable(new Item());
self.Items = ko.observableArray([]);
self.addItem = function (item) {
self.Items.push(item);
self.ItemToAdd(new Item());
}
};
var vm = new ViewModel();
vm.addItem(new Item('Iphone', 'SmartPhone', 50000));
ko.applyBindings(vm);
In your html, your table body will look like this:
<tbody data-bind="foreach: Items">
<tr>
<td data-bind="text: Name"/>
<td data-bind="text: Category"/>
<td data-bind="text: Price"/>
</tr>
</tbody>
what this does is loops through each item in the Itemlist and creates a <tr> for each one and binds the values of the Item object in the observableArray to the <td> elements.
to add new items to the table in your markup:
<div data-bind="with: ItemToAdd">
<input data-bind="value: Name" id="name"/>
<input data-bind="value: Category" id="category"/>
<input data-bind="value: Price" id="price"/>
<button data-bind="click: $parent.addItem">Add</button>
</div>
this sets the context of the div element to a new Item object, and when you click the Add button, it calls the parent context's (ViewModel) addItem function, and automatically passes the context item for the div element (ItemToAdd). Then its just a matter of pushing it on to the observableArray and the table will update with the new item.
Updated Fiddle: http://jsfiddle.net/BJQgw/4/
if this was a for-real application, you would perform some sort of validation prior to adding the item to the list (preferably using knockout-validation)

Build knockout model and view dynamically, radio buttons not being set

I am in the process of making one of my previous questions fully dynamic in that the model is built from server data, and the view loops through the viewmodel via the knockout ko foreach functionality.
The problems I am facing are:
The radio options don't stay with the value set, i.e. I click on the Operating System, and then select a Database option, and then the Operating System setting disappears.
The dependent options (in this case database and clustering) do not have their initial selection selected when the dependent option changes (i.e. when OS changes, DB should go back to the first option, none).
My fiddle is here and i think the problem is either related to the code below:
computedOptions.subscribe(function () {
var section = this;
console.log("my object: %o", section);
section.selection(section.options()[0].sku);
},section);
Or my view bindings:
<!-- ko foreach: selectedOptions -->
<h3><span data-bind="text: description"></span></h3>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th colspan="2" style="text-align: left;">Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: options -->
<tr>
<td><input type="radio" name="$parent.name" data-bind="checkedValue: $data, checked: $parent.selection" /></td>
<td style="text-align: left;"><span data-bind="text: name"></span></td>
<td style="text-align: left;"><span data-bind="text: price"></span></td>
</tr>
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
I am not sure which and would appreciate a fresh eyes as my brain hurts from the jsfiddle session.
You have two problems:
You are not correctly binding your radio button's names: name="$parent.name" is not a knockout binding expression and it just assigns the string "$parent.name" to all of your radio buttons. What you need is to use the attr binding:
<input type="radio" data-bind="checkedValue: $data,
checked: $parent.selection,
attr: { name: $parent.name }" />
The initial selection is not working because you are using the checkedValue: $dataoption this means that your checked should contain the whole object and not just one property (sku) so you need to change your computedOptions.subscribe to:
computedOptions.subscribe(function () {
var section = this;
section.selection(section.options()[0]);
},section);
Demo JSFiddle.

Categories

Resources