I'm using a table in which I'm displaying some objects. I'm using jquery (bad, I know. But only thing I could get working) to add/remove class ng-hide from all elements with a specific ID. This results in a column being hidden and it works fine. But when any updates from the server comes and I use $scope.rows.push(object) and $scope.apply() the order of the columns gets messed up and the hidden column gets right back..
<!doctype html>
<html ng-app="plunker">
<head>
<script data-require="angular.js#*" data-semver="1.2.0" src="http://code.angularjs.org/1.2.0/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng:controller="MainCtrl">
<table>
<thead style="font-weight: bold;">
<tr>
<td class="text-right" data-col-id="Value1">Value1</td>
<td class="text-right" data-col-id="Value2">Value2</td>
<td class="text-right" data-col-id="Value3">Value3</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td class="text-right" data-col-id="Value1">{{row.Value1}}</td>
<td class="text-right" data-col-id="Value2">{{row.Value2}}</td>
<td class="text-right" data-col-id="Value3">{{row.Value3}}</td>
</tr>
</tbody>
</table>
<p>Visible Columns:</p>
<br />
<div class="cbxList" ng-repeat="column in columnsTest">
<input type="checkbox" ng-model="column.checked" ng-change="columnToggled(column)"> {{column.id}}
</div>
</div>
<script>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.columnsTest = [{
id: 'Value1',
checked: true
}, {
id: 'Value2',
checked: true
}, {
id: 'Value3',
checked: true
}];
$scope.rows = [{
id: 1,
"Value1": 911,
"Value2": 20,
"Value3": 20
}, {
id: 2,
"Value1": 200,
"Value2": 20,
"Value3": 20
}];
$scope.columnToggled = function(column) {
$('[data-col-id="' + column.id + '"]').each(function() {
var element = this;
if ($(element).hasClass('ng-hide')) {
$(element).removeClass('ng-hide');
} else {
$(element).addClass('ng-hide');
}
});
};
//trigger update
window.setInterval(function() {
$scope.simulateUpdates($scope.rows[0]);
}, 5000);
$scope.simulateUpdates = function (row) {
var newRow =
{
id: 1,
"Value1": Math.floor(Math.random() * 100) + 1,
"Value2": Math.floor(Math.random() * 100) + 1,
"Value3": Math.floor(Math.random() * 100) + 1
}
updateRow(newRow);
$scope.$apply();
}
function updateRow(row) {
for (var i = 0; i < $scope.rows.length; i++) {
if ($scope.rows[i].id === row.id) {
$scope.rows[i] = row;
}
}
}
});
Here is a demo of my problem in a minor scale: http://plnkr.co/edit/1tGci7qX9ZFIk69uNfIf?p=preview (uncheck one of the columns)
You overcomplicate things a bit: your model seems to be pretty simple actually. The key is using templates to express it properly. That's how it might look like, for example:
<table>
<thead>
<tr>
<th class="text-right" ng-repeat="column in columnsTest"
ng-if="column.checked" ng-bind="column.id"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columnsTest"
ng-if="column.checked" ng-bind="row[column.id]"></td>
</tr>
</tbody>
</table>
<p>Visible Columns:</p>
<br />
<div class="cbxList" ng-repeat="column in columnsTest">
<input type="checkbox" ng-model="column.checked">{{column.id}}
</div>
See? No need for that extra function: when you change the specific column checked attribute, it's automatically updated in all the corresponding views.
Demo.
I have fixed your code without using jQuery.
<table>
<thead style="font-weight: bold;">
<tr>
<th ng-repeat="column in columnsTest" class="text-right" data-col-id="column.id" ng-show="column.checked">
{{column.id}}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td class="text-right" data-col-id="Value1" ng-show="columnsTest[0].checked">{{row.Value1}}</td>
<td class="text-right" data-col-id="Value2" ng-show="columnsTest[1].checked">{{row.Value2}}</td>
<td class="text-right" data-col-id="Value3" ng-show="columnsTest[2].checked">{{row.Value3}}</td>
</tr>
</tbody>
</table>
<div class="cbxList" ng-repeat="column in columnsTest">
<input type="checkbox" ng-model="column.checked">{{column.id}}
</div>
You don't need to bind the ng-change function to the input checkbox since you already assigned it the ng-model using two-way data-binding.
The following is the working :Plunker
Related
I tried the code of AngularJS as follows:
var app = angular.module('MyApp', []);
app.controller('new_controller', function($scope) {
$scope.people= [{
name: "GeekAgashi",
Age: 12,
attended: 1
}, {
name: "GeekSatoshi",
Age: 16,
attended: 0
}, {
name: "GeekNakumato",
Age: 14,
attended: 1
}];
});
In HTML file the code:
<div class="sidebar-pane" ng-app="MyApp" id="flight_info" ng-controller="new_controller">
<table>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<tr ng-repeat="p in people">
<td>{{p.name}}</td>
<td>{{p.Age}}</td>
</tr>
</table>
</div>
On the webpage, I can only get the table frame which I think means that the data is successfully passed. But I cannot get the table items:
<table>
<tbody>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<!-- ngRepeat: p in people -->
<tr ng-repeat="p in people" class="ng-scope">
<td></td>
<td></td>
</tr>
<!-- end ngRepeat: p in people -->
<tr ng-repeat="p in people" class="ng-scope">
<td></td>
<td></td>
</tr>
<!-- end ngRepeat: p in people -->
<tr ng-repeat="p in people" class="ng-scope">
<td></td>
<td></td>
</tr>
<!-- end ngRepeat: p in people -->
</tbody>
</table>
I am just wondering if it is a bug or there is something wrong with my code, or the browser (chrome v79) does not support it.
Appendix:
I write the backend in Django and I found that if I directly run the plain HTML, the table is visible. The problem happens if I run it through a Django server. Since I am a green hand to the frontend, I am wondering if it is possible that the problem lies in the CSS file or the confliction with other local javascript?
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div class="sidebar-pane" ng-app="MyApp" id="flight_info" ng-controller="new_controller">
<table>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<tr ng-repeat="p in people">
<td>{{p.name}}</td>
<td>{{p.Age}}</td>
</tr>
</table>
</div>
<script>
var app = angular.module('MyApp', []);
app.controller('new_controller', function($scope) {
$scope.people= [{
name: "GeekAgashi",
Age: 12,
attended: 1
}, {
name: "GeekSatoshi",
Age: 16,
attended: 0
}, {
name: "GeekNakumato",
Age: 14,
attended: 1
}];
});
</script>
</body>
</html>
Kindly check if the above code is running?
I copy & paste your code to the snipped (Angulerjs version 1.7.5) and it seems like it's working.
var app = angular.module('MyApp', []);
app.controller('new_controller', function($scope) {
$scope.people = [{
name: "GeekAgashi",
Age: 12,
attended: 1
}, {
name: "GeekSatoshi",
Age: 16,
attended: 0
}, {
name: "GeekNakumato",
Age: 14,
attended: 1
}];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div class="sidebar-pane" ng-app="MyApp" id="flight_info" ng-controller="new_controller">
<table>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<tr ng-repeat="p in people">
<td>{{p.name}}</td>
<td>{{p.Age}}</td>
</tr>
</table>
</div>
In your question you specify that you are using django. I assume that you are using it to retrieve the people data (correct me if I'm wrong). Can you please show us how you retrieve the data? This might just be a case of async operation, so the data isn't there at the time the page is rendered.
The reason is a confliction of django and angularJS templates. The solution could be find in this question.
I'm not sure what could be the issue here but I'm using nuxt to make a SPA app. and I'm getting an error from an already compiled piece of code I got from codepen. link to codepen
https://codepen.io/jjelic/pen/yevNLZ?editors=1010
When I try this code my my nuxt app I get an error.
I've added a file called monitor.vue in pages folder and added the html and js like so
Is this root element error common as I have never encountered it before with html and how can I avoid?
Vue.filter('currencyDisplay', {
// model -> view
read: function(val) {
if (val > 0) {
return accounting.formatMoney(val, "$", 2, ".", ",");
}
},
// view -> model
write: function(val, oldVal) {
return accounting.unformat(val, ",");
}
});
Vue.directive('sortable', {
twoWay: true,
deep: true,
bind: function() {
var that = this;
var options = {
draggable: Object.keys(this.modifiers)[0]
};
this.sortable = Sortable.create(this.el, options);
console.log('sortable bound!')
this.sortable.option("onUpdate", function(e) {
that.value.splice(e.newIndex, 0, that.value.splice(e.oldIndex, 1)[0]);
});
this.onUpdate = function(value) {
that.value = value;
}
},
update: function(value) {
this.onUpdate(value);
}
});
var vm = new Vue({
el: '#app',
data: {
rows: [
//initial data
{
qty: 5,
description: "Something",
price: 55.20,
tax: 10
},
{
qty: 2,
description: "Something else",
price: 1255.20,
tax: 20
},
],
total: 0,
grandtotal: 0,
taxtotal: 0,
delivery: 40
},
computed: {
total: function() {
var t = 0;
$.each(this.rows, function(i, e) {
t += accounting.unformat(e.total, ",");
});
return t;
},
taxtotal: function() {
var tt = 0;
$.each(this.rows, function(i, e) {
tt += accounting.unformat(e.tax_amount, ",");
});
return tt;
}
},
methods: {
addRow: function(index) {
try {
this.rows.splice(index + 1, 0, {});
} catch (e) {
console.log(e);
}
},
removeRow: function(index) {
this.rows.splice(index, 1);
},
getData: function() {
$.ajax({
context: this,
type: "POST",
data: {
rows: this.rows,
total: this.total,
delivery: this.delivery,
taxtotal: this.taxtotal,
grandtotal: this.grandtotal,
},
url: "/api/data"
});
}
}
});
<template>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th>Description</th>
<th style="width: 80px;">Qty</th>
<th style="width: 130px;" class="text-right">Price</th>
<th style="width: 90px;">Tax</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody v-sortable.tr="rows">
<tr v-for="row in rows" track-by="$index">
<td>
{{ $index +1 }}
</td>
<td>
<input class="form-control" v-model="row.description" />
</td>
<td>
<input class="form-control" v-model="row.qty" number />
</td>
<td>
<input class="form-control text-right" v-model="row.price | currencyDisplay" number data-type="currency" />
</td>
<td>
<select class="form-control" v-model="row.tax">
<option value="0">0%</option>
<option value="10">10%</option>
<option value="20">20%</option>
</select>
</td>
<td>
<input class="form-control text-right" :value="row.qty * row.price | currencyDisplay" v-model="row.total | currencyDisplay"
number readonly />
<input type="hidden" :value="row.qty * row.price * row.tax / 100" v-model="row.tax_amount | currencyDisplay"
number />
</td>
<td>
<button class="btn btn-primary btn-xs" #click="addRow($index)">add row</button>
<button class="btn btn-danger btn-xs" #click="removeRow($index)">remove row</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" class="text-right">TAX</td>
<td colspan="1" class="text-right">{{ taxtotal | currencyDisplay }}</td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right">TOTAL</td>
<td colspan="1" class="text-right">{{ total | currencyDisplay }}</td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right">DELIVERY</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery | currencyDisplay"
number /></td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right"><strong>GRANDTOTAL</strong></td>
<td colspan="1" class="text-right"><strong>{{ grandtotal = total + delivery | currencyDisplay }}</strong></td>
<td></td>
</tr>
</tfoot>
</table>
<button #click="getData()">SUBMIT DATA</button>
<pre>{{ $data | json }}</pre>
</div>
</template>
This problem is actually a very simple problem. I don't know vue, but the render method has the same limits of react's one: every component must have only one root element in its template.
This means that a situation like this isn't accepted:
<template>
<div></div>
<div></div>
</template>
But like this is correct:
<template>
<div></div>
</template>
This means that surely, somehow in the code you didn't show us, you're putting two elements as root of your template
I'm trying to populate an input two ways. The first method is to simply type an amount into the input, which works perfectly. The second method (which I'm struggling with) is to check the checkbox generated within the ngRepeat directive.
The desired behavior is that the checkbox will take the value of item.amount from the JSON data and populate the input with that value. Here is the markup:
<table class="table table-striped header-fixed" id="invoiceTable">
<thead>
<tr>
<th class="first-cell">Select</th>
<th class="inv-res2">Invoice #</th>
<th class="inv-res3">Bill Date</th>
<th class="inv-res4">Amount</th>
<th class="inv-res5">Amount to Pay</th>
<th class="inv-res6"></th>
</tr>
</thead>
<tbody>
<tr ng-if="invoices.length" ng-repeat="item in invoices | filter: {status:'Unpaid'}">
<td class="first-cell"><input type="checkbox" /></td>
<td class="inv-res2">{{item.invoiceNum}}</td>
<td class="inv-res3">{{item.creationDate}}</td>
<td class="inv-res4" ng-init="invoices.total.amount = invoices.total.amount + item.amount">{{item.amount | currency}}</td>
<td class="inv-res5">$
<input ng-validate="number" type="number" class="input-mini" ng-model="item.payment" ng-change="getTotal()" step="0.01" /></td>
</tr>
</tbody>
</table>
<table class="table">
<tbody>
<tr class="totals-row" >
<td colspan="3" class="totals-cell"><h4>Account Balance: <span class="status-error">{{invoices.total.amount | currency }}</span></h4></td>
<td class="inv-res4"><h5>Total to pay:</h5></td>
<td class="inv-res5">{{total | currency}}</td>
<td class="inv-res6"></td>
</tr>
</tbody>
</table>
And here is the JavaScript:
myApp.controller('invoiceList', ['$scope', '$http', function($scope, $http) {
$http.get('assets/js/lib/angular/invoices.json').success(function(data) {
$scope.invoices = data;
});
$scope.sum = function(list) {
var total=0;
angular.forEach(list , function(item){
total+= parseInt(item.amount);
});
return total;
};
$scope.total = 0;
$scope.getTotal = function() {
$scope.total = 0;
$scope.invoices.forEach(function(item){
$scope.total += parseFloat(item.payment);
});
};
$scope.pushPayment = function(item){
if($scope.checked == 'checked'){
return item.payment;
}
};
}]);
If I understand correctly you want a toggle-able check box, If it is checked then you want to copy that invoices amount into the input box below. You could do something similar to below with a combination of ng-model and ng-change
<tr ng-if="invoices.length" ng-repeat="item in invoices | filter: {status:'Unpaid'}">
<td class="first-cell">
<input type="checkbox" ng-model="item.checked" ng-change="select(item)"/>
</td>
<td class="inv-res5">$
<input ng-validate="number" type="number" class="input-mini" ng-model="item.payment" step="0.01"/>
</td>
</tr>
and add the following to your controller
$scope.select = function(item) {
if(item.checked){
item.payment = item.amount;
}
}
What this should do:
You bind the status of the check box to $scope.checked using ng-model
Every time the checkbox status changes ng-change is called, therefore selectInvoice is called.
Select invoice checks whether the checkbox is checked and adjusts the item.payment value accordingly which is bound to the inputs ng-model
See this Plunker for a working example (Note I thinned out the code so its only the bit we're interested in
As an aside, you don't need to have the input box call getTotal when its value changes. Just change the last few lines to:
<td class="inv-res4"><h5>Total to pay:</h5></td>
<td class="inv-res5">{{getTotal() | currency}}</td>
And modify your JavaScript to:
$scope.getTotal = function() {
var total = 0;
$scope.invoices.forEach(function(item){
total += parseFloat(item.payment);
});
return total;
};
It will still be up to date every time Angular 'digests'
The Plunker
The html:
<table class="table table-striped header-fixed" id="invoiceTable">
<thead>
<tr>
<th class="first-cell">Select</th>
<th class="inv-res2">Invoice #</th>
<th class="inv-res3">Bill Date</th>
<th class="inv-res4">Amount</th>
<th class="inv-res5">Amount to Pay</th>
<th class="inv-res6"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in mainCtrl.invoiceList.invoices">
<td class="first-cell"><input type="checkbox" ng-model="item.selected" /></td>
<td class="inv-res2">{{item.invoiceNum}}</td>
<td class="inv-res3">{{item.creationDate}}</td>
<td class="inv-res4" ng-init="invoices.total.amount = invoices.total.amount + item.amount">{{item.amount | currency}}</td>
<td class="inv-res5">$
<input type="text" ng-model="mainCtrl.getAmount(item)"/></td>
</tr>
</tbody>
</table>
<table class="table">
<tbody>
<tr class="totals-row" >
<td colspan="3" class="totals-cell"><h4>Account Balance: <span class="status-error">{{invoices.total.amount | currency }}</span></h4></td>
<td class="inv-res4"><h5>Total to pay:</h5></td>
<td class="inv-res5">{{mainCtrl.getTotalAmount()}}</td>
<td class="inv-res6"></td>
</tr>
</tbody>
</table>
The JS:
var app = angular.module('plunker', []);
app.controller('MainCtrl', ['tempDataStorageService', function(tempDataStorageService) {
var myCtrl = this;
myCtrl.invoiceList = tempDataStorageService;
myCtrl.getAmount = function(item){
return item.selected? item.amount : "";
};
myCtrl.getTotalAmount = function(){
var total = 0;
for(var i = 0; i < tempDataStorageService.invoices.length; i++){
if(tempDataStorageService.invoices[i].selected){
total = total + tempDataStorageService.invoices[i].amount;
}
}
return total;
}
}]);
app.factory('tempDataStorageService', function() {
// The service object
var storage = this;
storage.invoices = [{invoiceNum: 1, creationDate: "1/1/16", amount: 1.50, selected: false},
{invoiceNum: 2, creationDate: "1/2/16", amount: 2.50, selected: false},
{invoiceNum: 2, creationDate: "1/2/16", amount: 2.50, selected: false},
{invoiceNum: 3, creationDate: "1/3/16", amount: 3.50, selected: false},
{invoiceNum: 4, creationDate: "1/4/16", amount: 4.50, selected: false},
{invoiceNum: 5, creationDate: "1/5/16", amount: 5.50, selected: false},
{invoiceNum: 6, creationDate: "1/6/16", amount: 6.50, selected: false},
{invoiceNum: 7, creationDate: "1/7/16", amount: 7.50, selected: false},
{invoiceNum: 8, creationDate: "1/8/16", amount: 8.50, selected: false}];
// return the service object
return storage;
});
That's a way of doing it
Add an attribute amountToPay to your invoices and send the item to the getTotal function:
<input ng-validate="number" type="number" class="input-mini" value="{{pushPayment()}}"
ng-model="item.amountToPay" ng-change="getTotal(item)" step="0.01" /></td>
In your checkbox change the ng-model to item.checked:
<input type="checkbox" ng-checked="item.checked" /></td>
Add this to your getTotal() function:
$scope.getTotal = function(item) {
item.checked = true;
$scope.total = 0;
$scope.invoices.forEach(function(item){
$scope.total += parseFloat(item.payment);
});
};
If you need to populate your input, just modify the amountToPay attribute
Thanks for the assistance, but I think I was overthinking it. I got to work with simply adding:
ng-click="item.payment=item.amount" ng-change="getTotal()"
to the checkbox. I still have to incorporate this into the sum function, but I solved the issue.
I have an Angular SPA that features a cart (array) that users can add items to. I'd like to prevent the user from adding any particular item to the cart twice.
function CartForm($scope) {
$scope.products = [{
"description": "BB-8 Droid",
"qty": "1",
"cost": "99"
}, {
"description": "C-3PO Droid",
"qty": "1",
"cost": "499"
}, {
"description": "R2-D2 Astromech Droid",
"qty": "1",
"cost": "899"
}, {
"description": "R5-D4 Astromech Droid",
"qty": "1",
"cost": "899"
}, {
"description": "IG-88 Bounty Hunter Droid",
"qty": "1",
"cost": "899"
}];
$scope.invoice = {
items: []
};
$scope.addItem = function(product) {
$scope.invoice.items.push(product);
},
$scope.removeItem = function(index) {
$scope.invoice.items.splice(index, 1);
},
$scope.total = function() {
var total = 0;
angular.forEach($scope.invoice.items, function(item) {
total += item.qty * item.cost;
})
return total;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h2>Shopping Cart Example</h2>
<div ng:controller="CartForm">
<table class="table">
<thead>
<th>Description</th>
<th>Qty</th>
<th colspan="2">Price</th>
</thead>
<tr ng-repeat="product in products">
<td>{{product.description}}</td>
<td>{{product.qty}}</td>
<td>{{product.cost | currency }}</td>
<td>
<button class="btn btn-danger" ng-click="addItem(product)">ADD TO CART</button>
</tr>
</table>
<table class="table">
<tr>
<th>Description</th>
<th>Qty</th>
<th>Cost</th>
<th>Total</th>
<th></th>
</tr>
<tr ng:repeat="item in invoice.items">
<td>
<input type="text" ng:model="item.description" class="input-small">
</td>
<td>
<input type="number" ng:model="item.qty" ng:required class="input-mini">
</td>
<td>
<input type="number" ng:model="item.cost" ng:required class="input-mini">
</td>
<td>{{item.qty * item.cost | currency}}</td>
<td>
[<a href ng:click="removeItem($index)">X</a>]
</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Total:</td>
<td>{{total() | currency}}</td>
</tr>
</table>
</div>
See working JSFiddle here: http://jsfiddle.net/tedleeatlanta/22591h2y/15/
You can add some logic to your AddItem to deal with all of this.
This isn't the most elegant way, but will get you going in the right direction - Something like this works well:
$scope.addItem = function(product) {
var exist = false;
for(var i = 0; i < $scope.invoice.items.length;i++){
if ($scope.invoice.items[i].description==product.description)
{
// having to use parseInt here because your Qty isn't a number...naughty naughty
$scope.invoice.items[i].qty = parseInt($scope.invoice.items[i].qty)+1;
exist = true;
}
}
if (!exist)
$scope.invoice.items.push(product);
},
It increases the Qty if it already exists, or adds it if it doesn't
See it running here http://jsfiddle.net/22591h2y/16/
Or, for something that doesn't need to parseInt - change your objects qty to ints, rather than strings.
See this update http://jsfiddle.net/22591h2y/17/
Assuming data structured like:
[
{name: 'first', data1: [1,2,3], data2: [4,5]},
{name: 'second', data1: [9,8,7], data2: [6,6,4,5]},
...
]
(Note data1 and data2 are not fixed lengths and not necessarily the same length).
I'd like to create an html table like:
<table>
<thead>
<th> Name </th>
<th> Data 1 </th>
<th> Data 2 </th>
</thead>
<tbody>
<tr>
<td rowspan="4">first</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td> </td>
</tr>
<tr>
<td> </td>
<td> </td>
</tr>
<tr>
<td rowspan="5">first</td>
<td>9</td>
<td>6</td>
</tr>
<tr>
<td>8</td>
<td>6</td>
</tr>
<tr>
<td>7</td>
<td>5</td>
</tr>
<tr>
<td> </td>
<td>5</td>
</tr>
<tr>
<td> </td>
<td> </td>
</tr>
</tbody>
<table>
Notice that in the display there is at least one extra empty cell after the last bit of data in each column. I'd like that cell, the first empty cell for each of data1 and data2, to have an ng-click handler which if clicked would show a input in that column instead of %nbsp; where one could enter a new value.
What's the best way to accomplish this?
Currently I can almost create the above structure by having a second variable on the scope which watches the first and when the first changes it flattens the data so the above data would be stored in another variable as:
[ { name: 'first', rowsNeeded: '4', data: [ [1,4], [2,5], [3, undefined], [undefined, undefined]], ... ]
and then rendering it like
<tbody ng-repeat="item in flattenedData">
<tr ng-repeat="data in item.data">
<td ng-if="$first" rowspan="{{data.rowsNeeded}}">{{data.name}}</td>
<td>{{data[0] == undefined ? ' ' : macroData[0] }}</td>
<td>{{data[1] == undefined ? ' ' : macroData[1] }}</td>
</tr>
</tbody>
But I'm wondering a few things. One, do I really need to have a second variable using a watch statement to create the table or is there a cleaner way? Two, I'm not sure how I'd add the click event so on the first 'undefined' in each column I could add an instead of ' '. Lastly, is it really necessary to have a different tbody tag for each row?
Thanks for your help!!
The general idea is to extract data formatting into filters. From there on, I'd combine everything into directive to simplyfy table component usage. This example (JSBin) shows only filters and template, non-optimal, but working:
<table>
<thead>
<tr>
<th>Name</th>
<th ng-repeat="key in datas[0]|dataKeys">
Data {{$index+1}}
</th>
</tr>
</thead>
<tbody ng-repeat="set in datas">
<tr ng-repeat="row in (set|setMaxLength|range)">
<td
rowspan="{{(set|setMaxLength)+1}}"
ng-if="!$index"
>
{{set.name}}
</td>
<td ng-repeat="data in (set|setDatas)">
{{data[$parent.$index]}}
</td>
</tr>
<tr>
<td
ng-repeat="data in (set|setDatas)"
ng-click="editing = true"
>
<span ng-if="!editing"> </span>
<form
ng-submit="data[data.length] = input.value"
name="input"
>
<input
type="text"
ng-if="editing"
ng-model="input.value"
autofocus
>
</form>
</td>
</tr>
</tbody>
</table>
angular
.module("app", [])
.controller("ctrl", function($scope) {
$scope.datas = [{
name: 'first',
data1: [1, 2, 3],
data2: [4, 5]
}, {
name: 'second',
data1: [9, 8, 7],
data2: [6, 6, 4, 5]
}];
})
.filter("range", function() {
return function(length) {
return Array.apply(0, Array(length))
.map(function(_, i) {
return i;
})
}
})
.filter("dataKeys", function() {
return function(set) {
return Object.keys(set)
.filter(function(key) {
return key.indexOf("data") !== -1;
})
}
})
.filter("setDatas", function($filter) {
return function(set) {
return $filter("dataKeys")(set)
.map(function(key) {
return set[key]
});
}
})
.filter("setMaxLength", function($filter) {
return function(set) {
return $filter("dataKeys")(set)
.map(function(key) {
return set[key].length;
})
.sort().pop();
}
})