custom directive in cell template in ui-grid angularjs - angularjs

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

Related

Angularjs ng-repeat: check boxes are colored black in IE10

I have noticed that in IE10, a checkbox's background colour is filled with black then transitions back to white. When I sort using ng-repeat, IE10 does not seem to fire the event that turns the checkbox background colour back into white/normal colour.
Here is the HTML that does the ng-repeat for checkboxes, and filter's them according to status and name:
<div ng-repeat="item in (items | orderBy:['!status','itemName'])">
<input type="checkbox" ng-click="itemClickEvent($event, item.itemId)" ng-model="item.status"/>
</div>
Controller method that has been bind to the click event:
$scope.itemClickEvent = function ($event, itemId) {
var checkbox = $event.target;
var checkboxState = checkbox.checked;
if (checkboxState) {
$scope.items = itemsFactory.doSomething(itemId);
} else {
$scope.items = itemsFactory.doAnotherthing(itemId);
}
};
IE10 version: IE10.0.9200.17229
Angularjs version: AngularJS v1.2.23
Screenshot of the issue:
Can someone help me?
I would start off by using ng-change instead here is a plunker that does something similar. Unfortunately I have IE 8 on the computer I am currently using and can't confirm :-(.
I would really avoid using ng-checkbox I think this is the one I used to base mine.
Here is what I came up with
<div ng-repeat="box in checks">
<input type="checkbox" ng-model="box.checked" ng-change="check(box)">{{box.name}}</input>
</div>
JS
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.checks = [
{
name: "Test 1",
checked: false
},
{
name: "Test 2",
checked: true
},
{
name: "Test 3",
checked: false
}
]
$scope.check = function(box){
if (box.checked) {
alert(box.name + "was checked!");
// $scope.items = itemsFactory.doSomething(itemId);
} else {
alert(box.name + "was unchecked!");
// $scope.items = itemsFactory.doAnotherthing(itemId);
}
}
});
Basically my guess is it is related to $event
I was able to solve this issue with a small workaround. I created an AngularJS directive which updates css and performs the intended task.
Directive:
Angular.directive('directives.checkbox', [])
.directive('checkBox', [function () {
return {
restrict: 'E',
require: 'ngModel',
replace: true,
template: '<span class="icon-xxs iconUnCheck"></span>',
scope:{
checkboxCallbackFunction: '=',
linkId: '=',
checkStatus: '='
},
link: function (scope, elem, attrs, ctrl) {
var isCheckboxChecked = false;
function toggleCheckBoxIcon() {
isCheckboxChecked = !isCheckboxChecked;
scope.checkStatus = isCheckboxChecked;
if (isCheckboxChecked) {
elem.removeClass('icon-xxs iconUnCheck');
elem.addClass('icon-xxs iconCheck');
} else {
elem.removeClass('icon-xxs iconCheck');
elem.addClass('icon-xxs iconUnCheck');
}
}
elem.bind('click', function () {
scope.$apply(function () {
toggleCheckBoxIcon();
scope.checkboxCallbackFunction(scope.linkId, isCheckboxChecked);
});
});
}
}
}]);
but with this solution I have encountered a performance issue. When a user selects more than 5 checkboxes, it took some time to check new checkboxes. Somehow this solved the issue with checkboxes getting colored.

Angularjs Directive not loading PopOver content

I am trying to develop a FaceBook like notification (like when friend requests are received there is an icon that glows with number of notifications on the top right corner).
For this i wrote a popover directive.
app.directive('popOver', function ($compile) {
var itemsTemplate = "<div ng-repeat='item in items'>{{item}} <br/><hr/></div> ";
var getTemplate = function (contentType) {
var template = '';
switch (contentType) {
case 'items':
template = itemsTemplate;
break;
}
return template;
}
return {
restrict: "A",
transclude: true,
template: "<span ng-transclude></span>",
link: function (scope, element, attrs) {
var popOverContent = "<div></div>";
if (scope.items) {
var html = getTemplate("items");
popOverContent = $compile(html)(scope);
}
var options = {
content: popOverContent,
placement: "bottom",
html: true,
title: scope.title
};
$(element).popover(options);
},
scope: {
items: '=',
title: '#'
}
};
});
The items are populated in the Controller, and there i am using $timeout to fetch new data from database and fill scope.Items
In the UI i have a button which shows number of new notifications and on click of it i want to show a popover with items. The problem is when is click the button i the popover is not loading the new items.
<button pop-over items="items" class="navbar-btn btn-info round-button" title="Notifications:" > {{newAlertCount}} </button>
Directives have their own scope, so I'm supposing that when you change $scope.items in your controller you're talking about a different scope; I think that what you want is to look directly at the original $scope.items object, so I would add this:
scope : {
items : '=items'
},
to your directive.

Using Angular template for Kendo UI Grid detail template

