AmazingDayToday
AmazingDayToday

Reputation: 4272

Ember: modify model in a route

I am working on an Ember app and I am having issues. I have to filter out results at a certain point of app, say, in a controller or in a view, like this:

At view.hbs I am eliminating passed tests at {{#if test.passed}}...:

{{#each tests as |test|}}<br>
    {{#if test.passed}}
        {{test.name}}
    {{/if}}
{{/each}}

I can do it at a controller that initially loads and can filter out in an input field:

import Controller from '@ember/controller';
export default Controller.extend({
    actions: {
        filterByTest: function (param) {
            if (param !== '') {
                return this.get('store').query('test', { name: param }).then((filteredTests) => {
                    return { query: param, tests: filteredTests.filterBy('passed', true) };
                });
            } else {
                return this.get('store').findAll('test').then((tests) => {
                    return { query: param, tests: tests.filterBy('passed', true) };
                });
            }
        }
    }
});

Now I need to filter out at a route level. model() in a route is can't be edited, if it can, then it has to be a path, which I don't want. How can I achieve that? How can I make model editable? At the moment I can get to the route model, but it can't be edited. Here's the way I talk to the model from view.hbs file and it works.

{{#each model as |test|}}<br>
    {{test.name}}
{{/each}}

Please see below the model that returns file to above view.hbs template. Again, it is working:

import Route from '@ember/routing/route';

export default Route.extend({
    model() {
        return this.get('store').findAll('test').then(tests => {
            return tests('passed', true);
        })
    }
});

How can I pass argument to the model and edit it, so that my UI gets changed as well?

This is my model I need to modify. At the moment the return works, but not modifiable.

Upvotes: 1

Views: 1267

Answers (3)

real_ate
real_ate

Reputation: 11289

Hi @AmazingDayToday 👋 I'm going to go into a bit of a longer explanation on how we saw your question and how we would solve it based on the information available. It seems like @Jeff has already answered you but hopefully this will help give you more context of how to think about these issues.

Our first assumption was that you were hoping to pass a filter parameter as part of the action. When testing this we created a series of buttons that passed a name attribute to the action as follows

<button {{action 'filterByTestName' 'face'}}>Face</button>
<button {{action 'filterByTestName' 'first test'}}>First Test</button>
<button {{action 'filterByTestName' ''}}>Clear</button>

The last button is intended to utilise the same action to clear the filter. I would generally tend to create a separate clear action in these cases but it works for now 🙂

One of the main ways that I implement these sorts of filters is by having a filteredModel computed property on the controller for the route in question. The point of this computed property is to do the filtering based on a "filterValue" on the controller that can be set by the filterByTestName action. Let's start there:

actions: {
  filterByTestName: function(name) {
    this.set('filterValue', name);
  }
}

the design of this action is to make it so that when filterValue is set we then filter the model based on that value. Let's move on to the computed property that will be doing the filter for us:

filteredModel: Ember.computed('filterValue', '[email protected]', function() {
  if(Ember.isEmpty(this.get('filterValue'))) {
    return this.model;
  }

  return this.model.filter((test) => test.get('name') === this.get('filterValue'));
}),

The first thing that you will notice on that computed property is that we have two dependant keys i.e. this computed property will update when filterValue or any of the name values on any of the models change.

The second thing of note in this implementation is the "base case" of when filterValue is empty we want to pass the current model through without modification.

And the last part of this implementation is that we want to actually preform the filter on the current model based on the current value of the filterValue.

You can see this entire example working on this Ember Twiddle and you can see us solving this problem live on this YouTube video

I hope this helps 🎉

Upvotes: 3

Sukima
Sukima

Reputation: 10064

If I am understanding the same as @Jeff has you want to query your back end based on the options a user has selected via buttons or some means. It is best to think in terms of state in this regard. The controller will have a state that represents the filter view you want to represent and your model() hook will use that state to know how to query for the specific set of models you want to show.

Here is a working twiddle that demonstrates how this is accomplished (the back end is using ember-cli-mirage): https://ember-twiddle.com/150708278ceafc6f9f99961122a14f07

The highlights are that my route defines a queryParams object (same as @Jeff's answer) which instructs the route to refresh its model every time the category property on the controller changes.

queryParams: {
  category: { refreshModel: true }
},

In the model() hook I use the params.category value to construct the correct query payload.

model(params) {
  if (params.category === 'all') {
    return this.store.findAll('test');
  }
  let passed = params.category === 'passed';
  return this.store.query('test', { 'filter[passed]': passed });
}

finally I make sure that my controller knows that category is driven from the URL:

queryParams: ['category'],
// A default is required to train ember on how to serialize
// the value in the URL
category: 'all',

You can now change the category value and the whole route will automatically refresh for you or you can use the link-to helpers

<button {{action (mut category) "passed"}}>Show passed</button>
{{#link-to "index" (query-params category="passed")}}Show passed{{/link-to}}

Upvotes: 4

Jeff
Jeff

Reputation: 6953

This is a bit of a guess, as I'm not 100% sure what you want to achieve.

I don't know where that param comes from, but I assume a query parameter like that yourroute?name=TestName. With this you can access that param like so:

export default Route.extend({
  // if you leave that out, it'll work for the first entry, but the model will not update if you transitionToRoute with only the queryParams changed)
  queryParams: {
    category: {
      refreshModel: true
    }
  },

  model(params) {
    // params has format of { name: "TestName" }
    return this.get('store').query('test', params);

    // you can of course put more complex logic in here, as you had it in controller
    // f.e.: 
    // return this.get('store').query('test', {name: params.name, passed: true});
    // or check first if you have a param at all...
  }
}

And here's the docs.

Note:
You cannot set a param on the controller to change the model. I've tried it...deperately.
To change it you'd have to do a {{link-to "routeName" (query-params name="newName")}} in template or a transitionToRoute({ queryParams: { name: 'anotherName' } }); in your controller (docs for transitionToRoute()).

Sidenote: having filterByTest as an action is strange anyway. This should be a computed property.

Upvotes: 2

Related Questions