I have been trying to figure out how to generate a table using a directive and an array of data. Basically I want to be able to structure my html like this:
<div ng-init="myData = [name: 'John', age: 22]"></div>
<dynamic-table data="myData">
<field title="column 1" class="myClass" expression="{{object.name}}" />
<field title="column 2" class="myClass" expression="{{object.age}}" />
</dynamic-table>
From that html, I want my directive to generate the following HTML:
<table>
<tr>
<th>column 1</th>
<th>column 2</th>
</tr>
<tr>
<th>John</th>
<th>22</th>
</tr>
</table>
The problem is that I need the expression {{object.name}} to be evaluated inside the directive rather than prior to begin passed in to the directive. I have a jsfiddle here demonstrating the problem. I would also be interested to hear about any other ways to accomplish this. I've tried it using the compile directive function but eventually you run into the same thing with the expression. Of course, I could do this using ng-repeats, but my directive will eventually do more than just this.
Here is the directive from the fiddle:
var appModule = angular.module('app', [])
.directive('dynamicTable', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<table>' +
'<tr>' +
'<th ng-repeat="field in fields">{{field.title}}</th> ' +
'</tr>' +
'<tr ng-repeat="object in data" ng-transclude>' +
'<td ng-repeat="field in fields">' +
'{{field.expression}}' +
'</td>' +
'</tr>' +
'</table>',
scope: {
data: '='
},
controller: function ($scope) {
$scope.fields = [];
}
};
}).directive('field', function () {
return {
restrict: 'E',
require: '^dynamicTable',
link: function (scope, element, attrs, controller) {
var exists = false;
for (var idx = 0; idx < scope.fields.length; idx++) {
if (scope.fields[idx].title === attrs.title) {
exists = true;
break;
}
}
if (!exists) {
scope.fields.splice(0, 0, { title: attrs.title, 'class': attrs.class, expression: attrs.expression });
}
}
};
});
Related
I am putting together a directive that will wrap a transcluded table and will be used with another directive to prepend checkboxes to the first cell in a table row to allow for multi selections of rows, plus a header checkbox cell for toggle select\deselect-all functionality.
I have started with a table header directive, here is what I have come up with so far:
DIRECTIVES
angular
.module('app.components')
.directive('myTable', myTable);
function myTable() {
var myTable = {
restrict: 'E',
transclude: true,
scope: {
someProperty: '='
},
template: '<div class="myTable">' +
'<md-toolbar class="md-menu-toolbar">' +
'<div class="md-toolbar-tools">' +
'OTHER CONTENT HERE...'
'</div>' +
'</md-toolbar>' +
'<ng-transclude></ng-transclude>' +
'</div>',
controller: controller,
controllerAs: 'vm',
bindToController: true
};
return myTable;
function controller($attrs) {
var self = this;
self.enableMultiSelect = $attrs.multiple === '';
}
}
angular
.module('app.components')
.directive('myTableHead', myTableHead);
myTableHead.$inject = ['$compile'];
function myTableHead($compile) {
var myTableHead = {
restrict: 'A',
require: ['myTableHead', '^^myTable'],
controller: controller,
controllerAs: 'vm',
bindToController: true,
link: link
};
return myTableHead;
function link(scope, element, attrs, ctrls) {
var self = ctrls.shift(),
tableCtrl = ctrls.shift();
if(tableCtrl.enableMultiSelect){
element.children().prepend(createCheckbox());
}
self.toggleAll = function () {
console.log('toggleAll');
};
function createCheckbox() {
var checkbox = angular.element('<md-checkbox>').attr({
'aria-label': 'Select All',
'ng-click': 'vm.toggleAll()'
});
return angular.element('<th class="md-column md-checkbox-column">').append($compile(checkbox)(scope));
}
}
function controller() {
}
}
HTML
<my-table multiple>
<table>
<thead my-table-head>
<tr>
<th></th>
<th></th>
...
</tr>
</thead>
<tbody>
<tr ng-repeat="thing in things">
<td>{{thing.propA}}</td>
<td>{{thing.propB}}</td>
</tr>
</tbody>
</table>
</my-table>
I am encountering an issue where as soon as I have added the my-table-head directive to my thead element of the table being transcluded in, no rows from the ng-repeat get rendered.
The checkbox header cell does actually render just fine, but I also am finding when clicking it, the toggleAll function its bound to (defined in my link function) is not firing as I dont see any console log messages appearing.
I can't figure out why, can anyone point out what I am doing wrong with my approach above?
Hy,
I created a directive for a simple use of tables in my application.
I'm struggling to get an ng-repeat to work.
The problem is that my directive will tell the table from what controller it will get the list with the data for the ng-repeat and this seems not to work, i could use another directive to create an ng-repeat but i don´t want to change the way the developer can use the angular directives.
So if anyone already struggled with this problem please share how you resolve it.
this is the markup.
<body ng-app="List">
<div my-table pl-emptytext="No data found!">
<table>
<thead>
<tr>
<th pl-sort="Id"> Id </th>
<th pl-sort="Name"> Name </th>
<th pl-sort="Roles"> Roles </th>
<th pl-sort="Claims"> Claims </th>
</tr>
</thead>
<tbody>
<tr pl-obj="user">
<td>{{user.Id}}</td>
<td>{{user.Name}}</td>
<td>
<span ng-repeat="role in user.Roles">
{{role.RoleType}}
</span>
</td>
<td>
<span ng-repeat="claim in user.Claims">
{{role.ClaimType}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</body>
This is the directive myTable
angular
.module('List')
.directive('myTable', [
'$compile',
function($compile) {
return {
restriction: 'AE',
scope: {
plEmptytext: '#'
},
transclude: true,
replace: true,
template: '<div class="container-fluid">' +
'<div class="row">' +
'<div class="col-lg-12">' +
'<div class="alert alert-warning" ng-cloak ng-show="ListIsEmpty">' +
'{{plEmptytext}}' +
'</div>' +
'<div ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element) {
// Do something else, bind events, etc ...
var table = element.find('table');
$compile(table)(scope);
}
};
}
])
.directive('table', [
function() {
return {
link: function(scope, element, attrs) {
attrs.$addClass("table");
attrs.$addClass("table-striped");
}
};
}
])
.directive('thead', [
function() {
return {
link: function(scope, element, attrs) {
}
};
}
])
.directive('th', [
function() {
return {
link: function(scope, element, attrs) {
if (attrs.plSort) {
attrs.$set("ngClick", "resort('" + attrs.plSort + "')");
}
}
};
}
])
.directive('tbody', [
function() {
return {
link: function(scope, element, attrs) {
attrs.$set("ng-controller", "listCtrl");
}
};
}
])
.directive('tr', [
function() {
return {
link: function(scope, element, attrs) {
if (attrs.plObj) {
attrs.$set("ngRepeat", attrs.plObj + " in List");
}
}
};
}
]);
And this is the listCtrl
angular.module('List', [])
.controller('listCtrl', ['$scope', '$timeout',
function($scope, $timeout) {
$timeout(function() {
$scope.List = [{
Id: '1',
Name: 'teste1',
Roles: [{
Id: 1,
RoleType: '1'
}, {
Id: 2,
RoleType: '2'
}],
Claims: [{
Id: 1,
CalimType: '1'
}, {
Id: 2,
ClaimType: '2'
}]
}, {
Id: '1',
Name: 'teste1',
Roles: [{
Id: 2,
RoleType: '2'
}],
Claims: [{
Id: 2,
ClaimType: '2'
}]
}];
}, 1000)
}
]);
The problem is when i try to call the ng-repeat on this 2 td it dosen´t work
<td>
<span ng-repeat="role in user.Roles">
{{role.RoleType}}
</span>
</td>
<td>
<span ng-repeat="claim in user.Claims">
{{role.ClaimType}}
</span>
</td>
Anyone can point me in the direction of the problem? Already look to different things and different solutions from different people, yet it seems that no one had face my problem.
Best,
Plunker
First of all, your pl-obj directive is literally just an ng-repeat. So use <tr ng-repeat="user in List">.
Second of all, your controller doesn't appear to be hooked up. You're trying to hook it up using a strange directive, but you may be doing it in a way Angular doesn't understand (properties of the attrs object are camelCased, not hyphen-ated). Just use an ng-controller attribute on your table element: <table ng-controller="listCtrl">.
Once you're using more standard Angular in this way, I expect your problem will be easier to troubleshoot.
EDIT: Here's a Plunker with the changes I recommended (and some corrected typos). https://plnkr.co/edit/iEEtBycevB3Wh6eHFEAI?p=preview. Seems to work fine now.
EDIT 2: If you want to use the same controller for all <tbody> (I don't recommend this, but it's something you can do), use this in your directive:
function() {
return {
controller: 'listCtrl',
...
};
}
I´m trying to do a parent directive with 2 child directives.
The main reason to do this is to force that all the tables in the application use the same "template".
My problem here is that i need to copy the Dom inside the directives.
So my main directive is the table directive, and the frist child is the thead and the second child is the tbody.
This is the markup
<div my-table >
<my-thead>
<tr>
<th ng-click="alert('Id')"> Id </th>
<th ng-click="alert('Name')"> Name </th>
</tr>
</my-thead>
<my-tbody>
<tr ng-repeat ="item in List">
<td>{{item.Id}}</td>
<td>{{item.Name}}</td>
</tr>
</my-tbody>
</div>
The directive my-table wil generate all the code necessary for the struct of a table while the dir my-thead and my-tbody will create the tags thead and tbody and give them to do parent div.
My problem remains on copying the inner markup of my-thead and my-tbody since they are tr, any suggestion?
Next i present to you the most recent code i get so far with a plunker
myTable
angular
.module('App')
.directive('myTable', ['$compile',
function($compile){
var tableSettings = {};
return {
restriction :'AE',
scope : {},
replace : false,
link : function(scope,element){
var divContainerFluid = angular.element('<div>');
var divRowTable = angular.element('<div>');
var divColTable = angular.element('<div>');
divContainerFluid.addClass('container-fluid');
divRowTable.addClass('Row');
divColTable.addClass('col-lg-12');
var divTable = angular.element('<div>');
var table = angular.element('<table>');
table.addClass('table');
table.addClass('table-striped');
//add from thead
table.append(tableSettings.tHead);
//add from tbody
table.append(tableSettings.tBody);
divTable.append(table);
divColTable.append(divTable);
divRowTable.append(divColTable);
divContainerFluid.append(divRowTable);
$compile(divContainerFluid)(scope);
element.append(divContainerFluid);
},
controller: function () {
this.receiveTHead = function (tHead) {
tableSettings.tHead = tHead;
}
this.receiveTBody = function (tBody) {
tableSettings.tBody = tBody;
}
}
}
}]);
myHead
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
myBody
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
Plunker
Thanks, for the help.
UPDATE
missing listCtrl
angular.module('App',[])
.controller('listCtrl',['$scope','$timeout',
function($scope,$timeout){
$timeout(function () {
$scope.List = [ {Id : '1', Name : 'Name1'}, {Id : '2', Name :'Name2'}];
}, 1500);
}
]);
UPDATE
I recreated the problem from a different perspective. But the body doesn't get populated with the info in the scope.List, any ideas?
<div my-table>
<table>
<thead>
<tr>
<th ng-click="resort('Id')"> Id </th>
<th ng-click="resort('Name')"> Name </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in List">
<td>{{user.Id}}</td>
<td>{{user.Name}}</td>
</tr>
</tbody>
</table>
</div>
myTable
angular
.module('App')
.directive('myTable', [
'$compile',
function($compile) {
return {
restriction: 'AE',
//scope: {
// plEmptytext: '#'
//},
transclude: true,
template:
'<div class="container-fluid">' +
'<div class="row">' +
'<div class="col-lg-12">' +
'<div ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element) {
var table = element.find('table');
table.addClass('table');
table.addClass('table-striped');
var tHead = element.find('thead');
var tBody = element.find('tbody');
tBody.attr('ng-controller', 'listCtrl');
$compile(tHead)(scope);
$compile(tBody)(scope);
$compile(table)(scope);
}
};
}
]);
the listCtrl is the same
Plunker 2
SOLUTION:
For the ones who might come here my solution has to add to the directive tBody return{ controller: 'listCtrl' ...
A different approach that might still provide your desired "one template for all" goal would be writing an html template file, and referencing it whenever a table is used by declaring an ng-include. Similar to the old server-side includes, it gives a way to inject a block of html anywhere, anytime, without all the overhead of a directive.
We've actually stepped back from making so many directives and started using includes more when there isn't any work to be done in the directive.
Angular documentation on ngInclude
Sounds like you need a "Multi-slot Transclusion".
This is available in angular versions > 1.5
https://docs.angularjs.org/api/ng/directive/ngTransclude
For the ones who might come here my solution has to add to the directive tBody
return{ controller: 'listCtrl' ...
For me it was the solution I was looking for.
I have made a custom directive for a table in Angular JS which represents like this
`<div mytable api="data">
<div col="A"></div>
<div col="B"></div>
</levelOne>`
myTable directive has a templateUrl associated with it which has table and paging options. right now i have hard code the column names. But i want my column names col=A and col=B inside the parent directive mytable. Problem is that mytable is replaced by the templateUrl and i cannot get the inside elements.
I did not understand what are you trying to achieve with that but would like to provide a solution which you can build up on.
Declare your directives (note the hyphenated element names):
<div mytable api="data">
<div col="ID"></div>
<div col="NAME"></div>
</div>
Now define the directive mytable as:
var myApp = angular.module('myApp',[]);
myApp.run(function($rootScope) {
$rootScope.data = [{
id: 1,
name: 'One'
}, {
id: 2,
name: 'Two'
}];
});
myApp.directive('mytable', function() {
return {
restrict: 'AE',
transclude: true,
template:
'<table>\
<thead></thead>\
<tbody>\
<tr ng-repeat="d in data">\
<td ng-bind="d.id"></td>\
<td ng-bind="d.name"></td>\
</tr>\
</tbody>\
</table>',
controller: function($scope, $element, $attrs) {
$scope.data = $scope.$eval($attrs.api);
},
link: function(scope, element, attrs, NullController, $transcludeFn) {
$transcludeFn(function(tElement) {
var headHTML = '<tr>';
for (var i = 0; i < tElement.length; i++) {
if (tElement[i].nodeType === 1) {
headHTML+= '<td>' + angular.element(tElement[i]).attr('col') + '</td>';
}
}
headHTML+= '</tr>';
element.find('thead').html(headHTML);
});
}
};
});
New Demo: http://jsfiddle.net/codef0rmer/aa18tuzf/
Old Demo: http://jsfiddle.net/codef0rmer/yy4rc49L/
I'm trying to put together a reusable but custom table-type control using directives and tansclude in AngularJS and I'm running into a wall with not being able to customize the content that goes into the table's columns using {{syntax}}.
I've tried many different variations but have been unable to get it working. I'd include code but I don't think I am on the right track yet?
What I want to achieve:
data:
mymodel.items = [{ number: 1, name: 'John'}, {number: 2, name: 'Bob'}]
html:
<grid items="mymodel.items">
<column title="#">#{{item.number}}</column>
<column title="Name"><b>Hello {{item.name}}</b></column>
</grid>
template:
<table>
<thead>
<th ng-repeat="column in columns">{{column.title}}</th>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td ng-repeat="column in columns">{{columnValue(item, column.content)}}</th>
</tr>
</tbody>
</table>
expected output:
<table>
<thead>
<th>#</th>
<th>Name</th>
</thead>
<tbody>
<tr>
<td>#1</td>
<td>Hello John</td>
</tr>
<tr>
<td>#2</td>
<td>Hello Bob</td>
</tr>
</tbody>
</table>
I can't seem to be able to figure out how to transclude the content in "grid" such that it can be interpolated.
You can manually create the template:
Here is a plunker: http://plnkr.co/edit/C7HTRYZ4Hs1uOC9PXuza?p=preview
app.directive('grid', function() {
return {
restrict: 'E',
replace:true,
scope: {
items: "="
},
template: function(tElm, tAttrs) {
var td = "", th = "";
angular.forEach(tElm.find('column'), function(column){
th = th + "<th>" + column.title + "</th>";
td = td + "<td>" + column.innerHTML + "</td>";
});
var template = '<table>' +
'<thead>' + th + '</thead>' +
'<tbody>' +
'<tr ng-repeat="item in items">'+ td +'</tr>' +
'</tbody>' +
'</table>';
return template;
}
};
});
You can also build it with more directives:
Here is a demo plunker: http://plnkr.co/edit/a3xOSS6Un6XE9b1hQhvw?p=preview
One directive for saving a copy of transcluded columns (must run first):
app.directive('grid', function() {
return {
priority: 1200,
restrict: 'E',
compile: function(tElm, tAttrs) {
tAttrs.columns = tElm.find('column');
tElm.empty();
},
controller: function($attrs) {
this.columns = $attrs.columns;
}
};
});
Other directive for creating an isolated scope and replacing with a template:
app.directive('grid', function() {
return {
priority: 1100,
restrict: 'E',
scope:{
items: "="
},
templateUrl: 'grid.html'
};
});
Now I came up with this template:
<table>
<thead title-transclude></thead>
<tbody>
<tr ng-repeat="item in items" row-transclude></tr>
</tbody>
</table>
And two different directives to clone the headers and the contents. Inside these directives you have a reference to the transcluded columns via the grid controller:
app.directive('titleTransclude', function($compile) {
return {
require: '^grid',
link: function(scope, elm, attr, grid) {
var clones = [];
angular.forEach(grid.columns, function(col) {
var th = document.createElement('th');
th.innerHTML = col.title;
clones.push(th);
});
elm.append(clones);
$compile(clones)(scope);
}
};
});
app.directive('rowTransclude', function($compile) {
return {
require: '^grid',
link: function(scope, elm, attr, grid) {
var clones = [];
angular.forEach(grid.columns, function(col) {
var td = document.createElement('td');
td.innerHTML = col.innerHTML;
clones.push(td);
});
elm.append(clones);
$compile(clones)(scope);
}
};
});