Delete an item with confirmation in Ember.js
09 June 2017
This post means to be a recipe from a cookbook. It describes how to implement deleting an item after having confirmed the action with the user.
UI first
We'll first sketch up the UI. As alert boxes are so 1990s, we'll use a modal dialog with a "go ahead" and a cancel button. It should look something like this:
We'll use the modal dialog provided by the ember-bootstrap add-on so we need to install it:
1$ ember install ember-bootstrap
Popping open the template that renders the items (now: songs) that we want to safely delete, let's add a small icon for each song that will trigger the process to delete it:
1<!-- app/templates/bands/band/songs.hbs --> 2(...) 3{{#each sortedSongs as |song|}} 4 <div class="list-group-item song"> 5 {{song.title}} 6 <span class="song-delete-action glyphicon glyphicon-trash" {{action 'setSongToDelete' song}}></span> 7 {{star-rating item=song rating=song.rating maxRating=5 setAction="setRating"}} 8 </div> 9{{else}} 10 (...) 11{{/each}}
To reduce clutter, let's only display the trash can icon only when the user hovers over the song. We can do this easily with css:
Ok, the UI elements for starting a deletion are in place, let's now implement the modal.
The ember-bootstrap
addon provides a bs-simple-modal
component that implements the dialog (and a whole lot more). Let's open up the template where we want to have the modal dialog come up and insert the following at the very end:
1<!-- app/templates/bands/band/songs.hbs --> 2 {{#bs-modal-simple 3 title="Please confirm" 4 closeTitle="Cancel" 5 submitTitle="Confirm" 6 size=null 7 fade=false 8 open=songToDelete 9 onSubmit=(action "deleteSong" songToDelete) 10 onHide=(action "cancelDeletingSong")}} 11 You're about to delete {{songToDelete.title}}. Are you sure? 12 {{/bs-modal-simple}}
The first few properties we pass in are for which labels and styles to use.
The interesting ones are open
, onSubmit
and onHide
.
open
is a flag that determines when should this modal be visible on screen. In our case, if songToDelete
is a true value, the dialog will become visible and when it's false, it will be closed.
onSubmit
is the action that ember-bootstrap
calls when the user confirms the action and onHide
gets called when the modal dialog is closed.
From these you can already see that it's via setting songToDelete
that we'll control the appearance of the modal dialog. We have to set it to the song the user is about to delete when we want to bring up the dialog, and set it to null
(or any falsey value) when we want it to go away.
Delete, confirm and cancel actions
As our Ember app would freak out anyway if we don't have those actions defined, let's do that now:
1// app/controllers/bands/band/songs.js 2export default Ember.Controller.extend({ 3 songToDelete: null, 4 // (...) 5 6 actions: { 7 // (...) 8 setSongToDelete(song) { 9 this.set('songToDelete', song); 10 }, 11 12 cancelDeletingSong() { 13 this.set('songToDelete', null); 14 }, 15 16 deleteSong(song) { 17 return song.destroyRecord() 18 .then(() => { 19 this.set('songToDelete', null); 20 }); 21 }, 22 }, 23});
These actions almost write themselves based on the template. The only thing we have to watch out for is to manually reset the songToDelete
after a successful delete operation. Otherwise, the modal dialog would persist on the screen.
It's alive! Both cancelling a deletion and confirming it now works:
Did you say boilerplate?
Two of the above actions, setSongToDelete
and cancelDeletingSong
feels quite boilerplate-y, so you might be curious if there is a way to not write them. Well, I'm glad you asked because there is a built-in Ember helper to get rid of those actions, and it's called mut
.
(I'm firmly in the camp who say it should be pronounced as in "mute" and not as in "mutt". If you think about it, it comes from the word "mutate".)
It takes the object to set (mutate) as its first argument and returns an action. That action needs to be called with the value to set it to. Let's do that now:
1{{#each sortedSongs as |song|}} 2 <div class="list-group-item song"> 3 {{song.title}} 4- <span class="song-delete-action glyphicon glyphicon-trash" {{action 'setSongToDelete' song}}></span> 5+ <span class="song-delete-action glyphicon glyphicon-trash" {{action (action (mut songToDelete) song)}}></span> 6 {{star-rating item=song rating=song.rating maxRating=5 setAction="setRating"}} 7 </div> 8{{else}} 9 (...) 10{{/each}} 11 12{{#bs-modal-simple 13 (...) 14- onHide=(action "cancelDeletingSong")}} 15+ onHide=(action (mut songToDelete) null)}} 16 You're about to delete {{songToDelete.title}}. Are you sure? 17{{/bs-modal-simple}}
In the first case, the action returned by mut
is called with the song that the user is about to delete and so songToDelete
will be set to that song. In the onHide
action of the bs-modal-simple
component, it will just be reset to null.
(If you're baffled about the different invocation forms of the action
helper, I share your confusion. I highly recommend Sam Selikoff's article on the subject on the EmberMap blog.)
We can now delete the setSongToDelete
and cancelDeletingSong
actions from our controller.
Conclusion
Deleting an item is an irreversible operation that needs to be safeguarded (unless the item is easy to recreate or does not have real value). A decent way to do that is to ask the user for confirmation and to present a modal dialog for doing it.
We saw how to do just that in Ember.js, first writing the needed actions in JavaScript and then switching the ones that felt they could be generalized to use the built-in mut
(pronounced as in "mute") helper.
I built this example on top of the demo version of Rock and Roll with Ember.js. The demo project is open-sourced and can be found on Github.
I go through building the full version of the app in my book, Rock and Roll with Ember.js. If you're interested, you can download a sample chapter by inputting your email in the box below:
Share on Twitter