Intro
For our demonstration we want to allow a user to select a single fruit from a list of available fruits. Later on we will be improving it with autocomplete behavior. In this post we’ll start with a naive select-based filter and show how to convert that to a fully encapsulated Dropdown component.
We will start with a parent component, Filters.vue which will hold our various filter implementations and indicate what the currently selected fruit is:
Naive filter
The simplest approach for letting a user select a fruit is to add a select to our template and set the v-model attribute to selectedFruit.
This does the trick and may be all that is required for a given use-case.
However, if we begin to enhance the behavior of this drop-down we will find that we are filling up the parent component Filters with drop-down-specific code. Additionally, if we want the same behavior with a different drop-down and a different set of options we will run into duplication of code.
Our goal is to re-factor this drop-down in a DRY way so that over time we can improve the behavior of the drop-down without having to constantly rewrite the behavior of any parent component which includes it.
We’ll start by capturing the existing implementation as a stand-alone component.
Dropdown component
Basic setup
Let’s start with an empty Dropdown.vue file and add just the select element and options to the file:
Custom options
Since we want our component to be generic, we need to factor out the options into a prop. The available options will be provided to the Dropdown component by its parent component.
The structure of the options prop will be an object where keys are unique human-readable strings which will be used as the text of the option. Values can be almost anything. Vue.js supports binding arbitrary objects as selected values in a drop-down.
The Project filter I mentioned in Part 1 used fully populated Project objects from the Teamwork API as values with the project name as the string-key.
In our case the values will be strings which exactly match the fruit names.
In Dropdown.vue we add our options prop:
In the template we use v-for to loop over and render the options:
Note the use of the object key syntax for v-for.
We can now add the component to the Filters.vue template:
and specify the options:
Implement v-model
We can now see our select but it doesn’t support reactively updating the selected value when the drop-down value is changed. It is straightforward to add support for v-model binding in a custom Vue.js component.
All your component needs to do is:
- Provide a “value” prop which is used by the parent to set the value of your component
- Emit an “input” event when the value of your component changes
However, the specifics of this can trip you up, particularly when you are starting simple and expanding to a more complex implementation.
There are some general principles which will help to avoid pitfalls:
- Determine how your component’s current value will be represented (e.g. string, number, custom object).
emitan “input” event when the selected value changes (how and when you do this may change depending on the details of your component).- Provide a
valueprop which accepts valid values for your component. - Ensure that the controls in your component reflect this value when mounted and when the value of the prop changes.
Making sure you follow the above principles will give you correct behavior for simple cases like our drop-down as well as the more complex cases we will see later.
For our drop-down, the current value can be pretty much anything since the options are provided by the parent. We’ll keep that in mind as we continue.
The only time our component’s value changes is when the select element changes. We can handle #2 with a single event listener bound to the select’s @input event:
Adding the prop for #3 is simple:
In our implementation the parent component is responsible for setting value and for providing the set of valid options. We set the prop to null which means any valid type is supported.
If you are implementing v-model support in a component that is responsible for its own options you would likely be more restrictive with this property definition and potentially include a validation callback here.
Finally, we need to make sure that the select element updates to reflect the value prop when it changes. The simplest way to do this is to create a data property to represent the currently selected value:
and use v-model to bind it to the select:
With that in place we can handle #4 by setting selectedOption in a mounted() method and in a watcher for the value prop:
You might be wondering at this point why we didn’t just
v-modeltheselectelement to thevalue. One reason, is that doing this will causevalueto be modified whenever theselectis changed. Vue.js will throw a warning when this occurs. Thevalueprop should never be modified directly. You signal value changes to the parent by emitting theinputevent.The other reason not to
v-modelthe value prop is that your component’s internal representation will likely be different from the representation which is used in thevalueprop. You need to have the ability to transform those representations and usingv-modelin this way couples those values too closely together. In fact, when we add autocomplete later on we will remove theselectand thev-modelattribute entirely.
With the above in place we can now add the v-model directive to our component in Filters.vue:
You should be able to change the value of selectedFruit by changing either drop-down and both drop-downs should update to reflect the currently selected value.
We are now in a great position to enhance this component with autocomplete functionality.
Comments
This was really helpful.
This was really helpful. Thanks!
Small suggestion, you can
Small suggestion, you can eliminate the need for your mounted lifecycle hook with the watch immediate setting by doing something like the following (formatting is being lost):
watch: {
value: {
immediate: true,
handler: function (newValue) {
this.selectedOption = newValue
}
}
}
seem to bee running off the
seem to bee running off the screen in Internet explorer.I'm nnot sure iff thiss is a format issue or something to do with web broser compatibility but I thought I'd post tto let you know. The style and design ook great though! Hope you get the issue solved soon.
Add new comment