Knockout filtered array issue - javascript

I have a table containing "device" data, when the user selects a device, the device form is filled. The device type drop-down list contains all the device types and the device type of the chosen device is automatically selected. There is also a device model drop-down list that displays only the models related to the selected device type. When the user select a device from the device list, the form is well populated and all works, but I wish that the device models drop-down will also update when the user manually selects another type of device from the device drop-down, and this is not working.
Snippet:
<select data-bind="options: selectedDevice() ? modelsByDeviceType(selectedDevice().DeviceTypeID) : null, optionsText: 'DeviceModelName', optionsValue: 'DeviceModelID', value: selectedDevice() ? selectedDevice().DeviceModelID : 0, optionsCaption: ''"></select>
self.modelsByDeviceType = function (selectedDeviceType) {
return ko.utils.arrayFilter(self.deviceModels(), function (m) {
return (m.DeviceTypeID === selectedDeviceType);
});
};
Full code: https://jsfiddle.net/rickhaar/9aLvd3uw/8/

Your currently work with static data, you need to change it to knockout observables, then your values would update 2-way. Mapping plugin does the trick for you:
function vm() {
var self = this;
var devicesData = [{
DeviceID: 1,
DeviceName: 'DVR1',
DeviceTypeID: 1,
DeviceModelID: 1
}, {
DeviceID: 2,
DeviceName: 'DVR2',
DeviceTypeID: 1,
DeviceModelID: 2
}, {
DeviceID: 3,
DeviceName: 'Cam1',
DeviceTypeID: 2,
DeviceModelID: 3
}, {
DeviceID: 4,
DeviceName: 'Cam2',
DeviceTypeID: 2,
DeviceModelID: 4
}];
var deviceTypesData = [{
DeviceTypeID: 1,
DeviceTypeName: 'DVR'
}, {
DeviceTypeID: 2,
DeviceTypeName: 'Camera'
}];
var deviceModelsData = [{
DeviceModelID: 1,
DeviceTypeID: 1,
DeviceModelName: 'BOSH AN 5000'
}, {
DeviceModelID: 2,
DeviceTypeID: 1,
DeviceModelName: 'Aver NEH1116HN'
}, {
DeviceModelID: 3,
DeviceTypeID: 2,
DeviceModelName: 'Axis M1054'
}, {
DeviceModelID: 4,
DeviceTypeID: 2,
DeviceModelName: 'FLIR A65'
}];
self.devices = ko.observableArray([]);
self.deviceTypes = ko.observableArray([]);
self.deviceModels = ko.observableArray([]);
self.selectedDevice = ko.observable();
self.init = function() {
ko.mapping.fromJS(devicesData, {}, self.devices);
ko.mapping.fromJS(deviceTypesData, {}, self.deviceTypes);
ko.mapping.fromJS(deviceModelsData, {}, self.deviceModels);
};
self.selectDevice = function(index, item) {
self.selectedDevice(item);
};
self.modelsByDeviceType = function(selectedDeviceType) {
return ko.utils.arrayFilter(self.deviceModels(), function(m) {
return (m.DeviceTypeID() === selectedDeviceType());
});
};
self.init();
return self;
}
ko.applyBindings(vm());
.selectedRow {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<table id="tblData" class="dataTable" border="1">
<thead>
<tr>
<th>Device ID</th>
<th>Device Name</th>
</tr>
</thead>
<tbody data-bind="foreach: devices">
<tr data-bind="click: selectDevice.bind($data, $index()), css: { selectedRow: selectedDevice() === $data }">
<td data-bind="text: DeviceID"></td>
<td data-bind="text: DeviceName"></td>
</tr>
</tbody>
</table>
<br/>
<table border="0">
<tbody>
<tr>
<td>Name</td>
<td>
<input type="text" style="width:80px" data-bind="value: selectedDevice() ? selectedDevice().DeviceName : ''" />
</td>
</tr>
<tr>
<td>Type</td>
<td>
<select data-bind="options: deviceTypes, optionsText: 'DeviceTypeName', optionsValue: 'DeviceTypeID', value: selectedDevice() ? selectedDevice().DeviceTypeID : 0, optionsCaption: ''"></select>
</td>
</tr>
<tr>
<td>Model</td>
<td>
<select data-bind="options: selectedDevice() ? modelsByDeviceType(selectedDevice().DeviceTypeID) : null, optionsText: 'DeviceModelName', optionsValue: 'DeviceModelID', value: selectedDevice() ? selectedDevice().DeviceModelID() : 0, optionsCaption: ''"></select>
</td>
</tr>
</tbody>

Related

Dynamic data-title with ng-repeat in AngularJS

I'm looking for a solution to show dynamic header in the angular table for some of its <td>
my data looks like
let data = [
{
id: 1,
name: 'name',
fields: {
field 1: { value: '123'},
field 2: {value: 'macx'}
}
},
{
id: 2,
name: 'name2',
fields: {
field 1: { value: '456'},
field 2: {value: '3333'}
}
}
]
it should show in one table, I mean fields attr's should show as extra columns in the same table
note: fields are dynamic and I can't know it exactly so I need to do something like this in code
if any idea how I can get that work or any other idea to get the view as explained
<tr ng-repeat="data in $data">
<td data-title="'id'|translate"
sortable="'id'">
{{data.id}}
</td>
<td ng-repeat="(key, value) in data.fields track by $index"
ng-show="columnsHash[key]"
data-title="customFieldsTitles[$index]"
filterable="{field:'fields', type:'text', align:'LEFT'}"
data-title-text="customFieldsTitles[$index]">
{{value && value.value || ''}}
</td>
<td ng-show="columnsHash.totalBenefitTarget"
data-title="'target_total_benefit' | translate"
sortable="'total_benefit_target'"
style="text-align:center;"
filterable="{field: 'total_benefit_target', type:'number_range', options: {min: Number.MIN_VALUE, max: Number.MAX_VALUE}}">
{{data.total_benefit_target | number: 0}}
</td>
<td ng-show="columnsHash.totalBenefitActual"
data-title="'actual_total_benefit' | translate"
sortable="'total_benefit_actual'"
style="text-align:center;"
filterable="{field: 'total_benefit_actual', type:'number_range',
options: {min: Number.MIN_VALUE, max: Number.MAX_VALUE}}">
{{data.total_benefit_actual | number: 0}}
</td>
<tr>
showing columns order is important so writing it like code above
thanks in advance
angular table use scope.$column to render tb cols so I solved that by using scope binding
<table ng-table="tableParams" ng-init="initTable()">
<td ng-repeat="(key, value) in data.fields"
data-title="'Custom Field'"
sortable="'fields'"
filterable="{field:'fields', type:'text', align:'LEFT'}">
{{value && value.value || ''}}
</td>
</table>
in controller
var tableColumns;
$scope.initTable = function(){
var scope = this;
$timeout(function(){
tableColumns = scope.$columns;
});
};
after loading data for table call this function to update columns title
function updateCustomFields(){
var columnTemplate, index;
var colCount = 0;
if (!tableColumns) {
return;
}
tableColumns.map(function(col, i){
if (col.title() === 'Custom Field'){
columnTemplate = col;
index = i;
tableColumns.splice(index, 1);
return true;
}
});
for(var fieldLabel in $scope.customFieldsHash){
(function (label) {
var column = angular.copy(columnTemplate);
column.id = column.id+colCount/10;
column.title = function(){ return label; };
tableColumns.splice(index+colCount, 0, column);
colCount++;
})(fieldLabel);
}
}

