AngularJS - Building a dynamic table based on a json - javascript

Given a json like this:
{
"name": "john"
"colours": [{"id": 1, "name": "green"},{"id": 2, "name": "blue"}]
}
and two regular html inputs:
<input type="text" name="name" />
<input type="text" name="color" />
<input type="submit" value="submit" />
I need to build a table with all the possible variations, ex:
John green
John blue
That means that if a user continues adding values through the inputs new rows will appear building the new variations, for instance:
I also need to have available the id to handle it, and I need that when I add new values using the inputs for instance: "Peter" "Black", I need to autofill the id (colour id) dynamically like an auto increment in mysql, resulting in something like this:
{
"colours": […...{"id": 3, "name": "black"}]
}
Is that possible? Which options do I have for doing that with angular? I'm still thinking in the jQuery way and I would like to do it in the angular way.
I took a look to hg-repeat, and used it, but I'm not figuring out how to deliver the expected result, the only thing that come to my mind was to use nested ng-repeats, but it didm´t work.
Thanks so much in advance,
Guillermo

Just want to share with what I used so far to save your time.
Here are examples of hard-coded headers and dynamic headers (in case if don't care about data structure). In both cases I wrote some simple directive: customSort
customSort
.directive("customSort", function() {
return {
restrict: 'A',
transclude: true,
scope: {
order: '=',
sort: '='
},
template :
' <a ng-click="sort_by(order)" style="color: #555555;">'+
' <span ng-transclude></span>'+
' <i ng-class="selectedCls(order)"></i>'+
'</a>',
link: function(scope) {
// change sorting order
scope.sort_by = function(newSortingOrder) {
var sort = scope.sort;
if (sort.sortingOrder == newSortingOrder){
sort.reverse = !sort.reverse;
}
sort.sortingOrder = newSortingOrder;
};
scope.selectedCls = function(column) {
if(column == scope.sort.sortingOrder){
return ('icon-chevron-' + ((scope.sort.reverse) ? 'down' : 'up'));
}
else{
return'icon-sort'
}
};
}// end link
}
});
[1st option with static headers]
I used single ng-repeat
This is a good example in Fiddle (Notice, there is no jQuery library!)
<tbody>
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.description}}</td>
<td>{{item.field3}}</td>
<td>{{item.field4}}</td>
<td>{{item.field5}}</td>
</tr>
</tbody>
[2nd option with dynamic headers]
Demo 2: Fiddle
HTML
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th ng-repeat="header in table_headers"
class="{{header.name}}" custom-sort order="header.name" sort="sort"
>{{ header.name }}
</th>
</tr>
</thead>
<tfoot>
<td colspan="6">
<div class="pagination pull-right">
<ul>
<li ng-class="{disabled: currentPage == 0}">
<a href ng-click="prevPage()">« Prev</a>
</li>
<li ng-repeat="n in range(pagedItems.length, currentPage, currentPage + gap) "
ng-class="{active: n == currentPage}"
ng-click="setPage()">
<a href ng-bind="n + 1">1</a>
</li>
<li ng-class="{disabled: (currentPage) == pagedItems.length - 1}">
<a href ng-click="nextPage()">Next »</a>
</li>
</ul>
</div>
</td>
</tfoot>
<pre>pagedItems.length: {{pagedItems.length|json}}</pre>
<pre>currentPage: {{currentPage|json}}</pre>
<pre>currentPage: {{sort|json}}</pre>
<tbody>
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sort.sortingOrder:sort.reverse">
<td ng-repeat="val in item" ng-bind-html-unsafe="item[table_headers[$index].name]"></td>
</tr>
</tbody>
</table>
As a side note:
The ng-bind-html-unsafe is deprecated, so I used it only for Demo (2nd example). You welcome to edit.

Here's an example of one with dynamic columns and rows with angularJS: http://plnkr.co/edit/0fsRUp?p=preview

TGrid is another option that people don't usually find in a google search. If the other grids you find don't suit your needs, you can give it a try, its free

Check out this angular-table directive.

