I have problem with showing and hiding rows, when I select two select buttons at once.
Using only one button, I can show/hide the correct rows.
Using both buttons at once, no rows will be displayed.
Where is the logical error in the code?
Please check: http://jsfiddle.net/xEyJZ/83/
<div ng-controller="MyCtrl">
<p>
<input type="checkbox" ng-init="showNew=false" ng-click="showNew =! showNew"><span> Show new only</span> <br>
<input type="checkbox" ng-init="showOld=false" ng-click="showOld =! showOld"><span> Show old only </span>
</p>
<table border="1">
<tr ng-repeat="person in persons" ng-class="{'newp':person.newp, 'oldp':person.oldp}"
ng-hide="(!person.newp && showNew) || (!person.oldp && showOld)">
<td>{{ person.id }}</td>
<td>{{ person.name }}</td>
<td>{{ person.city }}</td>
</tr>
</table>
</div>
You can use comparator in your filter:
ng-repeat="person in persons | filter:{newp:showNew, oldp:showOld}:showTest"
and in controller set comparison function:
$scope.showTest = function(actual, expected){
if(!$scope.showNew && !$scope.showOld)
return true;
return angular.equals(expected, actual);
}
http://jsfiddle.net/D79F7/2/
You have error in your boolean expression:
Correctly as follows:
ng-hide="(!person.newp && showNew ) || (!person.oldp && showOld)"
http://jsfiddle.net/ZWy93/2/
Using a custom filter:
<input type="checkbox" ng-model="showNew">..Show new only..
<input type="checkbox" ng-model="showOld">..Show old only..
..
<tr ng-repeat="person in persons | personsFilter : showNew : showOld" ..>
JS
myApp.filter("personsFilter",function(){
return function(input, showNew, showOld){
if(!showNew && !showOld){
return input;
}
else if(showNew && !showOld){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].newp && !input[i].oldp)
temp.push(input[i]);
}
return temp;
}
else if(showOld && !showNew){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].oldp && !input[i].newp)
temp.push(input[i]);
}
return temp;
}
else if(showOld && showNew){
var temp = [];
for(var i = 0; i < input.length; i++){
if(input[i].oldp || input[i].newp)
temp.push(input[i]);
}
return temp;
}
}
});
Fiddle
Related
Order entry form contains product name, price and quantity columns:
<table id="order-products" class="mobileorder-table">
<colgroup>
<col style="width: 80%;">
<col style="width: 10%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr>
<td>
Product1
</td>
<td>
<span class="mobileorder-price">0,98</span>
</td>
<td>
<input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus"
type="number" min="0" max="999999" value=""
onblur="orderSumRefresh()" />
</td>
</tr>
</tbody>
</table>
Order total <p id="js-doksumma"></p>
If quantity is changed, order total value should updated. I tried
<script>
function parseFloatFormatted(txt) {
if (typeof txt !== 'string' || txt === null || txt === "") {
return 0
}
return parseFloat(txt.replace(',', '.').replace(' ', ''))
}
function orderSumRefresh() {
let totalAmount = 0
const table = document.getElementById("order-products")
table.rows.forEach((row) => {
//for (let i in table.rows) {
// const row = table.rows[i]
const hind = row.cells[1].querySelector(".mobileorder-price").value
const kogus = row.cells[2].querySelector(".quantity").value
const rowSum = Math.round(parseFloatFormatted(hind)* parseFloatFormatted(kogus) * 100) / 100
totalAmount += rowSum
});
var dok = document.getElementById("js-doksumma")
dok.innerText = totalAmount.toFixed(2)
}
</script>
but got error
How to properly implement this ? Should pure CSS, javascript or query used?
Modern Chrome browser is used in mobile phone, ASP.NET 6 MVC Razor application.
As Nick Vu said a first problem is in the for loop and I changed to:
for (let i = 0; i < table.rows.length; i++) {
I find more problems in the code for example the index of the childNodes is wrong, using
console.log(row.cells[1].childNodes)
you can see there are 3 child and you are searching for the middle one (index: 1)
Then for accessing the data of the input element you need to use the .value property like this:
const kogus = row.cells[2].childNodes[1].value
********************* EDIT *******************
Changing the code as the answer has changed.
For accessing the data of the html element use .innerHTML property.
function parseFloatFormatted(txt) {
if (typeof txt !== 'string' || txt === null || txt === "") {
return 0
}
return parseFloat(txt.replace(',', '.').replace(' ', ''))
}
function orderSumRefresh() {
let totalAmount = 0
const table = document.getElementById("order-products")
/*
for (let i = 0; i < table.rows.length; i++) {
const row = table.rows[i]
const hind = row.cells[1].childNodes[1].innerHTML
const kogus = row.cells[2].childNodes[1].value
const rowSum = Math.round(parseFloatFormatted(hind) * parseFloatFormatted(kogus) * 100) / 100
totalAmount += rowSum
}
*/
for (const row of table.rows) {
const hind = row.cells[1].querySelector(".mobileorder-price").innerHTML
const kogus = row.cells[2].querySelector(".quantity").value
const rowSum = Math.round(parseFloatFormatted(hind)* parseFloatFormatted(kogus) * 100) / 100
totalAmount += rowSum
}
const dok = document.getElementById("js-doksumma")
dok.innerText = totalAmount.toFixed(2)
}
<table id="order-products" class="mobileorder-table">
<colgroup>
<col style="width: 80%;">
<col style="width: 10%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr>
<td>
Product1
</td>
<td>
<span class="mobileorder-price">0,98</span>
</td>
<td>
<input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus"
type="number" min="0" max="999999" value="" onblur="orderSumRefresh()" />
</td>
</tr>
</tbody>
</table>
Order total <p id="js-doksumma"></p>
I suggest you to use the console.log() and log some variable to see if there is somethink wrong with the code.
Nick is correct. Remember that table.rows is not an array but an HTMLCollection. You can fix the issue simply doing:
const table = document.getElementById("order-products")
for (const row of Array.from(table.rows)) {
}
If you want to see for yourself that there is an "length" property being iterated over, open the dev tools, select the table from the elements tab, and run this snippet in the console:
for (let i in $0.rows) {
console.log(i);
console.log($0.rows[i].cells[0]);
}
You will see the last iteration print "length" and then throw an exception.
Your problem is from here
for (let i in table.rows) {}
The value will be "0" and "length" (not index like your expectation), so it throws an error while trying to access row.cells[0].childNodes (row.cells is undefined)
I'd suggest you modify it to
for (const row of table.rows) {}
The full code can be
function parseFloatFormatted(txt) {
if (typeof txt !== 'string' || txt === null || txt === "") {
return 0
}
return parseFloat(txt.replace(',', '.').replace(' ', ''))
}
function orderSumRefresh() {
let totalAmount = 0
const table = document.getElementById("order-products")
for (const row of table.rows) {
const hind = row.cells[1].childNodes[0].innerHTML
const kogus = row.cells[2].childNodes[0].innerText
const rowSum = Math.round(parseFloatFormatted(hind) * parseFloatFormatted(kogus) * 100) / 100
totalAmount += rowSum
}
const dok = document.getElementById("js-doksumma")
dok.innerText = totalAmount.toFixed(2)
}
<table id="order-products" class="mobileorder-table">
<colgroup>
<col style="width: 80%;">
<col style="width: 10%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr>
<td>
Product1
</td>
<td>
<span class="mobileorder-price">0,98</span>
</td>
<td>
<input data-product="4750211645618" class="quantity" id="product_Soodkogus" name="product.Soodkogus" type="number" min="0" max="999999" value="" onblur="orderSumRefresh()" />
</td>
</tr>
</tbody>
</table>
Order total
<p id="js-doksumma"></p>
PROBLEM:
I have form which submits list of fields. My form includes 2 'overwrite' fields:
counter - how many items to be submitted
field overwrite - if filled it suppose to overwrite all inputs in table with the same value
finally I have 5 field inputs (field_1, field_2, field_3, field_4 and field_5).
What I am trying to do is:
Counter - when filled it will disable field_ with number lower than value in counter, eg. when counter = 3, inputs field_4 and field_5 will get disabled.
field_0 - when empty, I would like user to be able to fill anything in table. When populated, I would like field_0 to be copied over to all cells in table.
WHAT I HAVE DONE:
I currently have extremely inefficient working code. I have complicated 'if' statement which checks counter and field_0 individually for every single of items (field_1 - field_5) one by one and sets them to enable/disable or copies over field_0 value. I also have 'clearFieldClass' function which clears all items with class 'field' when field_0 is being changed. While it works for 5 fields and one field type final version of the page will have 200 fields x 10 different classes. I am trying to avoid having 2000 lines of code doing basically he same thing.
function clearFieldClass() {
var elements = [] ;
elements = document.getElementsByClassName("field");
for(var i=0; i<elements.length ; i++){
elements[i].value = "" ;
}
}
<form action="https://www.tobesubmitted.to.com?" onchange="
if (counter.value > 0 && field_0.value == '') {document.getElementById('field_1').disabled = false;} else if (counter.value > 0 && field_0.value !== '') {field_1.value = field_0.value, document.getElementById('field_1').disabled = false;} else {document.getElementById('field_1').disabled = true; field_1.value = ''};
if (counter.value > 1 && field_0.value == '') {document.getElementById('field_2').disabled = false;} else if (counter.value > 1 && field_0.value !== '') {field_2.value = field_0.value, document.getElementById('field_2').disabled = false;} else {document.getElementById('field_2').disabled = true; field_2.value = ''};
if (counter.value > 2 && field_0.value == '') {document.getElementById('field_3').disabled = false;} else if (counter.value > 2 && field_0.value !== '') {field_3.value = field_0.value, document.getElementById('field_3').disabled = false;} else {document.getElementById('field_3').disabled = true; field_3.value = ''};
if (counter.value > 3 && field_0.value == '') {document.getElementById('field_4').disabled = false;} else if (counter.value > 3 && field_0.value !== '') {field_4.value = field_0.value, document.getElementById('field_4').disabled = false;} else {document.getElementById('field_4').disabled = true; field_4.value = ''};
if (counter.value > 4 && field_0.value == '') {document.getElementById('field_5').disabled = false;} else if (counter.value > 4 && field_0.value !== '') {field_5.value = field_0.value, document.getElementById('field_5').disabled = false;} else {document.getElementById('field_5').disabled = true; field_5.value = ''};
">
<table border="0">
<tr>
<th align="left">Overwrites</th>
<th></th>
</tr>
<tr>
<td><label>Counter </label></td>
<td><input required type="text" id="counter" name="counter" placeholder="Max 50"></input></td>
</tr>
<tr>
<td><label>Overwrite field: </label></td>
<td><input required type="text" id="field_0" name="field_0" placeholder="Field" onchange="clearFieldClass()"></input></td>
</tr>
</table><br><br>
<table>
<tr align="left">
<th>Field</th>
</tr>
<tr>
<td><input required type="text" class='field' id="field_1"></td>
</tr>
<tr>
<td><input required type="text" class='field' id="field_2"></td>
</tr>
<tr>
<td><input required type="text" class='field' id="field_3"></td>
</tr>
<tr>
<td><input required type="text" class='field' id="field_4"></td>
</tr>
<tr>
<td><input required type="text" class='field' id="field_5"></td>
</tr>
</table>
<input type="submit" value="Submit form"></input>
Remove onchange attribute from your html and use the following code:
document.querySelector('form').addEventListener('change', () => {
document.querySelectorAll('.field').forEach((_el, index) => {
if(!isNaN(+counter.value) && +counter.value != 0 && index + 1 > +counter.value){
_el.disabled = 'disabled';
_el.value = '';
console.log('in')
}
else{
_el.removeAttribute('disabled');
_el.value = field_0.value;
}
});
})
I have the next table:
<table id="table_id">
<tr>
<td>Test</td>
<td>Description</td>
<td>Date</td>
</tr>
<tr>
<td><select>
<option val="1">Math</option>
<option val="2">Chemistry</option>
<option val="3">Biology</option>
</select></td>
<td><input type="text" id="txt_desc"/></td>
<td><input type="text" id="date"/></td>
</tr>
</table>
And the next javascript code for get the values
val_test_op = '';
parent.$('#table_id option:selected').each(function (index,value){
test = $.text(value).trim();
if ( index === 0 && test || index % 1 === 0 && test ) {
val_test_op += index +' TEST: ' + test;
return;
}
})
val_test_input = '';
parent.$('#table_id td>input').each(function (index,value){
test = this.value;
if ( index === 0 && test || index % 2 === 0 && test ) {
texto_examen_input += index +' Description: ' + test;
return;
}
if ( index === 1 && test || index % 3 === 0 && test ) {
texto_examen_input += index +' Date: ' + test;
return;
}
})
My question is: ¿ How i get the value from the option:selected and input text in a only "each function" ?
I don't think you need a each function for getting values from fields in the table.
If you add ids to the fields, you could select those directly with jquery then use the val function to get their values easily.
<table id="table_id">
<tr>
<td>Test</td>
<td>Description</td>
<td>Date</td>
</tr>
<tr>
<td><select id="txt_test">
<option val="1">Math</option>
<option val="2">Chemistry</option>
<option val="3">Biology</option>
</select></td>
<td><input type="text" id="txt_desc"/></td>
<td><input type="text" id="txt_date"/></td>
</tr>
</table>
Then with javascript:
val_test_op = $('#txt_test').val();
texto_examen_desc = $('#txt_desc').val();
texto_examen_date = $('#txt_date').val();
If you really want to use a single each function to get all the values, you could use the :input selector that jquery provides and do the following:
values = '';
$('#table_id').find(':input').each(function (index, input) {
values += $(input).val();
});
I added in a button to alert what the selected value is.
function myFunction(){
var theSelect = document.getElementById("table_id").getElementsByTagName("select")[0];
var selectedOption = theSelect.options[theSelect.selectedIndex].text;
alert(selectedOption);
}
document.getElementById("myButton").addEventListener("click", myFunction, false);
https://jsfiddle.net/e1hxn6kL/
Give your select an ID such as id="opts"
Then
var opts = [];
$.each($("#opts:selected"),function(opt){
opts.push(opt.Val());
}
var txt_desc = $("#txt_desc").Val();
var date = $("#date").Val();
Or similar
$scope an array consisting Code and Amount on controller. When calculating summary on a function, browser gets Uncaught Error: 10 $digest() iterations reached. Aborting! error which caused from infinite loop (Strange but it is working).
Is there any proper way to combine new Array while ng-repeat without getting infinite loop errors?
Any help would be appreciated
jsFiddle Link
Update: Lines variables are not static, can be added, modified or removed.
jsFiddle Line for Update
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},
{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},
{Code:'Y', Amount:'1'}];
$scope.Sums = function(){
var sums = new Array();
for (var i = 0; i < $scope.Lines.length; i++) {
var added = false;
for (var j = 0; j < sums.length; j++) {
if (sums[j].Code == $scope.Lines[i].Code) {
sums[j].Amount = parseFloat( sums[j].Amount) + parseFloat($scope.Lines[i].Amount);
added = true;
break;
}
}
if (!added) {
sums.push( { Code:$scope.Lines[i].Code, Amount: $scope.Lines[i].Amount } );
}
}
return sums;
}
}
Html:
<div ng-controller="MyCtrl">
<table style="border: 1px solid black;">
<tr ng-repeat="line in Lines">
<td>{{ line.Code }}</td>
<td>{{ line.Amount }}</td>
</tr>
</table>
Summary
<table style="border: 1px solid black;">
<tr ng-repeat="sum in Sums()">
<td>{{ sum.Code }}</td>
<td>{{ sum.Amount }}</td>
</tr>
</table>
</div>
Does this still happen if you do this:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},{Code:'Y', Amount:'1'}];
$scope.Sums = [];
calculate();
var calculate = function(){
$scope.Sums.length = 0;
for (var i = 0; i < $scope.Lines.length; i++) {
var added = false;
for (var j = 0; j < $scope.Sums.length; j++) {
if ($scope.Sums[j].Code == $scope.Lines[i].Code) {
$scope.Sums[j].Amount = parseFloat( $scope.Sums[j].Amount) + parseFloat($scope.Lines[i].Amount);
added = true;
break;
}
}
if (!added) {
$scope.Sums.push( { Code:$scope.Lines[i].Code, Amount: $scope.Lines[i].Amount } );
}
}
}
}
Note that it is important to never create a new array once Sums is watched by angular. Use $scope.Sums.length = 0 instead if you need to empty it.
In your view: <tr ng-repeat="sum in Sums">
As Andre Kreienbring pointed out the problem is because your sum() is returning an object.
I would suggest using filters to accomplish what you need, like so
HTML
<div ng-controller="MyCtrl">
<table style="border: 1px solid black;">
<tr ng-repeat="line in Lines">
<td>{{ line.Code }}</td>
<td>{{ line.Amount }}</td>
</tr>
</table>
Summary
<table style="border: 1px solid black;">
<tr ng-repeat="sum in Lines | unique : 'Code'">
<td>{{ sum.Code }}</td>
<td>{{ Lines | filter: { Code: sum.Code } : true | sum: 'Amount' }}</td>
</tr>
</table>
</div>
Script
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Lines = [ {Code:'X', Amount:'10'},{Code:'Y', Amount:'10'},
{Code:'Z', Amount:'20'},{Code:'Y', Amount:'1'}];
}
myApp.filter('unique', function() {
return function(input, key) {
var unique = {};
var uniqueList = [];
for(var i = 0; i < input.length; i++){
if(typeof unique[input[i][key]] == "undefined"){
unique[input[i][key]] = "";
uniqueList.push(input[i]);
}
}
return uniqueList;
};
});
myApp.filter('sum', function() {
return function(input, key) {
var sum = 0;
for(var i = 0; i < input.length; i++){
sum += Number(input[i][key]);
}
return sum;
};
});
The unique filter is from https://stackoverflow.com/a/18382680/360067
Fiddle - http://jsfiddle.net/34od99sz/
I'd rather put a $watch on $scope.Lines which will create an array and populate it as $scope.sums such that ng-repeat doesn't have to call the method again and again. See Fiddle
Edit (added missing parameter for deep watching):
Fiddle
if I am not mistaken in the line
for (var j = 0; j < sums.length; j++)
you try to take the length of sums whitch is a new array. it has no length though. Also why do you use
var sums = new Array();
to initialize the array and not
var sums = [];
I add dynamically rows in my table with ng-repeat, coming from an array.
Now I want to get the sum of all sums per row (group.sum * group.perc / 100.0). I need it in a variable because I need this value for further calculations. Thank you
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}}</td>
</tr>
SCRIPT
var taxTotals = 0;
var taxTotals =
for (i=0; i<group.length; i++) {
taxTotal = taxTotal + group[i].taxes;
};
console.log(taxTotals);
};
Create a Filter:
app.filter('sumFilter', function() {
return function(groups) {
var taxTotals = 0;
for (i=0; i<groups.length; i++) {
taxTotal = taxTotal + groups[i].taxes;
};
return taxTotals;
};
});
Use the $filter service:
app.controller('myController', function($scope, $filter) {
$scope.groups = [...];
var taxTotals = $filter('sumFilter')($scope.groups);
console.log(taxTotals);
});
Use it in your HTML:
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ groupsArr | sumFilter | currency }}
</tr>
An addition for best answer... I am using filter in my very huge table, so it is how to implement with dynamic filters.
THE FILTER
app.filter('sumStatusFilter', function(){
return function (items, filtersStatus, filterLocations){
var filtered = [];
var filtered1 = [];
var total = 0;
if (typeof filtersStatus != 'undefined') {
angular.forEach(items, function(item) {
for(i = 0; i < filtersStatus.length; i ++){
if(filtersStatus[i] == item.status_message)
filtered.push(item);
}
});
}
if (typeof filterLocations != 'undefined') {
angular.forEach(filtered, function(item) {
for(i = 0; i < filterLocations.length; i ++){
if(filterLocations[i] == item.office_location)
filtered1.push(item);
}
});
filtered = [];
filtered = filtered1;
}
if (filtered.length == 0) {
filtered = this.jobs
}
angular.forEach(filtered, function(value, key){
total += value.restoration_reserve
});
return total;
}
});
in HTML
<tr><td>Total: {{ report_controller.items | sumStatusFilter:report_controller.status_message_selections:report_controller.office_selections | currency }}</td></tr>
UPDATE AFTER ANSWER coming from pixelbits
Thanks to pixelbits. Here is my filter, which works perfect within the view.
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ groupsArr | sumFilter | currency }}
</tr>
Filter
angular.module('App.filters', []).filter('sumFilter', [function () {
// filter for tax sum
return function(groups, lenght) {
var taxTotal = 0;
for (i=0; i < groups.length; i++) {
taxTotal = taxTotal + ((groups[i].perc * groups[i].sum) / 100);
};
return taxTotal;
};
}]);
If I want to access from my controller, it doesn´t work: I cannot get the variable taxTotals *Cannot read property 'length' of undefined
As mentioned, in the view it works.
Filter Service
var taxTotal = $filter('sumFilter')($scope.groups);
console.log(taxTotal);
Or use Map Reduce!
Controller
$scope.mappers = {
tax: function(m){
return group.sum * group.perc / 100.0;
}
}
$scope.sum = function(m){
if($scope.groupsArr.length == 0) return;
return $scope.groupsArr.map(m).reduce(function(p, c){
return p + c;
}) || 0;
};
HTML
<tr ng-repeat="group in groupsArr">
<td class="total-rows" ng-model="taxes">{{group.sum * group.perc / 100.0 | currency :""}} </td>
</tr>
<tr>
<b> Tax Totals: </b> {{ sum(mappers.tax) }}
</tr>