Angular 7 iterate table

I want to iterate table with one property but to have 4 td in one row, and to continue like that till the end...
I want like this:
<table>
<tr>
<td>
<mat-checkbox>1</mat-checkbox>
</td>
<td>
<mat-checkbox>2</mat-checkbox>
</td>
<td>
<mat-checkbox>3</mat-checkbox>
</td>
<td>
<mat-checkbox>4</mat-checkbox>
</td>
</tr>
<tr>
<td>
<mat-checkbox>5</mat-checkbox>
</td>
<td>
<mat-checkbox>6</mat-checkbox>
</td>
<td>
<mat-checkbox>7</mat-checkbox>
</td>
</tr>
</table>
I tried like this, but it's al in one column :
lista = [
{ value: "1" },
{ value: "2" },
{ value: "3" },
{ value: "4" },
{ value: "5" },
{ value: "6" },
{ value: "7" },
<table *ngFor="let list of lista">
<tr>
<td>
<mat-checkbox>{{ list.value }}</mat-checkbox>
</td>
</tr>
</table>
You need to group your array firstly in groups of 4 (chunk size), then iterate over it simply in your template.
In your component:
const lista = [
{ value: "1" },
{ value: "2" },
{ value: "3" },
{ value: "4" },
{ value: "5" },
{ value: "6" },
{ value: "7" }
];
const chunkSize = 4;
// Group in chunks of 4 and take only truthy values
const groups = lista
.map((x, index) => {
return index % chunkSize === 0 ? lista.slice(index, index + chunkSize): null;
})
.filter(x => x);
In your template:
<table >
<tr *ngFor="let item of groups">
<td *ngFor = "let innerItem of item">
<mat-checkbox>{{ innerItem.value }}</mat-checkbox>
</td>
</tr>
</table>
you can use 2d array for your table like below:
lista = [
[
{value: '1'},
{value: '2'},
{value: '3'},
{value: '4'}
],
[
{value: '5'},
{value: '6'},
{value: '7'},
{value: '8'}
]
];
and for HTML
<table >
<tr *ngFor="let row of lista">
<td *ngFor="let col of row">
<mat-checkbox>{{ col.value }}</mat-checkbox>
</td>
</tr>
</table>

Angularjs: How do I ng-repeat through an array of objects with a field that is also an array?

I have an array that looks like this:
0: {ID: null,
name: "test",
city: "Austin",
UserColors: [{color: "blue"},{hobby:"beach"} ... ]}
}...
I am trying to ng-repeat through the initial array but once I try to loop through the list, i see nothing, heres the html/angular
<tr ng-repeat="c in vm.people">
<td>{{c.name}}</td>
<td>{{c.city}}</td>
<td ng-repeat="uc in c.UserColors">
<td>{{uc.color}}</td>
</td>
</tr>
I am not sure what is wrong, and I would appreciate your help, I thank you in advance.
I would process the field with a custom filter:
<td ng-repeat-start="(key, value) in c.UserColors | reduce">
<b>{{key}}</b>
</td>
<td ng-repeat-end>
{{value}}
</td>
The filter:
app.filter("reduce",function() {
return function(items) {
var x = items.map(o => Object.entries(o));
var x2 = x.reduce(((a,x) => (a.concat(x))), []);
var x3 = x2.reduce(((o,x) => (o[x[0]]=x[1],o)), {});
return x3;
}
})
The DEMO
angular.module("app",[])
.controller("ctrl",function(){
var vm = this;
vm.people = {
0: {ID: null,
name: "test",
city: "Austin",
UserColors: [{color: "blue"},{hobby:"beach"}]
},
1: {ID: null,
name: "best",
city: "Boston",
UserColors: [{colorx: "red"},{shirt:"black"}]
},
2: {ID: null,
name: "rest",
city: "Paris",
UserColors: [{colory: "yel"},{fruit:"peach"}]
},
}
})
.filter("reduce",function() {
return function(items) {
var x = items.map(o => Object.entries(o));
var x2 = x.reduce(((a,x) => (a.concat(x))), []);
var x3 = x2.reduce(((o,x) => (o[x[0]]=x[1],o)), {});
return x3;//items;
}
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as vm">
<h3>Table</h3>
<table>
<tr ng-repeat="c in vm.people">
<td>{{c.name}}</td>
<td>{{c.city}}</td>
<td ng-repeat-start="(key, value) in c.UserColors | reduce">
<b>{{key}}</b>
</td>
<td ng-repeat-end>
{{value}}
</td>
</tr>
</table>
</body>

How to reinitialize Angular model with nested data?

I've start a little proof of concept this week with Angular Material, and in this POC, I have a table that displays nested data:
<table>
<thead>
<tr>
<th colspan="2">Employee Name</th>
<th>Ovr</th>
<th> </th>
<tr>
<thead>
<tbody ng-repeat="employee in evaluation.employees" >
<tr ng-class-odd="'odd-row'">
<td class="photo"><img src="{{employee.photo}}" /></td>
<td class="name"><span class="firstname">{{employee.firstName}}</span><br/><span class="lastname">{{employee.lastName}}</span></td>
<td class="column-align-center"><span>{{employee.grade}}</span></td>
<td class="column-align-center"><md-button ng-click="toggleAptitudes(employee.id)" class="md-raised md-primary custom-button">+</md-button></td>
</tr>
<tr ng-repeat="skill in employee.skills" ng-show="employee.displayAptitudes">
<td colspan="4" style="padding: 0px 20px 0px 20px;">
<md-slider-container>
<span>{{skill.name}}</span>
<md-slider class="md-primary" flex min="0" max="100" ng-model="skill.value" ng-change="calculateAptitudesGrade(employee.id)" aria-label="skill.name" id="red-slider">
</md-slider>
<md-input-container>
<input flex type="number" ng-model="skill.value" aria-label="skill.title" aria-controls="red-slider">
</md-input-container>
</md-slider-container>
</td>
</tr>
</tbody>
</table>
Snippet from the Controller:
var self = this;
// Mock data...
self.employees = [
{ id: 1, firstName: 'FirstName1', lastName: 'LastName1', photo: 'img/photo1.png', grade: 0, aptitudes: [...], displayAptitudes: false },
{ id: 2, firstName: 'FirstName2', lastName: 'LastName2', photo: 'img/photo2.png', grade: 0, aptitudes: [...], displayAptitudes: false }
];
$scope.calculateAptitudesGrade = function(employeeId) {
// The overall calculation happen here where I collect all the skills values for the employee.
...
};
It's working fine for the first row I modify. I click the toggle button, it shows a list of skills with sliders, I move the slider and the overall calculation works very well.
THE PROBLEM: whenever I choose another employee, the sliders are set visually with the previous values. How to have the sliders set to 0 for each employee?
For your ng-click on the button change it from toggleAptitudes(employee.id) to employee.displayAptitudes = !employee.displayAptitudes
Ok ok ok, I've found the problem!!
Like I says, it is a POC and I did this quick. The problem come from the «Aptitudes» array that I have defined... and reused for every employee defined in the array...
self.aptitudes= [
{ id: 1, title: 'Aptitude 1', value: 0 },
{ id: 2, title: 'Aptitude 2', value: 0 },
{ id: 3, title: 'Aptitude 3', value: 0 }
];
// Mock data...
self.employees = [
{ id: 1, firstName: 'FirstName1', lastName: 'LastName1', photo: 'img/photo1.png', grade: 0, aptitudes: self.aptitudes, displayAptitudes: false },
{ id: 2, firstName: 'FirstName2', lastName: 'LastName2', photo: 'img/photo2.png', grade: 0, aptitudes: self.aptitudes, displayAptitudes: false }
];
Instead of declaring an array, I have create a function that return the array:
function getAptitudes() {
return [
{ id: 1, title: 'Aptitude 1', value: 0 },
{ id: 2, title: 'Aptitude 2', value: 0 },
{ id: 3, title: 'Aptitude 3', value: 0 }
];
}

add formatting from scope object when binding element

I have a table which is build up from a array I create in my controller. When trying to bind I would like to add formatting, for example | number, from my object array.
So, in the td-element inside tbody, I would like to use something like
row[column.rowValue] | column.filter
I've tried using {{}} around, and also tried ng-bind-template and so on..
How should I do this?
JSFIDDLE
html:
<script data-require="angular.js#*" data-semver="1.2.0" src="http://code.angularjs.org/1.2.0/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<div ng:controller="MyCtrl">
<table class="table table-striped table-hover table-responsive table-bordered" border="1">
<thead style="font-weight: bold;">
<tr>
<th class="text-right" ng-repeat="column in columns" ng-bind="column.rowHeader"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns" ng-bind="row[column.rowValue] | column.filter"></td> <--doesnt work
</tr>
</tbody>
</table>
</div>
js:
var app = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.columns = [
{ checked: true, rowValue: 'value1', rowHeader: 'Value1', filter: 'number' },
{ checked: true, rowValue: 'value2', rowHeader: 'Value2', filter: 'number' },
{ checked: true, rowValue: 'ModelName', rowHeader: 'Name' }
];
$scope.rows = [
{ value1: 100, value2: 5, ModelName: "This is a cell value" },
{ value1: 15, value2: 5, ModelName: "This is a cell value2" },
{ value1: 38, value2: 2, ModelName: "This is a cell value3" }
];
}
Use a function in you controller for format the value and if you need use a custom filter, inject the $filter service.
var app = angular.module('myApp',[]);
function MyCtrl($scope, $filter) {
$scope.columns = [
{ checked: true, rowValue: 'value1', rowHeader: 'Value1', filter: 'number' },
{ checked: true, rowValue: 'value2', rowHeader: 'Value2', filter: 'number' },
{ checked: true, rowValue: 'ModelName', rowHeader: 'Name' }
];
$scope.formatRow = function(value, filterName){
return $filter(filterName)(value);
};
$scope.rows = [
{ value1: 100, value2: 5, ModelName: "This is a cell value" },
{ value1: 15, value2: 5, ModelName: "This is a cell value2" },
{ value1: 38, value2: 2, ModelName: "This is a cell value3" }
];
}
HTML
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns" ng-bind="formatRow(row[column.rowValue], column.filter)"></td>
</tr>
</tbody>

Categories