Angular- Getting closest element on mouseover - angularjs

I'm new to Angular JS and i'm trying to create a small web app for learning.. I am trying to make a Tooltip text on mouseover but i'm not sure how to get it done the "Angular way"..
I created 2 spans, when hovering the first, i want to show the second
I tried using ng-mouseover and ng-mouseleave to call the actions-
<span class="info" ng-mouseover="info_in();" ng-mouseleave="info_out();">
<img src="images/info.png" />
</span>
<span class="info_bubble" ng-show="info">The Tooltip Text</span>
And that's where i got with the JS-
$scope.info_in = function() {
this.parent().find('.info_bubble') = true;
};
$scope.info_out = function() {
this.parent().find('.info_bubble') = false;
};
There are going to be more than 1 Tooltip text on each page and i'm not sure how to get it done.. I tried with "next()" and "closest()" but couldn't get it to work
When i try to mouseover the element, i get "this is not a function"

You've got the right idea but your implementation is moving toward the jQuery way, not the Angular way. :)
Try this:
<span class="info" ng-mouseover="info=true" ng-mouseleave="info=false">
<img src="images/info.png" />
</span>
<span class="info_bubble" ng-show="info">The Tooltip Text</span>
No controller code is necessary for this to work.
What you're doing is that when the mouse enters the image, Angular will set $scope.info to true. And since your tooltip is watching that scope variable, it will trigger the ng-show directive to fire which will show your tooltip.
The ng-show directive can be translated as: When $scope.info == true, then show() this element. When $scope.info == false, then hide() this element.
In fact, you could be more verbose (which is good for learning) writing your tooltip element like this:
<span class="info_bubble" ng-show="info==true">The Tooltip Text</span>
I notice that you're using the jQuery method of specifically trying to find an element in the DOM in order to work with it.
The Angular way is to change variables on the $scope. Other HTML elements will monitor variables on the $scope and will automatically change themselves depending on what the new value is. The jQuery way is to reach out and specifically touch and set a value on a DOM element. The Angular way is akin to shouting to the wind, "Hey, my name is $scope.info and I'm now true!" and expecting that some other element will hear it and go, "Ok cool, now I can show myself because $scope.info is true."
That's the main difference between the way jQuery and Angular work.

Related

Passing multiple 'button' parameters to Angular component

I'm creating a reusable item picker component using Angular 1.5. The picker has a search field and a list of items to choose from. An example use case for the picker is a popup where the user selects some items and then has a "Continue" button to proceed.
Conceptually, the search field and the list of items belong to the component and the "Continue" button belongs to the surrounding dialog. However, I want to position the button next to the search field. In some cases there are no extra buttons, sometimes one extra button, sometimes two.
Something like this:
What is the best way to create such a component?
Options I've thought of:
Create a component / directive for the item picker, put the button before or after the directive in the HTML, and use CSS to position the button.
Here the positioning of the button is ugly and fragile, as it's not in the proper position within the HTML. It would probably need a wrapper div and absolute positioning on top of the picker component:
<div style="position: relative">
<item-picker></item-picker>
<button name="Continue" ng-click="submit()" style="position:absolute; top:5px; right: 5px"></button>
</div>
Somehow pass the buttons and callbacks as parameters to the item picker component. Here the ugliness is in the hard-coding of the buttons and styles and amount of buttons:
<item-picker btn1-text="Continue" btn1-style="primary" btn1-callback="submit()" btn2-text="Cancel" btn2-style="secondary" btn2-callback="cancel()"></item-picker>
I'm unsure whether the button configuration and callbacks could be passed as a single configuration object. I'm mainly concerned about the callback functions, whether they will work properly if passed through a configuration object instead of proper '&' callback binding.
Stop trying to make the picker into a component / directive and just use <ng-include> to include the picker code which reads the button configuration from the scope. Ugliness is in lack of scoping and not using components.
Is there some best practise for such cases?
One possible solution is to use ng-transclude, so your code could look something like:
Markup
<item-picker>
<button ng-click="parentScopeFn()">Btn 1</button>
...
</item-picker>
Directive
angular.module('myApp', [])
.directive('itemPicker', function() {
return {
restrict: 'E',
transclude: true,
scope: {
...
},
templateUrl: 'item-picker.html'
};
});
itemPicker template markup
<div class="item-picker">
<div class="item-picker-controls">
<div class="item-picker-search"><input type="search" ng-model="..."></div>
<div class="btn-group" ng-transclude></div>
</div>
<ul class="item-picker-list">
<li ng-repeat="item in items" ng-bind="item"></li>
</ul>
</div><!-- end item-picker template -->
Of course the above code is just an example and is making a lot of assumptions about your itemPicker component. Also, you'll still need to use CSS to position your buttons, but it might be easier to reason with b/c it'll be in the context of your component.
Note
You could also make use of "multi slot transclusion". This is probably useful in cases where the number and type of buttons you'll have is predictable and you want them arranged in a consistent way no matter how they are placed in the markup.
Hope this helps.

