Custom table header directives not behaving as expected - angularjs

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?

Related

Angular directive for table as parent and theader and tbody as child

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.

Angular is rendering directive outside of table

We are using Angular 1.3.15
I have an element directive like
(function () {
var controller = function ($scope, $timeout) {
this.$timeout = $timeout;
$scope.$watch("vm.spinning", this.throttle.bind(this));
};
controller.prototype = {
throttle: function (spinning, oldValue) {
if (spinning === oldValue) return;
this.$timeout.cancel(this.timeout);
if (spinning) {
this.timeout = this.$timeout(this.setSpinning.bind(this), 100);
} else {
this.setSpinning();
}
},
setSpinning: function() {
this.spinningThrottled = this.spinning;
}
};
myapp.controller("SpinnerController", ["$scope", "$timeout", controller]);
myapp.directive("spinner", function () {
return {
restrict: "E",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/Spinner.html"
};
});
myapp.directive("spinnerGrid", function () {
return {
restrict: "E",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/SpinnerGrid.html"
};
});
})();
The spinner grid template
<tr data-ng-if="vm.spinningThrottled">
<td colspan="100">
<i class="fa fa-spinner fa-spin"></i> Please wait...
</td>
</tr>
Usage
<table>
<thead>...</thead>
<tbody>
<spinner-grid spinning="searching"></spinner-grid>
<tr ng-repeat="...">..</tr>
</tbody>
</table>
For somereason the content of the directive is moved outside of the table?
edit: The question might be a duplicate, but K.Toress answer is not since it utilizes EA restriction
I think its a normal behavior of the table, if there is a div or any other tag (excluding <tr>) inside a table without inside of a <td> it will remove that and place it on the table.
In your case when html rendering time (which is before the angular executes its scripts) html detects there is a unappropriated tag inside the table which is not inside a td it will remove and place on top of the table, Then when the angular execution time the directive element is not inside the table instead it will top of the table. that's why its render on top of the table.
Please check you can do something like this,
<tbody>
<tr data-ng-if="vm.spinningThrottled"><spinner-grid spinning="searching"></spinner-grid></tr>
<tr ng-repeat="...">..</tr>
</tbody>
SpinnerGrid.html
<td colspan="100">
<i class="fa fa-spinner fa-spin"></i> Please wait...
</td>
or check this one
directive as a attribute of the <tr>
<tbody>
<tr spinner-grid spinning="searching"></tr>
<tr ng-repeat="...">..</tr>
</tbody>
change the directive definition to support the directive as a attribute. restrict: "EA".
myapp.directive("spinnerGrid", function () {
return {
restrict: "EA",
scope: {
spinning: "=",
},
replace: true,
controller: "SpinnerController",
controllerAs: 'vm',
bindToController: true,
templateUrl: "Views_App/Components/SpinnerGrid.html"
};
});

generate dynamic template with directive parameters

I create a directive to dynamically generate tables... the directive should contact the data service an then show some rows, the rows head is id, name, regions.
<planets data="{ view: 'planets', params: ['id','name','regions'] }"></planets>
test.directive('planets', function () {
return {
restrict: 'E',
scope: {
'data' : '='
},
template: '<div>{{data.params}}!</div>'
}
});
i need a advice how to generate the
<table>
<tr>
<th>id</th>
<th>name</th>
<th>regions</th>
</tr>
</table>
dynamic? and how to pass the data out of the service to the
iam not shur to use template or link?
thanks!
EDIT:
test.controller('projects', function ($scope, DataService, $resource) {
DataService.query(function(response) {
$scope.projects = response;
});
});
test.directive('planets', function () {
return {
restrict: 'E',
scope: {
'data' : '='
},
templateUrl: 'templates/table.html'
};
});
It depends how dynamic you need to be. However, to generate your sample, you only need an ng-repeat inside the template:
template: '<table><tr><th ng-repeat="param in data.params">{{param}}</th></tr></table>'

directive not working inside <tr> that is ng-repeat bound

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

Generating a Table from a Directive

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 });
}
}
};
});

Resources