My table looks like this, with more than one tbody:
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed()">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed($event)">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
In my collapseDetail() function I toggle a class collapsed on the tbody.
And so I would like to have the detail row hidden only if the parent tbody hasClass('collapsed`).
Is that legal? What I have isn't working:
$scope.collapseDetail = function (e) {
var targ = angular.element( e.currentTarget );
$scope.$apply( function(targ){
targ.parent().toggleClass('collapsed');
});
}
$scope.groupIsCollapsed = function (e) {
if (e == undefined) return false;
var targ = angular.element( e.currentTarget );
return targ.parent().hasClass('collapsed');
}
To give a neater solution had to work on creating a directive for you, as over here you require an isolated scope on individual tbody, so that they can show/hide the detail.
Using Angular Directive has many advantages like
having isolated scope (as mentioned above)
Reducing html markup
NO DOM manipulation in controller ( a strict no taking angular perspective into account, all DOM manipulations to be done only in
directive making it more maintainable)
HTML Code:
<table>
<tbody rendered key="assasa" val="tgtrtghrt"></tbody>
<tbody rendered key="fsfgsd" val="teeger"></tbody>
</table>
Controller Code for this question:
angular.module('t', [])
//You can see that nothing is in the controller now
.controller('test', function ($scope) {});
Directive Code:
.directive('rendered', function ($compile) {
return {
restrict: 'EA',
replace: false,
scope: {
key: '#',
val: '#'
},
link: function (scope, element, attrs) {
var ele = "<tr ng-init='collapseTbody = false;' class='group-header' ng-click='collapseTbody=!collapseTbody'><td>{{key}}</td></tr><tr class='detail' ng-hide='collapseTbody'><td>{{val}}</td></tr>";
scope.$watch('key', function () {
element.html(ele);
$compile(element.contents())(scope);
});
},
}
});
Working Fiddle
More on Angular Directives
Related
Let say we have the following directive:
app.directive('testList', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//do stuff
}
}
});
I would like to call this directive from multiple element in html
<button test-list-add></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list-click>list.item</td>
</tr>
</table>
is it possible to get into the link function for each attribute starting with test-list (test-list-add, test-list, test-list-click)?
Thank you
You want to pass attributes to your directive and doing some action, depending on the attribute:
app.directive('testList', function () {
return {
restrict: 'A',
scope: {
'add': '#', // Text
'click': '&' // function
},
link: function (scope, element, attrs) {
// scope.add - your Text you passed
// scope.click(); - calls your function you passed
}
}
});
<button test-list add="test"></button>
<table test-list="listObject">
<tr ng-repeat="list in testList">
<td test-list click="myFunc()">list.item</td>
</tr>
</table>
You can't repeat a directive (what you obvis trying to do).
If you want to make multiple suffix for a directive, you have to declare them one by one:
app.directive('testListAdd', function () {...});
app.directive('testListclick', function () {...});
<test-list-add></test-list-add>
<test-listclick></test-listclick>
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 a nested parent - child directives, the purpose if to draw a table..
The child directive is not getting called when called from within the parent (tag).
It works fine when tested independently.. I seems to have followed all the rules/syntax, require is in place.. I don't see the console logs statements I have in the child directive, also there are no errors in the log.
Directives -
var app = angular.module ('gridapp', []);
app.directive ('gridControl', function(tableDataFactory){
return {
restrict: 'E',
scope : {},
controller : function($scope){
$scope.columns = [];
$scope.column = [];
$scope.addColumnProperties = function(columnProperties) {
console.log("In addColumnProperties "+ columnProperties);
$scope.column = columnProperties;
$scope.columns.push($scope.column);
$scope.column = [];
}
},
link : function (scope, element, attrs) {
console.log(attrs.source);
tableDataFactory
.get(
'http://localhost:8000/AngularTableWidget/json/accounts.json')
.then(
function(data) {
scope.items = data.items;
console.log("In grid directive" + scope.items);
});
},
templateUrl : '../template/gridtemplate.html'
};
});
//child directive...
app.directive('tableColumn', function(){
return{
restrict : 'E',
scope : {},
require : '^gridControl',
link : function(scope, element, attrs, gridCtrl) {
console.log("In tablecolumn "+ attrs.source);
var colProp = [];
console.log("In tablecolumn "+ attrs.caption);
colProp.push(attrs.caption);
colProp.push(attrs.source);
gridCtrl.addColumnProperties(colProp);
}
};
});
HTML -
<div>
<grid-control source="gridtable.json">
<table-column caption="Name" source="name"> </table-column>
<table-column caption="Account" source="account"> </table-column>
</grid-control>
template -
<div>
<table>
<tbody ng-repeat="row in items track by $index">
<tr ng-repeat ="col in columns">
<td>
Test
</td>
</tr>
</tbody>
</table>
</div>
On grid-control directive, add transclude = true. Inside the grid-control template, add ng-transclude where ever the child directive going to be inserted. Without using transclude, the system will ignore the child directive.
I hope this helps.
Austin
I have a table where the rows are repeated via ng-repeat.
I am trying to create a template that generates columns <td> for each row <tr>
app.directive("customtd", function(){
return {
restrict: 'E',
template: "<td>{{position.Name}}</td><td>{{position.Code}}</td>",
replace: true,
scope: {
position: '='
}
}
});
<table>
<tr ng-repeat="p in positions">
<customtd position="p"></customtd>
</tr>
</table>
The issue is my custom td template is not rendered at all.
Here I intend to replace <customtd> with n number of <td>s - which will be decided based on number of properties on my data object, but at the moment I am just trying to get a simple directive working that will output two columns.
MYPLUNKER : shows an instance of this issue and the directive code.
As pointed out in comments the template of a directive should have single root element. So I would suggest you to move the tr element to the template of the directive, like this: http://plnkr.co/edit/YjLEDSGVipuKTqC2i4Ng?p=preview
As Pavlo wrote, you can move the tr element to the template for the directive. Another option is to use a td element and directive that replaces your td with the template that you want to use.
<table>
<tr ng-repeat="p in positions">
<td replace-me template="mytemplate.html" position="p"></td>
</tr>
</table>
Directive replaceMe
.directive("replaceMe", ["$compile", '$http', '$templateCache', function ($compile, $http, $templateCache) {
return {
restrict: 'A',
scope: {
position: "="
},
link: function (scope, element, attrs) {
function getTemplate(template) {
$http.get(template, {cache: $templateCache}).success(function (templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
scope.$watch(attrs.template, function () {
if (attrs.template) {
getTemplate(attrs.template);
}
});
}
}
}]);
mytemplate.html
<td>{{position.Name}}</td>
<td>{{position.Code}}</td>
<td another-my-directive></td>
plunker
I am attempting to set up a simple highlighting mechanism in a data table:
<table>
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="file in files" highlightable> <!-- Multiple instances of highlightable -->
<td>{{file.name}}</td>
<td>{{file.owner}}</td>
</tr>
</tbody>
</table>
And I have a directive that handles the highlighting. You click on the <tr> and it will attempt to first un-highlight all other <tr>'s and then highlight the one clicked.
directive('highlightable', function() {
return {
require: 'highlightable',
controller: function($scope, $element) {
this.unhighlight = function(file) {
$element[0].style.backgroundColor = 'transparent';
};
},
link: function(scope, element, attrs, ctrl) {
var color = '#DEE4FC';
element.bind('click', function(e) {
ctrl.unhighlight(scope.file);
element[0].style.backgroundColor = color;
});
}
};
});
Problem is though that it doesn't seem to be accessing every instance of the directives' controllers. When requiring another directive, how can I ensure that I am requiring every instance of that directive in an ng-repeat scenario, and manipulating each instance via each repeated directive's controller methods?
http://jsfiddle.net/txBJ6/1/
Given what you're trying to achieve, I would do this. Basically use scope notification to communicate among the elements.
directive('highlightable', function() {
return {
link: function(scope, element, attrs) {
var color = '#DEE4FC';
scope.$on("reset", function() {
element[0].style.backgroundColor = 'transparent';
});
element.bind('click', function(e) {
scope.$parent.$broadcast("reset");
element[0].style.backgroundColor = color;
});
}
};
});
Demo: link
Updated
sze correctly points out that my solution is only suitable if you need exactly one list (which appears to be the case from your question). However, it's very easy to accommodate multiple lists while still maintaining the code conciseness.
<tr ng-repeat="file in files" highlightable="list1">
<td>{{file.name}}</td>
<td>{{file.owner}}</td>
</tr>
...
<tr ng-repeat="file in files" highlightable="list2">
<td>{{file.name}}</td>
<td>{{file.owner}}</td>
</tr>
...
directive('highlightable', function () {
return {
link: function (scope, element, attrs) {
var color = '#DEE4FC';
scope.$on(attrs.highlightable, function () {
element[0].style.backgroundColor = 'transparent';
});
element.bind('click', function (e) {
scope.$parent.$broadcast(attrs.highlightable);
element[0].style.backgroundColor = color;
});
}
};
});
Demo: link
The problem of [#buu Nyuyen]'s apporach is he missed some logic to deal with the scope. If you have another list modified by the directive highlightable, the second list will be impacted if the event is broadcasted from the first list and make the directive not reusable. You can see this issue here. Issue
However, you can easily achieve it by looping through other elements. The trick is you can get all repeated elements with element[0].parentElement.children.
directive('highlightable', function () {
return {
require: 'highlightable',
controller: function ($scope, $element) {
this.unhighlight = function (element) {
element.style.backgroundColor = 'transparent';
};
},
link: function (scope, element, attrs, ctrl) {
var color = '#DEE4FC';
element.bind('click', function (e) {
angular.forEach(element[0].parentElement.children, function (element) {
ctrl.unhighlight(element);
})
element[0].style.backgroundColor = color;
});
}
};
});
Working Demo
The easiest solution is the one proposed by Buu Nguyen, I offer a more difficult one.
One of the typical ways to resolve this is to have a parent directive which will have knowledge of every children. So you can register there every row and when you click on one, the parent will unhighlight the other children.
Is more complicated but more configurable. You can create an attribute called... multi to be able to highlight more than one. Or even be able to select a max of x rows... What you want.
If you're curious, I leave you the demo here: http://jsfiddle.net/5NSW3/1/