1

I'm currently making a small app to practice JavaScript and jQuery. I have an array of fruits.

var fruits = [apples, bananas, grapes, watermelons, tomatoes];

And on a click event I want to remove tomatoes but if I change my mind clicking the same button, it will add tomatoes back to the array fruits. I've been using splice to remove but I don't know what to use to add the splice element back into the array.

Edit for clarification:

The element is not necessary going to be tomatoes, but it could be any random element in the fruits array. I'm using

fruits.splice(i,1);
OrangePineapple
  • 65
  • 2
  • 2
  • 12
  • 1
    Could you provide your current code (html/js)? – sinisake Nov 27 '16 at 08:54
  • `fruits.push(tomatoes);` – connexo Nov 27 '16 at 08:55
  • 2
    So your problem is not re-adding, but temp-storing your spliced object to keep it for re-adding? – connexo Nov 27 '16 at 09:09
  • 1
    `var deletedFruits = fruits.splice(i,1);` will contain an array of the removed elements since that is the return value of `splice()`. – connexo Nov 27 '16 at 09:12
  • @connexo Yes, that would make more sense. hmmm filter? – OrangePineapple Nov 27 '16 at 09:12
  • Is this what are you looking for? https://jsfiddle.net/6LL9suhk/1/ – Luigi Cerone Nov 27 '16 at 09:12
  • @connexo Yes, omg so simple. A var. that would make it more easier. THANK YOU. so simple. – OrangePineapple Nov 27 '16 at 09:13
  • You could even store the position of the deleted element: `var deletedFruit = { fruit: fruits.splice(i,1)[0], index: i }` so if need be you can even restore deleted fruits at their original array position using the aforementioned `fruits.splice(deletedFruit.index, 0, deletedFruit.fruit);`. – connexo Nov 27 '16 at 09:23
  • I would just make a copy of the entire array instead of worrying about where to re-insert the deleted element, e.g. `var saveFruits = fruits.slice(0);` and then to restore it you can just do `fruits = saveFruits;`. – Michael Geary Nov 27 '16 at 09:27
  • By reference, or would you clone it? Why waste memory? – connexo Nov 27 '16 at 09:30
  • Who cares about wasted memory? Memory is cheap compared to debugging time. – Michael Geary Nov 27 '16 at 09:31
  • `tomatoes` could be an arbitrarily complex object. Imagine you're using Knockout.js or some other library and the object contains observables, subscriptions etc. – connexo Nov 27 '16 at 09:31
  • So what? I would still "waste" the memory. A simple and reliable solution is better than premature optimization. – Michael Geary Nov 27 '16 at 09:33
  • 1
    This has nothing to do with premature optimization. It's coding the required functionality on-point. – connexo Nov 27 '16 at 09:34
  • If we use our imagination, there are many things we could imagine about OP's code and data. But there's nothing in the actual question to indicate that anything so fancy is going on. It's good to be aware of both a finely tuned approach and a simple brute force approach. Myself, I've had more than one occasion where I jumped directly to a solution that I thought would save memory - and then had the off-by-one errors to prove it. – Michael Geary Nov 27 '16 at 09:44

3 Answers3

5

To insert a value back into an array (at the same position) after you spliced it, can in general be done like this:

// delete:
var deleted = fruits.splice(i, 1);
// restore:
fruits.splice(i, 0, deleted);

Note that deleted is an array with one element here.

It can also done by taking a kind of backup of the original array:

// backup
var backup = fruits.slice(); // copy
// delete:
fruits.splice(i, 1);
// restore:
fruits = backup;

Undo Stack

To support multiple undo actions, you could use an undo stack, which would just keep track of all the versions of your array. When the user performs an undo-action, the previous version is popped from that stack. This way you can undo more than one removal:

var fruits = ['Apples', 'Bananas', 'Grapes', 'Watermelons', 'Tomatoes'];
var undoStack = [];

function showFruits() {
    $('#fruits').html('').append(
        // translate the array to a list of LI elements with delete buttons 
        fruits.map(function(fruit) {
            return $('<li>').text(fruit).append(
                $('<button>').addClass('delete').text('Delete'));
        })
    );
}

$(document).on('click', 'button.delete', function () {
    undoStack.push(fruits.slice()); // save a copy of the current array on the stack
    fruits.splice($(this).parent().index(), 1); // remove from array at index
    showFruits(); // update display
});