Trigger a click on first item of ng-repeat, initially and each time new data is loaded

I'm very very new to Angular JS and have the following requirement:
I have an ng-repeat as shown below:
<div class="panel" ng-repeat="(appname, value) in chart.accordionData" style="margin-top: 0;">
<div class="accordion collapsed" data-parent="#accordion1"
data-toggle="collapse"
data-target="#{{appname.replace(' ','')}}">
<div class="accordion-head" initial-select index="{{$index}}">
<div class="arrow chevron"></div>
<h4><i></i>{{appname}}</h4>
</div>
</div>
<div class="accordion-body collapse" id="{{appname.replace(' ','')}}">
<p class="highlightable" >
Some data
</p>
</div>
</div>
The data in this ng-repeat comes from the server.
So the problem statement I have is to perform a click trigger on the first element under ng-repeat, .accordion-head so that the first item in the list is always open. I tried various approaches of putting $watch etc. but when the number of items in the list are same, then the trigger doesn't fire.
(the first item in list has to be clicked even when new data is loaded)
I thought of writing a directive initial-select and perform click based on index but that happens only once. I really need an experts advice.
Any solutions?
I think the simplest option is to use data-ng-init="collapse = !$first;".
Another option:
Have you used Angular UI Bootstrap? Also, are you using $http or $resource to get data from the server?
If you use $resource, you can set a variable on the success callback (interceptor response) to open=true. Then, you can either bind this variable to Angular UI Bootstrap is-open attribute or to your data-toggle. Also, with $resource you can set data-ng-if="chart.accordionData.$resolved" (or chart.$resolved depending on your setup), which is nice.
You can use a watch to watch the array contents.
Check out the following fiddle:
http://jsfiddle.net/akonchady/w0qdsen7/3/
I have used a variable to keep track when the array content changes since directly watching the array will be more memory intensive.
Here is the main code that watches the variable:
$scope.$watch('arrModified', function(newValue, oldValue) {
if(newValue) { //array is modified
//Trigger ng-repeat div click
setTimeout(function() {
$('.item:first').trigger('click');
$scope.arrModified = false;
$scope.$digest();
}, 0);
}
});

Rending a Modal in AngularJS

