Push rows in a table rendered with ng-repeat in angular - angularjs

I want to push in an extra row inline in a table when the client clicks on the row. The data should not be prefetched, since I expect there to be at most 30 rows but where each row has associated data that would be unreasonable to fetch in one get.
My approach so far is to use ng-repeat to iterate my collection and render a table. When the client presses the row, the client expects details about the row to be shown inline as an extra row under the pressed row.
<tr ng-repeat="court in courts">
<td>{{court.name}}</td>
<td>{{court.number}}</td>
<td>{{court.nrOfPlayers}}</td>
<td>
<a href ng:click="toggle(court.number)">Details</a> <!-- click toggles extra row with data loaded async -->
</td>
</tr>
<!-- extra row here -->
I have managed to show the details beneath the table with a ng-show in a hacky way, but that is not what I want.
How do you accomplish this with angular.js? What is the angular way to do this?
Here is a fiddle with a stupid squash court example http://jsfiddle.net/HByEv/

I think a possible solution could be http://jsfiddle.net/HByEv/2/.
There is also an alternative for the "No players" message commented in the fiddle if you want to also get rid of the extra <tr ng-show="..."></tr>.
Edit:
As pointed in the comments, in AngularJS 1.2+ you can now use ng-repeat-start and ng-repeat-end to solve this problem.
Jossef Harush provided a fiddle: http://jsfiddle.net/3yamebfw/

One sample
var app = angular.module('test-app', []);
app.controller('MyController', function($scope, $rootScope, $timeout){
$scope.copy = {
p1: ['c1 p1', 'c1 p2'],
p3: ['c3 p1', 'c3 p2', 'c3 p3', 'c3 p4', 'c3 p5']
}
$scope.courts = [
{
"number": 1,
"name": "the best court",
"nrOfPlayers": 2
}, {
"number": 2,
"name": "the bad court",
"nrOfPlayers": 0
}, {
"number": 3,
"name": "the other court",
"nrOfPlayers": 5
}
];
$scope.loadPlayers = function(court){
//Implement your logic here
//Probably using ajax
$timeout(function(){
$scope.players = $scope.copy['p' + court.number] || [];
}, Math.random() * 2000);
}
$scope.shouDetails = function(court){
if(court.nrOfPlayers) {
delete $scope.players;
$scope.loadPlayers(court);
} else {
$scope.players = [];
}
}
})
Demo: Fiddle

Well. In fact, the main issue with your design is that you want to show thousands of rows in the same table. It will work, but it might be hard to render in some browsers (IE). For each rows, you will have a few bindings and each binding add watchers. You should always try to minimize the amount of binding in the page. I suggest you to use a pagination system in your array. Pagination on a list using ng-repeat
If you really want to do what you want without prerendering the rows, you will have to edit the dom, which is not a good practice in angular when avoidable. In my case, i would place the data somewhere else on the page, in a static zone. When i did something like this, I added a twitter bootstrap modal in my page and when the user clicked on "more info" the modal was opened with the info of the selected object.

Related

capturing the form data inside the Angularjs controller

I am new to Angularjs. I am having the form data which I need to make a post request to the server passing the data. I have done the UI and controller part inside the angularjs but do not know how to capture the form data. Please find the link to plnkr - Plnkr link
By clicking the add button, a new li element gets added and the same gets deleted when the minus sign is clicked. I need to get all the key value items into the below format for sending for Post request.
{
"search_params": [
{
"key": "search string",
"predicate": "matches",
"value": "choosen text"
},
{
"key": "search string",
"predicate": "not-matches",
"value": " search value"
},
{
"key": "search string",
"predicate": "matches",
"value": " search value"
}
]
}
How to capture the form data and construct the param object inside my controller inside the searchParams function inside the controller. Please let me know as I am new to Angularjs.
Updated Question
Based on the inputs, I am able to get the user details in the controller. But there are few things:
By default there will be one li element and when the user submits, the current li elements data should be captured in the controller.
Only when I add the criteria using the plus button, the array is getting updated, but the last elements data is not being updated in the model when submitted.
The same is holding good for the deleting the criteria too.
Link to updated Plunker - Plnkr Link
Expanding on #Chris Hermut and assuming you want an array of map, according to json you posted. You can do that by
var arr = [];
var form = {
name: 'asd',
surname: 'aasdasdsd',
wharever: 'asd'
}
arr.push(form);
//prentending to be another (key,value)
form.surname = 'hfg';
arr.push(form);
here's a fiddle illustrating just that.
Directives like <select>, <input> require ng-model attribute to correctly bind your input to $scope properties.
In your HTML markup you'll have to update your form elements with required attributes (like ng-model).
I would recommend (in controller/link) to use only one object for form data with different properties like
var form = {
name: '',
surname: '',
wharever: ''
}
Corresponding <input> would be ex. <input ng-model="form.name" type="text">
After you have your 'form' object populated you can do JSON.stringify(form) before your request (if your using some other content-type then application/json).

