Set observable/computed value from a foreach of inputs knockoutjs - javascript

It's my first time working with KnockoutJS, and I have a little bit of problem.
I'm trying to get multiple values from inputs then set the addition of the values to a observable or make a computed that get the result of the addition. But I can't figure out how, because the table it's generated from a database and I don't know the quantity of rows until I run the app. Any help will be appreciated. Thanks.
Here is a capture of the site
I need in the field "INVERSION TOTAL DEL MES" the adition of the values from the inputs from "IMPORTE TOTAL"
Here's the part of the html with the bindings
<tbody data-bind="foreach: RendicionesPager.pagedRows">
<tr><td class="text-right">$ <input style="width:90%;text-align:right" data-bind="textInput:ImporteTotal" /></td>
</tr>
</tbody>
<tr>
<td colspan="7" class="text-right" >INVERSION TOTAL DEL MES</td>
<td class="text-right"><span data-bind="text:'$ ' + InversionDelMes()"></span></td>
</tr>

Do something like:
InversionDelMes = ko.computed(function(){
return RendicionesPager.pagedRows.reduce(function(p,n){ return p.ImporteTotal + n.ImporteTotal ;});
});
You will need to adjust all contexts, add () if ImporteTotal is an observable etc.etc but you get the idea :)
Edit: to explain better, create a ko computed, that will be updated when RendicionesPager.pagedRows is changed, and each time it will aggregate the ImporteTotal field of all contained elements and return it.
Also have in mind that this will work when you add/remove items from your observable array RendicionesPager.pagedRows (if it is obsArray that is). If you want to recalculate your values as a total each time a user changes an input value then you would have to subscribe to each observable field and run a similar logic,for example
ImporteTotal.subscribe(function(){
myTotal = RendicionesPager.pagedRows.reduce(function(p,n){
return p.ImporteTotal + n.ImporteTotal ;
});
});
so that when any of the observables changes its value, it will fun this function which will in turn update the total value

Related

html table is duplicating defaultValue's for no apparent reason

