HEY! I'm working on an awesome course on Golang and Ember.js skills that's live soon. Get early access to it here:

nerdyworm

Building an App With Ember.js and Go - Part 3

Last time we added the ability to create new kittens to our app. Now let’s add the ability to update and delete them.

Client Side Update

public/js/app.js
1
2
3
4
App.Router.map(function() {
  this.route('create');
  this.route('edit', {path: '/edit/:kitten_id'});
});

In order to edit a kitten we need a page with a form.

We add a route, name it edit, and define the path. Any path that contains a : will be treated as a dynamic segment. This segment will be replaced by the kitten’s id, i.e. /edit/1.

public/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/x-handlebars" data-template-name="index">
<ul class="thumbnails">
{{#each controller}}
  <li class="span3">
    <div class="thumbnail">
      <img {{bindAttr src="picture"}}/>
      <div class="caption">
        <h3>{{name}}</h3>
        {{#linkTo edit this}}Edit{{/linkTo}}
      </div>
    </div>
  </li>
{{/each}}
</ul>
</script>

We need a way to navigate to the page. In the index template we add a linkTo passing in the name of our route edit and the current kitten. Inside an each loop the current object can be referred to as this or ..

public/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/x-handlebars" data-template-name="_form">
<form {{action save on="submit"}} class="form-inline">
  {{input type="text" value=name}}
  <button type="submit" class="btn btn-primary">Save</button>
</form>
</script>

<script type="text/x-handlebars" data-template-name="create">
<h1>Create kitten</h1>
{{partial 'form'}}
</script>

<script type="text/x-handlebars" data-template-name="edit">
<h1>Edit kitten</h1>
{{partial 'form'}}
</script>

Our edit view requires the same form as the create template so we move the form into a partial. Then we can use Ember’s partial helper to render the form. The partial helper will use the containing template’s context. This means that it is equivalent to having the mark up in the containing template.

public/js/app.js
1
2
3
4
5
6
7
8
App.EditController = Ember.ObjectController.extend({
  save: function() {
    var kitten = this.get('model');
    kitten.save().then(function() {
      this.transitionToRoute('index');
    }.bind(this));
  }
});

Our edit page needs to handle the save action. We use an object controller here because our kitten model will be assigned to the controller’s model property. When the form is submitted our save function will be called. The kitten is available as model property and we can call the save function on it.

Server Side Update

server.go
1
2
3
4
5
func main() {
    // ...
    r.HandleFunc("/api/kittens/{id}", UpdateKittenHandler).Methods("PUT")
    // ...
}

Once again the wonderful Gorilla mux package allows us to specify the end point and the HTTP verbs that we want to use.

Ember data expects to be able to issue a PUT request to the resources end point, i.e. /kittens/1.

server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func UpdateKittenHandler(w http.ResponseWriter, r *http.Request) {
  // Grab the kitten's id from the incoming url
  vars := mux.Vars(r)
  id, err := strconv.Atoi(vars["id"])
  if err != nil {
    panic(err)
  }

  // Decode the incoming kitten json
  var kittenJSON KittenJSON
  err = json.NewDecoder(r.Body).Decode(&kittenJSON)
  if err != nil {
    panic(err)
  }

  // Find the kitten in our kittens slice and upate it's name
  for index, _ := range kittens {
    if kittens[index].Id == id {
      kittens[index].Name = kittenJSON.Kitten.Name
      break
    }
  }

  // Respond with a 204 indicating success, but no content
  w.WriteHeader(http.StatusNoContent)
}

First we need to pull the kitten’s id out of the incoming URL. We use gorilla’s mux.Vars function and it returns a string based map for us. Then we have to convert the string to an int.

Next we need to grab the payload from the incoming request. The update request is the same as the create request so there is no need to create a new data structure.

Next we want to update the kitten in our kittens slice. We loop over the slice and see if our id’s match. If so then we simply update the name of the kitten with the incoming JSON data.

The response of an update call should be a 200 with a JSON payload or a 204 without a payload.

Take it for a spin, you should be able to update the kitten’s name and it should persist with page reloads.

Delete the poor little guys

public/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/x-handlebars" data-template-name="index">
<ul class="thumbnails">
{{#each controller}}
  <li class="span3">
    <div class="thumbnail">
      <img {{bindAttr src="picture"}}/>
      <div class="caption">
        <h3>{{name}}</h3>
        {{#linkTo edit this}}Edit{{/linkTo}}
        <button {{action deleteKitten this}}>Delete</button>
      </div>
    </div>
  </li>
{{/each}}
</ul>
</script>

Since there is no page to link to we must use an action. We name the action deleteKitten and pass in the kitten model. This will make the kitten available to the function that handles the event.

public/js/app.js
1
2
3
4
5
6
7
8
9
10
11
12
App.IndexRoute = Ember.Route.extend({
  model: function() {
    return App.Kitten.find();
  },

  events: {
    deleteKitten: function(kitten) {
      kitten.deleteRecord();
      kitten.save();
    }
  }
});

In Ember the action will bubble up from the controller to route. Meaning that if your controller does not implement that function then it will check the current route. In this case our route implements the function and will be called when the user clicks the delete button.

Ember data requires us to call deleteRecord then commit that change to the server by calling save. You can also call commit on the application’s store. save is just a new convenience method.

Server Side Delete

server.go
1
2
3
4
5
func main() {
    // ...
    r.HandleFunc("/api/kittens/{id}", DeleteKittenHandler).Methods("DELETE")
    // ...
}

The delete route is the same as the update route, except the HTTP verb should be DELETE.

server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func DeleteKittenHandler(w http.ResponseWriter, r *http.Request) {
  // Grab the kitten's id from the incoming url
  vars := mux.Vars(r)
  id, err := strconv.Atoi(vars["id"])
  if err != nil {
    panic(err)
  }

  // Find the index of the kitten
  kittenIndex := -1
  for index, _ := range kittens {
    if kittens[index].Id == id {
      kittenIndex = index
      break
    }
  }

  // If we actually found a kitten remove it from the slice
  if kittenIndex != -1 {
    kittens = append(kittens[:kittenIndex], kittens[kittenIndex+1:]...)
  }

  // Respond with a 204 indicating success, but no content
  w.WriteHeader(http.StatusNoContent)
}

Like the update function we have to grab the kittens Id from the incoming URL and convert it to an int.

We loop over the kittens and find the index of the kitten but it’s Id. Once we have that we can break out of the loop since all the ids are unique.

If we actually found a kittenIndex then we can remove the kitten from the slice.

To accomplish this we slice the current kittens up to the deleted kitten’s index. Since the range operator is zero based we have the index of just before the kitten. Then we skip one past the deleted kitten and append the rest of the slice. This gives us all the kittens minus the deleted kitten.

To inform the client that we successfully deleted the kitten we return a 204.

Concludes Part 3

This concludes part 3. We have successfully implemented all the CRUD operations for our kittens app.

Ember really allows us to write a minimal amount of code for our clients. It almost feels like we are cheating to get this kind of functionality.

Go on the sever side is a pleasure to work with. It feels more verbose than some of the scripting languages I am used to, but it reads well and is extremely clear what is happening. The lack of magic is actually refreshing.

Comments