<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th ng-repeat="header in headers | filter:headerFilter | orderBy:headerOrder" width="{{header.width}}">{{header.label}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users" ng-class-odd="'trOdd'" ng-class-even="'trEven'" ng-dblclick="rowDoubleClicked(user)">
<td ng-repeat="(key,val) in user | orderBy:userOrder(key)">{{val}}</td>
</tr>
</tbody>
<tfoot>
</tfoot>
</table>
refer this https://gist.github.com/ebellinger/4399082

First off all I would like to thanks #MaximShoustin.
Thanks of you I have really nice table.
I provide some small modification in $scope.range and $scope.setPage.
In this way I have now possibility to go to the last page or come back to the first page.
Also when I'm going to next or prev page the navigation is changing when $scope.gap is crossing. And the current page is not always on first position. For me it's looking more nicer.
Here is the new fiddle example:
http://jsfiddle.net/qLBRZ/3/

Related

ng-if and ng-class-even/odd doesn't work well together and won't get expected outout

Here,I am loading a json file and using ng-repeat I display json data into table form.I am adding following features to code.
checkboxes to add the two CSS:
Bubble CSS
text-danger [In built class]
when you click on those check boxes, the CSS is applied only to the even rows in the table.
Using the ng-if or ngHide/Show to display the User-info which have the gender : Male.
I use ng-if to fulfill condition because ng-if will remove elements from DOM. This means that all your handlers or anything else attached to those elements will be lost.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements.
I created Codepen demo for my problem.Here ng-if and ng-class-even doesn't give expected o/p.
HTML:
<body ng-app="module1" ng-controller="module1Ctrl as flight">
<div class="page-header">
<h1>Angular app</h1>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="danger">
Something's Wrong
</label>
<label>
<input type="checkbox" ng-model="bubble">
Zoom In/Out
</label>
</div>
<table class="table table-striped table-bordered">
<tr>
<th>name</th>
<th>Birthdate</th>
<th>gender</th>
<th>father</th>
<th>mother</th>
</tr>
<tr ng-repeat="x in flightData"
ng-class-even ="{'text-danger':danger,'bubble': bubble}"
ng-if="x.sex=='f'">
<td>{{x.name}}</td>
<td>{{ x.born |date:'yyyy-MM-dd THH:mm:ss'}}</td>
<td>{{x.sex }}</td>
<td>{{x.father}}</td>
<td>{{x.mother}}</td>
</tr>
</table>
</div>
</body>
JS
var app=angular.module('module1',[]);
app.controller('module1Ctrl', function ($scope,$http) {
$scope.getDataFromServer = function () {
$http.get('http://codepen.io/timivey/pen/emQZYY.js').then(
function(res){
console.log(res.data);
$scope.flightData = res.data;
}
,function (err) {
console.log(err.message);
})
};
$scope.getDataFromServer();
$scope.showList='table';
});
Just a small change to Joel's answer. You no need to create a separate filter method.
<tr ng-repeat="x in flightData | filter:{sex:'f'}"
ng-class-even ="{'text-danger':danger,'bubble': bubble}">
There is nothing wrong with your code, but there is a small error with your understanding:
<tr ng-repeat="x in flightData"
ng-class-even ="{'text-danger':danger,'bubble': bubble}"
ng-if="x.sex=='f'">
The ng-if="x.sex == 'f' statement is omitting each <td> from display, but it does not remove them from the flightData array. ng-repeat is run for each item in the array, not for each item that is displayed.
If you add {{$index}} to each row of the table you will see that the order is not 0,1,2,3... it is 1,2,7,11,14...
If you remove the ng-if statement, you will see that every even row has correct class applied.
Instead of using ng-if to remove the males from the list, use a filter instead:
<tr ng-repeat="x in flightData | females"
ng-class-even ="{'text-danger':danger,'bubble': bubble}">
Append this filter to your controller statement in the JS code:
.filter('females', function() {
return function(input) {
var out = [];
angular.forEach(input, function(x) {
if(x.sex == 'f') {
out.push(x);
}
});
return out;
};
})
http://codepen.io/anon/pen/mABJyo
What this does is remove the males from the list before it runs the ng-repeat and will correctly output in the order 0,1,2,3...

Meteor JQuery Start Rating Issue

I am getting issues trying to use the follow Meteor Package: dandv:jquery-rateit.
I am using it for a ticket system where each update will have a start rating. But when I have more than 1 comment the second one always return 0 value.
Here is my code:
JS:
Template.rating.events({
'click .rateit': function(e){
var rating = $('#rate').rateit('value');
console.log(rating);
Session.set('UpdateId', this._id);
var UpdateId = this._id;
console.log(UpdateId);
/*Meteor.call('saveRate',rating,UpdateId);*/
return false;
}
});
Template.rating.rendered = function () {
this.$('.rateit').rateit();
}
HTML:
<template name="Update">
{{#each Updates}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" align="center">Update</h3>
</div>
<div class="panel-body">
<table class="table">
<tr>
<td width="15%" nowrap align="left">Created By:</td>
<td width="35%" align="left">{{createdBy.username}}</td>
<td width="15%" nowrap align="left">Created At:</td>
<td width="35%" align="left">{{formatDate createdAt}}</td>
<td width="15%" nowrap align="left">Rating:</td>
<td width="35%" align="left">{{> rating}}</td>
</tr>
<tr>
<td colspan="4">{{description}}</td>
</tr>
</table>
</div>
</div>
{{/each}}
</template>
<template name="rating">
<div class="rateit" id="rate"></div>
</template>
Now when I try to rate the second comment is return 0. Screenshot: http://screencast.com/t/ejaTI98X
No matter what star do I select, it always return 0. I think that the error should be probably in the HTML.
I really appreciate all your help on it. If you have any question just let me know.
Have a great day.
You iterate over Updates and I assume there are more than one update. For each update you call the template {{>rateit}} which then renders a div with id=rate, so you will end up with several divs with the same id, so you don't really know which one $('#rate') you are accessing.
In your event handler you also use the global jQuery handler.
I suggest you use the following pattern instead to scope jQuery local to the templates context
Template.rating.events({
'click .rateit': function(e,template){
var rating = template.$('.rateit').rateit('value');
console.log(rating);
Session.set('UpdateId', template.data._id);
var UpdateId = template.data._id;
console.log(UpdateId);
/*Meteor.call('saveRate',rating,UpdateId);*/
return false;
}
});

how to bind data from dynamically named inputs inside a ng-repeat

my goal is to be able to copy data from a table row to another table row.
if the data from 2015 has not changed from 2016 the user needs a quick way of copying the values into the 2016 input fields. the models are dynamically created for these forms. the data you see in this image is assigned to a section. the input models are name 'price_min + section_id', price_max + section_id' , etc...
the history model does not have the section_id added to the end of the model names. so there needs to be a mapping function that i need help with. I need to map the history values to the current model convention and update the view with the values.
currently i have a click function that brings in the matched section history. here is a screen shot of what that looks like.
in that same function i have the 2016 object array with the current model naming convention.
i need to copy the history values into the inputArray. how i go about doing this, i dont know? I have complete control on how this works. and in the plunker you will see how i did this. if i need to change something else to make this work then that is ok. javascript, jquery, lodash, linq.js is currently being used in project.
working plunker
working plunker
$scope.copyHistoryData = function (section) {
var selected = Enumerable.From(sectionsHistory).Where("x => x.section_id == '" + section.section_id + "'").ToArray();
selected = selected[0];
var inputArry = section.sectionInputs;
};
I'm not sure why you use such complex data structure, but here is my take on it
$scope.copyHistoryData = function (section, input) {
var historyId=input.model.split('-')[0];
var historyVal=section.sectionHistory[section.sectionHistory.length-1][historyId];
$scope.model[input.model]=historyVal;
};
To fill all fields:
$scope.copyHistoryData = function (section) {
angular.forEach(section.sectionHistory[section.sectionHistory.length-1], function (historyVal, historyId) {
var inputModel=historyId+"-"+section.section_id;
$scope.model[inputModel]=historyVal;
});
};
http://plnkr.co/edit/OOEmgzKB1pqKjSJMayVF?p=preview
I agree with #ssh. The data structure is a mess. I think this is a better representation of what it should look like. Probably not the best but you shouldn't have to iterate through the data to then display it like that.
http://plnkr.co/C9DWV1dSvkk8lcYdm0An?p=preview
<div class="hpanel" ng-repeat="section in sections">
<div class="panel-body">
<div class="row">
<div class="col-xs-12">
<ul class="list-inline">
<li>
<h5>
<b>SecID</b>
</h5>
<span>{{section.section_id}}</span>
</li>
<li>
<h5>
<b>Section Name</b>
</h5>
<span>{{section.section_name}}</span>
</li>
</ul>
<hr/>
<button ng-click="section.new_section_history = section.section_history">copy row</button>
<table>
<tr>
<td ng-repeat="label in labelIndex">
{{label.label}}
</td>
</tr>
<tr>
<td ng-repeat="label in labelIndex">
{{section.section_history[label.index]}}
</td>
</tr>
<tr>
<td ng-repeat="label in labelIndex">
<input ng-model="section.new_section_history[label.index]"/>
</td>
</tr>
<tr>
<td ng-repeat="label in labelIndex">
<button ng-click="section.new_section_history[label.index] = section.section_history[label.index]">copy</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
I have checked your code, and I agree with #Steven Kaspar, the anchors in every row doesn't make much sense. I have solved it using jQuery (I know it doesn't follow your scheme with Angular, but it's another solution).
I have added a new <tr> to add a button inside it.
Check this out:
<tr>
<td colspan="10"><button class="copyRow">Copy complete row</button></td>
</tr>
And in the app.js:
$(document).on("click", ".copyRow", function(){
var $btn = $(this),
$tbody = $btn.parent().parent().parent(),
$trs = $tbody.find("tr");
$.each($trs.eq(0).find("td"), function(index, td){
$trs.eq(1).find("td").eq(index).find("input").val(parseFloat($(td).text().replace("$", "")));
});
})
Here is the updated plunker. I hope it helps

Can Knockout.js bindings apply to container tags AND descendants both?

Let me setup the question with a simple case.
I have an HTML table, the rows of which are controlled by an observableArray. It works great.
If the observableArray has zero elements in it however, I want a single row to say so. I tried this markup, which "kind of" works:
<tbody data-bind="if: $root.data.contacts().length == 0">
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<tbody data-bind="foreach: $root.data.contacts">
SNIP - a tbody with the rows is here when elements > zero
</tbody>
When I say "kind of", I mean VISIBLY. It really does show up at zero elements and really does go away at > zero elements like what you would expect. However when you open the DOM inspector (dev tools) and look at the DOM in memory, you find that there are TWO tbody sections, not one. Now one tbody is always empty of course, but two tbody tags is not HTML5 correct, so this must be fixed this is not the desired markup.
Being a Knockout newbie, I tried to fix this problem with a virtual element:
<!-- ko if: $root.data.contacts().length == 0 -->
<tbody>
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<!-- /ko -->
Unfortunately this doesn't work for our build process: we minify HTML prior to compression and comments get eliminated.
I was under the impression that KO bindings applied to the CONTAINER ELEMENT ITSELF as well as descendants, but this seems to not be so. Is there a way to tell KO to apply to container elements as well as children, or do I need to change the markup in some way OTHER THAN a virtual container?
Like you, my first choice would be virtual tags for an if binding. But since that's not an option, how about swappable templates?
var vm = {
contacts: ko.observableArray()
};
ko.applyBindings(vm);
setTimeout(function() {
vm.contacts(['One', 'Two', 'Three']);
}, 2500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<template id="empty-body">
<tbody>
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
</template>
<template id="normal-body">
<tbody data-bind="foreach: contacts">
<tr>
<td data-bind="text:$data"></td>
</tr>
</tbody>
</template>
<table data-bind="template: contacts().length === 0 ? 'empty-body' : 'normal-body'"></table>
The Knockout-Repeat binding applies the binding to the element itself. It does so by using a node preprocessor to wrap elements with the repeat binding in virtual (comment-based) elements at run time.
var vm = {
contacts: ko.observableArray()
};
ko.applyBindings(vm);
setTimeout(function() {
vm.contacts(['One', 'Two', 'Three']);
}, 2500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="https://rawgit.com/mbest/knockout-repeat/master/knockout-repeat.js"></script>
<table>
<tbody data-bind="repeat: !contacts().length && 1">
<tr>
<td>There are no contacts specified yet.</td>
</tr>
</tbody>
<tbody data-bind="repeat: contacts().length && 1" data-repeat-bind="foreach: contacts">
<tr>
<td data-bind="text:$data"></td>
</tr>
</tbody>
</table>

Rails+jQuery: Get the Value of an Item Inside a Table to Use in a Prepend Function

Say I have a table:
<table class="table table-bordered table-hover employes_list_panel">
<thead>
<tr>
<th></th>
<th></th>
<th class="center">Name <i class="fa fa-angle-double-down"></i></th>
<th class="center">IQ <i class="fa fa-angle-double-down"></i></th>
<th class="center">Efficiency <i class="fa fa-angle-double-down"></i></th>
<th class="center">Focus <i class="fa fa-angle-double-down"></i></th>
<th class="center">Happiness <i class="fa fa-angle-double-down"></i></th>
<th class="center">Quality <i class="fa fa-angle-double-down"></i></th>
<th class="center">Salery <i class="fa fa-angle-double-down"></i></th>
</tr>
</thead>
<tbody>
<%Employe.where(company_id: company.id, request: false).each do |employe|%>
<tr>
<td class="center cd-popup-trigger" id="popup3"><i style="color: red;" class="fa fa-close"></i></td>
<td class="center cd-popup-trigger" id="popup4"><i style="color: green;" class="fa fa-arrow-up"></i></td>
<td class="center"><%=employe.name%></td>
<td class="center"><%=employe.iq%></td>
<td class="center"><%=employe.efficiency%></td>
<td class="center"><%=employe.focus%></td>
<td class="center"><%=employe.happiness%></td>
<td class="center"><%=employe.quality.capitalize%></td>
<td class="center"><%=employe.salery%></td>
</tr>
<%end%>
</tbody>
</table>
And I want to get the value of the employe name <%=employe.name%> when popup3 for example is clicked. Note there can be more than one "employe" in the list. How can I get the value of the employe that is on the line where the user clicked the popup, so that I can prepend it using jQuery into the popup <p>.
Ex:
Click to Open Popup | Kevin |...
Click to Open Popup | Sam |...
I need to get the value Sam when I click the "second popup", the
problem is all popups currently have the same id.
Thank you.
Given your code, popup3 (and popup4 for that matter) is gonna be present multiple times on the page (once per tr). You need to give it a class, not an id. I personally like to prefix my JavaScript classes (not used for styling) with js-. In your HTML:
<td class="center cd-popup-trigger js-popup3"><i style="color: red;" class="fa fa-close"></i></td>
You also need to identify the cells which contain the employee name (you'll see why later).
<td class="center js-employee-name"><%= employe.name %></td>
In your JS code, you first need to select the all the td with the js-popup3 class:
$('js-popup3')
Then, you want to trigger something when an event occurs on the element you selected. With jQuery, you would do it like this:
$('js-popup3').on('click', function() {});
Lastly, you want to describe what should be done when the event occurs. This is done in the callback function.
$('.js-popup3').on('click', function() {
# employeeName is the value you want
employeeName = $(this).siblings('.js-employee-name').text();
});
I invite you to read a lot about JS (try to code without jQuery first). Once you feel confident with it, start looking at jQuery.
You can create a click function for the Table tr's and get the value of the third column (index 2):
$('table.reference.notranslate tbody').delegate("tr", "click", function(){
var YourEmployeName = $(this).find('td').eq(2).text();
alert(YourEmployeName);
});
To make the popups ahve unique id's according to employee try something like this:
">
Then I presume you have some js somewhere to handle the popup clicks. This js can extract the employee id with
emp_id = my_clicked_popup_trigger.attr("id").split('-')[-1]

Categories

Resources