Add DOM element when transcluding except for the last one child - javascript

I would like to add a <hr> element between each clone of my transclude function except for the last one clone.
The parent directive have a custom HTML template.
In this template, I call a Attribute directive to transclude the elements.
Sample:
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
transclude(function (clone, scope) {
element.append(clone);
});
}
};
}
This directive help me to manually handle the transclude.
I just have to figure out now how to conditionnally add the <hr>.
I assume to do that I need to add for example element.append('<hr>'); after the first append.
So how could I know how many elements the transclude have to append ?
Is there a $last value or something to tell me that this is the last loop ?
Thanks for the help !

The controller has been instantiated by the time the link function runs, so I would just keep track of it in a controller attribute.
I'm assuming in the below example that you're passing this directive a collection to iterate over, or at least a count of number of elements you want. I'll also assume that argument is named collection, and that you're using bindToController so that this directive binding is accessible on the controller by this point. Finally, I'm assuming you've also attached an attribute to your controller named iterationCount.
function directiveTransclude() {
return {
link: function (scope, element, attr, ctrl, transclude) {
ctrl.iterationCount++;
transclude(function (clone, scope) {
element.append(clone);
if(ctrl.iterationCount < (ctrl.collection.length - 1)) {
element.append('hr');
}
});
}
};
}
Although, my suggestion would be to do this with css, if <hr> isn't some sort of strange requirement. Using a border and the last-child selector would be MUCH simpler.

Just in case you wonder:
It's late but I just find the solution by using an alternative (with CSS).
As mentioned by #Aaron Pool, I can use last-child selector to achieve that.
the last-of-type selector is finicky, and the last-child selector is actually applied to the child
It's better (faster than DOM injection and safer visually speaking, because <hr> could easily get edited by CSS rules).
I already tried that nevertheless it didn't works as expected.
Despite, today I figure out that my LESS code was that:
&:last-child
Instead of
:last-child
So I was focusing the wrong DOM element, sadly.
I'm happy even if now I feel so dumb.
Greetings and happy new year !

Related

How do I clone an AngularJS node while also cloning the event bindings?

I have a directive where I want to clone the element that the directive applies to. In one particular use of the directive, the element has a button with a click handler bound to it. I want my cloned element to also have this click handler. However, the conventional methods I know for cloning nodes aren't working for me. An example of what I've tried so far follows. Keep in mind this example is simplified for demonstration and the ultimate purpose of my directive is not just to clone the element.
module.exports = function($window, $injector){
return {
restrict: 'A',
link: function(scope, element, attrs){
// Neither of the following methods actually copy the click handler over to the clone.
var myFirstClone = element[0].cloneNode(true);
var mySecondClone = angular.element(container).clone(true)[0];
element[0].after(myFirstCone); // doesn't have the click handler
}
}
}
Is there another method that I should be using? Thanks!
Instead of cloning the element, you should use ng-if or ng-show in your top level controller. Have a property that checks if you are scrolled past a certain point. When the property evaluates to true, the element will show up. Also, you can use css to make transitions smoother (e.g. ease in/out), I wouldn't rely on javascript for visuals behaviors like that.

Angularjs - is there a way to programatically click on an HTML <select> element?

