I am trying to write a directive to deal with changing an icon class for table headers. What I would like is (what I believe anyway) the standard way of dealing with sorting by table headers. The directive would add a link element and upon a user's click sort by desc and change the icon to desc, upon click again sort by asc and once again the the icon. Here is what I have so far, but I am now at a loss for how to deal with the icon class as well as resetting other elements on the same table but outside of the directive's scope. Any help would be great!
angular.directive("tableHeaders", function() {
return {
restrict: 'E',
scope: {},
template:'<i class="glyphicon glyphicon-filter"></i>',
link: function(scope, element, attrs) {
attrs.class = 'glyphicon glyphicon-sort-by-alphabet-alt';
}
}
});
Here is what I have for the html side:
<th>First Name<a ng-click="newOrderBy('_firstName')"><table-headers></table-headers></a></th>
<th>Last Name<a ng-click="newOrderBy('_lastName')"><table-headers></table-headers></a></th>
<tr ng-repeat="item in items | orderBy:orderBy:reverse>
<td>{{item._firstName}}</td>
<td>{{item._lastName}}</td>
</tr>
The order by is currently handled in the controller:
$scope.newOrderBy = function(order) {
$scope.orderBy = order;
$scope.reverse = !$scope.reverse;
};
What you need to do is for each element using your directive providing both an order and the current order (the one from your controller).
BTW I think your directive will be a better match as an attribute and not a tag. You can check the following code :
angular.module('myApp', []).directive("sort", function() {
return {
restrict: 'A',
transclude: true,
template :
'<a ng-click="onClick()">'+
'<span ng-transclude></span>'+
'<i class="glyphicon" ng-class="{\'glyphicon-sort-by-alphabet\' : order === by && !reverse, \'glyphicon-sort-by-alphabet-alt\' : order===by && reverse}"></i>'+
'</a>',
scope: {
order: '=',
by: '=',
reverse : '='
},
link: function(scope, element, attrs) {
scope.onClick = function () {
if( scope.order === scope.by ) {
scope.reverse = !scope.reverse
} else {
scope.by = scope.order ;
scope.reverse = false;
}
}
}
}
});
And the plunker that goes with it : http://plnkr.co/edit/P4cAm2AUGG36nejSjOpY?p=preview
The directive is used as such :
<thead>
<tr>
<th sort by="order" reverse="reverse" order="'name'">Name</th>
<th>Phone</th>
<th sort by="order" reverse="reverse" order="'age'">Age</th>
</tr>
</thead>
Unless you are intent on writing your own directive, you might consider looking at what is available.
ngmodules.org shows some directives that are already set up for table headers.
Here are a couple options with some sample code to give you a feel for both. They both look to be developed and very customizable.
ngTable
angular.module('main', ['ngTable'])
.controller('DemoCtrl', function($scope, $filter, ngTableParams) {
var data = [{name: "Moroni", age: 50}, ... ]
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
name: 'asc' // initial sorting
}
}, {
total: data.length, // length of data
getData: function($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ?
$filter('orderBy')(data, params.orderBy()) :
data;
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
});
UI Grid
angular.module('app', ['ngAnimate', 'ui.grid'])
.controller('MainCtrl', function ($scope) {
$scope.gridOptions1 = {
enableSorting: true,
columnDefs: [
{ field: 'name' },
{ field: 'gender' },
{ field: 'company', enableSorting: false }
],
onRegisterApi: function (gridApi) {
$scope.grid1Api = gridApi;
}
};
});
Related
I've wrote a directive that should emulate the AngularJS-DataTable.
In this case I need to execute some function on the last <td> since they're buttons. I don't want to pass the functions to the directive to keep the directive as independet as possible.
So in this case, when I specify "renderable" on a data, and a "render" function, if it got a ng-click I need that function, defined in the controller, to be executed, but when i Click on the buttons, nothing happens.
This is the data I've in my Controller, with the function "print()" that I need to call from the directive
$scope.print = function(){
console.log("It worked!");
};
$scope.tableData = {
data: data.response,
columns: [{
title:"",
data: "priority",
renderable: true,
render: function(data){
return "<span class='btn btn-xs fa fa-fw fa-angle-down' ng-click='lowerPriority()'></span>";
}
},
{
title: "Nome Servizio",
data: "title"
},
{
title: "Descrizione",
data: "description",
renderable: true,
render: function(data, row){
var html = "<div ng-click='print()'>"+row.sum+"</div>";
return html;
}
},
],
}
In my page I'm calling
<smart-table data="tableData" ></smart-table>
And then in my directive template
<tr ng-repeat="row in data.data | filter: search.value" repeat-done>
<td ng-repeat="cell in data.columns">
<span ng-if="cell.renderable" ng-bind-html="trustHtml(cell.render(row[cell.data], row))"></span>
<span ng-if="!cell.renderable">{{row[cell.data]}}</span>
</td>
</tr>
Lastly, this is my directive
var smartTable = angular.module('smartTable', ['ngSanitize']);
smartTable.directive('smartTable',['$compile', '$sce', '$templateRequest', function($compile, $sce, $templateRequest) {
return {
restrict: 'AE',
replace: true,
templateUrl: '/public/components/directives/smartTable.tpl.html',
link: function(scope, elem, attrs, parentScope) {
scope.trustHtml = function(data){
var template = angular.element(data);
elem.append(template);
// $compile(angular.element(data))(scope);
return $sce.trustAsHtml(data);
};
$templateRequest('/public/components/directives/smartTable.tpl.html').then(function(html){
console.log(scope);
scope.$watch(attrs.data, function(elemasd) {
var template = angular.element(html);
elem.append(template);
elem.html(html);
scope.data = scope[attrs.data];
$compile(elem)(scope);
});
});
}
};
}]);
Ater your template slightly to use $last, if you're looking for the last td:
<td ng-if="$last" ng-click="vm.print()"></td>
Do something like this bind your function
smartTable.directive('smartTable',['$compile', '$sce', '$templateRequest', function($compile, $sce, $templateRequest) {
return {
restrict: 'AE',
replace: true,
scope: {data: '=',
print: '&'},
templateUrl: '/public/components/directives/smartTable.tpl.html',
link: function(scope, elem, attrs, parentScope) {
scope.trustHtml = function(data){
var template = angular.element(data);
elem.append(template);
// $compile(angular.element(data))(scope);
return $sce.trustAsHtml(data);
};
$templateRequest('/public/components/directives/smartTable.tpl.html').then(function(html){
console.log(scope);
scope.$watch(attrs.data, function(elemasd) {
var template = angular.element(html);
elem.append(template);
elem.html(html);
scope.data = scope[attrs.data];
$compile(elem)(scope);
});
});
}
};
}]);
HTML
<smart-table data="tableData" print="print"></smart-table>
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 define a ui-grid to display data, and I define a cell template to set the column style. At the same time I also create a directive, here I just add it to the cell template. But the link function execution times is less than expectation.
Here's the whole thing on plunker: LINK
var app = angular.module("app", ['ui.grid']);
app.controller("dataCtrl", function ($scope, $element, $attrs) {
var vm = this;
vm.gridOptions = {
data: "ctrl.dataList",
columnDefs: [
{
name: "ID",
displayName: "User ID",
width: 200
},
{
name: "Name", displayName: "User Name",
cellTemplate: "<div class=\"ui-grid-cell-contents\" pop-tip><span style=\"margin-left:5px\">{{row.entity[\"Name\"]}}</span></div>"
}
],
enableRowSelection: true,
enableRowHeaderSelection: false,
multiSelect: false,
noUnselect: true,
};
vm.dataList = [];
vm.loadData = function () {
for (var i = 1; i <= 100; i++) {
var user = {};
user.ID = i;
user.Name = 'user ' + i;
vm.dataList.push(user);
}
}
vm.loadData();
});
app.directive("popTip", function ($compile) {
return {
restrict: 'A',
scope: false,
link: function ($scope, $element, $attrs) {
console.log($scope.row.entity.Name);
}
};
})
You can get the browser log to view the time of link execution.
The result is that when the data amount is large that appears an vertical scroll, when we drag scroll bar the custom directive will not execute link function anymore.
It's quite likely that there is some optimization built into ui-grid, whereby they reuse already-linked row elements, rather than link new ones.
You could inspect that (and, it should get you what you need) by $watch-ing the changes in the scope:
link: function ($scope, $element, $attrs) {
//console.log($scope.row.entity.Name);
$scope.$watch("row.entity.Name", function(v){
console.log(v);
});
}
This will display all the rows when scrolling.
Demo
I need to dynamically choose the controller at ngRepeat based on component name as a string. Plunker: http://plnkr.co/edit/osLLf0c4eNCpNokyyXCI?p=preview
As you can see I found a solution, but I don't think this is an Angular way, because I've created a stack of controllers and assign them to "controllers" variable outside the angular scope.
Here is HTML:
<table border="1" class="item" data-ng-repeat="component in components">
<tr>
<td>
<div component="component"></div>
</td>
</tr>
</table>
Javascript
var app = angular.module("sortableApp", []);
app.controller("appCtrl", ["$scope", function($scope) {
$scope.components = [{
name: 'simpleText',
tpl: 'simple-text.html',
content: 'Simple text'
}, {
name: 'heading',
tpl: 'heading.html',
content: 'Heading'
}];
}]);
app.directive("component", ["$templateCache", "$compile", function($templateCache, $compile) {
return {
restrict: "A",
scope: {
component: "="
},
controller: function($scope) {
if(controllers[$scope.component.name]) {
return controllers[$scope.component.name]($scope);
}
},
link: function(scope, element, attrs) {},
template: '<div ng-include="component.tpl"></div>'
}
}]);
var controllers = {
simpleText: function($scope) {
$scope.add = function() {
alert('add text');
}
},
heading: function($scope) {
$scope.add = function() {
alert('add heading');
}
}
}
How to achieve my goal in angular way? thanks for your answer!
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/