Knockout.js loop over children of parent in table - json

My MVVM looks like this:
<script type="text/javascript">
function Company(data) {
this.name = ko.observable(data.name);
this.legal_form = ko.observable(data.legal_form);
this.company_number = ko.observable(data.company_number);
this.type_account = ko.observable(data.type_account);
this.type_supplier = ko.observable(data.type_supplier);
this.type_competitor = ko.observable(data.type_competitor);
this.type_other = ko.observable(data.type_other);
this.children = ko.observableArray(data.child);
}
function CompanyListViewModel() {
// Data
var self = this;
self.companies = ko.observableArray([]);
$.getJSON(Routing.generate('contacts_companies_get_json'), function(allData) {
var mappedCompanies = $.map(allData, function(item) { return new Company(item) });
self.companies(mappedCompanies);
});
}
ko.applyBindings(new CompanyListViewModel());
</script>
My View looks like this:
<tbody data-bind="foreach: companies">
<tr>
<td>
<a href="#" class="title">
<span data-bind="text: name"></span> <span data-bind="text: legal_form"></span>
</a>
</td>
<td data-bind="if:$data.company_number"><span data-bind="text: company_number"></span></td>
<td><span data-bind="if: type_account" ><i class="icon-check"></i></span></td>
<td><span data-bind="if: type_supplier" ><i data-bind="if: type_supplier" class="icon-check"></i></span></td>
<td><span data-bind="if: type_competitor" ><i data-bind="if: type_competitor" class="icon-check"></i></span></td>
<td><span data-bind="if: type_other" ><i data-bind="if: type_other" class="icon-check"></i></span></td>
<td>Details</td>
</tr>
</tbody>
I would like to add hidden <tr> for every child that a parent company has and add a plus sign before the parent to expand the hidden .
The problem is that I can only access the child within the original parent <tr> otherwhise will tell me "children" is not defined.
Any suggestions on how to achieve this?

You can use the foreach without a container element to display the children:
<tbody data-bind="foreach: companies">
<tr>
<!-- company columns -->
</tr>
<!-- ko foreach: children -->
<tr>
<!-- children columns -->
</tr>
<!-- /ko -->
</tody>
And with the help of a new property (like showChildren on the Company) you can do the show hide of the childrens:
Demo JSFiddle.
If you want to display the full hierarcy with the same columns so you want to have to display the children of the child campanies you can achieve with recursive templates.
There were some questions about recursive templates latelly:
Recursive template with knockout js
Working with Knockout 'foreach' looping through multidimensional array

Related

On click of each row,corresponding row data should get print in Angular js

