close-others "true" not working in ui-accordion when content is transclude - angularjs

I have implemented accordion in my application. but close-other = "true" seems not working.
I implemented a common compenent to use collapsible component in my application-
Component-
var uiCollapsiblePanel = {
transclude: {
header: 'uiCollapsiblePanelHeader',
body: 'uiCollapsiblePanelBody'
},
bindings: {
isBlockExpanded: '<',
triggerCollapse: '&'
},
templateUrl: './ui-collapsible-panel.html',
controller: 'uiCollapsiblePanelController',
controllerAs: 'ucpc'
};
angular
.module('common')
.component('uiCollapsiblePanel', uiCollapsiblePanel);
ui-collapsible-panel.html -
<div class=" ui-collapsible-panel">
<uib-accordion close-others="true">
<div uib-accordion-group is-open="$ctrl.isBlockExpanded">
<uib-accordion-heading>
<div ng-transclude="header">
</div>
</uib-accordion-heading>
<div ng-transclude="body"></div>
</div>
</uib-accordion>
</div>
Calling ui-coolapsible-component-
<div class="app-custom-accordian">
<ui-collapsible-panel is-block-expanded='false'>
<ui-collapsible-panel-header>
<request-header all-requests-data="mrtt.allRequestsData">
</request-header>
</ui-collapsible-panel-header>
<ui-collapsible-panel-body>
<request-body all-requests-data="mrtt.allRequestsData" class="request-body-container"></request-body>
</ui-collapsible-panel-body>
</ui-collapsible-panel>
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
can someone help me to identify whats the issue?

