Using scope variables with non-form elements - angularjs

If I have 2 divs (removed ng-click function for simplicity)
<div ng-class="{selected: header.type == 'percent'}" data-type="percent"></div>
<div ng-class="{selected: header.type == 'invisible'}" data-type="invisible"></div>
This will apply the class of .selected to one of the divs, depending on the value of $scope.header.type
However, I also have it so when I click on the div that does not have the .selected class, i remove the selected class from the div that had it, and apply it to the div that was just clicked.
Now, on the controller, how do I get the data-type of the div that has the .selected class?
Basically I'm trying to set $scope.header.type to hold the value of data-type of the div that has the .selected class
Just in case it's needed, here's the ng-click fn (which is not angular-like, but I couldn't find an alternative)
$scope.changeOfferbox = function($event) {
var selected = angular.element(document.querySelector('.selected'))
selected.removeClass('selected')
var clicked = angular.element($event.target).addClass('selected')
}

A simple solution might be to pass the data-type to your click function ; this way, you actually don't even need to manually add/remove classes, the ng-class directives will automatically update when header.type changes :
$scope.changeOfferbox = function($event, localType) {
if (localType !== $scope.header.type) {
$scope.header.type = localType;
}
};
<div ng-class="{selected: header.type == 'invisible'}" ng-click="changeOfferbox('invisible');" data-type="invisible"></div>

Related

How to change the class using click and hovering

View:
<a ng-repeat="control in controls | filter:name" ng-href="#{{control.id}}" ng-click="restart(control.name)" ng-class="{active: control.name == selected}">{{control.name}}
controller- app.js
$scope.restart = function (controlName) {
$scope.selected = controlName;
}
Here i have added the active class while clicking.how can i add the hover class using mouseover and remover the class using mouse-leave.
As suggested above, use ng-class with ng-mouseover and ng-mouseleave:
<div ng-class="class" ng-click="class='active'" ng-mouseover="class='hovering'" ng-mouseleave="class=''"></div>
This way, you'll have 3 event listener to a single variable (in a mutually exclusive way).
If you want to have both at the same time, use an array this way:
<div ng-class="[classClick, classHover]" ng-click="classClick='active'" ng-mouseover="classhover='hovering'" ng-mouseleave="classHover=''"></div>
Use ng-class, ng-mouseover anf ng-mouseleave:
<div ng-class='{"classtoadd": add, "active": click, "hover": hover}' ng-mouseover="add=true;" ng-mouseleave="add=false" ng-click="click = true" ng-mouseover="hover = true"></div>
Thanks for your response. i want to add 'active' class while clicking.and i want add the add the class' hover' while 'hovering'

AngularJS addClass isn't working

I have been trying to addClass through AngularJS and the code doesn't seem to work, weird thing is addClass is working on Parent Menu Item but doesn't work on Sub item.
I have a nested UL and LI, when I click on the Parent LI ParentLi function gets called and it adds a "focused" class to the Clicked LI, this works fine but when I click on Nested LI's I call childLi and I do the same operation as done for the Parent but class doesn't get added. I am new to Angular and I hope I am doing this in the right way.
$scope.parentLi = function(event) {
var liElement = angular.element(event.target.parentNode);
var allParentLiElements = document.getElementsByClassName('parent-dropdown');
if (!liElement.hasClass('focused')) {
angular.element(allParentLiElements).removeClass('focused');
liElement.addClass('focused');
} else
liElement.removeClass('focused');
};
$scope.childLi = function(event){
var liElement = angular.element(event.target.parentNode);
var allParentLiElements = document.getElementsByClassName('child-dropdown');
if(!liElement.hasClass('focused')){
angular.element(allParentLiElements).removeClass('focused');
$(event.target).closest('.parent-dropdown').addClass('focused');
liElement.addClass('focused');
} else
liElement.removeClass('focused');
}
Note that i have edited my jsfiddle code based on the answer given by Jiam30.
adding focused class should work like active class i.e the menu that i just clicked should have focused class other should not, same way if i have hover on menu item and click on subitem, both the subitem and the parent item should have focused class.
Fiddle
Manipulating elements in a controller should be avoided.
Use ng-class instead (also use ng-repeat to avoid HTML repetition). For instance:
<li class="dropdown parent-dropdown" ng-click="parentLi()" ng-class="{'focused': isDropdownFocused}"></li>
With this function in the controller:
$scope.parentLi = function() {
$scope.isDropdownFocused = !$scope.isDropdownFocused;
};
Updated Fiddle: http://jsfiddle.net/6be56/127/

Strange behaviour of angular bootstrap collapse

I faced with strange behaviour of uib-collapse.
Let's assume I have a list of elements and i want each of them to be collapsed. Also i want to refresh its content periodically depend on something.
For example: i have some items and each of them have description which consists of some sections. I can pick item and description sections should be populated with item's description content. The problem is that each time i refresh its content, some sections are collapsing (despite the fact i set uib-collapse to false)
My controller:
var i = 0;
$scope.sections = [0,1,2];
$scope.next = function(nextOffset) {
i+=nextOffset;
$scope.sections = [i, i+1, i+2]
}
My template:
<button ng-click="next(1)" style="margin-bottom: 10px">Next item</button>
<button ng-click="next(2)" style="margin-bottom: 10px">Next next item</button>
<button ng-click="next(3)" style="margin-bottom: 10px">Next next next item</button>
<div ng-repeat="section in sections">
<div uib-collapse="false">
<div class="well well-lg">{{ section }}</div>
</div>
</div>
So when i click first button, only one section does transition. When i click second, 2 section do transition and click to third button leads to all section transition.
See plunkr
Any ideas?
UPD: if $scope.sections is array of object, not of primitives, then all sections have transition in each of 3 cases. It is so ugly...
You are not refreshing the existing content, you are adding new arrays each time, which will make ng-repeat remove the old DOM elements and insert new ones.
If you try with track by $index you will see the difference:
<div ng-repeat="section in primitiveSections track by $index">
Demo: http://plnkr.co/edit/hTsVBrRLa8nWXhaqfhVK?p=preview
Note that track by $index might not be the solution you want in your real application, I just used it for demonstration purposes.
What you probably need is to just modify the existing objects in the array.
For example:
$scope.nextObject = function(nextOffset) {
j += nextOffset;
$scope.objectSections.forEach(function (o, i) {
o.content = j + i;
});
};
Demo: http://plnkr.co/edit/STxy1lAUGnyxmKL7jYJH?p=preview
Update
From the collapse source code:
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
if (shouldCollapse) {
collapse();
} else {
expand();
}
});
When a new item is added the watch listener will execute, shouldCollapse will always be false in your case so it will execute the expand function.
The expand function will always perform the animation:
function expand() {
element.removeClass('collapse')
.addClass('collapsing')
.attr('aria-expanded', true)
.attr('aria-hidden', false);
if ($animateCss) {
$animateCss(element, {
addClass: 'in',
easing: 'ease',
to: {
height: element[0].scrollHeight + 'px'
}
}).start().finally(expandDone);
} else {
$animate.addClass(element, 'in', {
to: {
height: element[0].scrollHeight + 'px'
}
}).then(expandDone);
}
}
If this is the intended behavior or not I don't know, but this is the reason why it happens.
this is a comment on the original ui-bootstrap library: (and the new uib prefixed directive doesn't comply this comment.)
// IMPORTANT: The height must be set before adding "collapsing" class.
Otherwise, the browser attempts to animate from height 0 (in
collapsing class) to the given height here.
use the deprecated "collapse" directive instead of new "uib-collapse" until it gets fixed.

How to find out if something in a given div has focus?

Using angularjs, I'm showing a 2-level list like this
- first main item
- first subitem of the first main item
- second subitem of the first main item
- AN EMPTY ITEM AS PLACEHOLDER TO ENTER THE NEXT SUBITEM
- second main item
- first subitem of the second main item
- second subitem of the second main item
- AN EMPTY ITEM AS PLACEHOLDER TO ENTER THE NEXT SUBITEM
In order to save place, I'd like to show the PLACEHOLDER only if anything in the corresponding div has focus, so that there's only one such placeholder. I know that there's ngFocus, but I'd prefer something simpler than creating tons of event handlers. Maybe something like this :
<div ng-focus-model="mainItem.hasFocus" ng-repeat="mainItem in list">
... main item line
... all subitems
</div>
A unidirectional binding would be sufficient as I don't need to set the focus.
The problem here is the following; we want to avoid adding event listener to each and every child, but add it only to the parent. The parent will be responsible for taking the appropriate action. The general solution to this, is to use even propagation (delegation). We attach only one listener to the parent, when an event occurs on the child (focus on input element in this example), it will bubble up to the parent and the parent will execute the listener.
Here's the directive:
app.directive('ngFocusModel', function () {
return function (scope, element) {
var focusListener = function () {
scope.hasFocus = true;
scope.$digest();
};
var blurListener = function () {
scope.hasFocus = false;
scope.$digest();
};
element[0].addEventListener('focus', focusListener, true);
element[0].addEventListener('blur', blurListener, true);
};
});
The directive listens for events and accordingly sets the value on scope, so we can make conditional changes.
There are several things to notice here.
focus and blur events don't "bubble", we need to use "event capturing" to catch them. That's why element.on('focus/blur') is not used (it doesn't allow for capture, afaik) but an addEventListener method. This method allows us to specify if the listener will be executed on "event bubbling" or "event capturing" by setting the third argument to false or true accordingly.
We could have used focusin and focusout events which "bubble", unfortunatelly these aren't supported in Firefox (focusin and focusout).
Here's a plunker with the implementation.
Update:
It occurred to me that this can be done with pure CSS using the :focus pseudo-class, the only downside is that the placeholder needs to be in proper position (sibling) relative to the input elements. See codepen.
Unfortunately the only rock solid way to do what you want is to respond to the focus\blur events on the inputs...that's the only way to get notified.
You could put a hidden input as the first element in each div and put the NgFocus attribute on it but that only works if a user tabs into it.
DEMO
I created a small directive that can be used for what you need:
app.directive('childFocus', function($window){
var registered = [];
// observing focus events in single place
$window.addEventListener('focus', function(event){
registered.forEach(function(element){
if(element.contains(event.target)){
// if element with focus is a descendant of the
// element with our directive then action is triggered
element._scope.$apply(element._scope.action);
}
});
}, true)
return {
scope : {
action : '&childFocus' // you can pass whatever expression here
},
link : function(scope, element){
// keep track ref to scope object
element[0]._scope = scope;
// (probably better would be to register
// scope with attached element)
registered.push(element[0]);
scope.$on('destroy', function(){
registered.splice(registered.indexOf(element[0]),1);
});
}
}
});
You could use the focus event of the '.parent *' selector to capture any focus events, then loop through each of the parent DIVs and use the :focus JQuery selector to check for child elements with focus, then add a class to the parent DIV and use that class to show/hide the placholder (see this jsfiddle):
$(function(){
$('.parent *').focus(function(){
$('.selected').removeClass('selected');
$('.parent').each(function(index, el){
(function($el){
setTimeout(function(){
console.log($el.attr('id'));
if($el.find(':focus').length){
$el.addClass('selected');
}
});
})($(el));
});
});
});
.parent{
padding:1rem;
margin:1rem;
border:solid 1px green;
}
.selected{
border:solid 1px red;
}
.parent .placeholder{
display:none;
}
.parent.selected .placeholder{
display:block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='parent' id='div1'>
<input type="text" />
<div class='placeholder'>Placeholder</div>
</div>
<div class='parent' id='div2'>
<input type="text" />
<div class='placeholder'>Placeholder</div>
</div>
<div class='parent' id='div3'>
<input type="text" />
<div class='placeholder'>Placeholder</div>
</div>
<div class='parent' id='div4'>
<input type="text" />
<div class='placeholder'>Placeholder</div>
</div>

How to expand/collapse all rows in Angular

I have successfully created a function to toggle the individual rows of my ng-table to open and close using:
TestCase.prototype.toggle = function() {
this.showMe = !this.showMe;
}
and
<tr ng-repeat="row in $data">
<td align="left">
<p ng-click="row.toggle();">{{row.description}}</p>
<div ng-show="row.showMe">
See the plunkr for more code, note the expand/collapse buttons are in the "menu".
However, I can't figure out a way to now toggle ALL of the rows on and off. I want to be able to somehow run a for loop over the rows and then call toggle if needed, however my attempts at doing so have failed. See them below:
TestCase.prototype.expandAllAttemptOne = function() {
for (var row in this) {
if (!row.showMe)
row.showMe = !row.showMe;
}
}
function expandAllAttemptOneTwo(data) {
for (var i in data) {
if (!data[i].showMe)
data[i].showMe = !data[i].showMe;
}
}
Any ideas on how to properly toggle all rows on/off?
Using the ng-show directive in combination with the ng-click and ng-init directives, we can do something like this:
<div ng-controller="TableController">
<button ng-click="setVisible(true)">Show All</button>
<button ng-click="setVisible(false)">Hide All</button>
<ul>
<li ng-repeat="person in persons"
ng-click="person.visible = !person.visible"
ng-show="person.visible">
{{person.name}}
</li>
</ul>
</div>
Our controller might then look like this:
myApp.controller('TableController', function ($scope) {
$scope.persons = [
{ name: "John", visible : true},
{ name: "Jill", visible : true},
{ name: "Sue", visible : true},
{ name: "Jackson", visible : true}
];
$scope.setVisible = function (visible) {
angular.forEach($scope.persons, function (person) {
person.visible = visible;
});
}
});
We are doing a couple things here. First, our controller contains an array of person objects. Each one of these objects has a property named visible. We'll use this to toggle items on and off. Second, we define a function in our controller named setVisible. This takes a boolean value as an argument, and will iterate over the entire persons array and set each person object's visible property to that value.
Now, in our html, we are using three angular directives; ng-click, ng-repeat, and ng-show. It seems like you already kinda know how these work, so I'll just explain what I'm doing with them instead. In our html we use ng-click to set up our click event handler for our "Show All" and "Hide All" buttons. Clicking either of these will cause setVisible to be called with a value of either true or false. This will take care of toggling all of our list items either all on, or all off.
Next, in our ng-repeat directive, we provide an expression for angular to evaluate when a list item is clicked. In this case, we tell angular to toggle person.visible to the opposite value that it is currently. This effectively will hide a list item. And finally, we have our ng-show directive, which is simply used in conjunction with our visible property to determine whether or not to render a particular list item.
Here is a plnkr with a working example: http://plnkr.co/edit/MlxyvfDo0jZVTkK0gman?p=preview
This code is a general example of something you might do, you should be able to expand upon it to fit your particular need. Hope this help!

Resources