I have many repeated content elements in a single view. Within each content element, there's an anchor. When a user mouses over this anchor, I want to toggle a class on a sibling element within that particular content element.
Here's a simple example of what I want to do:
<div class="content-element">
<div ng-class="visibleClass">
I should have class 'visible' when the user mouses over the link within content-element div.
</div>
<a ng-mouseover="" ng-mouseleave="" href="#">Mouseover</a>
</div>
I initially wrote a controller to handle this, but the controller's $scope is tied to the entire view, not a single content-element, so this turned out to not be a graceful solution.
There are many 'content-elements' that are not generated with angular, but are just repeated in the template.
I'm fairly new to angular and trying to wrap my head around this new way of thinking. I can definitely solve this problem easy writing some javascript (capture the event, get the target, get the sibling, etc.) but this doesn't seem like the proper way to do it with angular.
So... what's the appropriate angular way to do this? Should I be writing a custom directive?
Simply create a directive with a new scope and have something like this in the HTML:
<div class="content-item">
<div class="" ng-class="{someClass:hovered}">My transparency should change.</div>
<a ng-mouseover="hovered = true">Mouseover me.</a>
</div>
PLUNKER
Note that if you use ngRepeat, it creates isolate scopes automatically and you don't need the directive.
Directive which founds siblings of the element on mouseover event. You can do what you want with the siblings then:
app.directive('mousiee',function(){
return{
restrict: 'A',
link: function(scope,elem,attrs){
var siblings;
elem.on('mouseover',function(){
siblings = $(elem.parent()).siblings();
console.log(siblings);
});
}
};
});
http://plnkr.co/edit/gWkNpiHMUEUBwuug9C3q?p=preview
(Note that I've added jQuery to your index.html)
Related
I am still new to Angular and I'm struggling to get the DOM Element of an Angular Click Listener.
What I have is the following component HTML:
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top" (click)="itemClicked($event, !!menuItem.submenu)">
<!-- Additional divs inside... -->
</div>
</div>
I would like to toggle a class of "menuItem__top" when it is clicked. My approach was to use a click event listener but I can't get the source Element to apply the class on.
itemClicked(event, hasSubmenu){
console.log(this) //is the component
let target = event.target || event.srcElement || event.currentTarget;
if(hasSubmenu){
console.log(target);
}
}
It could be done by getting the target of the $event but this way I would need to check the target and move with closest(".menuItem__top") up to the correct source element.
Is there an easy way in Angular to pass the source element of the click listener to the click function or a way to get it inside the click function?
In vanilla JS it would be as easy as using "this" inside the click function, but in Angular this is bind to the component. (In this case, it would be ok to loose the binding to the component if this is the only way.)
I thought about two ways:
Assigning a dynamic reference containing some string and the itemId, passing the itemId and retrieving the reference object based on the itemId in the listener.
Using a #HostListener to listen on every "menuItem__top" click and toggling the class every time.
What do you think is the best way? I feel like I am missing something simple here.
Go the other way around. People are used to jQuery and the way it works (selecting elements already present in the DOM, then adding them a class). So in Angular, they try to do the same thing and grab the element that was clicked, to apply it a class. Angular (and React and others) work the other way around. They construct the DOM from data. If you want to modify an element, start with modifying the data it was generated from.
This <div class="menuItem__top"> that you click on is constructed from let menuItem. All you have to do is add a property, say "isClicked" to menuItem.
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top"
[class.isClicked]="menuItem.isClicked"
(click)="menuItem.isClicked = true">
<!-- Additional divs inside... -->
</div>
</div>
I have a simple DOM hierarchy and I want to grab a specific set of elements (I want all the canvas elements). This is the entire template for this directive:
<div id='charts-container'>
<div class='chart-wrapper' ng-repeat='chart in getNumberOfCharts() track by $index'>
<canvas id="{{'exam-chart-' + $index}}" class='chart-canvas'></canvas>
</div>
</div>
I want to create a list with all the canvas within the #charts-container element, but I just can't. This is what I'm trying to do inside the link function:
link: function (scope, element, attributes, controller) {
var look = element.find('#chart-canvas');
$log.debug(look);
}
And then I get this element, but I don't know how to get all the chart-wrapper elements from here.
I tried doing look.context.children, it returned an empty list, but at the same time showed me what I wanted as if the list were populated, here is an image. If I try to access any index of this list, it returns undefined (which is fine, because it's an empty list. But why the console is showing me these values?)
What is the best way to achieve this? To get all the canvas elements in this template? (there are 15 of them). Thanks!
--- UPDATE ---
I realized that if I remove the ng-repeat attribute I'm using in the template, it works! But I need the ng-repeat...
element.find('.chart-canvas') manage to get the canvas element, but only if there are no ng-repeat attribute.
Is this a common issue when using ng-repeat. Is there any special treatment when falling in this case?
I found a solution in this thread.
Apparently I was trying to access DOM that hasn't been rendered yet. So, wrapping my query for the canvas with
$timeout(function() {
var canvasList = element.find('.chart-canvas');
}, 0);
solved the issue, since using $timeout will wait until all $digest cycles are complete.
I would like to get to know better how the rendering pipeline works in AngularJS, to avoid falling in issues of this kind. If anyone has a link to a thread or explanation of this, it would be much appreciated.
I think it should be element.find('.chart-canvas') instead of element.find('#chart-canvas'). Does it work with that change ?
I have been developing an advanced web-based GUI in AngularJS. Recently, I decided to use the call document.getElementsByClassName() (I hate using element collecting methods, but here I had to use one) and my boss flipped his lid for accessing the document element. He says that I "need to use only Angular calls for everything", even for element collection! Is there an "Angular way" to collect elements by class name? If so, which way is better to use within the Angular framework? Please provide reasons why. Thanks!
UPDATE: Why I need to use an element collector...
So, I really wish I didn't have to do this, but I do...
I am using a third-party directive that I found online called the Bootstrap DateTimePicker. Its pretty cool and very nice looking, yet it might have a bug...
First, I make a directive bound to an attribute, stating that the element I pass in is meant to be a "DateTimePicker". I then pass that element to the DateTimePicker function.
When invoked, this function creates a new div with absolute positioning and appends it to the body of the page.
Now, I open a dialog in my GUI that has a table in it. On each row of the table, I have two DateTimePickers: one for end-date and one for start-date.
My problem is that, once I leave my screen and the elements which the DateTimePickers were bound to are destroyed, the DateTimePickers still remain! If I open the dialog box again, it creates a ton more of these divs as well!
Until I could determine a true solution to this issue, I decided to use the element collector as a temporary quick-fix. I grab all of the elements with the datetimepicker class and perform a:
elem[i].parentNode.removeChild(elem[i]);
Not having your exact use case but knowing that you are attempting to aggregate elements by class name in your controller makes me agree with you boss. Think of the controller as an object which exposes data and and services to your declarative html page. The data is bound into the markup for presentation and possible modification. THe services are usually wrapped in functions on your controller which are then tied to event handling directives like ng-click or ng-change. These services should operate exclusively on your data and never touch the DOM. If you need to modify a DOM element in your declarative markup then that should be done through directives like ng-class etc.
In any case, It would be useful to know what you are trying to accomplish so as to give you a better idea of the "angular way" to approach the problem.
Well, I have my answer. This does not solve the question "Grab all elements with a certain class name without touching the document element" yet it does solve my problem and eliminates my need to use document.getElementsByClassName.
First of all, it turns out that every element using the DateTimePicker directive have an element.datetimepicker("remove") function.
I use a directive for each DateTimePicker:
components.directive('DateTimePicker', function() {
// Requires bootstrap-datetimestamp.js
return {
restrict: 'E',
replace: true,
scope: {
dateTimeField: '='
},
template:
'<div>' +
'<input type="text" readonly data-date-format="yyyy-mm-ddThh:ii:ssZ" data-date-time required/>'+
'</div>',
link: function(scope, element, attrs, ngModel)
{
var input = element.find('input');
input.
datetimepicker(
{
//stuff
})
.on('changeDate', function(ev)
{
//more stuff
});
...
Directive drastically shortened for the sake of your eyeballs...
I then need to remove the DateTimePicker and the input it is bound to from the DOM on destruction of the dialog box that the input is a child of. To do so, I added this to my directive:
scope.$on("$destroy",function handleDestroyEvent()
{
input.datetimepicker("remove");
input = null;
});
And it works! The DateTimePicker gets removed, the DateTimePicker's handles to the input are cleaned up, and I've marked my input for the GC! WooHoo! Thanks everybody!
If you include jQuery in your project before AngularJS, Angular will use jQuery instead of jqLite for the angular.element function. This means you should be able to use jQuery's selectors for finding / referencing DOM elements.
Got a little situation here.
I'm just learning Angular and I'm stuck with the following problem.
I've a list that repeats itself (ng-repeat) and when it is rendered, you can click on it and I add a class to the list-item you clicked on, jQuery-style:
$('#'+id).addClass("myClass");
And the id is stored in a scope-variable.
But when I change the scope for the list-item, new items are loaded, and when I change them back to the state were I started, I'm trying to add the class to the same id, but doens't work.
Do I miss something?
Thanks in advance
A better approach here would be to alter your data on click and then to conditionally attach a class based on the data:
<div ng-repeat="item in items" ng-class="{myClass:item.changed}">
{{item.name}}
<button ng-click="item.changed = !item.changed">Change me!</button>
</div>
Here it is in action: http://jsfiddle.net/wilsonjonash/NTpLS/
As I mentioned in the comments, when programming in angular, think in terms of your model first. Most things you would achieve in jQuery-land with DOM manipulations can be achieved in angular with model-dependent markup (directives).
Best of luck!
You can use the ng-class instead of addClass : )
ngClass Document
I am trying to add a couple of custom buttons to the right part of the header of fullcalendar (http://arshaw.com/fullcalendar/docs/).
I am using angular.element to add the buttons like this:
var calendarHeaderRight = angular.element(".fc-header-right");
if (calendarHeaderRight)
calendarHeaderRight.html('<div>CalendarList </div>');
The buttons do get appended to the calendar and are displayed. The problem is that nothing is happening when I click the buttons.
So the question is, how do I append elements which can be clicked ?
thanks
Thomas
var element = $compile(angular.element('<div>CalendarList </div>'))(scope);
then you can wrapped this element in your div. Here is angular document about compile.
: ) enjoy it.
var html = angular.element('<div>CalendarList </div>')
var compiled = $compile(html)
calendarHeaderRight.html(html)
compiled($scope)
View compilation in Angular basically traverses the entire DOM tree of whatever node you give it, creating a single function, that when called will execute every linking function from every directive it finds in that DOM tree, with the appropriate scope, element, attributes and (optionally) controllers.
Pretty much the same thing Tyler did just broken down.