In this example you implemented single accordion. This function will work if you have multiple uib-accordion-group's. And opening of one will cause other to close.
By documentation (https://angular-ui.github.io/bootstrap/): close-others $ C (Default: true) - Control whether expanding an item will cause the other items to close."
Hope, it will help.
If this is not the case, i suggest to expand example with more details
(edited)
so as i understood correctly you are trying to do generic reusable accordion. For that you created 'ui-collapsible-panel.html'.
The root wrapper 'uib-accordion' needs multiple uib-accordion-group's.
In this example you are transcluding inside one group.
<div uib-accordion-group is-open="$ctrl.isBlockExpanded">
<uib-accordion-heading>
<div ng-transclude="header"></div>
</uib-accordion-heading>
<div ng-transclude="body"></div>
</div>
Which means you wont able to use 'close-others'.
For this to work, you either need some ng-repeat or transclude directly uib-accordion-group's. Basicaly this multiple transclude (for header and body) is not usefull, because all this data must be in same parent "uib-accordion". Example of direct transclude:
<ui-collapsible-panel>
// preload some data (for example groups)
<div uib-accordion-group is-open="$ctrl.isBlockExpanded" ng-repeat="group in $ctrl.groups">
<uib-accordion-heading>
// and use group data to for request-header and request-body
<request-header all-requests-data="mrtt.allRequestsData"></request>
<uib-accordion-heading>
<request-body all-requests-data="mrtt.allRequestsData" class="request-body-container"></request-body>
</div>
</ui-collapsible-panel>
Reference https://angular-ui.github.io/bootstrap/ (orignal uib-bootstrap plunker example)

Related

Angularjs compile a directive inside ng-repeat with isolated scope

I have a directive in the form of a dropdown, pretty simple. The user can click a button to add as many as they need to in a ul, make their selections, and save it off. This is all inside of several ng-repeats.
I'm having trouble mastering the scope. As I expected, this works:
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li><new-case group='group'></new-case></li>
</ul>
</div>
</div>
When I say "works", I mean that group is properly scoped (the data of the entire group is necessary for the resulting input).
When I switch it to "click to add":
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li>add case</li>
</ul>
</div>
</div>
group is undefined in the scope. Here is my createNewCase function:
function createNewCase($event) {
var thisLi = angular.element($event.target).closest('li');
var listItem = $compile('<li><new-case group=\'group\'></new-case></li>');
var html = listItem($scope);
thisLi.before(html);
}
$scope.createNewCase = createNewCase;
And the newCase directive:
angular.module('groups.directives.newCaseDirective', [])
.directive('newCase', ['$window', function() {
return {
restrict: 'EA',
scope: { group: '=' },
templateUrl: 'groups/views/newcase.tpl.html'
};
}]);
I've been reading for days and I've tried a few other derivatives but I'm ultimately just not getting it. Help is greatly appreciated.
Thanks!
The issue is that group is created by ng-repeat and is only available in child scopes of ng-repeat.
Each repeated element is in it's own child scope. So your directive version works but your other one doesn't because the controller doesn't see those child scopes.
You would have to pass group as argument of the function if you want to access it in controller
<a href="#" ng-click="createNewCase($event, group)">

AngularJS: Using directives as templating

I'm using a directive to provide a basic template for many of the pages in my Angular app. It looks like this:
angular.module('app.basicLayout', [])
.directive('basicLayout', function () {
return {
restrict: 'E',
transclude: true,
templateUrl: '/assets/views/templates/basicLayout.html'
}
});
And HTML:
<basic-layout>
<h1>My layout goes here</h1>
</basic-layout>
On some of these pages I would like to add a sidebar and still be able to use the layout from my <basic-layout> directive. Is it possible to make something like the following?
<basic-layout>
<h1>My content goes here</h1>
<the-sidebar>
<h2>Sidebar content here</h2>
</the-sidebar>
</basic-layout>
Update
My template file of the directive currently look like this:
<div class="container basic-layout">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div ng-transclude></div>
</div>
</div>
</div>
If <the-sidebar> is set, I would like to change the template file to something like this:
<div class="container basic-layout">
<div class="row">
<div class="col-md-8">
<!-- The content here -->
<div ng-transclude></div>
</div>
<div class="col-md-4">
<!-- The sidebar here -->
<div ng-transclude></div>
</div>
</div>
</div>
That's the exact case for transclusion. You can parametrize your directive layout with some variable layout (sidebar in this case). To do this your have to set the transclude property in the directive config object to true and also specify where in your directive's layout the changing content should be injected by using the ng-transclude directive. Like this:
return {
...
transluce: true,
...
}
and now in the directive template:
//other layout here
<div ng-transclude></div>
This way all the content you put inside the <basic-layout> directive will be transfered into the element on which you use ng-transclude.
For this to work, you need to manually transclude using the transclude function passed as a 5th parameter to the link function. To make it easier, I would change the template to have placeholders:
<div>
<content-placeholder></content-placeholder>
<div>
<sidebar-placeholder></sidebar-placeholder>
</div>
</div>
Then, place each content where it belongs:
transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
transclude(function(clone){
var sidebar = clone.find("the-sidebar").remove();
element.find("content-placeholder").replaceWith(clone);
element.find("sidebar-placeholder").replaceWith(sidebar);
});
}
This should work for you, but it's not clear to me why you want to build a directive for a general layout.
If you have many pages in the Web app (in a classical non-SPA sense), then it's probably better to create the scaffolding in a "master page" on the server.
Otherwise, if you mean that you have many "views" of the same app, the I suggest looking into ui-router - specifically into a section of Nested States and Nested Views.

How to show and hide items in dynamic rows in AngularJS?

