In the previous lessons we started with a simple Pixel component, we grouped up 900 Pixel components in a Canvas component, and we used 4 Pixel components in a ColorPicker component.

We made it possible to color Pixels, and to have a set of colors to choose from. We also set a default color.

Now let’s add the last bit of functionality: we want to color pixels in the canvas, with the color selected in the ColorPicker.


When I click a pixel, that’s going to transform its background color to the currently active color in the color picker.

The Pixel component already has a click handler, because we added it when we wanted to change the default color in the ColorPicker.

Luckily this is just calling the parent prop so we can pass our handler in the Canvas component:

In the last lesson, we passed an “interactive” prop to make the Pixel change the default color when clicked.

Now, this is not a good option anymore, because we also want to react on click when a Pixel is in the Canvas, but in a different way.

So we change that prop to isInColorPicker and we add another one called isInCanvas. Now, there are other ways to do this, which we’ll see during the rest of the course, but in this module, I want to keep things as simple as possible.

So, in ColorPicker.vue:

<template>
  <div>
    <Pixel isInColorPicker color="white" :current="color == 'white' ? true : false"/>
    <Pixel isInColorPicker color="lightblue" :current="color == 'lightblue' ? true : false"/>
    <Pixel isInColorPicker color="blue" :current="color == 'blue' ? true : false" />
    <Pixel isInColorPicker color="darkblue" :current="color == 'darkblue' ? true : false" />
  </div>
</template>

and in Pixel.vue:

<template>
  <div :class="['pixel', color, current ? 'current' : '']" @click="isInColorPicker && changeColor(color)"></div>
</template>

<script>
export default {
  name: 'Pixel',
  props: {
    color: String,
    current: Boolean,
    isInColorPicker: Boolean
  },
  methods: {
    changeColor: function(color) {
      this.$root.$emit('updatecolor', color)
    }
  }
}
</script>

Now we abstract the log in the @click handler into the method. We call it handleClick():

<template>
  <div :class="['pixel', color, current ? 'current' : '']" @click="handleClick"></div>
</template>

<script>
export default {
  name: 'Pixel',
  props: {
    color: String,
    current: Boolean,
    isInColorPicker: Boolean
  },
  methods: {
    handleClick: function() {
      if (this.isInColorPicker) {
        this.$root.$emit('updatecolor', color)
      }
    }
  }
}
</script>

Things should work like before at this point.

Now we add the isInCanvas prop:

<template>
  <div :class="['pixel', color, current ? 'current' : '']" @click="handleClick"></div>
</template>

<script>
export default {
  name: 'Pixel',
  props: {
    color: String,
    current: Boolean,
    isInColorPicker: Boolean,
    isInCanvas: Boolean
  },
  methods: {
    handleClick: function() {
      if (this.isInColorPicker) {
        this.$root.$emit('updatecolor', this.color)
      }
      if (this.isInCanvas) {
        //TODO
      }
    }
  }
}
</script>

Now, when a click a Pixel we need to change its color. Who should own the state of the color choice?

I am going to centralize this in the App component, in an array, and pass down the state to each individual Pixel.

I will when a Pixel changes, I will send the index of the Pixel in an event, and the props chain will automatically change the Pixel color with the currently chosen default color.

Let’s do it!

I add a pixels property to the App component data:

<template>
//...
    <Canvas :pixels=pixels />
//...
</template>

<script>
//...
const defaultColor = 'white'

export default {
  name: 'App',
  data: function() {
    return {
      color: defaultColor,
      pixels: Array(30 * 30)
        .fill()
        .map(() => defaultColor)
    }
  },
  //...
}
</script>

This piece of JavaScript creates an empty array of 900 elements:

Array(30 * 30).fill()

and I initialize them with the default color with:

Array(30 * 30).fill().map(() => defaultColor)

While we’re here, I add an event handler that will change the color of an item in this array:

<script>
//...
export default {
  //...
  mounted() {
    //...
    this.$root.$on('clickedpixel', index => {
      this.pixels.splice(index, 1, this.color)
    })
  }
}
</script>

One important thing to notice is that we can’t just write this.pixels[index] = this.color because Vue would not be able to intercept that. We use the splice() method like in the code above, or we could also do Vue.set(this.pixels, index, this.color) (but I prefer the native JavaScript method) - see more on this page.

Ok! We’re done here. We can move over to the Canvas component, where we included the Pixels in this way:

<Pixel v-for="n in 30*30"/>

We need to

  1. Set the isInCanvas prop
  2. get the value of the color in that array position and pass it in the color prop
  3. pass the index

We use this code:

<Pixel isInCanvas v-for="(color, index) in pixels" :color="color" :key=index :index=index />

The final piece of the puzzle is in the Pixel component.

First, we need to accept the index prop, which we didn’t use before.

Above we extracted the handleClick() method. We need to emit an event here, telling the event receiver, which is in the App component, that this specific Pixel, with a specific index in the array, has been clicked. Time to update its color!

<script>
export default {
  name: 'Pixel',
  props: {
    //...
    index: Number
  },
  methods: {
    handleClick: function() {
      if (this.isInColorPicker) {
        this.$root.$emit('updatecolor', this.color)
      }
      if (this.isInCanvas) {
        this.$root.$emit('clickedpixel', this.index)
      }
    }
  }
}
</script>

We are done!

Here is the result:

The end result

And here is the full app in CodeSandbox for you to try out if you missed a step: https://codesandbox.io/s/1xllqxz13

Wrapping up

We finally finished our little application. We can now start playing it to draw pixel art all day long 😄

. . .

I’m joking. Time to go to the next lesson, to wrap up the module!