How to update only specific rows when using ng-repeat - javascript

I have a UI similar to excel, where the user can change the data in which ever row he wants.
Currently i am using ng-repeat to display the existing data, after fetching from mongoDB. This data is an array of Json Objects , so ng-repeat works fine.
Now i want to have a single save button, clicking which, all the edits should be saved. My question is how to maintain or know which rows have been changed ?? i can just send the whole array and update in the db, but when the number of rows is large and data of just one row has changed there will be an unnecessary overhead. so let me know how this can be optimised.
Thanks in advance

Add an ng-change event to fields where they can edit the values.
On ng-change, set a new key 'modified: true' on 'obj in objects'.
Something like this:
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="markAsChanged($index)" ng-model="row.textValue">
</div>
And your JS:
$scope.markAsChanged = function(index) {
$scope.rows[index].changed = true;
}
Then your update function could iterate all rows and send changed rows to the webservice for updating instead of sending them all.

$scope.rowsChanged = [];
$scope.Changed = function(row) {
$scope.rowsChanged.push(row);
}
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="Changed(row)" ng-model="row.textValue">
</div>
Hope it helps..

This thing can be achieved by maintaining an array which contain index for all the rows that has changed. On clicking on save button , an iteration will be required on this array (For rows which have changed).
A new $index can be pushed to this array anytime when the user is just clicking on a row or on changing any input in the array.
HTML:
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="markAsChanged($index)" ng-model="row.textValue">
</div>
Controller:
var listOfChangedRows = [];
$scope.markAsChanged = function(index){
if(listOfChangedRows.indexOf(index)==-1)
{
listOfChangedRows.push(index);
}
}
//Save button Clicked
$scope.saveEditedRows = function(){
var updatedItems = [];
for(var i=0;i<listOfChangedRows.length;i++)
{
updatedItems.push($scope.rows[i]);
}
//Now send the updatedItems array to web service
}

Related

Filters per column in a javascript table (Angular) combined results

I have this ngx-datatable for Angular that doesn't support filter by column. I would like to add an input filter for every column (some are strings, some are multiple choices etc) and combine them to a single filter so I can use it to get data with rxJs from the backend.
What I have for now:
This is the filter component on every column header:
<div class="input-group mb">
<div class="input-group-addon">
<span class="input-group-text" id="search-addon"><em class="icon-magnifier"></em></span>
</div>
<input aria-describedby="search-addon" id="order_id" aria-label="Customer" placeholder="Search" class="form-control" type="text" (keyup)='updateFilter($event)'>
</div>
The update filter function
updateFilter(event) {
let columnName = event.currentTarget.id;
const val = event.target.value.toString().toLowerCase();
const filteredData = this.temp.filter(function(d) {
return d[columnName].toString().toLowerCase().indexOf(val) !== -1 || !val;
});
this.rows= filteredData;
this.table.offset = 0;
}
This works for every column. But how can I combine all the filters and start observing the API response?
Your updateFilter() methods needs to values of all filter inputs, not only the one passed in via $event.
One way of doing can be to create an object filters in your component and two-way bind it's properties to your search inputs in the column headers. Listen to the ngModelChange event and trigger the actual filtering.
class MyComp {
// Other stuff
filters = {};
filter = () => {
// Do the filtering, all filters are set in this.filter object
}
}
In your HTML template bind it and listen to the ngModelChange event to trigger the filtering whenever the value changes (better than using keyUp, as it also triggers when the content changes without a key being pressed, e.g. copy-pasting via context menu).
<input id="order_id" [(ngModel)]="filters.order_id" (ngModelChange)="filter()" ... />

How to define a model for dynamically created checkboxes in angular

I am creating a table of users where I want to add a checkbox on each row and a delete button. When I click the delete button, I want to delete all users who were selected.
Now I am creating these user entries from an API response which gives me say id, name and email.
So my view looks something like this:
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
What I want in my controller is to have an object with id of all the users for whom the checkbox was clicked.
Even if I create an object and assign it as model for checkbox, how do I add a key as id in that object?
You could simply do <input type="checkbox" ng-model="user.isSelected">
And then just filter MyCntrl.data.users for those that have isSelected === true
Because of JavaScript dynamic typing nature, nothing stops you from adding a field named 'isSelected' (or alike) to your models. Then, you can add ng-model="user.isSelected" to your checkbox tag.
Then, on deletion, check which entries have isSelected set to true and delete them.
Here's an example for how you can track all the selected users in an another array:
Example: Plunker
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox" ng-model="tempVar" ng-click="toggleSelection($index)"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
<!-- AngularJS Code -->
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.selectedUsers = []; // initially the selected users array is empty
$scope.toggleSelection = function(index){
var positionInSelectedArray;
var arr = $scope.MyCntrl.data.users;
var tempUser = arr[index]; // refers to the selected user object in $scope.MyCntrl.data.users array (further, we'll call it "arr")
var userAlreadySelected = $scope.selectedUsers.filter(function( obj ) {
return obj.userId == tempUser.userId;
})[0]; //checks whether the user is already selected or not (if selected, then returns the user object)
if (angular.isUndefined(userAlreadySelected)) {
$scope.selectedUsers.push(tempUser); //insert the object in array containing selected users
}else{
positionInSelectedArray = $scope.selectedUsers.indexOf(userAlreadySelected);
$scope.selectedUsers.splice(positionInSelectedArray, 1); //removes the user object from array containing selected users
}
};
});
</script>