Hello friends, I am new in angular js.I need help to print the each
row's data on click of button assigned to each row. Below is my
code.
HTML code:
List item
enter code here
<div class="jumbotron">
<table border='1'>
<tr ng-show="reports.length!=0" ng-repeat="report in reports">
<td>{{report.first_name}}</td>
<td>{{report.emp_id}}</td>
<td>{{report.month_calendar_days}}</td>
<td>{{report.pay_days}}</td>
<td>{{report.paid_days}}</td>
<td> 5" ng-repeat="key in noAlphabetSortPlease(report)" style="padding-left:10px;">{{report[key]}}</td>
<td><button ng-click="PrintRow()">Print Row</button></td>
</tr>
</table>
</div> </div>
Controller Code:
angular.module('app', [])
.controller('mainController', ['$scope', '$filter', function($scope,
$filter) {
$scope.noAlphabetSortPlease = function(obj){
return Object.keys(obj);
}
$scope.reports = [{"emp_id":"10001","first_name":"siva","status":1,"month_calendar_days":29,"pay_days":29,"paid_days":21,"salary_head_value1":0,"salary_head_value2":7550,"salary_head_value3":1600,"salary_head_value4":1800,"salary_head_value5":345,"salary_head_value6":6400,"salary_head_value7":5000,"salary_head_value8":31955,"salary_head_value9":1250,"salary_head_value10":12000,"salary_head_value11":6000,"salary_head_value12":47900,"salary_head_value13":15945,"salary_head_value14":4000,"salary_head_value15":2400},
{"emp_id":"10002","first_name":"naren","status":1,"month_calendar_days":29,"pay_days":29,"paid_days":21,"salary_head_value1":15501,"salary_head_value2":7551,"salary_head_value3":1601,"salary_head_value4":1801,"salary_head_value5":346,"salary_head_value6":6401,"salary_head_value7":5001,"salary_head_value8":31957,"salary_head_value9":1251,"salary_head_value10":12001,"salary_head_value11":6001,"salary_head_value12":47907,"salary_head_value13":15950,"salary_head_value14":4001,"salary_head_value15":2401},
{"emp_id":"10003","first_name":"Bhaki","status":1,"month_calendar_days":29,"pay_days":29,"paid_days":21,"salary_head_value1":15502,"salary_head_value2":7552,"salary_head_value3":1602,"salary_head_value4":1802,"salary_head_value5":347,"salary_head_value6":6402,"salary_head_value7":5002,"salary_head_value8":31959,"salary_head_value9":1252,"salary_head_value10":12002,"salary_head_value11":6002,"salary_head_value12":47914,"salary_head_value13":15955,"salary_head_value14":4002,"salary_head_value15":2402}];
$scope.PrintRow=function($index){ window.print();
} }]); </script>
Pass the corresponding object to the function call and overide your reports variable.
<tr ng-show="reports.length!=0" ng-repeat="report in reports">
<td>{{report.first_name}}</td>
<td>{{report.emp_id}}</td>
<td>{{report.month_calendar_days}}</td>
<td>{{report.pay_days}}</td>
<td>{{report.paid_days}}</td>
<td> 5" ng-repeat="key in noAlphabetSortPlease(report)" style="padding-left:10px;">{{report[key]}}</td>
<td><button ng-click="PrintRow(report)">Print Row</button></td>
</tr>
$scope.PrintRow=function(obj){
$scope.reports=[];
$scope.reports.push(obj);
}
Or If you want all data to persist and want to print the corresponding data of each row below the new table.Then you could do something like this
<tr ng-show="reports.length!=0" ng-repeat="report in reports">
<td>{{report.first_name}}</td>
<td>{{report.emp_id}}</td>
<td>{{report.month_calendar_days}}</td>
<td>{{report.pay_days}}</td>
<td>{{report.paid_days}}</td>
<td> 5" ng-repeat="key in noAlphabetSortPlease(report)" style="padding-left:10px;">{{report[key]}}</td>
<td><button ng-click="PrintRow(report)">Print Row</button></td>
</tr>
{{newArray}}
$scope.newArray=[];
$scope.PrintRow=function(obj){
$scope.newArray.push(obj);
}
Format new array to your desire.
<tr ng-show="reports.length!=0" ng-repeat="report in newArray">
<td>{{report.first_name}}</td>
<td>{{report.emp_id}}</td>
<td>{{report.month_calendar_days}}</td>
<td>{{report.pay_days}}</td>
<td>{{report.paid_days}}</td>
<td> 5" ng-repeat="key in noAlphabetSortPlease(report)" style="padding-left:10px;">{{report[key]}}</td>
<td><button ng-click="PrintRow(report)">Print Row</button></td>
</tr>

Angular directive nested in table body renders above table