I'm fairly new to AngularJS and I can't seem to find a way to do this appropriately. I created a custom directive to Apply a function a pass in the row Index. However, I can't seem to think of the way to show items in a row. What would be the best way to do this? I want to show specific and hide a target row via controller.
HTML:
<div class="row" data-index="{{$index}}">
<div>other information</div>
<div class="item hidden" ng-class="{hidden: hidden[{{$index}}]}">
Item
</div>
</div>
My Directive:
scope.$apply(function () {
scope.$parent.showItem(index);
});
Controller:
$scope.teamDrop = function(index) {
$scope.hidden[index] = false;
};
You can use the ng-show and ng-hide directives to hide and show elements.
You can also use the ng-if directive to remove elements from the dom.
For your example I'd change your ng-class to an ng-hide
<div class="row" data-index="{{$index}}">
<div>other information</div>
<div class="item hidden" ng-hide="hidden[$index]">
Item
</div>
</div>
You also don't need to use the {{}} syntax in the ng-class becausue it's already expecting an angular expression, that's for data binding.

Angular.js ng-switch-when not working with dynamic data?

I'm trying to get Angular to generate a CSS slider based on my data. I know that the data is there and am able to generate it for the buttons, but the code won't populate the ng-switch-when for some reason. When I inspect the code, I see this twice (which I know to be correct as I only have two items):
<div ng-repeat="assignment in assignments" ng-animate="'animate'" class="ng-scope">
<!-- ngSwitchWhen: {{assignment.id}} -->
</div>
My actual code:
<div ng-init="thisAssignment='one'">
<div class="btn-group assignments" style="display: block; text-align: center; margin-bottom: 10px">
<span ng-repeat="assignment in assignments">
<button ng-click="thisAssignment = '{{assignment.id}}'" class="btn btn-primary">{{assignment.num}}</button>
</span>
</div>
<div class="well" style="height: 170px;">
<div ng-switch="thisAssignment">
<div class="assignments">
<div ng-repeat="assignment in assignments" ng-animate="'animate'">
<div ng-switch-when='{{assignment.id}}' class="my-switch-animation">
<h2>{{assignment.name}}</h2>
<p>{{assignment.text}}</p>
</div>
</div>
</div>
</div>
</div>
EDIT: This is what I'm trying to emulate, though with dynamic data. http://plnkr.co/edit/WUCyCN68tDR1YzNnCWyS?p=preview
From the docs —
Be aware that the attribute values to match against cannot be expressions. They are
interpreted as literal string values to match against. For example, ng-switch-when="someVal"
will match against the string "someVal" not against the value of the expression
$scope.someVal.
So in other words, ng-switch is for hardcoding conditions in your templates.
You would use it like so:
<div class="assignments">
<div ng-repeat="assignment in assignments" ng-animate="'animate'">
<div ng-switch="assignment.id">
<div ng-switch-when='1' class="my-switch-animation">
<h2>{{assignment.name}}</h2>
<p>{{assignment.text}}</p>
</div>
</div>
</div>
Now this might not fit your use case exactly, so it's possible you'll have to rethink your strategy.
Ng-If is probably what you need — also, you need to be aware of "isolated" scopes. Basically when you use certain directives, like ng-repeat, you create new scopes which are isolated from their parents. So if you change thisAssignmentinside a repeater, you're actually changing the variable inside that specific repeat block and not the whole controller.
Here's a demo of what you're going for.
Notice I assign the selected property to the things array (it's just an object).
Update 12/12/14: Adding a new block of code to clarify the use of ng-switch. The code example above should be considered what not to do.
As I mentioned in my comment. Switch should be thought about exactly like a JavaScript switch. It's for hardcoded switching logic. So for instance in my example posts, there are only going to be a few types of posts. You should know a head of time the types of values you are going to be switching on.
<div ng-repeat="post in posts">
<div ng-switch on="post.type">
<!-- post.type === 'image' -->
<div ng-switch-when="image" class="post post-image">
<img ng-src="{{ post.image }} />
<div ng-bind="post.content"></div>
</div>
<!-- post.type === 'video' -->
<div ng-switch-when="video" class="post post-video">
<video ng-src="{{ post.video }} />
<div ng-bind="post.content"></div>
</div>
<!-- when above doesn't match -->
<div ng-switch-default class="post">
<div ng-bind="post.content"></div>
</div>
</div>
</div>
You could implement this same functionality with ng-if, it's your job to decide what makes sense within your application. In this case the latter is much more succinct, but also more complicated, and you could see it getting much more hairy if the template were any more complex. Basic distinction is ng-switch is declarative, ng-if is imperative.
<div ng-repeat="post in posts">
<div class="post" ng-class="{
'post-image': post.type === 'image',
'post-video': post.type === 'video'">
<video ng-if="post.type === 'video'" ng-src="post.video" />
<img ng-if="post.type === 'image'" ng-src="post.image" />
<div ng-bind="post.content" />
</div>
</div>
Jon is definitely right on. Angular does not support dynamic ngSwitchWhen values. But I wanted it to. I found it actually exceptionally simple to use my own directive in place of ngSwitchWhen. Not only does it support dynamic values but it supports multiple values for each statement (similar to JS switch fall-throughs).
One caveat, it only evaluates the expression once upon compile time, so you must return the correct value immediately. For my purposes this was fine as I was wanting to use constants defined elsewhere in the application. It could probably be modified to dynamically re-evaluate the expressions but that would require more testing with ngSwitch.
I am use angular 1.3.15 but I ran a quick test with angular 1.4.7 and it worked fine there as well.
Plunker Demo
The Code
module.directive('jjSwitchWhen', function() {
// Exact same definition as ngSwitchWhen except for the link fn
return {
// Same as ngSwitchWhen
priority: 1200,
transclude: 'element',
require: '^ngSwitch',
link: function(scope, element, attrs, ctrl, $transclude) {
var caseStms = scope.$eval(attrs.jjSwitchWhen);
caseStms = angular.isArray(caseStms) ? caseStms : [caseStms];
angular.forEach(caseStms, function(caseStm) {
caseStm = '!' + caseStm;
ctrl.cases[caseStm] = ctrl.cases[caseStm] || [];
ctrl.cases[caseStm].push({ transclude: $transclude, element: element });
});
}
};
});
Usage
Controller
$scope.types = {
audio: '.mp3',
video: ['.mp4', '.gif'],
image: ['.jpg', '.png', '.gif'] // Can have multiple matching cases (.gif)
};
Template
<div ng-switch="mediaType">
<div jj-switch-when="types.audio">Audio</div>
<div jj-switch-when="types.video">Video</div>
<div jj-switch-when="types.image">Image</div>
<!-- Even works with ngSwitchWhen -->
<div ng-switch-when=".docx">Document</div>
<div ng-switch-default>Invalid Type</div>
<div>

how to make tags within the custom directives go to specific place of template?

I have a directive by name cngView
controller.js
//directive to include view panel
angular.module('cngFoundation', [])
.directive('cngView', function() {
return {
restrict : 'CAME',
templateUrl: 'templates/view.html'
};
});
to access directive
<div ng-app="cngFoundation">
<div id="mainbody" class="container">
<cng-view>
[AAA]
</cng-view>
</div>
</div>
The templates/view.html is as follows
<!-- View Panel-->
<div class="panel panel-primary " id="view" >
<div class="panel-heading">
<h3 class="panel-title" ><b>View</b></h3>
</div>
<div class="panel-body">
[BBB]
</div>
</div>
I have made the cng-view tag work but I don't know how to give contents inside the tag.
How to make contents I give at place "[AAA]" appear at "[BBB]" .
In short how to make contents I give within cng-view tag(a custom tag) go into the specific place inside the template content of the cng-view .
Please give guidance . At least please direct me to some online tutorial .
Thanks
You need to use ng-transclude which tells template where to place existing content
<div class="panel-body" ng-transclude></div>
Also need to set transclude:true in directive
See section Creating a Directive that Wraps Other Elements in directive docs
You can use the built in ng-transclude directive. In your view.html replace [BBB] with
<span ng-transclude></span>
also be sure to define transclude on your directive
transclude: true,
For a working example see this plunkr

Resources