An Autocomplete Component Edit Page
As they search for a rental, users might also want to narrow their search to a specific city. Let's build a component that will let them search for properties within a city, and also suggest cities to them as they type.
To begin, let's generate our new component. We'll call this component
filter-listing
.
1 |
ember g component filter-listing |
As before, this creates a Handlebars template
(app/templates/components/filter-listing.hbs
) and a JavaScript file
(app/components/filter-listing.js
).
The Handlebars template looks like this:
It contains an {{input}}
helper
that renders as a text field where the user can type a pattern to
filter the list of cities used in a search. The value
property of
the input
will be bound to the filter
property in our component.
The key-up
property will be bound to the autoComplete
action.
It also contains a button that is bound to the search
action in our
component.
Lastly, it contains an unordered list that displays the city
property
of each item in the filteredList
property in our component. Clicking
the list item will fire the choose
action with the city
property of
the item as a parameter, which will then populate the input
field with
the name of that city
.
Here is what the component's JavaScript looks like:
app/components/filter-listing.js | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export default Ember.Component.extend({ filter: null, filteredList: null, actions: { autoComplete() { this.get('autoComplete')(this.get('filter')); }, search() { this.get('search')(this.get('filter')); }, choose(city) { this.set('filter', city); } } }); |
There's a property for each of the filter
and filteredList
, and
actions as described above. What's interesting is that only the choose
action is defined by the component. The actual logic of each of the
autoComplete
and search
actions are pulled from the component's
properties, which means that those actions need to be passed
in by the calling object, a pattern known as closure actions.
To see how this works, change your index.hbs
template to look like this:
We've added the filter-listing
component to our index.hbs
template. We
then pass in the functions and properties we want the filter-listing
component to use, so that the index
page can define some of how it wants
the component to behave, and so the component can use those specific
functions and properties.
For this to work, we need to introduce a controller
into our app.
Generate a controller for the index
page by running the following:
1 |
ember g controller index |
Now, define your new controller like so:
app/controllers/index.js | |
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 |
export default Ember.Controller.extend({ filteredList: null, actions: { autoComplete(param) { if (param !== '') { this.store.query('rental', { city: param }).then((result) => { this.set('filteredList', result); }); } else { this.set('filteredList', null); } }, search(param) { if (param !== '') { this.store.query('rental', { city: param }).then((result) => { this.set('model', result); }); } else { this.store.findAll('rental').then((result) => { this.set('model', result); }); } } } }); |
As you can see, we define a property in the controller called
filteredList
, that is referenced from within the autoComplete
action.
When the user types in the text field in our component, this is the
action that is called. This action filters the rental
data to look for
records in data that match what the user has typed thus far. When this
action is executed, the result of the query is placed in the
filteredList
property, which is used to populate the autocomplete list
in the component.
We also define a search
action here that is passed in to the component,
and called when the search button is clicked. This is slightly different
in that the result of the query is actually used to update the model
of the index
route, and that changes the full rental listing on the
page.
For these actions to work, we need to modify the Mirage config.js
file
to look like this, so that it can respond to our queries.
app/mirage/config.js | |
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
export default function() { this.get('/rentals', function(db, request) { let rentals = [{ type: 'rentals', id: 1, attributes: { title: 'Grand Old Mansion', owner: 'Veruca Salt', city: 'San Francisco', type: 'Estate', bedrooms: 15, image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg' } }, { type: 'rentals', id: 2, attributes: { title: 'Urban Living', owner: 'Mike Teavee', city: 'Seattle', type: 'Condo', bedrooms: 1, image: 'https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg' } }, { type: 'rentals', id: 3, attributes: { title: 'Downtown Charm', owner: 'Violet Beauregarde', city: 'Portland', type: 'Apartment', bedrooms: 3, image: 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg' } }]; if(request.queryParams.city !== undefined) { let filteredRentals = rentals.filter(function(i) { return i.attributes.city.toLowerCase().indexOf(request.queryParams.city.toLowerCase()) !== -1; }); return { data: filteredRentals }; } else { return { data: rentals }; } }); } |
With these changes, users can search for properties in a given city, with a search field that provides suggestions as they type.