Explanation
I have a very simple calorie tracking app that uses the Nutritionix API to search for food items based on the user's input. The results are added to a results array, which is then displayed to the user. When a user clicks the "Add" button next to one of these items, the calories are added to a counter, and the food itself is added to a todaysFood array (using Ember's pushObject). This is then used to display which food the user has consumed today in a separate table.
When a user clicks the remove button next to one of the todaysFood items, it triggers an action, removeItem, and passes the index of the item clicked to removeItem. This index is used inside of Ember's removeObject to remove the item from the todaysFood array, and thus update the view (remove that item from the list and its calories from the counter).
Problem
When more than one of the same item are added to todaysFood, clicking remove on just one of those items removes ALL of the instances from todaysFood, and the view. This makes sense to me now, because of the docs' example:
var cities = ['Chicago', 'Berlin', 'Lima', 'Chicago'];
cities.removeObject('Chicago'); // ['Berlin', 'Lima']
cities.removeObject('Lima'); // ['Berlin']
cities.removeObject('Tokyo') // ['Berlin']
However, it also only removes the calories of ONE item, not all instances.
So, the question is: How do I remove only ONE instance of that item when remove is clicked? I.e., if two tacos are added, and I click remove on one, I only want that ONE to be removed (from the list and the calories).
Here is my removeItem action:
removeItem(index) {
var self = this;
// Store property paths for easy access
let todaysPath = this.get('healthData').todaysFood;
let caloriesPath = 'healthData.calories';
this.set(caloriesPath, this.get(caloriesPath) - Math.round(todaysPath[index].fields.nf_calories));
todaysPath.removeObject(todaysPath[index]);
}
Disclaimer
I'm aware that I may not be handling this correctly at all. I'm open to any suggestions to make this better. Thanks!
You have index of object to remove so you can try using removeAt() method:
todaysPath.removeAt(index);
Related
I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.
In my angular code on page load there are list of buttons like button 1, button 2, button 3.. etc on click of every button it shows J-SON on console the structure is same for all buttons but the values vary. There are two more buttons on page on up and down.
My question is if button 2 is selected and i click on up button then the position of button 2 should moved up to button 1 for that we have use one attribute in j-son but problem m facing is that how should i swap whole j-son like how to swap position of button 2 to button 1 and vice-versa for down arrow
searched for swapping on google but it showing items in array to swap. i want whole j-son to swap
up(){
exchange(this.jsondata,up,up+1);
}
private exchange(array: any, x: any, y: any) {
const temp = array[x];
array[x] = array[y];
array[y] = temp;
return array;
}
it should change the position of buttons on click of up down buttons
If what you want is swapping two elements in an array, your exchange function seems ok.
But you are calling it with only 2 parameters : up(up, up+1) while your function takes 3 parameters :
the array where the items are
the first item
the second item
If your buttons were in an array named myButtons, you would probably want to call it that way :
up(myButtons, up, up+1);
Side notes :
be careful what you're naming your parameters. Array is a global JavaScript object. It's not a good practice to use that name for a variable.
up is obviously the name of a function and a variable. That is kind of confusing.
your function returns the array that was passed as a paramter. That is not necessary since arrays are passed as references. You can make your function immutable and have it return a different array. Or modify the reference and not return anything.
So this follows on from my previous question:
knockout js, add additional elements to an array
Basically I have an app where a user fills in certain data, clicks next and this is added to an array. However, what I'd like to do is add some items into the array before the user even begins using the app (these items I get from a database). The idea being that at the start they can view each item in the array and then choose and an item and edit this item. I've got a feeling I'm missing something blindingly obvious but I cannot seem to figure it out
Knockout observable arrays have equivalent functions to native JavaScript arrays. See: http://knockoutjs.com/documentation/observableArrays.html
So you need just to use arr.pop(item) or arr.push(item).
In case you need to replace all items and want to avoid multiple events to raise, use observableArray.valueWillMutate() and valueHasMutated() functions. See sample where I do swap the entire array:
ko.observableArray.fn.replaceWith = function (valuesToPush) {
// NOTE: base on - ko.observableArray.fn.pushAll
var underlyingArray = this();
var oldItemcount = underlyingArray.length;
this.valueWillMutate();
// adding new items to obs. array
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
// removing old items (using KO observablearray fnc.)
if (oldItemcount > 0)
this.removeAll(underlyingArray.slice(0, oldItemcount));
this.valueHasMutated();
return this; //optional
};
I have 2 multi-select lists in angular that are dependent on on another. One is the parent list and one is the child list. When landing, there is nothing in the child list and once you select (one or many) items from the parent multi-select, it will then populate the child list with the children of the selected item(s) from the parent list. This works great - just for reference I have a $watch on the parent model, so when it changes (the user selects something in the parent list - it will then call an $http and fetch the results for the children list
like so -
$scope.$watch('selectedResources', function (newValue) {
angular.forEach($scope.selectedResources, function(data){
$scope.generalIDArray.push(data.id);
});
//id's = $scope.imageIDArray
//place new data in $scope.imageOptionsSub
$http({
method: 'POST',
url: '/listSubCategories',
data: {
page: 0,
ids: $scope.generalIDArray
}
})
.success(function(data){
//empty options
$scope.resourceOptionsSub = [];
//push new data in
angular.forEach(data.subCategories, function(index) {
$scope.resourceOptionsSub.push(index);
});
});
So - I push all the id's in and send them to get the results back.
However - here is my problem. I realized after trying this out that I don't want to completely replace resourceOptionsSub with all the new results, because the user has interacted with the child results - they have a .checked value on them that means the user has selected them, this will be wiped out and refreshed each time I make a new call because it empties out the scope and replaces it, even if it is the same items.
What I would like to happen is to kind of compare if there are items that already exist in resourceOptionsSub that are coming in with the call, and sort of keep the original resourceOptionsSub and compare it to the new one coming in, and maybe pull off the items that don't exist any more?
I'm thinking I should somehow compare the 2 objects 1 being the original, 2 being the new - and if 2 has anything 1 does not have, then pull it out of 1, because then I could keep the items that are the same untouched. If anyone could point me in the right direction here as to where to look I would much appreciate it, as I am at a bit of a loss as to how to handle this. Thanks for reading!
angular.extend will work but you will need start with an empty object like so:
var updatedObj = angular.extend({}, obj1, obj2);
I am working offline with SQLite, Javascript and Chrome
In my main page (main.html), I have two div: <div id="menuLeft"> that contains the list of items name with buttons to edit each item, and
<div id="content">
The list of item is written as follows:
<li>ItemName1
<div id="idItem1" class="editItem_btn">
<img src="btn_edit.png">`
</div>
</li>
In main.html, I have the following code:
$("#menuLeft").delegate(".editItem_btn", "click", function(e0)
{
e0.preventDefault();
var editItemId = $(this).attr("id");
editItemId = parseInt(editItemId);
var url="edititem.html"
$("#content").load(url,function(){
loadRecord(editItemId);`
});
});
When I click on the edit button of a given Item, the id of the Item is first retrieved from the id of the div around the edit button. Then I load the page edititem.html content. On success, I run the function loadRecord(editItemId), where loadRecord(i) is contained in edititem.html:
function loadRecord(j)
{
var item = dataset.item(j);
idItem.value = item['id'];
ItemName.value = item['ItemName'];
dateStart.value = item['dateStart'];
dateEnd.value = item['dateEnd'];
notes.value = item['notes'];
}
This function enables to display the parameters of Item (id, ItemName....) contained in the database.
Here is my problem, the code works but in a weird way meaning that if I click on the edit button of Item1, the parameters of Item2 are displayed. Same thing if I click on edit Item2, parameters of Item3 are displayed.
I then replaced:
var item = dataset.item(j);
with:
var item = dataset.item(j-1);
and that works. But I need to understand why it's behaving like that, and why I need to use (j-1). I placed some alert() in the jquery code to check that I have the right editItemId number, and in the function loadRecord(j). The right id number is retrieved after the click and the right id number is passed to the function. I have no idea what's the bug here!
Without seeing the sql side of things, and how that data is passed back to your script it's impossible to tell you exactly what's happening, but this is simply a case of some lists being 0 based and some lists being 1 based. For example, arrays are generally 0 based (unless you specifically create them a different way), but $("#id").each(function(Index)... is 1 based. You just have to know what you're working with and occasionally do as you have found and use -1 or +1 when relevant.
While I'm not familiar with the intricacies of SQLite, I suspect that dataset.item(j):
is accepting a 0-based index
that you are passing in the record_id (which in itself is not actually an array index)
and that the record_id for the dataset you are testing just happens to be the index + 1 (meaning you're getting lucky right now and that it will probably change when the next dataset is loaded).
I would check to see if there's an equivalent for dataset.item(j) which accepts a record_id and not an index. Otherwise, you'll probably want to store the index of the record somewhere in the record itself to be able to pass it to your loadRecord function.
Hope this helps,
Pete