How do I dynamically style uib-accordion-group - angularjs

I have created a uib-accordion in my Angular website and can get most of the functionality I want, with dynamic content changing accordingly.
I am having trouble styling the uib-accordion-group dynamically.
<uib-accordion-group panel-class="panel-danger">
<uib-accordion-heading>
Accordion Heading 1
Is fine and colours the whole heading Red/Pink, I want to change this to panel-warning or panel-info based on other variables on the page.
<uib-accordion-group panel-class="{{getPanelColor()}}">
<uib-accordion-heading>
Accordion Heading 1
The function seems to be called correctly and is triggered correctly with ng-click elsewhere.
I appears that I cannot change the value panel-class uses dynamically. So in this instance getPanelColor() returns 'panel-danger', 'panel-info' or 'panel-warning' depending on other variables. If I print this return value out on the page in another div or whatever it changes correctly. If I refresh the page the correct colours are displayed for the changed panel-group.
Is there another way of setting the color - I don't know what the classes are for the accordion-group. I have tried changing the color of a div withing the panel, but this is a child element and does not change the color of the whole heading.
Any help much appreciated. (I'll come up with a JSFiddle if the question is not clear)

If you look at the HTML after the panel-class has changed and Angular has digested the change, you will see this line:
<div class="panel panel-danger" ... panel-class="panel-default">
That is, there is a mismatch between class and panel-class (the former has panel-danger, whereas the latter has panel-default). The uib-accordion-group directive simply does not handle the change in the wanted manner.
One workaround is to add ng-if to the whole group:
<uib-accordion-group ng-if="render" panel-class="{{getPanelColor()}}">
... and just before you want to change panel-class, remove the whole element temporarily, so that Angular re-renders it from scratch. Hopefully, the following code explains the principle:
$scope.render = true;
$scope.panelColor = 'panel-danger';
$scope.setPanelColor = function(val) {
$scope.panelColor = val;
$scope.render = false;
$timeout(function () {
$scope.render = true;
});
};
$scope.getPanelColor = function() {
return $scope.panelColor;
};
See the proposal in action: http://plnkr.co/edit/XfJiPnNi1z4F9cgIVxxw?p=preview. Press 'Clear panel color OK'.
The downside is that the removal of the element causes some flickering.
I have added another button 'Clear panel color FAIL' that shows what happens in your failing case. Here is what the HTML looks like after you press the button, notice the mismatch panel-danger vs. panel-default:

Use an interpolated expression in the class attribute, for example:
class="{{!ctrl.valid?'notValid':'valid'}}"

Related

What am i missing here? Angularjs ionic sliders

I am using the sliders in the ionic framework and everything is working great. This is my html:
<ion-slide-box on-slide-changed="slideHasChanged($index)" show-pager={{showPager}}>
Pretty straightforward, notice that i am using showPager to dynamically show and hide the pager.
And in the controller:
$scope.showPager = true
$scope.slideHasChanged = ($index) ->
if $index == 4
$scope.$apply ->
$scope.showPager = false
It does as expected sets showPager value to false when the index of the slide is 4 in the web console.
BUT it does not really hide it on the web page. IF i explicitly set showPager to false, it does not display in the browser as expected, but if i try the above stuff to dynamically hide it, it does not work.
What am i missing?
<ion-slide-box> directive does not observe show-pager attribute. So it will consider only the value which was at the time of directive initialization.
For your use-case, you can show/hide pager by putting ng-class directive on <ion-slide-box> directive and based on that class you can write css to show/hide the pager.
Check this example codepen for working example.

Angular: Fading out element and then setting display none

When I click an element, I'd like another element to first fade out, and then be set to display: none.
I have the following code:
Partial:
<div class="main_menu_image" ng-class="{ fadeOut : MenuOpen==true }" /></div>
<div class="button" ng-click="ActivateMenu()"></div>
Then in my controller:
$scope.MenuOpen = false;
$scope.ActivateMenu = function(){
$scope.MenuOpen = $scope.MenuOpen === false ? true: false;
}
So when I click the button, element main_menu_image gets the class fadeOut. So it now fades out. But after the fading animation completes I would also like to set display to none on main_menu_image so it is completely hidden.
I don't want to resort to jQuery. Is there an Angular approved way of doing this?
Yes it's very easy to do:)
Like Svein says, you can use ng-show, and you can use ng-hide.
Working fiddle here
This hides things instantly though. But you can for example set a timeout, via the $timeout service, and set your hiding boolean in that way.
You can also use ng-if, this actually removes the element from the DOM if the condition is not met, rather than just setting display:none.
Update: Here's a more proper fiddle showcasing what you're trying to do
You can use ngShow. If you use the ngAnimate module, you'll have built in support for animations for most built-in Angular directives.
See here for more information:
https://docs.angularjs.org/guide/animations

Delayed compilation of hidden elements in angular.js

I have a fairly complex angular.js app that has a "show" mode and an "edit" mode on the same page. Most of the complexity is in the edit mode, and most users won't ever be in "edit" mode.
You can see an example here: http://www.chefsteps.com/activities/squeaky-cheese-curds (but you won't have access to edit mode). As you'll see, my page loads are slower than ideal.
In many places throughout the page I have whole complex nested sections that are protected by
ng-show="editMode"
Is there any way that I could have angular delay compiling the whole subtree under there either until editMode becomes true or at least until the rest of the page has rendered?
You could probably use a combination of ngSwitch and ngInclude.
For the sections you need to deal with, instead of the ng-show, use ng-switch:
<section ng-switch on='editMode'>
<!-- editing case, leave src
<div ng-switch-when='true'>
<div ng-include src='edittemplates.sectionFoo'></div>
</div>
<!-- show case -->
<div ng-switch-when='false'>
<!-- show some stuff -->
</div>
</section>
Usually, ng-switch will still compile all the DOM, but the trick here will be to initially
have the edittemplates object start out with empty keys:
App.controller('Foo', function($scope) {
$scope.edittemplates = {
sectionFoo: ''
};
});
Then later, when you switch to edit mode, fill in those template values:
$scope.edit = function() {
$scope.editMode = true;
if ($scope.edittemplatesLoaded) return; // Don't set these twice
$scope.edittemplates.sectionFoo = 'sectionFoo.html';
$scope.edittemplates.sectionBar = 'sectionBar.html';
// etc.
$scope.edittemplatesLoaded = true;
};
Because the src attribute initially will be empty, there will be nothing to compile initially.
I haven't tried this, as I haven't needed anything like it yet, but it should work.

How to get hold of template html in directive

I'm trying to create a simple time picker directive. http://plnkr.co/edit/VYGqhPbHf1yqXLpemGEP
When user click on input field I want to display the content of my template html as a dropdown below the input (will take care of css later) so that user can select a time from the list. I'm not sure how to get hold of the template (something like angular.element(template).show())
Thanks!
Edit: I managed to come up with this: http://plnkr.co/edit/zAplNKVfohXLbIzwjhy4?p=preview
Everything works except when I click any of the list, it does not set the correct model value.
Try the following:
Embed the the HTML for the date picker list
Hide the list from the html
If the input gets focus change the visibility.
Pseudo code:
HTML:
<ul ng-show="listVisible">
<li> .... </li>
</ul>
JavaScript
scope.listVisible = false;
element.$on('focus', function() {
scope.listVisible = true;
});
Do something similar in reverse.
I managed to get it working. I had to create a new scope for the dropdown element. http://plnkr.co/edit/zAplNKVfohXLbIzwjhy4?p=preview

What's a good way to control an angular-ui accordion programmatically?

I am using the accordion directive from http://angular-ui.github.com/bootstrap/ and I need to have more control over when the accordions open and close.
To be more precise I need a button inside the accordion-group that will close its parent accordion and open the next one (so basically mimic what clicking the next header would do if close-others was set to true).
I also need to do some validation before I can allow an accordion to be closed and the next one to be opened, and I also need to wire this up to click events on the accordion headers.
I am pretty new to angular and we're currently rewriting an application from Backbone+JQuery to Angular. In the Backbone-version we were using Twitter Bootstrap accordions and we were opening and closing them using JQuery. While we can still keep doing this I would rather get rid of JQuery DOM manipulation completely so I am looking for a pure angular solution to this.
What I've tried to do in terms of validation is
<accordion-group ng-click="close($event)">
and in my controller
event.preventDefault();
event.stopPropagation();
This obviously did not work as the DOM element is replaced by the directive and the click-handler is never added. I've been going over the source code (and found a few very nice undocumented features) but I'm at a loss over where to even begin solving this specific challenge. I was considering forking angular-ui and try to add this functionality to the accordion directive but if I can achieve this without modifying the directive that would be a lot nicer.
There is the is-open attribute on the accordion-group which points to a bindable expression. By using this expression you can control accordion items programatically, ex.:
<div ng-controller="AccordionDemoCtrl">
<accordion>
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
{{group.content}}
</accordion-group>
</accordion>
<button class="btn" ng-click="groups[0].open = !groups[0].open">Toggle first open</button>
<button class="btn" ng-click="groups[1].open = !groups[1].open">Toggle second open</button>
</div>
and the working plunk here: http://plnkr.co/edit/DepnVH?p=preview
For whoever the solution by #pkozlowski.opensource is not working (me for example) you could just force the component to accept the CSS that will close it (without transition that is).
The Theory: The angular directive gets expanded into standard HTML, div elements mainly, where the CSS styles give it the appearance of the accordion. The div with class .panel-collapse is the body of the accordion-group element. You can swap its second class from .in to .collapse along with a few other changes as seen below.
The Code:
$scope.toggleOpen = function(project) {
var id = '<The ID of the accordion-group you want to close>';
var elements = angular.element($document[0].querySelector('#'+id));
var children = elements.children();
for(var i = 0; i < children.length; i++) {
var child = angular.element(children[i]);
if(child.hasClass('panel-collapse')) {
if(child.hasClass('in')) { // it is open
child.removeClass('in');
child.addClass('collapse');
child.css('height', '0px');
} else { // it is closed
child.addClass('in');
child.removeClass('collapse');
child.css('height', 'auto');
}
}
}
};
As we are talking about Angular, it is very possible that you are generating the accordion through an ng-repeat tag. In this case you can also generate the id's for the elements like:
<accordion-group ng-repeat="user in users"
is-disabled="user.projects.length == 0"
id="USER{{user._id}}">
Given a Mongoose model User, notice that the id I am giving is not user._id but has 'USER' appended in front. This is because Mongoose might generate id's that start numerically and querySelector does not like that ;-) go figure!

Resources