I have st-sort directive on all columns in my smart-table. When an user clicks certain column how can I get current sorted column? Is there some trick or do I have to listen to the click event on those column headers?
you can use the directive st-pipe, this function will be called for sorting, paginating or filtering events.
<table st-table="displayedCollection" st-safe-src="rowCollection" st-pipe="customPipe">
$scope.customPipe = function(tableState){
console.log(tableState.sort);
}
I think the technique which will give you the best control will be to create a plugin which will watch changes in the table state and will call a provided callback whenever a change is detected (in your case you will pay particular attention to the "sort" namespace of the table state
module.directive('stSentinel',function (){
return{
require:'^stTable',
scope:{
onChange:'&stSentinel'
},
link:function(scope, element, attr, stTable){
scope.$watch(function(){
return stTable.tableState();
},function (newVal){
scope.onChange(newVal);
},
true)}
}
};
});
which you can use in your markup
<table st-table="foo" st-sentinel="myCtrl.applyChange(tableState)"> ... </table>
Your controller will define the applyChange method to react the changes
Related
I have a directive attached to a dynamically generated <table> element inside a template. The directive manipulates the DOM of that table inside a link function. The problem is that the directive runs before the table is rendered (by evaluating ng-repeat directives) - the table is empty then.
Question
How can I make sure that the directive is ran after the table has been fully rendered?
<table directive-name>
<tr ng-repeat="...">
<td ng-repeat="..."></td>
</tr>
</table>
module.directive("directiveName", function() {
return {
scope: "A",
link: function(scope, element, attributes) {
/* I need to be sure that the table is already fully
rendered when this code runs */
}
};
});
You can't, in a general sense, be ever "fully sure" by just having a directive on the <table> element.
But you can be sure in certain cases. In your case, if the inner content is ng-repeat-ed, then if the array of items over which ngRepeat works is ready, then the actual DOM elements will be ready at the end of the digest cycle. You can capture it after $timeout with 0 delay:
link: function(scope, element){
$timeout(function(){
console.log(element.find("tr").length); // will be > 0
})
}
But, in a general sense, you can't be certain to capture the contents. What if the ngRepeated array is not there yet? Or what if there is an ng-include instead?
<table directive-name ng-include="'templates/tr.html'">
</table>
Or, what if there was a custom directive that worked differently than ngRepeat does?
But if you have full control of the contents, one possible way to know is to include some helper directive as the innermost/last element, and have it contact its parent directiveName when it's linked:
<table directive-name>
<tr ng-repeat="...">
<td ng-repeat="...">
<directive-name-helper ng-if="$last">
</td>
</tr>
</table>
.directive("directiveNameHelper", function(){
return {
require: "?^directiveName",
link: function(scope, element, attrs, ctrl){
if (!ctrl) return;
ctrl.notifyDone();
}
}
})
Try wrapping in a $timeout the code from your link function as it will execute after the DOM is rendered.
$timeout(function () {
//do your stuff here as the DOM has finished rendering already
});
Don't forget to inject $timeout in your directive:
.directive("directiveName", function($timeout) {
There are plenty of alternatives but I think this one is cleaner as the $timeout executes after the rendering engine has finished its job.
A clean way would be to use something like lodash's _.defer method.
You can call it with _.defer(your_func, your_func_arg1, your_func_arg2, ...) inside your link to execute the method, when the current call stack has cleared and everything is ready.
This way, you don't have to estimate a $timeout by yourself.
I'm trying to create wrapper directive for ng-table directive. My wrapper should instantiate ng-table directive on same element as the first directive is applied to and add some custom configuration to ng-table.
I am using following code to create ng-table directive.
angular.module('main')
.directive('mkTable', function($compile) {
return {
'link': function ($scope, element, attributes) {
element.removeAttr('mk-table'); // Must remove attribute because of recursion
element.attr('ng-table', 'tableParams');
$compile(element)($scope);
}
}
})
It does create ng-table (you can see it by pagination) but it doesn't display any data. If you check console output you can see that getData() function is called.
I presume that problem is in compiling child elements (tr, td) and bounding it to new ng-table scope, but I was not able to find the solution.
Demo: http://plnkr.co/1aEAdr2ugl39WG9Ay0vN
I think the problem is ng-repeat on tr element is being compiled couple of times, so I did a little naughty trick :) -insert "fake" to break Angular binding-
<tr fake-ng-repeat="user in $data">
<td data-title="'Name'">{fake{user.name}}</td>
<td data-title="'Age'">{fake{user.age}}</td>
</tr>
Then in the directive remove all "fake(s)" before recompiling:
element.html(element.html().replace(/fake-?/g, ''));
Demo.
Although it's working, I believe it's dirty trick.
After a lot of experimenting I figured it out. Solution is to use compile function insted of link.
angular.module('main')
.directive('mkTable', function($compile) {
return {
compile: function(element, attributes) {
element.removeAttr('mk-table');
element.attr('ng-table', 'tableParams');
var compileFn = $compile(element);
return function($scope, element, attributes) {
compileFn($scope);
}
}
}
})
Updated demo: http://plnkr.co/vL4kg0KVp4GYEDpOlIrm
I am displaying a table and letting the user click on a particular cell in this table. When the user clicks on a particular cell, I highlight it.
Next I am trying to mobilize the arrow keys on the keyboard. i.e
when user presses "right-arrow" key .. the next cell should get highlighted and if the user presses "top-arrow" key.. the cell above the current one should get selected.
I believe you guys get the flow.
This is excel like functionality.
I am almost there but not yet.. any anyone point me in the right direction.
My plnkr here:
http://plnkr.co/edit/Hahh4uyQ130zOS8noC3D
I would create a directive watching a model value object called 'selected'. Something like this:
<tr ng-repeat="element in body">
<td ng-repeat="h in header"
my-cell="selected"
row="{{$parent.$index}}"
col="{{$index}}"
style="width:{{element.width}}px">{{element[h.column]}}
</td>
</tr>
UPDATE
The issue around the above solution is that ng-repeat has isolated scope for each $index entry it is in so changes in $scope.select were not being seen by other cells. To get around this I used $emit and $on events, look at: http://plnkr.co/edit/CS21gUe3arstrgubnpTr?p=preview
angular.module("CustomTable").directive("selectMe", function($rootScope) {
return ({
restrict: "A",
link: link,
require: "^customTable"
});
function link(scope, element, attributes, ctCtrl) {
var selected,
mkEvent = function(r,c) {
scope.$emit('selectMeEvent',{
row: r,
col: c
});
}
element.on('click', function(e) {
mkEvent(attributes.row,attributes.col);
});
$rootScope.$on('selectMeEvent',function(e,sObj) {
(sObj.row === attributes.row &&
sObj.col === attributes.col) ?
element.addClass('highlight-me') :
element.removeClass('highlight-me');
});
}
});
I have a directive applied to a table, the outer div has a controller applied to it:
<div ng-controller="MyController">
<table ng-mydirective>
...
</table>
</div>
The controller loads some data and then uses ng-repeat to create some rows/columns in the table.
This is fine so far.
The directive needs to access columns possibly generated by the data, but the directive runs before the controller.
Is it possible to run a directive after the data is loaded/rendered? Or is the only way to achieve this by using $watch on the dom of the table?
<table ng-show="myDataSource.length>0" ng-mydirective>
...
</table>
or how about applying that within your directive's template itself, i.e hide the table until after the data source is populated. But in both cases the problem is that what if you display a table whose query returned 0 results? That means you directive won't show at all. Either way the best way to handle this would be inside the directive. I.e. hide the table unless your $http call executed successfully.
If your directive isn't using an isolate scope, it will automatically receive access to the data member in the controller scope. If you reference that data member in the template, Angular automatically sets up a $watch on it and you don't have to do anything else; otherwise you will need to add your own $watch. If you're using an isolate scope then it's a little more complicated but a similar pattern holds.
You have two ways for doing this
Easy one is using ng-if for requested data field. For example
<table ng-mydirective ng-if="nameOfTheDataSource !== undefined">
...
</table>
Also you can send an attribute to the directive and watch it until data arrives. This is more complicated but seemless to the template level:
Template
<table ng-mydirective ng-model="nameOfTheDataSource">
...
</table>
Directive
angular.module(....)
.directive('ng-mydirective', function() {
return{
scope: {
....
ngModel: '='
}
link: function(scope, element, attrs) {
var firstload = scope.$watch('ngModel', function(value) {
if (scope.ngModel !== undefined) {
// Do what is needed
}
// Deregister itself
firstload();
}
}
}
}
I have a <table> where each row has a couple of input type="text". I want to validate if an input is empty and if so, then add a CSS class to this input field which will display an error. All I got in the $scope is the $$hashKey, I know this is an unique value to identify an element of a ng-repeat list.
How could I get the DOM object given this $$hashKey?. I was digging using the Developer Tools but I didn't find it.
Instead of trying to manipulate the DOM (ie find and element and add/remove a class) from your controller (or service), you should be doing it from a directive.
Write a directive that will do the validation for you:
.directive('validateField', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel){
scope.$watch(attrs.ngModel, function(newVal, oldVal){
var isValid = false;
// do some validation checking on newVal here
ngModel.$setValidity('tableInput', isValid);
});
}
};
});
As noted in the docs here, the $setValidity function will automatically add a class to the element for you, based on whatever key you provide. In this case, we provided a key of 'tableInput' so it will add a class of ng-invalid-table-input when the model is invalid, and a class of ng-valid-table-input when the model is valid.
So in your CSS, all you now have to do is create a rule with some special styles:
input.ng-invalid-table-input{
/* special styles go here */
}
input.ng-valid-table-input{
/* special styles go here */
}
And then you would use this in your view as such:
<table>
<tr ng-repeat="things in listOfThings">
<td ng-repeat="thing in things">
<input type="text" ng-model="someValue" validate-field />
</td>
</tr>
</table>
And then any input in your table will be dynamically (and automatically) validated and styled. Does that make sense? You'd have to modify the above example, but hopefully it points you in the right direction.