I have some data that is displayed in a table. When I add a table data cell (td) with JS the defaultValue and innerText of the previous table data cell (td) is being copied in the new table data (td). I don't understand why this happens? I just add a new html element which, in essence, has nothing to do with the already existing element I would say.
const [dataSet, setDataSet] = useState([
{
City: "Amsterdam",
Provence: "North-Holland"
},
{
City: "Rotterdam",
Provence: "South-Holland"
}
]);
const newCity = () => {
const city = [
{
City: "Groningen",
Provence: "Groningen"
}
];
setDataSet(city);
};
<button onClick={newCity}>Add city</button>
<table>
<tr>
<th>City</th>
<th>Provence</th>
</tr>
{dataSet.map(city => (
<tr>
<td>
<input defaultValue={city.City}/>
</td>
<td>
<input defaultValue={city.Provence}/>
</td>
</tr>
)}
</table>
So when I click on the 'newCity button' a new table row (tr) is added, but instead of populating the table data cells (td) with the correct data (City: 'Groningen', Provence: 'Groningen') it populates the td with the data from the previous table row (tr) (City: 'Amsterdam', Provence: 'North-Holland'). This seems really odd to me, since I only add a new html element that has no relation to the table row (tr) before it exempt for it being in the same table.
Here is a codebox link to see the behavior in action:
https://codesandbox.io/s/dry-water-rss2m8?file=/src/App.js
Why does it behave like this?
There are two important things to take into consideration here.
First, your program is outputting a warning:
Warning: Each child in a list should have a unique "key" prop.
Always deal with warnings. They are there for a reason!
Second, you are dealing with a default (as opposed to current) value.
When you replace the array with a new array, React generates new JSX and compares it to the existing DOM.
The first row has the same (lack of) key so it changes the default values of the existing row. (The second row no longer exists so it removes it).
Changing the default values doesn't have any effect through: The inputs already have values, which they continue to display.
You could give the generated rows keys:
<tr key={something}>
… but you will need to figure out what the something is.
You might use the city name, but it seems like that is a piece of data which might change — so changing the city name would make it look like a new row and not an update to an existing row.
You might consider using the uuid module to generate a unique ID when the object is created.
You could also make the inputs controlled by passing them values instead of default values. This would require that you update the value you pass to them as they are edited.
You need to set a key on the item
{dataSet.map((city) => (
<tr key={city.City}>
...
etc
I guess it is because you are working with uncontrolled components.
If you want your input values to reflect state changes you need to use the value and not the defaultValue, and you also probably want to have an onChange handler.
<input value={city.City} />
First, the native Input element doesn't have defaultValue attribute. Look at the attributes list in MDN. It has only value and placeholder.
Secondly, if you want to add new city to the dataSet, you must merge the city-object to the array, not replace it.
setDataSet(currentDataSet => {
return [...currentDataSet, city]
})

javascript - sum of values on input change

I'm modifying a draft website which is basically mde by storing data in local storage and fetching and rendering it. On the cart.html page input are created dynamically, depending upon how many packages are added in the cart.
There are 2 prices, one which is package's price (works totally fine) and the other one is Total price of all the items in cart, which doesn't seem to update and adds on values to the previous value. I'm not sure what I'm doing wrong but any help would be great!
I'm also attaching stackblitz to get better understanding of my application. Please readme if you want instructions
cart.html
<div class="totalPrice">
<table>
<tr>
<td>Total Price</td>
<td id="totalPriceCount" data-total=""></td>
</tr>
</table>
</div>
JS: (Don't really have idea how to achieve)
$(".myInput").on('input', function () {
varCurrentVal = this.value;
if (varCurrentVal == 0) {
return;
}
var closestSibAttr = $(this).parent().parent().find("#quantityBasedPrice").attr("data-val");
$(this).parent().parent().find("#quantityBasedPrice").html(varCurrentVal * closestSibAttr).attr("data-total", varCurrentVal * closestSibAttr);
// code to update #totalPriceCount to sum all the values into the Total Price
})
Stackblitz: https://stackblitz.com/edit/web-platform-8cynxp?file=turkey.html

How to track the progress of a large OrderBy

I have a standard enough dynamically filled table in bootstrap that is instantiated with the following definition
<table>
<tr ng-repeat="c in controller.items :
orderBy:controller.predicate:controller.reverse">...</tr>
</table>
And a pretty standard function that, when called with a string argument, will sort the table, using the string argument as the predicate.
controller.orderCasesBy = function (predicate) {
controller.reverse = (controller.predicate === predicate) ?
!controller.reverse : false;
controller.predicate = predicate;
}
The page I'm working on potentially has upwards to a thousand rows, so I was trying to figure out a way to track the progress of the ordering function and update a progress bar accordingly.
If this were a case where I was trying to track the progress of the entire table rendering for the first time, I could attach an ng-init function to each repeated row that could update the progress bar whenever it's called. ng-init functions don't seem to be called when a table is reordered, however, so I was wondering if there was anything I could do to achieve a similar effect.
If I can't, I'd love to hear suggestions on alternative ways to track this.
You can add an object parameter to the ordering function, which updates its value property (or call it whatever you want), then use the object.value to render your status indicator as you see fit.
Since you said you're open to alternatives, you don't need to actively reverse it at all (and therefore don't need to indicate progress for it). ngRepeat allows you to track by index, which means you can simply display them in reverse index order.
Example:
<table>
<tr ng-repeat="c in controller.items track by $index">
<td ng-model="controller.items[controller.items.length-($index+1)]"></td>
</tr>
</table>

Delete multiple ng-repeat elements on click of a single button

I have a table showing project names as shown in below image ,
On delete button click, I am passing the selected check-boxes data as array of objects to my controller
[{id:1,name:'Name 8'},{id:2,name:'Name 7'}]
Then deleting the names from the table at the server side. This all works fine but how do i remove the rows from DOM after deleting them?? I went through this post which says how to remove ng-repeat elemets from DOM but in that case the elements are removed one at a time, by passing the $index to the splice() function
In my case i need to remove multiple rows. If ill have to use the splice function in my controller how do i get the index from the selected rows object? Or is there any better way of doing it.
Hope my question is clear!
Update: jsFiddle
Solution: Well i had to modify #wickY26 answer a bit to suite my scenario. Here is my update jsFiddle
What i did was , in the delete() change code to
angular.forEach($scope.projects, function (row, index) {
if($scope.projects[index].checked) {
$scope.projects.splice(index,1);
}
});
You can keep selected rows on an object via binding checkbox with ng-model,
so example table html should be like this
HTML
<table class="table table-bordered">
<tbody>
<tr ng-repeat="row in data" ng-class="{'success' : tableSelection[$index]}">
<td>
<input type="checkbox" ng-model="tableSelection[$index]" />
</td>
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>
and if you define a function in your controller which travel through your data and splice array depends on tableSelection object boolean values...
UPDATE
after your comment I debug my code and see I cannot remove multiple rows same time, so I look at my code and change some part of it...
at my example you cannot delete multiple rows same time because everytime you splice an element from data array you shift indexes of array for rest, so right way to do it starting from last index,
Here new CONTROLLER
$scope.removeSelectedRows = function() {
//start from last index because starting from first index cause shifting
//in the array because of array.splice()
for (var i = $scope.data.length - 1; i >= 0; i--) {
if ($scope.tableSelection[i]) {
//delete row from data
$scope.data.splice(i, 1);
//delete rowSelection property
delete $scope.tableSelection[i];
}
}
};
I update my PLUNKER added comments and some other functionallty as well...

Knockout.js - Adding properties to data sent serverside

I've got some data sent via a server, formatted like this:
[
{"Username":"user1#domain.com", "id":1},
{"Username":"user2#domain.com", "id":2},
{"Username":"user3#domain.com", "id":3}
]
I bind it to a table, but I'd like the ability to add a class to the table row when the checkbox is checked (to indicate it's been selected). Here's what will eventually work, and I know the problem is that Selected is not a property currently in my data.
<table>
<tbody data-bind="foreach: Items">
<tr data-bind="css:{selected: Selected}">
<td>
<input type='checkbox' data-bind="attr:{name: id}, checked: Selected" />
</td>
<td data-bind="text: Username"> </td>
</tr>
</tbody>
</table>​
Since the concept of Selected is something purely for the UI, it seems a little dumb to have the server send that over the wire for each item in my data.
What I want to happen is basically this: http://jsfiddle.net/xSSMX/ but without having to add the observable Selected property on each item.
How can I add a property to each existing item in my data to achieve this?
You could just use map to add the field to the array that you get from the server like this...
data = data.map(function(item) {
item.Selected = ko.observable(false);
return item;
});
Which will add Selected on to each item. Although if I'm not mistaken map isn't supported in all browsers so you'd have to add support which you could do with a function similar to the one found here... http://www.tutorialspoint.com/javascript/array_map.htm. Or you could achieve the same effect using jQuery's $.each.
When the server sends over the data, you can create a userModel(id, userName). This model by default will have selected on false. I included a jsFiddle to make this a bit more clear.
http://jsfiddle.net/xSSMX/1/

Categories