We are making use of the angularjs accordion, however, our accordion is fairly big, and contains a lot of data.
What we are looking at doing is loading the data when the accordion opens. ie dont render the internal content, until a user click on the accordion header.
Below is how we are using the accordion at the moment:-
<accordion close-others="false">
<accordion-group heading="{{result.ReceivedDateTime}}{{result.OrderName}}" ng-repeat="result in resultsbyorder">
<table id="tbl" class="table table-striped table-bordered" ng-repeat="Grid in result.Results">
<tr ng-repeat="item in Grid" ng-show="$first">
<th ng-repeat="somat in item">
{{somat.ColumnHeader}}
</th>
</tr>
<tr ng-repeat="item in Grid">
<td ng-repeat="somat in item">
{{somat.ColumnValue}}
</td>
</tr>
</table>
</accordion-group>
</accordion>
as a side note, is it possible to only render the accordion headers that are displayed on the screen, then render the remaining on scrolling?
I think what you are better off doing is to compile it once when it is opened the first time.
That way, you don't have to compile the same content again, even though it was compiled before the last time the accordion group was opened.
Over the top of my head, I can think of using the ng-switch directive. As you must be aware, this directive allows to display DOM elements conditionally, based on the case variable.
However, where this directive differs from the ng-show or ng-hide directive is that the contents within the case statement are not compiled until the corresponding case variable is matched.
Thus, what you could do is have the following setup in your HTML document:
<accordion close-others="oneAtATime">
<accordion-group is-open="isopen">
<accordion-heading>
Accordion Heading
</accordion-heading>
<div ng-switch on="isopen">
<div ng-switch-when="true">
<!-- Content that should be compiled when the accordion is opened
goes here -->
</div>
</div>
</accordion-group>
</accordion>
A demonstration of the same can be found in this plunkr.
Also, I believe ng-if directive also achieves the same effect. So, you can use either of the directives to achieve your requirements.
I am doing this very thing at the moment and have found that Angular likes using a collection (even if it's just a single element) to make the accordion, to wit if you do, you can bind the is-open to an element on that group and then setup a watch on that group in the controller. Consider the following (think of putting an ajax call to get your data instead of logging to console):
var ngtestapp = angular.module("ngtestapp", ['ui.bootstrap']);
ngtestapp.controller("ngTestController", function ($scope) {
$scope.userComments = [
{
RowId: 123,
DateCreated: new Date(2015, 1, 2, 0, 0, 0, 0),
CreatedBy: 564,
Message: 'Carpe Dieum',
CreatedByDisplayName: 'Daniel Graham'
},
{
RowId: 124,
DateCreated: new Date(2015, 1, 5, 0, 0, 0, 0),
CreatedBy: 562,
Message: 'That was awesome',
CreatedByDisplayName: 'Peter Griffin'
}
];
$scope.feedbackGroup = [{ title: "User Comments", open: false }];
$scope.$watch('feedbackGroup', function (feedbackGroup) {
if (feedbackGroup[0].open) {
console.log("opened feedback group.");
} else {
console.log("closed feedback group.");
}
}, true);
$scope.userTaskNote = "user task test note";
});
.nav, .pagination, .carousel, .panel-title a { cursor: pointer; }
<html lang="en" ng-app="ngtestapp">
<head>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.1/ui-bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.1/ui-bootstrap-tpls.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
</head>
<body>
<div ng-controller="ngTestController">
<div class="container-fluid">
<accordion close-others="true">
<accordion-group ng-repeat="group in feedbackGroup" heading="{{group.title}}" is-open="group.open">
<ul class="list-group">
<li class="list-group-item" ng-repeat="comment in userComments track by $index | orderBy:comment.DateCreated">
<span class="badge" ng-bind="comment.DateCreated | date:'shortDate'"></span>
<strong ng-bind="comment.CreatedByDisplayName">Username</strong> <p ng-bind="comment.Message">comment</p>
</li>
</ul>
</accordion-group>
<accordion-group is-open="false" heading="Other">
<textarea class="form-control input-sm" ng-model="userTaskNote" placeholder="Add a task note here..."></textarea>
</accordion-group>
</accordion>
</div>
</div>
</body>
</html>
Related
I have an accordion object in AngularJS that is data driven. Here is what my html looks like:
HTML:
<div class="field-accordion" ng- if="field.fieldAccordion">
<ul class=a ccordion>
<li ng-repeat="fieldAccordion in field.fieldAccordion" ng- click="accordion.current = fieldAccordion.fieldName">
{{ fieldAccordion.fieldName }}
<ul ng-show="accordion.current == fieldAccordion.fieldName">
<li ng-repeat="fieldSub in fieldAccordion.fieldSub">
{{fieldSub.fieldName}}
</li>
</ul>
</li>
</ul>
</div>
Then in my JS file, I simply use it like this:
JS:
app.controller("myCtrl", function($scope) {
$scope.mySettings({
Header: '',
Title: '',
Img: '',
fieldAccordion: [{
// "this is my accordion list"
This works great for me. Whenever I click on the parent, it expands. The problem that I'm having is I can't get it to collapse. I am also trying to do it while keeping everything data driven like it is now. Is there anything I can change in my HTML to allow the text to collapse after expanding ?
Thank you
So the problem is because of a new scope being created, what you need to know is ng-if and ng-r creates a new scope, I checked your code and the ng-repeat is the root cause of the issue! hence the variable is not updating and the accordion is not toggling. Here is a simple way to update the parent controller using the $parent.
I have created a mockup highlighting the issue. So instead of updating accordion.current which will update the scope created by the ng-if you can just give it as $parent.accordion.current in the ng-click which will update the parent scope and the variable will be available for the ng-show and the accordion toggling works as expected.
Below is a working snippet, let me know if this fix solved your issue!
var app = angular.module('myApp', []);
app.controller('MyController', function MyController($scope) {
$scope.field = {
Header: '',
Title: '',
Img: '',
fieldAccordion: [{
fieldName: "one",
fieldSub: [{
fieldName: "oneone"
}]
}, {
fieldName: "two",
fieldSub: [{
fieldName: "twotwo"
}]
}]
};
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div class="field-accordion" ng-if="field.fieldAccordion">
<ul class="accordion">
<li ng-repeat="fieldAccordion in field.fieldAccordion" ng-click="$parent.accordion.current = fieldAccordion.fieldName">
{{ fieldAccordion.fieldName }}
<ul ng-show="accordion.current == fieldAccordion.fieldName">
<li ng-repeat="fieldSub in fieldAccordion.fieldSub">
{{fieldSub.fieldName}}
</li>
</ul>
</li>
</ul>
</div>
</div>
Here is my code
<script>
var app = angular.module('TestApp', []);
app.controller('TestCtrl', function ($scope) {
$scope.tests = [
{
id: '1',
name: 'Test 1',
count: '10',
children: [{
name: 'Test Child 1'
}]
},
{
id: '2',
name: 'Test 2',
count: '19',
children: [{
name: 'Test Child 2'
}]
},
{
id: '3',
name: 'Test 3',
count: '18',
children: [{
name: 'Test Child 3'
}]
}
];
});
function test() {
alert($('#testtest').attr('href'));
}
</script>
<div ng-app="TestApp" ng-controller="TestCtrl">
<div class="panel panel-default">
<div class="panel-heading clearfix" ng-repeat="test in tests">
<a id="testtest" data-toggle="collapse" href="#div_{{test.id}}" onclick="test();" class="pull-left" style="padding-top: 7.5px;">{{test.id + ' ' + test.name}} <span class="badge">{{test.count}}</span></a>
</div>
<div id="div_{{test.id}}" class="panel-collapse collapse">
<div class="panel-body">
<table id="tbl_{{test.id}}" class="table table-hover">
<thead>
<tr>
<td>Name</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="child in test.children">
<td>{{child.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
I'm trying to create a list of panels, with tables in it.
The outcome is:
When I click the name of the panel, it was suppose to expand the panel body, but the panel body wasn't expanding. So I alert() thehref="#div_{{test.id}}"it showed #div_1 for all three items.
It is suppose to be #div_1, #div_2 and #div_3 just like the 1,2 and 3 number generated on the panel header.
What is my mistake?
P/s: Sorry for the bad explaination
You are ng-repeating only the headers, you need to do it for the panels too, please try this out and let me know:
<div ng-app="TestApp" ng-controller="TestCtrl">
<div class="panel panel-default">
<div class="panel-heading clearfix" ng-repeat="test in tests">
<a id="testtest" data-toggle="collapse" href="#div_{{test.id}}" onclick="test();" class="pull-left" style="padding-top: 7.5px;">{{test.id + ' ' + test.name}} <span class="badge">{{test.count}}</span></a>
</div>
<div id="div_{{test.id}}" class="panel-collapse collapse" ng-repeat="test in tests">
<div class="panel-body">
<table id="tbl_{{test.id}}" class="table table-hover">
<thead>
<tr>
<td>Name</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="child in test.children">
<td>{{child.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
Malkus and Fedaykin both pointed out the important concept of development using AngularJS. My answer is just a wrap of these two. I have made a working example here.
Changes that I made:
<a id="testtest" data-toggle="collapse" ng-href="#div_{{test.id}}"
class="pull-left" style="padding-top: 7.5px;" ng-click="selectTest(test)">
{{test.id + ' ' + test.name}}<span class="badge">{{test.count}}</span>
</a>
As you can see, I have used ng-click to pass the select test object to the controller and console.log(test). You will be able to see the object in the console.
$scope.selectTest = function(test) {
console.log(test);
$scope.selectedTest = test;
}
This approach allows you to have the whole object and modify it.
Another change I made is:
<div class="panel-collapse collapse" ng-attr-id="div_{{test.id}}"
ng-repeat="test in tests">
...
</div>
As Fedaykin mentioned, you only ng-repeat the panel header, which means the scope of test is only in the header. You are not able to access the test out of the header, because it is out of scope. Therefor you have to do another ng-repeat on the panel body.
I don't like this approach because it gives you less control what it has to display. And when you are encountering a giant data, you don't want to render all the data to front-end because it will cause latency and etc. So, the best approach is display the selected data when users click the corresponding panel.
Suggestion
Try to using ng-click with ng-show, ng-hide, ng-if to do this.
I see a few things that are giving you trouble:
You want to use ng-href hence the errors with the alerts. Also you should use ng-click not onclick
<a id="testtest" data-toggle="collapse" ng-href="#div_{{test.id}}"
ng-click="test();" class="pull-left" style="padding-top: 7.5px;">
{{test.id + ' ' + test.name}}
<span class="badge">{{test.count}}</span></a>
For the panels that aren't exanding ng-repeat only repeats the element and its contents. The section below starts after the ng-repeat ends. You should also use ng-attr-id to update the ID with a value from the scope
<div ng-attr-id="div_{{test.id}}" class="panel-collapse collapse">
A good rule of thumb with angular:
any time you want to use something from the scope to update an element or element attribute see if angular has a directive to manage it. AngularJS compiles the DOM you should not be accessing or manipulating elements without a directive, in this case ng-href, ng-click and ng-attr-id
I'm using Angular JS v. 1.2.26 and I've spent hours trying to figure out why I cannot get certain expressions to appear in the view.
A complete, minimal, example is below (or jsfiddle here):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="text/javascript">
var screen_id = '430732';
</script>
<script type="text/javascript" src="/public/scripts/vendor/angular.min.js"></script>
<script>
var tsApp = angular.module('tsApp',[]);
var screenCtrl = tsApp.controller('screenCtrl', function($scope) {
$scope.columns = [null,[null,{"name":"","id":"583","column":1,"order":1,"display_class":"static-text","block_agency":"static-text","block_mode":"static-text","custom_param":"","custom_body":"<b>This demo screen is brought to you by TransitScreen.com</b>"},{"name":"U St NW+ 17th St NW","id":"591","column":1,"order":2,"display_class":"masstransit","block_agency":"metrobus","block_mode":"bus","stops":[{"agency":"metrobus","mode":"bus","name":"U St NW+ 17th St NW","vehicles":[{"agency":"metrobus","mode":"bus","name":"U St Nw + 17th St Nw","full_route":"96","short_route":"96","destination":"Capitol Heights Station","predictions":[9,42],"direction":"Eastbound","vehicle_number":0,"logo":"","display_route":"96","longname":"96","route_class":"bus_metrobus","destination_class":"bus_metrobus","longname_accessible":null,"prediction1":9,"prediction2":42,"units":"MINUTES"},{"agency":"metrobus","mode":"bus","name":"U St Nw + 17th St Nw","full_route":"90","short_route":"90","destination":"Anacostia Station","predictions":[10,39],"direction":"Southbound","vehicle_number":1,"logo":"","display_route":"90","longname":"90","route_class":"bus_metrobus","destination_class":"bus_metrobus","longname_accessible":null,"prediction1":10,"prediction2":39,"units":"MINUTES"}],"walk_directions":"","walk_minutes":null,"arrow_svg":null}]}]];
});//end screenCtrl
screenCtrl.$inject = ['$scope'];
</script>
</head>
<body class="screen-body total-cols-1" ng-app="tsApp">
<div class="page-holder" ng-controller="screenCtrl">
<div class="col" id="col-1">
<div ng-repeat="block in columns[1]">
<div ng-include="'/public/scripts/block.html'"></div>
</div>
</div>
</div>
</body>
</html>
Here is the contents of block.html:
<div ng-switch on="block.display_class">
<div ng-switch-when="masstransit">
<table>
<div ng-repeat="stop in block.stops">
<div ng-repeat="vehicle in stop.vehicles">
<h2>Test 1</h2>
{{ stop.mode }}
{{ block.display_class }}
{{ vehicle.vehicle_number }}
<tr class="" id="">
<td class="">
<h2>Test 2</h2>
{{ stop.mode }}
{{ block.display_class }}
{{ vehicle.vehicle_number }}
</td>
</tr>
</div><!-- ng-repeat -->
</div><!-- ng-repeat -->
</table>
</div>
</div><!-- end ng-switch -->
Which outputs this...
Test 1 shows the expected values twice (because there are two items in the loop). Then in Test 2 there is only one iteration and only the middle value appears.
The problem is not identified in your code above. There is nothing wrong with ng-repeat in a TR or TD. It is more likely something is wrong in your controller or somewhere else in your code.
I created a jsfiddle which shows an example of displaying data as near the data structure in your question as I could guess. It works fine.
<table ng-controller="myCtrl" border=1>
<tr ng-repeat="stop in block.stops">
<td ng-repeat="vehicle in stop.vehicles">
<dl>
<dt>Mode:</dt><dd>{{ stop.mode }}</dd>
<dt>Class:</dt><dd>{{ block.display_class }}</dd>
<dt>Number:</dt><dd>{{ vehicle.vehicle_number }}</dd>
</dl>
</td>
</tr>
</table>
EDIT
I fixed your jsfiddle to work. See edit 2 here You can't expect Angular to work properly on malformed HTML. You cannot wrap TRs in DIVs like that. Even if Angular works who knows how some browsers might treat it. You might have data popping out after the table. Table rendering is one of the parts of HTML that is fairly strict.
I know there are other questions about this but they don't seem to solve my problem.
This what my output is, and I thought it should be an accordion
I am fairly new to Angular so I may be missing something simple, but I can't seem to see it. This is going to go into a larger app that I am making, but I wanted to get it to work outside of that first.
Any help is appreciated.
Code:
demo.html
<!DOCTYPE html>
<html ng-app='myApp' >
<head>
<script src="js/app.js"></script>
<script scr="js/ui-bootstrap-tpls-0.10.0"></script>
<script src="js/angular.js"></script>
<link href="css/bootstrap.css" rel="stylesheet">
<title>Accordion Test</title>
</head>
<body>
<div class="col-md-3">
<div ng-controller="AccordionDemo">
<label class="checkbox">
<input type="checkbox" ng-model="oneAtATime">
Only open one at a time
</label>
<accordion close-others="oneAtATime">
<accordion-group heading="Item1" is-open="true">
the contenct of item one
</accordion-group>
<accordion-group heading = {{group.title}} ng-repeat="group in groups">
{{group.content}}
</accordion-group>
<accordion-group heading="{{dynamicitem}}">
<p>The body of the accordion group lol</p>
<button class="btn btn-default btn-lg glyphicon glyphicon-plus-sign" ng-click="addItem()">Add new items</button>
<div ng-repeat="item in items">{{item}}</div>
</accordion-group>
<accordion-group is-open="isopen">
<accordion-heading>
Open and Close <i class="pull-right glyphicon" ng-class=" {'glyphicon-chevron-down':isopen, 'glyphicon-chevron-right': !isopen} "></i>
</accordion-heading>
Your content is here
</accordion-group>
</accordion>
</div>
</div>
app.js
angular.module('myApp', ['ui.bootstrap']);
function AccordionDemo($scope){
$scope.oneAtATime = true;
$scope.groups = [
{
title: "Dynamic member 1",
content: "member 1 body"
},
{
title :"Dynamic member 2",
content :"member 2 body"
}
];
$scope.dynamicitem = 'Dynamic Item';
$scope.items = ['item 1', 'item 2', 'item 3'];
$scope.addItem = function(){
var newItem = $scope.items.length + 1;
$scope.items.push('Item' + newItem);
};
}
Edit:
Is it anything to do with the
<accordion-group is-open="isopen">
block? As the isopen might not bind to anything?
Plunk http://plnkr.co/edit/npZ35bGLiPkmVqaw5l4J?p=preview
as Ashesh said you had the angular paths wrong, you also have a working example on Angular Ui Team for the accordion control.
live example:
http://plnkr.co/edit/6zhm49PCgPZr8wKD5Szr?p=preview
You had the angular paths wrong, this, for example, gets the job done. You can start from there.
I am trying to make an angular.js view update itself when adding a comment. My code is as follows:
<div class="comment clearfix" data-ng-repeat="comment in currentItem.comments" data-ng-class="{bubble: $first}" data-ng-instant>
<div>
<p>
<span class="username">{{comment.user}}</span> {{comment.message}}
</p>
<p class="time">
10 minutes ago
</p>
</div>
</div>
<div class="comment reply">
<div class="row-fluid">
<div class="span1">
<img src="assets/img/samples/user2.jpg" alt="user2" />
</div>
<div class="span11">
<textarea class="input-block-level addComment" type="text" placeholder="Reply…"></textarea>
</div>
</div>
</div>
the scope is updated on enter:
$('.addComment').keypress(function(e) {
if(e.which == 10 || e.which == 13) {
$scope.currentItem.comments.push({
"user": "user3",
"message": $(this).val()
});
console.debug("currentItem", $scope.currentItem);
}
});
debugging $scope.currentItem shows that the comment has been added to it, however the view doesn't show the new comment. I suspect that the $scope is only being watched on its first level and that this is why the view doesn't update. is that the case? If so how can I fix it?
SOLUTION:
As Ajay suggested in his answer below I wrapped the array push into the apply function like this:
var el=$(this);
$scope.$apply(function () {
$scope.currentChallenge.comments.push({
"user": $scope.currentUser,
"message": el.val()
});
});
Modify the code to wrap inside scope.$apply because you are modifying the property outside the angular scope you have to use scope.$apply() to watch the values
I had to place my form inside the same div controller as the ng-repeat. I had two separate div controllers.
Use $scope.$parent.arrayObjet to modify the parent variables instead of $scope.arryaObject. It worked in my case.
<div class="col-sm-12 col-lg-12">
<div class="outer-container">
<div class="inner-container">
<div class="table-header">
<form id="teamForm" name="teamForm">
<table class="table table-bordered">
<thead>
<!-- Grid header -->
<tbody data-ng-if="(allTeamMembers.length==0)">
<tr>
<td class="text-center height_33_p" colspan="15"> {{noResultFound}} </td>
</tr>
</tbody>
<!-- Existing team member -->
<tbody data-ng-repeat="tm in teammemberDetailsArryobject|orderBy:orderByField:reverseSort" data-ng-form="innerForm_$index" data-ng-class="{'ng-submitted':innerForm_$index.$submitted}">
<tr></tr>
</tbody>
In the code I am deleting one record and add new row programmatically but all the rows are displayed in the ng-repeat rows(including deleted)