$('#undo').click(function () {
    if (!undoStack.length) return; // cannot undo 
    fruits = undoStack.pop(); // get previous state
    showFruits(); // update display
});

showFruits(); // show initial list
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="fruits"></ul>
<button id="undo">Undo</button>

More memory efficient alternative

If you are troubled by the memory usage of storing the complete array each time you delete an element, you could use the following alternative functions, which will only store the index and deleted value at every delete action:

$(document).on('click', 'button.delete', function () {
    var i = $(this).parent().index(); // get index where to delete
    var deleted = fruits.splice(i, 1); // remove from array at that index
    undoStack.push([i, deleted]); // save the index and value on the stack
    showFruits(); // update display
});

$('#undo').click(function () {
    if (!undoStack.length) return; // cannot undo 
    var restore = undoStack.pop(); // get information for re-inserting
    fruits.splice(restore[0], 0, restore[1]); // insert the value 
    showFruits(); // update display
});

If you would use the undo principle also for other modifications, like undoing an insert, or a modification of the label, then the first solution would not need much modification, while the more memory-efficient one would need a bit more.

For a more generic and elaborated solution on undo/redo operations on any object (not only arrays), see How to version control an object?

trincot
  • 317,000
  • 35
  • 244
  • 286
2

If the array position does not matter:

fruits.push(tomatoes);

If you want to insert it at a specific position (index) in the array:

fruits.splice(index, 0, tomatoes);

will insert tomatoes into fruits at the specified index (deleting 0 items first, so it's just an insert).

Array.prototype.splice()

The splice() method changes the content of an array by removing existing elements and/or adding new elements.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

Temp-storing deleted elements and re-adding them

var deletedFruits = fruits.splice(i,1); will contain an array of the removed element(s) because that is the return value of splice(). So

fruits = fruits.concat(deletedFruits);

will re-add the deleted fruits.

Re-adding deleted elements at their original position

Store the position of the deleted element:

var deletedFruit = { fruit: fruits.splice(i,1)[0], index: i } 

If need be you can restore deleted fruits at their original array position using the aforementioned

fruits.splice(deletedFruit.index, 0, deletedFruit.fruit);
Community
  • 1
  • 1
connexo
  • 53,704
  • 14
  • 91
  • 128
0

You can add and remove fruits with these two functions:

function addFruit(fruit) {
  fruits.push(fruit);
}

function removeFruit(fruit) {
  // indexOf method returns index of fruit in the list, or -1 if fruit is not found.
  var index = fruits.indexOf(fruit);
  if (index > -1) {
    fruits.splice(index, 1);
  }
}

This assumes you have already defined an array named fruits. Then you can do something like

<script>
  function updateOutput() {
    document.getElementById('output').innerHTML = fruits.join(', ');
  }

  function addSelectedFruit() {
    var selectedFruit = document.getElementById('fruit-select').value;
    addFruit(selectedFruit);
    updateOutput();
  }

  function removeSelectedFruit() {
    var selectedFruit = document.getElementById('fruit-select').value;
    removeFruit(selectedFruit);
    updateOutput();
  }
</script>
<input type="text" id="fruit-select"/>
<button onclick="addSelectedFruit();">Add</button>
<button onclick="removeSelectedFruit();">Remove</button>
List of fruits:
<p id="output"></p>

Example:

<script>
  var fruits = ['Apples', 'Pears', 'Pineapples'];
function addFruit(fruit) {
  fruits.push(fruit);
}
function removeFruit(fruit) {
  var i = fruits.indexOf(fruit);
  if (i > -1) {fruits.splice(i, 1);}else{alert(fruit + ' cannot be removed, as it is not present in the array of fruits.');}
}
function selectedFruit() {
  return document.getElementById('fruit-select').value;
}
function updateOutput() {
  document.getElementById('output').innerHTML = fruits.join(', ');
}
  </script>
Fruit:
<input type="text" id="fruit-select" value="Orange"/>
<button onclick="addFruit(selectedFruit());updateOutput();">Add</button>
<button onclick="removeFruit(selectedFruit());updateOutput();">Remove</button>
<p id="output">Apples, Pears, Pineapples</p>
Kyle Lin
  • 827
  • 8
  • 16