Dynamically add filter options in angularjs

Hello I have a small project in which I want to have perform search from multiple dynamically added text fields.
This is how I add the search fields:
<div class="form-group" ng-repeat="choice in choices">
<button ng-show="showAddChoice(choice)" ng-click="addNewChoice()">Add another choice</button>
<input type="text" ng-model="choice.name" name="" placeholder="Search criteria">
</div>
And later I have a table with ng-repeat and here is that part:
<tr ng-repeat="todo in todos | filter: {filter from all fields}">
.......
</tr>
What I want to do is to have the contents filtered with all dynamically added search fields.
You'll have to create your own filter to handle that. I've gone ahead and gotten you started.
$scope.myFilter = function(input){
for(var key in input){
for(var x = 0; x < $scope.choices.length; x++){
if(input[key] == $scope.choices[x].name){
return true;
}
}
}
return false;
}
Here is the jsFiddle of the output: http://jsfiddle.net/wsPrv/
Rather than using the filter, do the filtering in the controller yourself. Here is the updated fiddle with the solution. In the first textbox, replace choice1 with "some" and you will see the todo with text "Some stuff" being displayed.
See the relevant part below. For details, see the fiddle.
$scope.$watch('choices', function(newValue) {
$scope.DisplayedTodos = [];
// Filter items here and push to DisplayedTodos. Use DisplayedTodos to display todos
}, true);

KnockoutJS update row sum field

I'm stuck on how to update row sums in a foreach template using knockoutJS
<div id="timeEntryList" data-bind="foreach: timeEntries">
<table >
<tr>
...
<td> //there are more of this, not included here
<input type="number"
data-bind="value: Days[6].Hours,
event: { change: $root.setDirty }" />
</td>
<td> //this part needs to be updated when the above input is changed
<span data-bind="text: $root.sumRow($data)">
</span>
</td>
The last TD there contains a span element which displays the sum of hours reported for the current item in the foreach.
it displays correctly when the data is loaded, but then stays stale when I edit the elements.
How can I make this element update as I change the values of the input boxes?
Here is my view model in a very slimmed down version:
var TimeReportModel = function (init) {
this.timeEntries = ko.observableArray(init.TimeEntries);
//... helper functions
};
TimeEntries are objects representing a reported hours per week.
So it contains an array of days and each day has an hour property.
Based on what you're binding to, it appears you're binding to the result of a regular function. If you want to see the values updated when there are changes, you need to bind to an observable. Make the sum a computed observable in your view model and bind to it.
I have no idea what your view model looks like or what you are adding up but it would look something like this:
// calculate the sum of the hours for each of the days
self.totalDays = ko.computed(function () {
var sum = 0;
ko.utils.arrayForEach(self.days(), function (day) {
sum += Number(day.hours());
});
return sum;
});
Here's a fiddle to demonstrate.

how to update a list of html items based on a list of json data

I have a list of checkboxes in my html page, like so:
<ul id="myList">
<li class="checkboxItem">
<input type="checkbox" name="item1" value="001" id="item-1"/>
<label for="item-1" class="checkboxLabel">Display 1</label>
</li>
<li class="checkboxItem">
<input type="checkbox" name="item2" value="042" id="item-2"/>
<label for="item-2" class="checkboxLabel">Display 42</label>
</li>
</ul>
now I make a call to get some json data, which comes back like so:
[{"name":"002","title":"Display 1"}]
what I want to do is loop the returned json and update the list of checkboxes such that any item not in the returned list is disabled, and those where the title matches a given label, the input value is updated.
so in this example, item2 will be disables and item1 will have its value updates to 002.
here's what I have so far, i'm not quite sure where to go from here, that is, what to do inside the loop. I do have some control over how the json is returned, so if it makes sense to retunr the json in another format, I can do that.
EDIT, updated the function, see below. however, once I get inside the for loop inside the each function, elem is getting a value of "0" rather than a js object such as:
{"name":"002","title":"Display 1"}. clearly data, is being transferred from the outside scope of the function to the inside scope of the each function, but how do I make that happen?
function(data) {
$('#myList').children('li').each(function(i,e) {
for(var elem in data) {
var elemDescr = elem['title'];
var elemName = elem['name'];
if(elemDescr==$(this).find('.checkboxLabel').text()) {
$(this).find('input').attr('value',elemName);
}
}
});
It might be easier to have an outer loop for each checkbox, and an inner loop go through each json element, enabling or disabling based on whether the element/checkboxes have a match.
So an inversion of what you have:
function(data) {
$('#myList').children('li').each(function() {
// loop through your data elements here
});
}
Another option (probably less desirable because it may cause multiple disabled/enabled transitions) is to disable all checkboxes, and enable them as you loop through each element.
I have found my problem. rather than doing:
for(var elem in data) {
var elemDescr = elem['title'];
var elemName = elem['name'];
}
I needed to do:
for(var index in data) {
var elemDescr = data[index].title;
var elemName = data[index].name;
}

Categories