is there a way to get the DOM object given a $$hashKey? - angularjs

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.

Related

How to get current sorted column in smart-table

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

Add elements to datatable

I'm using angular-datatables and wondering how could I add various elements such as buttons, text inputs etc to already rendered and set datatable. I think it may be possible through new angular directive and link function, e.g.:
link = (scope, element, attrs) =>
$('#table_wrapper div.top').append(some nice custom element)
this directive is taking place in the parent div:
<div my-custom-directive>
<table id="table" datatable="ng" ... etc
The problem is datatable building elements such as 'table_wrapper' and others are not ready yet when I try to add my new custom elements to them.
One way as I see to handle that problem is:
angular-datatables has a directive called dt-instance:
< table id="table" datatable="ng" dt-instance="yourCallback",
where callback is a function wherein among other useful things you can set a control flag in the scope, so in my controller i have:
yourCallback: (dtInstance) =>
$scope.flag= 1
next in my-custom-directive I watch this flag:
link = (scope, element, attrs) =>
scope.$watch('flag', (newval, oldval) =>
if (newval)
$('#table_wrapper div.top').append(some nice custom element)

AngularJS directive runs before element is fully loaded

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.

jqxgrid and angular form

I'm using jqxgrid inside angular form. When you change something in grid, an angular form does not become dirty. I decided to bind to grid cellvaluechaned event in
which I call $setDirty() for my angular form. It works. But I do not want in each place where form is used to call $setDirty(). Could you please tell me how can I find the
closest form in DOM tree and make it dirty? I want to write this code one time and want that it works for each form where there is a grid inside these forms. Thanks guys.
You can create a directive that will loop over all the necessary html elements under it and add the relevant events.
Here's a template to get you started:
angular.module("app", []).directive("changeform", function() {
var directive = {
restrict: 'A',
require: 'form',
link: function(scope, element, attrs, ctrl) {
// here you would use element.find() to get the elements
// and then use .on() on the elements with the event
// and then use the ctrl (which is of type FormController)
// to set $dirty [https://docs.angularjs.org/api/ng/type/form.FormController]
}
}
})
and then the HTML should look like:
<form name="myForm" changeform> ... </form>
https://docs.angularjs.org/api/ng/type/form.FormController

Create Angular directive that keeps original element's attributes

I've seen other SO questions that kind of ask this, but they usually have some unique complication thrown in, and the answer seems surprisingly complicated in proportion to the simplicity of the desired result. I'd like to ask the simplest possible version of this question.
Let's say I have a directive that looks like this:
<my-input type="text" placeholder="foo">
I want the output to look like this:
<input type="text" placeholder="foo" class="bar">
All I want to do is output a new element and add some new attributes, but keep the original element's attributes. How can this be achieved?
Use an attribute for the directive rather than an element:
<input type="text" placeholder="foo" my-directive>
And make myDirective add the desired new attributes to its element.
You directive template function will give access to your all attributes defined on element. You can easily add those attribute inside html element.
Directive
app.directive('myInput', function(){
return {
restrict: 'E',
template: function(element, attrs){
var template = '<input type="'+ attrs.text +'"/>'
//add other attributes from here
return template;
}
}
});
To my knowledge there is not straight forward way to copy all attributes from one element to another, so you would need to loop through then or know what ones you want to support.
The following is the start of a directive that will replace the element the has the directive with a different element (input, in this case)
.directive('myInput', function() {
return {
replace: true,
template: function(element, attrs) {
var inputElem = angular.element('input');
// Copy attributes from element to inputElem;
angular.forEach(element[0].attributes, function(attr) {
inputElem[0].setAttribute(attr.name, attr.value);
})
// Add new attributes
return inputElem;
}
}
})

Resources