I've seen a javascript solution that goes a little something like this:
var select = document.getElementById('selectId')
select.click();
Is there an AngularJS approach/best practice to the same thing? (Off the top of my head, you'd wrap the above code in an ng-click)
Yes there is. Here's the angular equivalent of what you have in JavaScript
angular.element('#selectId').trigger('click');
Working example
Any DOM manipulation in angular should occur inside of a directive.
View
<div id="selectId" clickMe>content</div>
Inside of a directive the link function triggers after the view is compiled. The second parameter in the link function is the element which the directive is placed on, this gives performance benefits since there is no need to traverse the dom. It is a JQlite element which you can directly call methods on.
Directive
app.directive('click-me', function(){
return{
link(scope, el, attr){
$(el).trigger('click');
}
}
});

trying to add click inside a re-usable directive to a element that may have different classes be different element and may have different text

so here is the desire: I need to add a directive to any html element and pass it an id. This id is then used to populate a method that submits a form that has that id on the page.
Some devs have used a button some have used an anchor tag so I do not want the type of element to be part of the method. It is intended to be highly re-usable.
I already have it working like this:
angular.module("App").directive("submitform", submitFormDirective);
function submitFormDirective(){
return {
restrict: 'A',
scope: true,
link: function($scope, $element, $attrs){
var selector = $attrs.submitform;
$element.on('click', function(){
angular.element(selector).submit();
});
}
}
}
But this uses jquery for the "on" method. I want a fully angular method. Or one that does not "fight" with the framework or use jquery.
The way I am doing it now I only need to apply this attribute to the element. submit-form="#id-of-form" And I would like to keep that functionality becasue it is clean and concise. So I need to be able to add the ng-click to the element dynamically without using the limited template function which would limit the element being effected, inside the submitFormDirective.
The problem is that the $attrs.$set('ngClick', 'submitForm()') runs after the digest and $compile does not seem to work for me. I need to be able to add the click and run the compile on only this element or use a different phase that will happen before the app compile will run. But I have tried using compile to att the attr in conjunction with link adding the method to the scope to no avail. Seems strange that you would not be able to add a event from a directive. I must be missing something. Can someone help?

Getting element width inside directive the Angular way

I have a directive which contains an element inside it with width: 100%. However, the directive must know the clientWidth of that element -- that is, in pixels rather than percent. The element is buried deep in the directive's DOM.
The containing controller may have multiple instances of this directive, so doing document.getElementsByClassName('myclass')[0].clientWidth won't work. Selecting the directive's element works, but the code for that looks similar to element.children().children().children()[32].clientWidth, which doesn't look very stable. What is the right, Angular way of doing this?
Whenever you need to do any DOM manipulation in Angular, directives are the way to go. The "Angular" approach would be to use your directive's link function.
DEMO: http://plnkr.co/edit/83aZUmsRzuTUFotxUYSB?p=preview
Template Use Case:
<div element-width></div>
Directive Example:
angular.module('myApp').directive('elementWidth',function(){
return {
restrict: "A",
link:function($scope, element){
// element param being passed in refers
// to the element the directive is bound to, in this case, the DIV
// do something with the element width here
}
}
});
Hope this helps and let me know if you have any questions.
I figured it out. Although single elements do not have .getElementsByClassName, you can still use .querySelector. So my finished code was:
element[0].querySelector('.myclass').clientWidth

How to set ngModelOptions by javascript?

I'm creating a custom directive, and this directive will needing handle ngModelOptions of element, maybe I can do this changing element html with jQuery selector, but I wanted to know if can I do this by javascript for better consistency
UPDATE 1
Because I will need set ngModelOptions="{updateOn:'blur'} for all input in my form
So I want create a directive for do this, likely <input type='text' updateBlur/>
I wanted to know if is possible set this options with code, likely this
myElement.attrs.ngModelOptions.updateOn = "blur default";
You have to do it in the "angular way", so you should do something like this in your directive:
angular.module("myApp").directive('myDirective', function($compile){
link: function(scope, element, attrs) {
scope.$watch( 'scope.someValue', function(){
element.attr('ng-model-options', "new value");
$compile(element.contents())(scope);
});
}
});
In this way you might check on the change of scope.someValue, and apply the new value to the directive. You cannot do this with jquery, because angularJS won't get the changes.
But, using this $compile(element.contents())(scope); will do the trick.
EDIT: $scope.apply(), shouldn't work. Because it's necessary to force a digest cycle (such as more elements are added to an array). Here you're changing the structure, so you need to re-compile it
Please be more specific on your problem.
The answer for the general question is that you should not use jQuery selectors for manipulating the DOM if you are using angular.
If what you need is to manipulate the ngModelOptions attribute, you should do that from within your directive, using the arguments of the link function:
link: function(scope, element, attrs){
...
//if you want to change the ngModelOptions attribute
$(element).attr('ng-model-options', 'whatever')
}
For a more detailed explanation:
https://docs.angularjs.org/guide/directive

Categories