Okay so, I'm using Angular 1.5.7, and I'm trying to do some table rendering with ng-repeat and stuff. This is what my table markup looks like:
<table class="table table-hover">
<thead>
<tr>
<td>Property name</td>
<td>Property value</td>
</tr>
</thead>
<tbody>
<adm-preset-property
ng-repeat="(propertyName, definition) in
componentDefinition.component_properties"
property-name="propertyName"
property-value="component.component_properties"
property-definition="definition"></adm-preset-property>
</tbody>
</table>
The <adm-preset-property> directive has a replace: true property and is rendered from a root <tr></tr> tag.
Now the loop works fine, BUT, instead of the table rows being rendered inside the table body, where they are nested, they are rendered ABOVE the table. I end up with
<tr>
{{ content }}
</tr>
<tr>
{{ content }}
</tr>
<tr>
{{ content }}
</tr>
<table>...</table>
What's worse, I can't seem to reproduce this on JSFiddle. What am I doing wrong?
EDIT: As requested, here's the template for the <adm-preset-property>
Template:
<tr>
<td>
<span data-toggle="tooltip" ng-attr-title="{{ ::propertyDefinition.description }}">{{ ::propertyDefinition.name }}</span>
</td>
<td ng-switch="propertyDefinition.editor_type">
<div ng-switch-when="select">
<ui-select append-to-body="true" ng-model="propertyValue[propertyName]" theme="bootstrap">
<ui-select-match placeholder="Select option...">{{ $select.selected.value }}</ui-select-match>
<ui-select-choices repeat="option.key as (key, option) in propertyDefinition.editor_properties.options | filter:{'value':$select.search} track by $index"
ng-value="key">
{{ option.value | highlight: $select.search }}</ui-select-choices>
</ui-select>
</div>
<div ng-switch-when="boolean">
<input type="checkbox" ng-model="propertyValue[propertyName]">
</div>
<div ng-switch-when="float">
<input type="range" step="0.01" ng-model="propertyValue[propertyName]" min="{{propertyDefinition.editor_properties.min}}"
max="{{propertyDefinition.editor_properties.max}}"> {{ propertyValue[propertyName] }}
</div>
<div ng-switch-when="color">
<input type="color" style="width: 75%;" ng-model="propertyValue[propertyName]">
</div>
<div ng-switch-when="int">
<input type="number" style="width: 75%;" ng-model="propertyValue[propertyName]" min="{{propertyDefinition.editor_properties.min}}"
max="{{propertyDefinition.editor_properties.max}}"> <br/>
<small>{{::propertyDefinition.editor_properties.min}} - {{::propertyDefinition.editor_properties.max}}</small>
</div>
<div ng-switch-default>
<input type="text" style="width: 75%;" ng-bind="propertyValue[propertyName]" />
</div>
</td>
</tr>
Directive:
(function() {
"use strict";
angular
.module('adomee.admin')
.directive('admPresetProperty', admPresetProperty);
/* #ngInject */
function admPresetProperty($log)
{
return {
restrict: 'E',
replace: true,
scope: {
propertyName: '=',
propertyValue: '=',
propertyDefinition: '='
},
templateUrl: 'app/preset/components/preset-property.tmpl.html',
link: function($scope, $element, $attributes) {
if ($scope.propertyDefinition.editor_type === 'select' && typeof($scope.propertyDefinition.defaultValue) === 'number') {
$scope.propertyValue[$scope.propertyName] = String($scope.propertyDefinition.defaultValue);
}
}
}
}
})();
Okay, after some research and consulting with my boss, we have concluded that Angular, naturally, compiles the directive's template into the DOM after the primary markup has been loaded.
Since the table expects a <tr> tag followed by <td>, but it runs into my custom tag instead, it removes the custom tag and places it outside of the table, which then compiles to my template afterwards, resulting in table rows on top of the actual table.
What I did was:
Change restrict: 'E' to restrict: 'A' in the directive.
Remove the replace property from it.
Remove the root <tr> tag and leave two <td> tags.
Place the directive into the table onto a <tr> and ng-repeat it.
Here's what it looks like now.
<tr adm-preset-property
ng-repeat="(propertyName, definition) in componentDefinition.component_properties"
property-name="propertyName"
property-value="component.component_properties"
property-definition="definition"></tr>

How to bind Knockout model data to Multiple tables

I am using knockout binding to bind some data into html tables. My knockout view Model had multiple products and each product will have multiple chars. I want to display the products in one table and when i select the link "show chars" it should display the corresponding chars in below table.
This is my View Model
var ProductViewModel = function(items) {
this.items = ko.observableArray(items);
this.itemToAdd = ko.observable("");
this.addItem = function() {
if (this.itemToAdd() != "") {
this.items.push(this.itemToAdd());
this.itemToAdd("");
}
}.bind(this);
};
And this is my html tables
<div id="productTable">
<table class="ui-responsive table">
<thead>
<tr>
<th >Product Name</th>
<th >Description</th>
<th >Parent?</th>
</tr>
</thead>
<tbody id="pBody" data-bind="foreach: items">
<tr class="success" >
<td><span data-bind="text: name"></span>
</td>
<td><span data-bind="text: desc"></span>
</td>
<td>show chars</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="productChars">
<div id="productCharTable">
<table class="ui-responsive table">
<thead>
<tr>
<th >Char Name</th>
<th >Description</th>
<th >Length</th>
<th >Type</th>
</tr>
</thead>
<tbody id="pBody" data-bind="foreach: $data['chars']">
<tr class="success">
<td><span data-bind="text: name"></span>
</td>
<td>asdf asdfasdf</td>
<td>10</td>
<td>String</td>
</tr>
</tbody>
</table>
</div>
I am able to bind the products into first table. But for characteristics i am not sure how to achieve the same.
Could someone please help me in figuring out how to achieve the same.
Here is the jsfiddle
https://jsfiddle.net/sirisha_k/0Ln7h2bo/7/
As #supercool pointed out you can use "data-bind='with selectedItem'" to populate the second table with chars data. For that you need to add one more item into your model called selectedItem and every time you select or add a row, you point the selectedItem to that elementdata. And use "data-bind='with selecteItem'" for second table.
var ProductViewModel = function(items) {
this.items = ko.observableArray(items);
this.selectedItem = ko.observableArray();
this.itemToAdd = ko.observable("");
this.addItem = function() {
if (this.itemToAdd() != "") {
this.items.push(this.itemToAdd());
this.itemToAdd("");
}
}.bind(this);
};
and on row select call some function selectedItem($data) where $data refers to the current item.
then set that data to selectedItem in model.
function selectedItem(prod){
ProductViewModel.selectedItem(prod);
}

