Ember: Using queryParams to distinguish “editing a record” from “making a new record” on a route

This guide uses Ember 1.13

ember_logo

The concept is simple and familiar: you have a page (“route”) in your Ember app that you want to use to either enter new data or modify existing data on the same record.

Example: your user is presented with a blank form to enter their shipping address.  The user enters their info and submits it (thus creating a new record). When your user goes to edit their saved shipping address, that same form is populated with their existing shipping information (loading a record). The user edits, saves, and the updated record is persisted.

One form, two behaviors (new and edit). 

This, like many things in Ember, was surprisingly painful to get working. I found queryParams to be a good solution, but the documentation on queryParams was difficult for this beginner to understand and it contains (what I think is) at least one mistake that held me up for a long time while I figured it out.

It didn’t take much code to get this working, just a lot of guessing and trying, so I thought I’d add my knowledge to the (vast, mostly out-of-date now that it’s 2016) library of Ember queryParms knowledge out there on the internet. Hopefully this helps someone.

About this tutorial

In this guide, I’m going to show you how I used queryParams in Ember to distinguish the “new” state from the “edit” state on the same route (“basics”).

About the example app

This guide uses my Ember app, “Classis”, for many of its examples, which you can explore in full on Github (it’s a work in progress but the queryParams part is done on the “basics” route if you want to look at the app that inspired this post).

“Classis” is a (fake) web app where people sell “seats” in classes they teach: tutoring, dog training, etc. It’s a lot like listing a room on AirBnB, dog boarding on Rover.com, etc. Think of classes as products people are selling, and you’ve got the idea. I call refer to “classes” as “products” in this tutorial to cut down on confusion with the programming concept, but they are called classes in my app. 

About queryParams in the context of editing records

You can use queryParams for lots of things. This particular guide shows you how to use them for editing records that have ids.

If the user is trying to edit an existing record, we can get its id and pass it along to the route where the user will edit that record. If the route sees you gave it the id, it will behave differently.

The “I’m making a new one” url looks like this:

http://localhost:4200/basics

The “hey, I’m editing a specific one” url looks like this:

http://localhost:4200/basics?id=1

Pass the queryParam from somewhere

There are lots of ways to pass a queryParam around, but in my case, I needed to pass it from an “Edit” button contained within a “class-card” component. That component’s job is to display a user’s completed product, and one “class-card” is displayed for each product the user has. Product data is passed into a class-card, and that product data includes the id.

Basically, I have a product with an id. The component knows about the product’s data, including its id. I want to pass that id to the basics page where I will edit the basic info about a product.

(I simplified the styling classes in this example and bolded the relevant part)

components/class-card.hbs

