In the previous lesson we introduced the ColorPicker component.
Now we put that component to good use. When the user clicks the button we’ll change the default color of the application. This will be in turn used in the next lesson when we’ll allow to color pixels by clicking with the mouse.
Define the color storage location
First, let’s define a default color in the App state to ‘white’.
In src/App.vue
we define the data
object, which is a function that returns an object that contains the color
property:
export default {
name: 'App',
data: function() {
return {
color: 'white'
}
},
//...
}
Note: it’s important that you defined
data
as a function that returns an object, not just an object.
Now we pass this property to the ColorPicker component in the template, as a prop:
<ColorPicker :color=color />
We need to use :color
here because the color is a dynamic value, stored in the component data.
Now inside the ColorPicker component, in src/components/ColorPicker.vue
, we add the props
object that defines the color
prop as a string:
export default {
name: 'ColorPicker',
props: {
color: String
},
//...
}
Cool! Now we can check if it works by printing it inside the template using {{color}}
.
Done?
Let each Pixel know if it’s the currently selected color
Now we can pass a current
prop, a boolean value that is true if the color assigned to the pixel is the current color.
Inside the ColorPicker component, we look up the color
prop in the template, and we use the ternary operator to assign a true/false value to the current
prop:
<template>
<div>
<Pixel color="white" :current="color == 'white' ? true : false"/>
<Pixel color="lightblue" :current="color == 'lightblue' ? true : false"/>
<Pixel color="blue" :current="color == 'blue' ? true : false" />
<Pixel color="darkblue" :current="color == 'darkblue' ? true : false" />
</div>
</template>
We don’t pass down the color
value to the Pixel component, because it does not need to know about it.
So now inside Pixel, we accept that prop:
<script>
export default {
name: 'Pixel',
props: {
color: String,
current: Boolean
}
}
</script>
and we assign a current
class to the pixel that has that assigned to true
:
<template>
<div :class="['pixel', color, current ? 'current' : '']"></div>
</template>
and we add a line of styling to the Pixel style tag:
.pixel.current {
border: 4px solid yellow;
}
Select a new color when clicked
Now when a Pixel component in the ColorPicker is clicked, we must update the currently selected color.
How do we do such a thing? We need to use events.
Vue has a very nice mechanism to handle events. For a simple use case like ours, it’s ideal.
We listen for a particular event name, and then we emit this event when something happens.
Now, let’s start with emitting the event.
In src/components/Pixel.vue
we add a click handler. Whenever this element is clicked, we trigger a brand new changeColor
method we add to Pixel. in the component methods
property:
<template>
<div @click="changeColor(color)" :class="['pixel', color, current ? 'current' : '']" ></div>
</template>
<script>
export default {
name: 'Pixel',
//...
methods: {
changeColor: function(color) {
this.$root.$emit('updatecolor', color)
}
}
}
</script>
The body of the changeColor method looks up the $root
element (the main Vue component) and triggers an updatecolor
event, passing color
which is the color prop that is passed down to Pixel.
The other side of this mechanism is the event receiver.
We set it up in the App component.
We add a mounted
property to the App component, which is a callback function that’s called when the component is added to the page. In this case, when the application starts at page load.
//...
mounted() {
this.$root.$on('updatecolor', color => {
this.color = color
})
}
//...
We use mounted()
instead of mounted: function mounted()
because this is a handy shortcut that ES6 provides us.
The callback function starts listening for the updatecolor
event and accepts a color
parameter. When it’s invoked, we set the color
property of the component data to the newly selected color.
We use this.$root because events must be referenced on the same Vue instance. In both components, we refer to the root vue instance as an event bus - a way to exchange events.
Here’s the full App component script at this point:
<script>
import Canvas from './components/Canvas'
import ColorPicker from './components/ColorPicker'
export default {
name: 'App',
data: function() {
return {
color: 'white'
}
},
components: {
Canvas,
ColorPicker
},
mounted() {
this.$root.$on('updatecolor', color => {
this.color = color
})
}
}
</script>
Play around and notice how you can click the ColorPicker pixels and what happens now is that the current
prop is updated every time they send the event to update the color, and so they are highlighted when you click them.
The full source at this point is available at https://codesandbox.io/s/jvn27wz2o9.
⚠️ There’s one thing though. We don’t want every pixel in the Canvas to be able to change the color when clicked. This is what’s happening right now! 😱
So what can we do in this case? One option is to tell the Pixel if it is interactive, and only, in this case, it will emit the event.
Sounds good, let’s do it!
In ColorPicker.vue
we add an interactive
prop to each Pixel:
<template>
<div>
<Pixel interactive color="white" :current="color == 'white' ? true : false"/>
<Pixel interactive color="lightblue" :current="color == 'lightblue' ? true : false"/>
<Pixel interactive color="blue" :current="color == 'blue' ? true : false" />
<Pixel interactive color="darkblue" :current="color == 'darkblue' ? true : false" />
</div>
</template>
and in Pixel.vue
we list it in the accepted props:
<script>
export default {
name: 'Pixel',
props: {
color: String,
current: Boolean,
interactive: Boolean
},
//...
}
</script>
Now we only want to emit the event if interactive
is true.
We do so by prepending interactive &&
in the click event handler:
<template>
<div @click="interactive && changeColor(color)" :class="['pixel', color, current ? 'current' : '']" ></div>
</template>
what this code means is “if interactive is true, then call changeColor(color)
”. It’s a rather terse JavaScript construct, but it is much simpler to read than a full-blown if
statement once you get the hang of it 🙂
We could have checked this in the changeColor method body, but not calling it in the first place sounds better to me.
Now you can go and check that only the Pixel components in the ColorPicker trigger the color change.
We’re almost done, except the app looks quite bad right now. Adding this CSS in the App.vue
file will make it a bit better, with the ColorPicker on the left rather than on top:
background-color: #333;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
Here’s our result so far!
Wrapping up
In this lesson, probably the most complex of this module, you first added a centralized place to store the current color in the App component.
Then you set up an event system to handle changing the color when a Pixel in the ColorPicker is clicked.
Let’s go to the next lesson to start coloring the pixels in the canvas!