I am using the Kendo UI package for Angular JS. I would like to use an Angular template for the row detail, very similar to what is done here:
Kendo Grid Detail Template
Essentially I would like to fetch some data when details are expanded, and pass an angular model to the template. Is this possible?
What I have done (so far) with this same need is use the changed event on the Grid to populate a 'SelectedRow' object on the $scope in my controller. For the DetailTemplate, I have a div that contains a directive that loads the template either from the $templateCache or using $http and compiles it and links it to the $scope. One of the problems with templates is compiling and linking them to the $scope and the timing of when that takes place. (My problem was even worse as I needed a different detail template for each row)
$scope.vm.options.productGridOptions = {
dataSource: new kendo.data.DataSource({
data: $scope.vm.solution.Products,
pageSize: 10
}),
change: $scope.vm.events.productSelected,
columns: $scope.vm.columns.productColumns,
detailTemplate: '<div data-template-detail type="#= EntityTemplateSK #"></div>',
filterable: false,
groupable: false,
pageable: true,
reorderable: true,
resizable: true,
selectable: 'single',
sortable: true
};
myApp.directive('templateDetail', ['$compile', '$http', '$templateCache',
function ($compile, $http, $templateCache) {
var detailTemplateNumbers = ['21', '22', '23', '26', '45', '69'];
var getTemplate = function (templateNumber) {
var baseUrl = '/App/Product/Views/',
templateName = 'productdetail.html',
templateUrl = baseUrl + templateName;
if (detailTemplateNumbers.filter(function (element) { return element === templateNumber; })[0]) {
templateName = 'productTemplate' + templateNumber + '.html';
templateUrl = baseUrl + templateName;
}
return $http.get(templateUrl, { cache: $templateCache });
};
var linker = function ($scope, element, attrs) {
var loader = getTemplate(attrs.type.toString());
if (loader) {
loader.success(function (html) {
element.html(html);
}).then(function () {
element.replaceWith($compile(element.html())($scope.$parent));
});
}
};
return {
restrict: 'A',
scope: {
type: '='
},
link: linker
};
}]);

AngularJS Directive for table header

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

View only updates on the next $digest, when modifying it inside of a directive's controller

I am trying to write a directive for something that will behave like an advanced combo-box, I've started with the skeleton and encountered some issues.
Here is the code for the directives:
/* Controller */
function MyCtrl($scope) {
$scope.bigListSelectedItem = 0;
$scope.bigComboItems = [
{value: 'item1 value', label: 'Item 1 label'},
{value: 'item2 value', label: 'Item 2 label'},
{value: 'item3 value', label: 'Item 3 label'}
];
$scope.addBigComboItem = function () {
$scope.bigComboItems.push({value: 'new item', label: 'Item 1 label'});
};
$scope.removeBigComboItem = function () {
console.log('removing');
$scope.bigComboItems.splice($scope.bigComboItems.length - 1, 1);
};
}
MyCtrl.$inject = ['$scope'];
/* Services*/
var services = angular.module('myApp', [])
.directive('bigCombo', function () {
return {
restrict: 'C',
transclude: true,
scope: true,
controller: function ($scope, $element, $attrs) {
$scope.items = [];
this.addItem = function (item) {
$scope.items.push(item);
};
this.removeItem = function (item) {
for (var i = 0; i < $scope.items.length; i++) {
if ($scope.items[i] === item) {
$scope.items.splice(i, 1);
break;
}
}
};
$scope.selectItem = function(item) {
$scope.selectedItem = item;
};
},
template:
'<div>' +
'<div>Selected Item {{selectedItem.value}}</div>' +
'<ul class="">' +
'<li ng-repeat="item in items">' +
'<a ng-click="selectItem(item)">{{item.value}}</a>' +
'</li>' +
'</ul>' +
'<div ng-transclude></div>' +
'</div>',
replace: true
};
}).
directive('bigComboItem', function() {
return {
require: '^bigCombo',
restrict: 'C',
scope: {
value: '#'
},
link: function(scope, element, attrs, controller) {
controller.addItem(scope);
scope.$on('$destroy', function () {
controller.removeItem(scope);
});
}
};
});
You can see it running here: http://jsfiddle.net/HP5tQ/
As you can see, the outer 'bigCombo' directive waits for 'bigComboItem' directives to call its 'addItem' function. That works fine.
But, if I remove one of the items, the View won't update until (at least that's what I suspect) the next $digest occurs.
In the example above, clicking 'Remove Item' will remove the last item from the array, which will cause ng-repeat to remove it's 'bigComboItem' directive from the DOM, which will emit a '$destory' event, which will call 'bigCombo's 'removeItem' function. 'removeItem' will then remove it, but the view doesn't update unless I add/remove another item from the array, or force a $digest on the scope.
Any ideas what am I doing wrong here?
Just use $timeout on the listener (inject it in the directive definition:
scope.$on('$destroy', function (ev) {
$timeout(function() { controller.removeItem(scope); });
});
This will ensure that the removeItem is called inside an $apply block. The good thing of this approach is that
1. If you are already in an $apply block, nothing different happens
2. If there is an $apply scheduled, then the function is put at the end of the already scheduled block
3. Otherwise, a $digest will be scheduled for ASAP, with the function inside.
So it's a win-win :)

Resources