Knockout.js - Ideas on transforming JSON data to HTML

I have an observableArray self.CustomerOrders which I populate with
self.CustomerOrders.push(new CustomerOrder(self.getOrderId(), today.toLocaleDateString() , self.selectedCustomer2(), JSON.stringify(self.cart(),null,4)));
where
self.getOrderId() is a method to get an Id for the order,
today.toLocaleDateString() prints today's date,
self.selectedCustomer2 is the selected customer of the order and
self.cart is another observableArray which includes all ordered items.
Here is how I populate self.cart
self.cart.push(new orderedItem(product.id, product.name, product.price, product.quantity()));
and here is my foreach
<tbody data-bind="foreach: CustomerOrders">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: date"></td>
<td data-bind="text: customer"></td>
<td data-bind="text: details"></td>
<td data-bind="click: $parent.selectedOrder"><a class="btn btn-primary" data-toggle="modal" data-target="#display-order">View</a>
</td>
<td data-bind="click: $parent.selectedOrder"><a class="btn btn-primary" data-toggle="modal" data-target="#edit-order">Edit</a>
</td>
<td data-bind="click: $parent.selectedOrder"><a class="btn btn-primary" data-toggle="modal" data-target="#delete-order">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
I succeed in saving all those data to the CustomersOrders observable array and then I print them in my UI using foreach. My problem is that the self.cart items are printed as JSON and I do not want to display JSON to the user but HTML.
How to implement this ?
Any ideas ?
Ok, so don't JSON.stringify your cart. Then, assuming your Details binding is where the cart part is supposed to end up, and it's supposed to be an array, you can just nest foreach bindings like this:
<td>
<ul data-bind="foreach: details">
<li data-bind="text: someProperty"></li>
</ul>
</td>
where someProperty is whatever property of the cart you want to display.
Of course, you can choose whatever html elements suit your requirements.
My problem is that the self.cart items are printed as JSON
Well, that's not surprising.
self.CustomerOrders.push(
new CustomerOrder(
self.getOrderId(),
today.toLocaleDateString(),
self.selectedCustomer2(),
JSON.stringify(self.cart(),null,4) /* guess what that does */
)
);
Just do
self.CustomerOrders.push(
new CustomerOrder(
self.getOrderId(),
today.toLocaleDateString(),
self.selectedCustomer2(),
self.cart
)
);
and use regular knockout bindings in your view to display the cart.

Knock Out data binding

I have a question about data binding using knockout.
Here's the problem: I have a table, I what I would like to do is that when a row in table is clicked, I want the values of the row to appear in the input fileds which are located above the table.
so here'
<tbody data-bind="foreach: customers">
<tr data-bind="click: doSomething">
<td data-bind="text: date"></td>
<td data-bind="text:staff"></td>
<td data-bind="text: ftype"></td>
<td data-bind="text: value"></td>
<td data-bind="text: message"></td>
</td>
</tr>
</tbody>
In my viewmodel, I have the following function:
doSomething: function(data) {
var self = this;
self.date(data.date);
self.staff(data.staff);
self.ftype(data.ftype);
self.value(data.value);
self.message(data.message);
}
Here's the error I am getting:
["Unable to parse bindings.↵Message: ReferenceError:… is not defined;↵Bindings value: click: doSomething", "views/myView/index", Object]
0: "Unable to parse bindings.↵Message: ReferenceError: doSomething is not defined;↵Bindings value: click: doSomething"
1: "views/myView/index"
2: Object
length: 3
__proto__: Array[0]
Let me know if I need to provide any more details. I will appreciate your help fplks!
A very basic pattern for this type of thing is to have an array of items and a selectedItem observable that you populate when selecting a row.
Then, you can use the with binding around a section to create your editor.
<table>
<tbody data-bind="foreach: customers">
<tr data-bind="click: $root.selectedCustomer">
<td data-bind="text: name"></td>
</tr>
</tbody>
</table>
<hr/>
<div data-bind="with: selectedCustomer">
<input data-bind="value: name" />
</div>
Sample: http://jsfiddle.net/rniemeyer/Z6VPV/
You need to bind your model to the view
var currentViewModel = function(){
this.doSomething = function(data){
var self = this;
self.date(data.date);
self.staff(data.staff);
self.ftype(data.ftype);
self.value(data.value);
self.message(data.message);
}
var viewModel = new currentViewModel();
ko.applyBindings(viewModel);