{{#link-to 'basics' (query-params id=class.id) class="mdl-button"}}
  EDIT
{{/link-to}}

Now the class id (product id) will get sent to the ‘basics’ route.

You can also pass queryParams on a transition in, say, an action. Here’s a hypothetical action method I made just for this post, it’s not in my app anywhere but I included it here in case you need to pass a queryParam from an action:

goToBasics() {
  const route = this;
  const id = this.controller.get('id') || '';
  route.replaceWith('basics', {queryParams: {id: id}});
 },

Define the queryParam on the “parent” controller

There are many ways to structure an Ember app, and I can’t claim to know them all. I do know, however, that my app uses application.js as its parent controller, so this is where I defined queryParams.

That definition looks like:

application.js

import Ember from 'ember';

export default Ember.Controller.extend({
 queryParams: ['id']
});

On a complex app, you might bury this deeper down inside the app, so that (perhaps) user ids don’t get confused with product ids or whatever, but on this simple app, the parent-most parent can handle it.

Add the queryParam to the route in router.js

All of my app’s routes are defined in app/router.js.  ‘basics’ is the route that you can pass an id to to distinguish “new” from “editing”.

Contrast with location/:id, which treats id as a dynamic segment (link goes to Ember documentation on the subject).

I included this here to illustrate the difference, because it’s easy to confuse the two (like I did when I was getting started with this stuff).

app/router.js

Router.map(function() {
  this.route('basics', {queryParams: 'id'}); //queryParam
  this.route('location', {path: 'location/:id'}); //dynamic segment
  this.route('main');
});

Url with a query param looks like:

http://localhost:4200/basics?id=1

Url with a dynamic id segment looks like:

http://localhost:4200/location/45

Long explanation of why I did it this way:

In my app, the user goes from basics to location, in that order. Basics is the first page the user encounters. It can be used to either create a new record or edit an existing. Location, by contrast, is always used to edit a record that exists, because that record was created by basics, the page before it. If you’re on /location, it’s because you are making changes to a record that has already been created, even if you’re entering location data for the first time.

In Ember, having some understanding of what order pages are encountered in can help you make these sorts of architectural decisions (at least, until your designer decides location should come first, then you get to change this stuff or maybe handle everything with a queryParam in the first place, or whatever makes sense for your app).

Add the queryParam to the route itself

Basics is where the user edits “name” and “description” of an individual product.

Inside Route.extend, define the queryParms object like so:

routes/basics.js

export default Ember.Route.extend({
  queryParams: {
    id: {refreshModel: true}
  },

Now /basics knows that it might sometimes get passed an id, and if it does, it should refresh its model.

Get the queryParams inside the model

!!This part was hard to figure out!!

Ember’s documentation just shows one parameter passed into model, but try as I might, param just came in as a useless empty { } object. I could see my queryParam in the url, but I couldn’t get at it from param.

After much Googling, I found the answer: the queryParams are on the second parameter accessed by model, which you can name transition:

(still in) routes/basics.js (right below the queryParams object you just made)

//queryParams are contained inside transition, which has to be passed as    the 2nd parameter to model

  model(param, transition) {
    let id = transition.queryParams.id;
    if (id) {
      return this.store.peekRecord('class', id);
    }
  },

IT WORKS!

I’m not sure if it’s my Ember newbieness or an actual oversight in the 1.13 documentation, but model(…) {…} totally takes more than one parameter and the second one contains that sweet, sweet queryParam you passed in.

The code in my example above looks at transition, sees if it contains an id, and if it has an id, it gets the matching ‘class’ record out of the store.

A lot of Ember tutorials perpetuate the idea that if you do model(param), the queryParam will be accessible on it, so maybe this worked at one point in Ember’s history or I just set it up wrong in mine.

Curiously enough, the guide to model hooks that enlightened me to the transition parameter also says there’s a third parameter for model straight up called “queryParams”(!!!), but in my case, that third parameter comes in empty. Only the second parameter (transition) had anything useful. I cannot explain this, I’m just reporting it in case it’s useful to someone else. (Maybe someday I will understand all of Ember’s mysteries.)

Load existing record data in setupController (but only if it exists)

Almost there: let’s load that model data into the controller now that we have it.

(still in) routes/basics.js (below the model method) 

setupController(controller, model) {
  let application = this.controllerFor('application');
  application.set('pageTitle', 'Basics');

  if (model) {
    //we're editing an existing class
    this.controller.set('existingClass', model);
    this.controller.set('editingExisting', true);
    this.controller.set('title', model.get('title'));
    this.controller.set('description', model.get('description'));
  } else {
    //this is a new class
    this.controller.set('title', '');
    this.controller.set('description', '');
  }

Now, when we visit /basics with a queryParam in our URL, we’ll see our product’s title and description! If we visit /basics without a queryParam, title and description will be empty.

We are almost done…

Update the existing Ember record if we’re editing an existing one, create a new record if it’s a new one

Finally, we need to tell our route what to do when continuing past this page. If we have a queryParam, we want to update that record and move on to /location. If we don’t have a queryParam, we want to make a new record and move on to /location.

routes/basics.js (in the action hash now) 

actions: {

  transitionToNext(newClass) {
    console.log("going to location");
    this.replaceWith('location', newClass);
  },

  failure() {
    console.log("failed!");
  },

  continue() {

    //editing an existing class
    if (this.controller.get('editingExisting')) {
      let existingClass = this.controller.get('existingClass');
      existingClass.set('title', this.controller.get('title'));
      existingClass.set('description', this.controller.get('description'));

      existingClass.save().then(() => {
        //go to location and pass along the this existing class's id
        this.replaceWith('location', existingClass.get('id'));
      }).catch(this.failure);

  } else {

    //making a new class
    var newClass = this.store.createRecord('class', {
      title: this.controller.get('title'),
      description: this.controller.get('description')
    });

    newClass.save().then(() => {
      //pass along the newly created class's id
      this.replaceWith('location', newClass.get('id'));
     }).catch(this.failure);
    }
  }
}

It’s the biggest block of code in this guide but it’s straightforward: if you have the editingExisting flag set in the setupController, set the updates and move onto location. If you don’t have this flag, make a new record.

Debugging advice

If you get stuck, remember you can look in the Ember Inspector to see if your records actually exist and confirm that they have the IDs you expect.

ember-data-record

You can also console log the id at various points in your code to see if it’s coming where you expect it to.

You can also display the id in the template, which I found helpful in confirming that what I was seeing was indeed, what I was getting, like so:

ember-display-class-id

I click EDIT and that same ID is now in the url:

ember-queryParams-url-example

That’s it!

This post was inspired by my work on a personal project I keep publicly available here on github – check it out if you’re new to Ember and want to see a relatively simple Ember app demonstrating queryParams and other concepts.

Ember: Making a set of radio buttons that change a value set on the controller

This guide was written for Ember 1.13

ember_logo

So, Ember doesn’t have anything in the way of built-in radio buttons, but there’s a great add-on called ember-radio-button, which you can install via ember-cli:

ember install ember-radio-button

In this particular tutorial, we’ll use ember-radio-button to set up two radio buttons that work as a pair (so only one can be “on” at a time) and set a variable on your controller based on which one the user picks. We will also set one of the radio buttons to be on by default.

Like this:

ember-radio-button-tutorial

This guide assumes you already have a template (yourpage.hbs) and a corresponding route (yourpage.js) to work in.

In Ember, adding your code to templates and routes is kind of chicken-and-eggy (where to start?), but I tend to start in the template (.hbs) file.

In your route’s .hbs file

Use the {{#radio-button}} label {{/radio-button}} syntax if you want labels for your radio buttons (which you probably do).

This will give you two side-by-side radio buttons:

{{#radio-button
  value="vanilla"
  groupValue=flavor
  changed="flavorToggled"}}
  Vanilla
{{/radio-button}}

{{#radio-button
  value="chocolate"
  groupValue=flavor
  changed="flavorToggled"}}
  Chocolate
 {{/radio-button}}
  • value is what will get automagically passed to the controller when you click this radio button
  • groupValue should match across all radio buttons in the set, or your radio buttons won’t know to un-select themselves when you click a different one
  • changed is the action method called when this radio button is clicked

When the user clicks one of these radio buttons, flavorToggled(choice) will be called and the value passed to it as choice will be “vanilla” or “chocolate”.

In your route’s .js file

Add a new action to your action hash. You can call “choice” anything you want (or omit it entirely if you don’t need a passed-in value for whatever reason).

The value that gets passed in here as “choice” was taken from the radio button’s “value” property.

actions: {
  flavorToggled(choice) {
    console.log("changing flavor choice", choice);
    this.controller.set('flavor', choice);
  }
}

If you want one of these radio buttons to be checked by default, you can do that in the setupController in your .js file like so:

setupController() {
  this.controller.set('flavor', "vanilla");
},

All done!

That’s all there is to it! The documentation that comes with ember-radio-button shows a few more things this tutorial did not cover. Thanks, yapplabs!

 

Ember: Making a button component that calls an action

This guide uses Ember version 1.13.

ember_logo

I recently started a new Ember 1.13 app to explore Google’s Material Design Lite style library. I use Ember in my day job, too, and something that has always stuck out to me as “should be easy but isn’t” in Ember is making a button component that calls an action. 

I wanted a “continue button” component that…

  • can be re-used throughout my app
  • calls an action customized per route (ie: the continue button on the ‘basics’ route goes to the ‘location’ route, the continue button on the ‘location’ route goes to the ‘students’ route, etc)

This was surprisingly unintuitive in Ember. I expected to pass an action into the component from the parent template and have the component know where to find that action.

What not to do

I think many people who try to put an action on a component start with something like this. Note that this approach doesn’t actually work:

badExampleRoute.hbs

{{bad-example-button action=”wontWork”}}

badExampleRoute.js

actions: {
  wontWork() {
    console.log("you'll never see this!");
  }
}

What’s missing? The call to the route’s action has to come from the component itself.

The rest of this guide will show you what does work for getting a component to fire an action on the parent’s route in Ember.

Generating the button component

Do this step inside your Ember project directory (assuming you are using ember-cli):

ember generate component continue-button

In the route’s template

First, add the component into your .hbs template. The action it calls, “continue”, needs to be defined in the action hash in this route’s .js file.

basics.hbs

{{continue-button actionToCall="continue"}}

In the route’s js file

In your route’s .js file, create an action hash if you don’t have one already (actions: {…}) and put a “continue” method inside it. I like to stick a console log in here, too, on the first try so I have something to look for in Chrome to confirm it’s working.

basics.js

actions: {
  continue() {
    console.log("continuing on to location page");
    this.replaceWith('location');
  }
}

Code for the button component

When you generated the button component you got an .hbs template file and a .js file.

continue-button.hbs

The important piece here is that the <button> tag contains {{action “doButtonThing”}}.

<button {{action "doButtonThing"}} class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent">Continue</button>

continue-button.js

doButtonThing is an action on the component’s own action hash. When you call doButtonThing, it’s going to fire a sendAction that references the actionToCall defined up on the component in the route template.

import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    doButtonThing() {
      this.sendAction('actionToCall');
    }
  }
});

Yes, this is confusing as #$*! and I’ve not seen a better way to do this in my year or so of developing in Ember. It feels very much like a child (the component’s .js) is telling a parent (the route’s .js) what to do.

Another way to think of it

The {{continue-button …}} up in basics.hbs knows what to do when clicked (call “continue” action in basics.js) but it’s not going to do it until told to do it. It sits around listening for the “fire!” command.

When the user clicks the button, the code on the button component itself has its own action to call. That action yells “fire!” to the instance of the button waiting up there on the route. The {{continue-button …}} up in basics.hbs hears the fire command, delivered from the component itself in the form of a send-action.

There is no “passing” of an action into the component, it’s more like a pairing of broadcaster and listener. 

The Ember community calls the larger concept at work here “data down, actions up” which took me a while to internalize. You can read more about DDAU here.

JavaScript and Regex: Using a regular expression and .replace() to strip (foo) from a string

Here’s a quick tutorial on using regular expressions in JavaScript to edit a string and return the modified result. My strings looked like this:

"Toyota (1999 - 2005)"
"Ford (1995 and up)"
"Honda (up to 2015)"
"Kia or Chevy (any)

In this case, each string was the name property on an object. (Ie: you’re not looking at an array of strings in the example above.)

For display purposes, I wanted to show (in my app’s template) only what came before the first parenthesis, like so:

Toyota
Ford
Honda
Kia or Chevy

Thanks to guide on how to use regex to remove everything after a particular character, and this refresher on how .replace() works, I had this problem solved in a matter of minutes with the following expression, where params[0] is the string (more about why my method looks like this after the code):

export function stripParensFromCarData(params/*, hash*/) {
  return params[0].replace(/\(.*\)/,'');
}

I used this method in an Ember helper I wrote to format strings for display in a handlebars template. The helper was hooked up like so in the .hbs file:

{{strip-parens-from-car-data carString}}

My favorite tool for testing regular expressions as I write them is regexr.com.

Ember 2: Calling an action from a checkbox in Ember 2.0 three different ways

I recently implemented a checkbox in an Ember 2.0 project that, when checked/unchecked, would call an action and change some properties.

Searching the web reveals many different approaches to this problem, some of which (depending on your project’s needs and stack) may be more suitable for you than others.

The first time I needed a checkbox to call an action in Ember, I was just building a quick ‘n dirty prototype and I wanted to keep things simple and skip observers, components, etc., which I was able to do with some basic HTML and an Ember controller.

Simple Ember checkbox HTML/JS solution

Here’s the simplest implementation of an Ember checkbox calling an action I can come up with. It’s just HTML and JavaScript.

See it working in this JSFiddle

HTML:

<input type="checkbox" 
 id="checkbox-toggle" 
 name="toggleCheckbox" 
 checked=toggleChecked 
 {{action "toggleCheckBox" on="change"}} 
 />

JS:

setupController: function() {
 this.controller.set('toggleChecked', true);
 },
actions: {
 toggleCheckBox: function() {
 this.controller.set('toggleChecked', !this.controller.get('toggleChecked'));
 }
}

Ember checkbox as a component

Ember offers an {{input }} component that you can now hook up to actions (that apparently wasn’t true in earlier versions of Ember, but it’s true as of this writing which uses Ember 2.0).

The input component implementation looks like this:

{{input type="checkbox"
      id="checkbox-policy"
      name="checkboxPolicy"
      checked=readPolicyChecked
      change=(action togglePolicyRead)}}

To register the action with the controller, you’ll need to add this to the corresponding .js file.

// Set a method on the controller which will send the action
this.controller.set('togglePolicyRead', function() {
  this.send('togglePolicyRead');
});

If you don’t set this method on the controller, you’ll get a console error like:

Uncaught Error: An action could not be made for `togglePolicyRead` in (generated page.policy-review controller). Please confirm that you are using either a quoted action name (i.e. `(action 'togglePolicyRead')`) or a function available in (generated page.policy-review controller).

As an aside, using Ember’s input component to generate the checkbox will just render into normal HTML (like you see in my first example) and you can confirm that by viewing the page source on your project.

Ember checkbox as HTML with handlebars for the checked property

What if you can’t use the {{input}} component because of a conflict with handlebars syntax? Maybe you’re working inside a component, or there’s some project-specific reason you need to go this route. Here’s an approach you can try that uses HTML for the input and wraps the checked={{property}} in handlebars.

<input type="checkbox"
       id="checkbox-green"
       name="greenCheckbox"
       checked={{greenChecked}}
       {{action "toggleGreen" on="change"}}/>

With this technique, you also don’t have to set up the action on the controller like I showed in the previous technique.

The Ember project I worked on serves its templates as .jsp files and this approach was the popular because we could skip the cumbersome “send” step on the controller. Now, whether that’s “the Ember Way”, I’m too new to Ember to say, but this got the job done.