I'm attempting to learn AngularJS (background in BackboneJS). I have a div with some content inside, and I hope to render this div as a modal upon clicking inside of it:
<div class="stickynote"> Content here </div>
My thinking is to add a modal class that I can style in CSS. However, I'm not too sure how to add the modal class upon clicking (and conversely, removing the modal class upon clicking after the modal is rendered). Would I have to use ng-click and somehow set the class property from the JavaScript (myApp.js) file?
If you want to use your own modal styling and if you simply want to achieve adding an extra item to class attribute of your element, you can use a combination of ng-class and ng-click:
<div class="stickynote"
ng-class="{yourModalCSSClass: isModalOpen}"
ng-click="isModalOpen = true">
And somewhere else, you need another ng-click to turn it off:
<button ng-click="isModalOpen = false">Close modal</button>
Beware that both div and button must be in the same scope hierarchy to be able to use the same isModalOpen value. And by the way, I haven't tried this code but this should give you an idea. If you have a controller/directive, you can set isModalOpen from there by introducing functions in the scope:
// controller
$scope.toggleModal = function () {
$scope.isModalOpen = !$scope.isModalOpen;
}
<div ...
ng-click="toggleModal()">
<button ng-click="toggleModal()">...
If you're open to using a third-party solution, ng-dialog is an outstanding solution for modals+Angular.
https://github.com/likeastore/ngDialog

In AngularJS, is it possible to pass the source element as a parameter in its ng-class attribute?

Please, consider the following example for understanding my question:
<button ng-class="$scope.controllerMethod($thisButton)" />
In my controllerMethod, I want to get a reference of the button who called ng-class. Is it possible?
(Something like passing $event.target in the ng-click button, so I can read the caller from the controller).
Any helps? Thanks!!
If you're hard coding each of your button in your menu, you won't need ng-class. Simply ng-click=doSomething('$event'), then the rest is just like your normal Javascript, do whatever you want with $event.target.
If you want do it the angular way, each button needs to have a corresponding model in the controller.
<ul>
<li ng-repeat='btn in buttons'>
<button ng-class='{"highlight":btn.clicked}' ng-click='doSomething(btn)'></button>
</li>
</ul>
In your controller:
$scope.buttons = [{text:'button1'},{text:'button2'}];
$scope.doSomething = function(btn){
btn.clicked = true;
}
In this example, ng-class will watch each button's clicked property, if it's true, then add highlight class onto this button.

I need to access a radio button array in angularjs

All of the examples I have found show the radio button group being built by some for item in items loop but none of them show a simple accessing of the radio button group array in the angularjs controller. What I need to do is traverse through the button group array to see if any of them are in "selected" state.
var radioSelected = false;
for(var i =0; i < items.length; i++) {
if(items[i].selected) {
radioSelected = true;
}
}
I have tried binding to the ng-model and accessing it .. I have tried using $scope.ButtonGroupName Nothing yeilds an array that I can traverse with a loop. Any suggestions on how to do this once VERY simple activity would be greatly appreciated.
Gotta love being forced to relearn web development because somebody broke out a new shiney hammer.
You would not traverse the DOM elements. You would use the same ng-model for all the radio elements, and that would be updated whenever you change the selected state of the radio button.
<input type="radio" ng-model="assignedValue" name="myRadio" value="one">
<input type="radio" ng-model="assignedValue" name="myRadio" value="two">
<input type="radio" ng-model="assignedValue" name="myRadio" value="three">
You would $watch the $scope.assignedValue for changes instead of traversing the DOM elements.
$scope.$watch('assignedValue', function (newValue, oldValue) {
..do stuff here
});
See here for documentation: https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
The reason you don't traverse the DOM is because it's constantly changing. The whole point of Angular is to work off of data and not the elements themselves.
Update: Based on your comments, it sounds like only want to execute an action if a radio button has been selected.
First, a radio button should always have a selected state. Let's pretend it doesn't though. You can enable / disable / show / hide elements in angular in a couple of ways without writing additional DOM manipulation code.
Taking the example above, this button will only be enabled if the assignedValue is two.
<button ng-disabled="assignedValue != 'two'">My button</button>
You can also conditionally include content using ng-if:
<div ng-if="assignedValue == 'two'>
My conditional content
</div>
This will also work with ng-switch
<div ng-switch on="assignedValue">
<div ng-switch-when="two">My additional content</div>
<div ng-switch-default>Here's switch fallback content</div>
</div>

Resources