I have a model which contains relationship between Tag and Task. So many TaskIDs can relate to many TagIDs
However, I want to display unique TagIDs and TagNames in a table. So instead of the duplicate rows in the JSFiddle example, it should return distinct rows i.e. only 2 rows in the table.
The second table I have works fine as I am returning just one column.
Below is my code...
var viewModel = function(data) {
var self = this;
self.tagTaskMappings = ko.observableArray([
{TagID: 2, TagName: "A", TaskID: 1, TaskName: "ManualItems"},
{TagID: 2, TagName: "A", TaskID: 2, TaskName: "Trades"},
{TagID: 3, TagName: "B", TaskID: 1, TaskName: "ManualItems"},
{TagID: 3, TagName: "B", TaskID: 2, TaskName: "Trades"},
{TagID: 3, TagName: "B", TaskID: 3, TaskName: "Cash"},
{TagID: 3, TagName: "B", TaskID: 4, TaskName: "ReportA"}
]);
self.filteredtagMappings = ko.computed(function () {
var types = ko.utils.arrayMap(self.tagTaskMappings(), function (item) {
return { TagID: item.TagID, TagName: item.TagName, IsTagActive: item.IsTagActive};
});
return ko.utils.arrayGetDistinctValues(types).sort();
}, this);
self.filteredtagMappings2 = ko.computed(function () {
var types = ko.utils.arrayMap(self.tagTaskMappings(), function (item) {
return item.TagName;
});
return ko.utils.arrayGetDistinctValues(types).sort();
}, this);
};
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<table class="table table-hover">
<thead>
<tr>
<th>Tag ID</th>
<th>Tag name</th>
<th>Task ID</th>
<th>Task name</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: filteredtagMappings -->
<tr>
<td class="ui-state-default" data-bind="text: TagID"></td>
<td class="ui-state-default" data-bind="text: TagName"></td>
<td></td>
</tr>
<!-- /ko -->
</tbody>
</table>
<hr />
<table class="table table-hover">
<thead>
<tr>
<th>Tag ID</th>
<th>Tag name</th>
<th>Task ID</th>
<th>Task name</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: filteredtagMappings2 -->
<tr>
<td></td>
<td class="ui-state-default" data-bind="text: $data"></td>
<td></td>
</tr>
<!-- /ko -->
</tbody>
</table>
http://jsfiddle.net/4qc570eo/2/
here is a solution using underscorejs and the knockout mapping pluggin.
http://jsfiddle.net/4qc570eo/6/
self.filteredtagMappings2 = function() {
var returnObject = ko.observableArray('');
var uniques = _.map(_.groupBy(ko.toJS(self.tagTaskMappings), function(item) {
return item.TagID;
}), function(grouped) {
return grouped[0];
});
ko.mapping.fromJS(uniques, {}, returnObject);
return returnObject;
};
};
Related
I am currently working with a relatively large Vue (Vue 2) project that uses a lot of tables, and I want to create a reusable table component where each column is a child component / slot. Something like this:
<Table :data="data">
<TableColumn field="id" label="ID" />
<TableColumn field="name" label="Name" />
<TableColumn field="date_created" label="Created" />
</Table>
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
Which in turn should output this:
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Foo</td>
<td>01.01.2021</td>
</tr>
<tr>
<td>2</td>
<td>Bar</td>
<td>01.01.2021</td>
</tr>
</tbody>
</table>
We've previously used Buefy, but the vendor size becomes unnecessarily large, as we only use a fraction of the components' functionality - so I want to create a lightweight alternative.
With this data you only need 2 Props, labels and data.
<!-- Component -->
<table>
<thead>
<tr>
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ label.text }}
</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ item[label.field] }}
</td>
</tr>
</tbody>
</table>
// Data and labels
const labels = [
{ text: ID, field: id },
{ text: Name, field: name },
{ text: Created, field: date_created },
]
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
<table-component
:labels="labels"
:data="data"
>
</table-component>
If you need something more complex you can use nested components combined with a named slots for the header or footer of the table (or other options like search).
I am working on a small Svelte application, for learning purposes (Im new to Svelte). The application uses an array of objects displayed in a view as an HTML table:
let countries = [
{ code: "AF", name: "Afghanistan" },
{ code: "AL", name: "Albania" },
{ code: "IL", name: "Israel" }
];
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Code</th>
<th>Name</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
{#if countries.length}
{#each countries as c, index}
<tr>
<td>{index+1}</td>
<td>{c.code}</td>
<td>{c.name}</td>
<td class="text-right">
<button data-code="{c.code}" on:click="{deleteCountry}" class="btn btn-sm btn-danger">Delete</button>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="4">There are no countries</td>
</tr>
{/if}
</tbody>
</table>
I am doing a delete operation this way:
function deleteCountry(){
let ccode = this.getAttribute('data-code');
let itemIdx = countries.findIndex(x => x.code == ccode);
countries.splice(itemIdx,1);
console.log(countries);
}
There is a REPL here.
The problem
I have been unable to render the table (view) again, after the countries array is updated (an element is deleted from it).
How do I do that?
add
countries = countries;
after this line
countries.splice(itemIdx,1);
since reactivity/rerendering/UI update only marked after assignment.
For svelte to pick up the change to your array of countries, you need to create a new reference of the array. For this you could use the Array.filter method.
<script>
let countries = [
{ code: "AF", name: "Afghanistan" },
{ code: "AL", name: "Albania" },
{ code: "IL", name: "Israel" }
];
function deleteCountry(code) {
countries = countries.filter(c => c.code !== code)
}
</script>
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Code</th>
<th>Name</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
{#if countries.length}
{#each countries as c, index}
<tr>
<td>{index+1}</td>
<td>{c.code}</td>
<td>{c.name}</td>
<td class="text-right">
<button on:click="{() => deleteCountry(c.code)}" class="btn btn-sm btn-danger">Delete</button>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="4">There are no countries</td>
</tr>
{/if}
</tbody>
</table>
Also you can directly use the country code as an argument for the deleteCountry method.
I am trying to bind array to a table, so it shows all my array contents.
I tried first example, which works (purely in HTML):
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
function Model() {
this.people = [
{ firstName: 'Bert', lastName: 'Bertington' },
{ firstName: 'Charles', lastName: 'Charlesforth' },
{ firstName: 'Denise', lastName: 'Dentiste' }
];
}
ko.applyBindings(new Model());
</script>
Then I got to the next level, and tried bigger example, which always shows error
Unable to process binding "foreach: function(){return interests }"
Message: Anonymous template defined, but no template content was provided
Below is faulty code:
// Activates knockout.js when document is loaded.
window.onload = (event) => {
ko.applyBindings(new AppViewModel());
}
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
this.fullName = ko.computed(() => this.firstName() + " " + this.lastName(), this);
this.interests = ko.observableArray([
{ name: "sport" },
{ name: "games" },
{ name: "books" },
{ name: "movies" }
]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody data-bind="foreach: interests"></tbody>
<tr>
<td data-bind="text: name"></td>
</tr>
</table>
I tired already with regular array, but with no luck.
You are closing the <tbody> before the inner template:
<tr>
<td data-bind="text: name"></td>
</tr>
So, the tr is now not in the context of the foreach binding.
Move the </tbody> to after the </tr> and before the </table> tags:
// Activates knockout.js when document is loaded.
window.onload = (event) => {
ko.applyBindings(new AppViewModel());
}
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
this.fullName = ko.computed(() => this.firstName() + " " + this.lastName(), this);
this.interests = ko.observableArray([
{ name: "sport" },
{ name: "games" },
{ name: "books" },
{ name: "movies" }
]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody data-bind="foreach: interests">
<tr>
<td data-bind="text: name"></td>
</tr>
</tbody> <!-- close here -->
</table>
Change your HTML to
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody>
<tr data-bind="foreach: interests">
<td data-bind="text: name"></td>
</tr></tbody>
</table>
Unable to read the data from parent scope to directive. Getting error like
TypeError: Cannot read property 'rowCollection' of undefined
Can you please help me out of this.
HTML
<div ng-controller="ctrl1 as one">
<ltcg-table options="one.rowCollection"></ltcg-table>
</div>
Grid HTML
<table st-table="rowCollection" class="table table-striped">
<thead>
<tr>
<th>first name</th>
<th>last name</th>
<th>birth date</th>
<th>balance</th>
<th>email</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rowCollection">
<td>{{row.firstName}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate}}</td>
<td>{{row.balance}}</td>
<td>{{row.email}}</td>
</tr>
</tbody>
</table>
Javascript Controller
(function () {
var myApp = angular.module('myApp', ['smart-table']);
function one() {
this.song="Murali";
// alert("gg");
this.rowCollection = [
{firstName: 'Laurent', lastName: 'Renard', birthDate: new Date('1987-05-21'), balance: 102, email: 'whatever#gmail.com'},
{firstName: 'Blandine', lastName: 'Faivre', birthDate: new Date('1987-04-25'), balance: -2323.22, email: 'oufblandou#gmail.com'},
{firstName: 'Francoise', lastName: 'Frere', birthDate: new Date('1955-08-27'), balance: 42343, email: 'raymondef#gmail.com'}
];
//alert($scope.gridOptions.columnDefs[1].name);
//alert($scope.gridOptions);
};
myApp.directive('ltcgTable', function() {
return {
restrict: 'E',
transclude: true,
scope: {
'options': '='
},
templateUrl: "ltcg-table.html",
link: function(scope, element, attr) {
alert(scope.$parent.options.rowCollection);
scope.rowCollection = scope.options.rowCollection;
}
}
});
myApp.controller('ctrl1', one)
})();
So, you have a directive with isolated scope. In this case scope parameter in link function referes to this scope, in your case this next object
{
'options': '='
}
So when you do in html options="one.rowCollection" value of one.rowCollection was binded to options property, so for access to it you should use scope.options in link function, on just options in view.
also $parent property set to parent scope, in your case - "ctrl1" controller scope. So you can directly go to controller and get what you want.
When use controller as syntax reference to controller saved in controller scope. So for access controller you should use it name.
Sample:
var myApp = angular.module('myApp', []);
function one() {
this.song = "Murali";
// alert("gg");
this.rowCollection = [{
firstName: 'Laurent',
lastName: 'Renard',
birthDate: new Date('1987-05-21'),
balance: 102,
email: 'whatever#gmail.com'
}, {
firstName: 'Blandine',
lastName: 'Faivre',
birthDate: new Date('1987-04-25'),
balance: -2323.22,
email: 'oufblandou#gmail.com'
}, {
firstName: 'Francoise',
lastName: 'Frere',
birthDate: new Date('1955-08-27'),
balance: 42343,
email: 'raymondef#gmail.com'
}];
//alert($scope.gridOptions.columnDefs[1].name);
//alert($scope.gridOptions);
};
myApp.directive('ltcgTable', function() {
return {
restrict: 'E',
transclude: true,
scope: {
'options': '='
},
templateUrl: "ltcg-table.html",
link: function(scope, element, attr) {
//go to controller directly
scope.rowCollection = scope.$parent.one.rowCollection
}
}
});
myApp.controller('ctrl1', one)
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="ctrl1 as one">
<ltcg-table options="one.rowCollection"></ltcg-table>
</div>
<script id="ltcg-table.html" type="text/ng-template">
<table st-table="rowCollection" class="table table-striped">
<thead>
<tr>
<th>first name</th>
<th>last name</th>
<th>birth date</th>
<th>balance</th>
<th>email</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="5">
Get data from scope.options
</td>
</tr>
<tr ng-repeat="row in options">
<td>{{row.firstName}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate}}</td>
<td>{{row.balance}}</td>
<td>{{row.email}}</td>
</tr>
<tr>
<td colspan="5">
<hr/>
</td>
</tr>
<tr>
<td colspan="5">
Get data saved from controller directly in link function
</td>
</tr>
<tr ng-repeat="row in rowCollection">
<td>{{row.firstName}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate}}</td>
<td>{{row.balance}}</td>
<td>{{row.email}}</td>
</tr>
</tbody>
</table>
</script>
</div>
You added options to your directive scope, so you can directly access it throughscope.options. By the way, directives scopes are isolated (with the scope: {} notation), so you can't just go up and try to read parent scopes.
Plunker sample
How to hide table column if all json value is null for any property
using angular js
index.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.isArray = angular.isArray;
$scope.data = [{
"Id": null,
"Title": "US",
"Description": "English - United States",
"Values": [{
"Id": 100,
"LanId": 1,
"KeyId": 59,
"Value": "Save"
}]
}, {
"Id": null,
"Title": "MX",
"Description": "test",
"Values": [{
"Id": 100,
"LanId": 1,
"KeyId": 59,
"Value": "Save"
}]
}, {
"Id": null,
"Title": "SE",
"Description": "Swedish - Sweden",
"Values": [{
"Id": 100,
"LanId": 1,
"KeyId": 59,
"Value": "Save"
}]
}]
$scope.cols = Object.keys($scope.data[0]);
$scope.notSorted = function(obj) {
if (!obj) {
return [];
}
return Object.keys(obj);
}
});
index.html
<table border=1 style="margin-top: 0px !important;">
<thead>
<tr>
<th ng-repeat="(k,v) in data[0]">{{k}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-repeat="(prop, value) in item" ng-init="isArr = isArray(value)">
<table ng-if="isArr" border=1>
<thead>
<tr>
<td>
<button ng-click="expanded = !expanded" expand>
<span ng-bind="expanded ? '-' : '+'"></span>
</button>
</td>
</tr>
<tr>
<th ng-repeat="(sh, sv) in value[0]">{{sh}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="sub in value" ng-show="expanded">
<td ng-repeat="(sk, sv) in sub">{{sv}}</td>
</tr>
</tbody>
</table>
<span ng-if="!isArr">{{value}}</span>
</td>
</tr>
</tbody>
</table>
You can filter out columns that have only null values with:
JavaScript
$scope.cols = Object.keys($scope.data[0]).filter(function(col) {
return $scope.data.some(function(item) {
return item[col] !== null;
});
});
and check in template if this column should be rendered:
HTML
<table border=1 style="margin-top: 0px !important;">
<thead>
<tr>
<!-- Iterate over non-null columns -->
<th ng-repeat="col in cols">{{col}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<!-- Use ngIf to hide redundant column -->
<td ng-if="cols.indexOf(prop)>=0" ng-repeat="(prop, value) in item" ng-init="isArr = isArray(value)" >
...
Plunker
http://plnkr.co/edit/PIbfvX6xvX5eUhYtRBWS?p=preview
So the id is null for every element in the array, then do
<th ng-repeat="(k,v) in data[0]" ng-show="v">{{k}}</th>
and
<td ng-repeat="(prop, value) in item" ng-init="isArr = isArray(value)" ng-show="value">
plnkr: http://plnkr.co/edit/rra778?p=preview
You need to make use of the cols property you defined in your $scope, but you also need to make sure its correct and responsive. You do that like this:
var colsFromData = function(data) {
var activeCols = {};
data.forEach(function(o) {
Object.keys(o).forEach(function(k) {
if(null == o[k])
return;
activeCols[k] = 1;
})
});
return Object.keys(activeCols);
}
$scope.cols = colsFromData($scope.data);
$scope.$watchCollection('data', colsFromData);
Then in your template, to use the now correct cols array:
...
<thead>
<tr>
<th ng-repeat="k in cols">{{k}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-repeat="(prop, value) in item" ng-init="isArr = isArray(value)" ng-if="cols.indexOf(prop) >= 0">
...
And the updated plunker