Add elements to datatable - angularjs

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)

Related

How to wrap an existing DOM element with a directive in AngularJS

I have an existing DOM element that has already been compiled and contains angular directives, for example
CurrentDirective
function link(scope, element, attr, ctrls) {
var element = element.children()[1]; //Ex HTML: <div testElement>test</div> )
// Wrap element now with WrapperDirective
Now I want to wrap it with a new directive called "WrapperDirective" .
The catch is, "WrapperDirective" needs it's "element" parameter at link time, to run this code:
WrapperDirective
link: function (scope, element, attrs) {
var testElements = angular.element(element[0].querySelectorAll('[testElement]'));
The result should be similar to having this code, but it has to be dynamically created using the already compiled inner piece.
<WrapperDirective>
<div testElement>test</div> // Just example, could be anything.
</WrapperDirective>
Basically I need something like
$compile("<WrapperDirective>" + Existing DOM Element(No Recompile)+ "</WrapperDirective")
I know I can
$compile("<WrapperDirective></WrapperDirective>")
and then append the rest, but the link function of WrapperDirective will not get the children element that it needs.

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

AngularJS Access DOM inside $watch function

I'm making a directive that resizes a div based on changes in the controller. I need to calculate the amount of available space left in the browser window when changes happen to the model. How do you pass in the element from the link function into the $watch function?
In short, how do I manipulate the DOM based on changes to the model?
var module = angular.module('cmsApp')
module.directive("changeWidth", function($timeout) {
return {
restrict: 'A',
link: function($scope, element, attrs) {
width = element.width();
$scope.$watch('currentFolder', function(value){
// manipulate dom here
});
}
}
});
<!-- need to calculate the size of this -->
<div change-width class="col-md-9 right-pannel"></div>
I don't think Angular is even executing your directive based on your template code. It should be
<div change-width class="col-md-9 right-pannel"></div>
I know this is a source of errors if you are new to Angular. From the docs:
Angular uses name-with-dashes for its custom attributes and camelCase
for the corresponding directives which implement them)

wrapping inputs in directives in angular

I had the idea to wrap inputs into custom directives to guarantee a consistent look and behavior through out my site. I also want to wrap bootstrap ui's datepicker and dropdown. Also, the directive should handle validation and display tooltips.
The HTML should look something like this:
<my-input required max-length='5' model='text' placeholder='text' name='text'/>
or
<my-datepicker required model='start' placeholder='start' name='start'/>
in the directives i want to create a dom structure like:
<div>
<div>..</div> //display validation in here
<div>..</div> //add button to toggle datepicker (or other stuff) in here
<div>..</div> //add input field in here
</div>
I tried various ways to achieve this but always came across some tradeoffs:
using transclude and replace to insert the input into the directives dom structure (in this case the directive would be restricted to 'A' not 'E' like in the example above). The problem here is, that there is no easy way to access the transcluded element as I want to add custom attributes in case of datepicker. I could use the transclude function and then recompile the template in the link function, but this seems a bit complex for this task. This also leads to problems with the transcluded scope and the toggle state for the datepicker (one is in the directives scope, the other in the transcluded scope).
using replace only. In this case, all attributes are applied to the outermost div (even if I generate the template dom structure in the compile function). If I use just the input as template, then the attributes are on the input, but I need to generate the template in the link function an then recompile it. As far as I understand the phase model of angular, I would like to avoid recompiling and changing the template dom in the link function (although I've seen many people doing this).
Currently I'm working with the second approach and generating the template in the link function, but I was wondering if someone had some better ideas!
Here's what I believe is the proper way to do this. Like the OP I wanted to be able to use an attribute directive to wrapper an input. But I also wanted it to work with ng-if and such without leaking any elements. As #jantimon pointed out, if you don't cleanup your wrapper elements they will linger after ng-if destroys the original element.
app.directive("checkboxWrapper", [function() {
return {
restrict: "A",
link: function(scope, element, attrs, ctrl, transclude) {
var wrapper = angular.element('<div class="wrapper">This input is wrappered</div>');
element.after(wrapper);
wrapper.prepend(element);
scope.$on("$destroy", function() {
wrapper.after(element);
wrapper.remove();
});
}
};
}
]);
And here's a plunker you can play with.
IMPORTANT: scope vs element $destroy. You must put your cleanup in scope.$on("$destroy") and not in element.on("$destroy") (which is what I was originally attempting). If you do it in the latter (element) then an "ngIf end" comment tag will get leaked. This is due to how Angular's ngIf goes about cleaning up its end comment tag when it does its falsey logic. By putting your directive's cleanup code in the scope $destroy you can put the DOM back like it was before you wrappered the input and so ng-if's cleanup code is happy. By the time element.on("$destroy") is called, it is too late in the ng-if falsey flow to unwrap the original element without causing a comment tag leak.
Why not doing a directive like that?
myApp.directive('wrapForm', function(){
return {
restrict: 'AC',
link: function(scope, inputElement, attributes){
var overallWrap = angular.element('<div />');
var validation = angular.element('<div />').appendTo(overallWrap);
var button = angular.element('<div />').appendTo(overallWrap);
var inputWrap = angular.element('<div />').appendTo(overallWrap);
overallWrap.insertBefore(inputElement);
inputElement.appendTo(inputWrap);
inputElement.on('keyup', function(){
if (inputElement.val()) {
validation.text('Just empty fields are valid!');
} else {
validation.text('');
}
});
}
}
});
Fiddle: http://jsfiddle.net/bZ6WL/
Basically you take the original input field (which is, by the way, also an angularjs directive) and build the wrappings seperately. In this example I simply build the DIVs manually. For more complex stuff, you could also use a template which get $compile(d) by angularjs.
The advantage using this class or html attribute "wrapForm": You may use the same directive for several form input types.
Why not wrap the input in the compile function?
The advantage is that you will not have to copy attributes and will not have to cleanup in the scope destroy function.
Notice that you have to remove the directive attribute though to prevent circular execution.
(http://jsfiddle.net/oscott9/8er3fu0r/)
angular.module('directives').directive('wrappedWithDiv', [
function() {
var definition = {
restrict: 'A',
compile: function(element, attrs) {
element.removeAttr("wrapped-with-div");
element.replaceWith("<div style='border:2px solid blue'>" +
element[0].outerHTML + "</div>")
}
}
return definition;
}
]);
Based on this: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
This directive does transclusion, but the transcluded stuff uses the parent scope, so all bindings work as if the transcluded content was in the original scope where the wrapper is used. This of course includes ng-model, also min/max and other validation directives/attributes. Should work for any content. I'm not using the ng-transclude directive because I'm manually cloning the elements and supplying the parent(controller's) scope to them. "my-transclude" is used instead of ng-transclude to specify where to insert the transcluded content.
Too bad ng-transclude does not have a setting to control the scoping. It would make all this clunkyness unnecessary.
And it looks like they won't fix it: https://github.com/angular/angular.js/issues/5489
controlsModule.directive('myWrapper', function () {
return {
restrict: 'E',
transclude: true,
scope: {
label: '#',
labelClass: '#',
hint: '#'
},
link: link,
template:
'<div class="form-group" title="{{hint}}"> \
<label class="{{labelClass}} control-label">{{label}}</label> \
<my-transclude></my-transclude> \
</div>'
};
function link(scope, iElement, iAttrs, ctrl, transclude) {
transclude(scope.$parent,
function (clone, scope) {
iElement.find("my-transclude").replaceWith(clone);
scope.$on("$destroy", function () {
clone.remove();
});
});
}
});

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

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.

Resources