Skip to content

Smart Component Demo

Micah Godbolt edited this page Oct 15, 2018 · 3 revisions

A smart component is one that does more than simply render markup to the page. A smart component may contain internal state such as isOpen or isSelected that can be modified by interacting with the component. It may also contain event handlers that perform specified functions whenever that event occurs. This could be a response to a keystroke, click, mouseenter/leave (interactive event), or upon load, render, or update, often called non-interactive event, or lifecycle.

Building our First Smart Component

Once again we can rely on Yeoman to help scaffold this component:

$ npm run scaffold Textfield

With the new Textfield scaffolded out, we can start to update the vue file.

Script Tag

We'll start here, as the props will affect the rest of the code.

props: ['value', 'placeholder', 'variant']

Value and placeholder will be used to drive the content in the Textfield, whereas the variant will be used to specify which visual variant of the component we'll use, either new or edit. This is similar to how you might pass a class through to specify different styles, but I find this to be easier to understand, and doesn't clutter up our class name.

Template Tag

  <input class="Textfield"
    autocomplete="off"
    :placeholder="placeholder"
    :data-variant="variant"
    :value="value"
    @input="$emit(`input`, $event.target.value)"
    @keyup.enter="$emit(`enter`)"
    @blur="$emit(`blur`)"
  >

This component has quite a few more attributes than our past example, so let's walk through them.

  • autocomplete="off" is the standard HTML property that makes sure the browser doesn't try to autocomplete previously entered values. Notice that this is the only attribute without a : or @ before it.
  • :placeholder="placeholder"uses the shorthand for v-bind to set this input's placeholder value to whatever is passed into the component's placeholder props.
  • :data-variant="variant" uses the same v-bind shorthand and sets this standard data attribute to our variant prop.
  • :value="value"- the value of this input is determined by the value prop. This textfield is what we call a "controlled" component.
  • @input="$emit(input, $event.target.value)" - Here is our first event! On each keystroke the current value of the input ($event.target.value) is passed up to the parent as the input event. Emits tell the parent component that something happened in the child (a reverse callback function), and in this case we name the emit the same as the native event that triggered it.
  • @keyup.enter="$emit(enter)" - Vue has special events for several common keystrokes, so in this case we are telling any parent component that someone pressed the "enter key" while inside of this input. You'll notice that I simply name the event "enter" and not anything related to adding or editing a todo.
  • @blur="$emit(blur)" - Lastly, we want to make sure that our parent components (or application) are able to respond to a blur event. Remember that the @ is just shorthand for v-on.

Isn't This Redundant?

Now you might be asking "why go through all this trouble just to pass up all of these event handlers. Isn't it easier to just USE the <input> along with all of it's event handlers?". The advantage of this approach is that you may want additional markup to be included with your input, defaults styles, or you want to handle these events differently before passing them up to the parent component. Simply put, wrapping your primitive HTML elements in a Vue component provides an abstraction that lets you do more while maintaining a standard interface.

Style Tag

The input styles are also more complex than previous examples. In this case we have 2 distinct sets of styles wrapped in variant() mixins. This simple mixin does nothing more than wrapping the styles in a [data-variant="edit"] selector. It's certainly not necessary, but is an example of a convention used to create component variants. Another "road runner rule". Notice that I don't even need to use the .Textfield selector. Since we're using scoped styles, these data attribute selectors will only target this component.

Adding it to App.vue

Since we haven't tackled our TodoItem component yet, we'll just use this Textfield to replace the old <input /> tag in the header.

Start with importing the component:

import Textfield from 'design-system/src/components/Textfield/Textfield.vue';

Then once you add it to the "components" object, we can use it in our App.vue template.

<header>
  <Textfield
    variant="new"
    placeholder="What needs to be done?"
    :value="newTodo"
    @input="(value) => { newTodo = value }"
    @enter="addTodo"
  />
</header>
  • Variant and placeholder are pretty straight forward, we're just passing strings down to our Textfield.
  • :value="newTodo" - This completes our Textfield being a "controlled" component as the actual value in the <input /> at any given time is actually determined by value stored in "newTodo". This gives us an easy way to clear out the input by simply setting the newTodo variable to "".
  • @input="(value) => { newTodo = value }" - We were intentional about not putting any business logic in our Textfield because in some cases the @input event updates the newTodo variable, and in other situations, it does not.
  • @enter="addTodo" - We can also attach a defined function to this event. Now, anytime the Textfield emits "enter", the application will take whatever is stored in newTodo and add it to the todos array.