Combine two json source in AngularJS repeat for 'Products' and 'SelectedProducts'

I've got a table that lists all available products based on a json array. I've also got a json array that contains the products the user has previously selected. When I come back to the list of products I want to update the displayed list to show how many you've already choosen of each product.
<table>
<tr ng-repeat='product in products'>
<td>{{product.productId}}</td>
<td>{{product.name}}</td>
<td>{{product.count}}</td>
</tr>
</table>
(...)
$scope.products = '[{"productId": 1, "name":"Product A", "count": 0},
{"productId": 2, "name":"Product B", "count": 0},
{"productId": 3, "name":"Product C", "count": 0}]';
$scope.selectedProducts = '[{"productId": 2, "name":"Product B", "count": 5}]';
How can I update the products so that I get the correct count on the second product?
In a very robust way you could use filters
<table>
<tr ng-repeat='product in products' ng-init="{{product.count = (selectedProducts | filter:product.name:true)[0].count || 0}}">
<td>{{product.name}}</td>
<td>{{product.count}}</td>
</tr>
http://plnkr.co/edit/tPe0laCPdrXntu7LXa5a?p=preview
otherwise you will have to iterate in your controller over each element to look for a match or you can create a filter to do it in partial or service to put the logic out of controller. I'm afraid there is no magic solution (at least nothing that I can think off from top of my head)
If you are using $http for both products and selected products you can utilise promises received from those calls and call the iteration once or destroy watch at a point when you are confident it shouldn't be fired anymore
promises: https://docs.angularjs.org/api/ng/service/$q
promises
products = $http.get(//products)
selected = $http.get(//selected)
$q.all([products, selected]).then(function(){
//do the iteration here
})
watch
watchDestroyer = $scope.$watch('selectedProducts', function (){
//destroy watch if you are confident there will be no updates
watchDestroyer()
}, true)

Edit ng-repeated item using ngclick

Im learning Angular and am trying to build bits and bobs to learn. Ive tried to explain myself as clear as possible below - any help will be much appreciated.
Example:
http://jsbin.com/micasafetise/2/
http://jsbin.com/micasafetise/2/edit?html,js,console,output
I've created myself this bit of data
$scope.people =
[
{
"personID": 1,
"first_name": "Sam",
"last_name": "Stimpson",
"attending": false
},
{
"personID": 2,
"first_name": "Alison",
"last_name": "van Schoor",
"attending": true
},
{
"personID": 3,
"first_name": "Lindsay",
"last_name": "van Schoor",
"attending": false
}
];
I've created an output in my view like this:
<div ng-repeat="person in people">
<a href="" ng-click="isAttending()">
{{person.first_name}}{{person.last_name}} - {{person.attending}}
</a>
</div>
Now the bit I'm stuck on. I want to be able to click on a person and update their attending field from false to true. I understand I can use ng-click like this
$scope.isAttending = function() {
alert("is attending");
};
but do not have a clue how to update the person Ive clicked to change false to true in the $scope.people.
When I have achieved this I plan to have another ng-repeat with a filter to show those attending but Ill be able to do that part I believe.
can anyone help me or give me some advise, anything will be much appreciated at the moment.
Thanks in advanced.
I wrote out an example of what I'm trying to do here:
http://jsbin.com/micasafetise/2/
http://jsbin.com/micasafetise/2/edit?html,js,console,output
You could just pass the repeated person <a href="" ng-click="isAttending(person)"> and have your handler method take that object, so you can update its property (well you could do that inline as well in the view, but it is better to separate out and place the logic in the controller as you originally had).
$scope.isAttending = function(person) {
person.attending = true;
};
Just add a filter as well on both the sections to show who are all attending and who are not.
<div ng-repeat="person in people | filter:{attending:false} track by person.personID">
<a href="" ng-click="toggleAttending(person)">
{{person.first_name}}{{person.last_name}} - {{person.attending}}
</a>
</div>
and
$scope.toggleAttending = function(person) {
person.attending = !person.attending;
};
Demo
In your case you could probably optimize better (you have 2 filters on same list of people) by filtering out from the controller and populate to 2 list of people people.invited and people.attendance.
function updatePeopleAttendance(){
$scope.people.attending = [];
$scope.people.invited = [];
angular.forEach(people, function(person){
$scope.people[person.attending ? 'attending' : 'invited'].push(person);
});
}
Demo
or even better you could pass the id, index and status in the method and pull the item out of one array and push it the other one.

Resolving object dependencies on client using Restangular

I have problems with restoring my entity relations at the (AngularJS) client after retrieving them via REST using Restangular. I searched a lot, but could not find a clean solution. I have some years of programming experience, but I'm quite new to the AngularJS ecosphere and the REST paradigm.
The task
At my Spring Data backend I have two entities, Article and ArticleGroup, in a 1:n relationship. The resulting REST json (via Spring Data Rest) looks like this:
Article
{
"_embedded": {
"articles": [
{
"id": 1,
"name": "A Drink",
"price": 2.90,
"created": null,
"modified": null,
"active": false,
"displayOrder": 0,
"_links": {
"self": {
"href": "http://localhost:8080/articles/1"
},
"articleGroup": {
"href": "http://localhost:8080/articles/1/articleGroup"
}
}
},
...
ArticleGroup
{
"_embedded": {
"articleGroups": [
{
"id": 1,
"name": "Drinks",
"printer": null,
"_links": {
"self": {
"href": "http://localhost:8080/articleGroups/1"
},
"articles": {
"href": "http://localhost:8080/articleGroups/1/articles"
}
}
},
...
Now I want to display a tabgroup containing the articleGroup.name as the tab's label and a table of the articles in this articleGroup as its content:
<tabset>
<tab ng-repeat="group in articleGroups" heading="{{group.name}}">
<table class="table">
<tr ng-repeat="article in group.articles">
<td ng-click="increase(article)">{{article.name}}</td>
<td>{{article.price | currency }}</td>
...
</tr>
</table>
</tab>
</tabset>
I retrieve the articleGroups easily in my AngularJS controller using Restangular:
Restangular.all('articleGroups').getList()
.then(function(groups) {
$scope.articleGroups = groups;
});
This works fine and the tabs show up nicely. Now I can do so for the articles as well, but here I come upon the problem which I'm dealing with for days now.
The problem
How can I filter the articles for their articleGroup so every portion of articles appears in the right tab? What is the right expression in the ng-repeat above where I just put "group.articles" as a placeholder now?
This sounds very easy, but my problem is that I have no operational identification of an Article's articleGroup to filter the right articles in the ng-repeat for each tab. What I tried:
using the self.href as an id à la article._links.articleGroup.href == articleGroup._links.self.href, but this doesn't work as you can see in the JSON above those two links are set up differently
adding (database) ids to the JSON, but of course an article does not contain the id of its articleGroup, but only the link relation
looping through articleGroups and retrieving each group's articles into an array with the articleGroup.id as the key, but I can't get this to work out as I either get undefined errors by JavaScript or infinite loops
Help
Before I continue fiddling along for hours, it would be great if you could give me hints on which direction to take and what a "clean" and methodologically sound approach to the task would be. How can I bring articles and their corresponding group back togehter?
It seems like such an easy (and frequent) problem, but I tried for hours and days to get it running and fear I'm missing something. I looked at many examples but none of them helped me to have a breakthrough.
I'm happy to provide any further information as needed. Thanks a lot in advance!
Well, I solved it. It turned out that I got lost in the handling of asynchronous requests, especially between having a data object itself and a promise for a request, i.e. placeholders which provide access to the data as soon as it is available - very well explained in this post.
Restangular always returns promises. So I came up with the following solution which works exactly as I wanted to:
For the controller code:
Restangular.all('articleGroups').getList()
.then(function(groups) {
$scope.articleGroups = groups;
for(i=0;i<groups.length;i++) {
$scope.articles[groups[i].id] = groups[i].getList("articles")
}
});
So when the articleGroups arrive from the server, I walk through all of them and put them into the array $scope.articles with the articleGroup.id as key. Notice that group[i].getList("articles") returns a promise for the articles relation. With Restangular's $object property, I receive the articles data array in the ng-repeat:
<tabset>
<tab ng-repeat="group in articleGroups" heading="{{group.name}}">
<table class="table">
<tr ng-repeat="article in articles[group.id].$object | orderBy:'id':false ">
<td>{{article.name}}</td>
<td>{{article.price | currency }}</td>
</tr>
</table>
</tab>
</tabset>
Now, all articles of the selected articleGroup's tab are displayed on the tab. While this seems fine, I'm still not sure if this is the best solution in terms of style or performance. But at least it's a step ahead. Comments very welcome.

Updating NVD3 /D3 chart as per user input radio button

http://nvd3.org/examples/cumulativeLine.html
This example show 4 lines chart. What i want to achieve below.
currently, Series 1, Series 2, Series 3, Series 4 (Charts and Respective legends)
I want keep Series 1, Series 2, Series 3 as common
and I have few radio buttons(Outside chart Area) like Series 5, Series 6, Series 7 and on and on.
Now clicking on those radio button i want to show respective chart and common charts(in this case series 1 ,2 and 3)
for Example
Series 5 radio button clicked
Now Series 1, Series 2, Series 3, Series 5 is shown, [This would removed chart3 legend-chart and add legend-chart of Series 5].
I am trying to achieve above in AngularJS and NVD3 Directive. But i am ok i am known to D3 code logic i should be able to change to angular way. Providing Angular work around is warm welcomed.
[Update]
Strange, instead of cumulative i used simple line chart. I changed the data and chart is being updated as anticipated. I dont what is wrong with cumulative. Any Idea?
With angularjs you can try angular-nvd3 directive. It allows you to manipulate with chart data and chart options via JSON, like:
//html
<nvd3 options="options" data="data"></nvd3>
//javascript
$scope.options = { /*some options*/ }
$scope.data = [ /*some data*/ ]
So, in your case, we just extend this approach to when we interactively update data. We can write something like (using checkboxes):
//html
<div ng-repeat="series in initData">
<input type="checkbox" ng-model="checkboxes[series.key]" ng-init="checkboxes[series.key]=true" ng-change="onchange()"/>{{series.key}}
</div>
<nvd3 options="options" data="data"></nvd3>
where initData is a whole static dataset. And in controller
//javascript
$scope.options = { /*some options*/ }
$scope.initData = [
{
key: "Long",
mean: 250,
values: [ ... ]
},
...
];
$scope.data = angular.copy($scope.initData);
$scope.checkboxes = {};
$scope.onchange = function(){
$scope.data = [];
angular.forEach($scope.initData, function(value, index){
if ($scope.checkboxes[value.key]) $scope.data.push(value);
})
}
See live example: http://plnkr.co/edit/ZLcS6M?p=preview
Note: angular-nvd3
http://embed.plnkr.co/tsJntqq5YJnNUGK6goLZ/preview
See plunkr, I did for a button adding a new serie so it shouldn't have too different just changing for the radio. Notice that if you are getting the data from Web Services or similar resources you probably have to redraw the graph
See https://github.com/krispo/angular-nvd3/issues/37
In the directive
<nvd3 options="options" data="data" api="api"></nvd3>
In the Controller
$scope.api.refresh();
Hope this help

Resources