DEV Community

Cover image for 9. Interactivity 3. Set `activeKey` by click. `shiftKey` app state
Rustam Apay
Rustam Apay

Posted on

9. Interactivity 3. Set `activeKey` by click. `shiftKey` app state

Set activeKey by click

Some people don't have a physical keyboard, but only a screen one. And they also want to learn letters with our cool app.

To add @click event to Key we need first to encapsulate key activation into a method.

Method

Add a new method to Keyboard.js.

Keyboard.js methods:

setActiveKey(keyContent) {
    this.activeKey = keyContent
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => (this.activeKey = { code: '' }), 1000)
}
Enter fullscreen mode Exit fullscreen mode

And call this new method from mounted()

Keyboard.js

mounted() {
    this.getKeyboardData(this.currentLang)

    window.addEventListener('keydown', event => {
        event.preventDefault()
        const { code, key, shiftKey } = event
        this.setActiveKey({ code, key, shiftKey })
    })
},
Enter fullscreen mode Exit fullscreen mode

Event

Send a new method setActiveKey from the Keyboard to the Key:

Keyboard.js template:

<vue-keyboard ... :setActiveKey="setActiveKey" />
Enter fullscreen mode Exit fullscreen mode

In Key.js receive this method (add it to props), and add its call to the template.

Key.js props:

props: {
    ...,
    setActiveKey: Function
}
Enter fullscreen mode Exit fullscreen mode

Key.js template

<div
    :class="['key', {active: activeKey.code === keyContent.code}]"
    @click="setActiveKey(keyContent)"
>
    <div class="main">{{main}}</div>
    <div class="shifted">{{shifted}}</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Now we see, that key became active also by mouse click (or tap from phone).

Image description

Diffs in code 9.1

The key full info

On the previous gif animation you can notice, that activeKey is different for keydown and @click. E.g. for q:

activeKey on keydown: { code: KeyQ, shiftKey: false }

activeKey on @click: { code: KeyQ, main: "q", shifted:"Q" }

That's because on keydown we assign to the activeKey an object {code, shiftKey} that we get from keyboard event.

And on @click we set activeKey from our data keyboardData/en.js -- which we filled with useful data before.

It is easy to get this data by @click in Key component -- because it is a prop keyContent. But keydown event doesn't contain these data.

We should add some code to extract keyContent from keyboardData by keydown event code.

Keyboard.js mounted

...
window.addEventListener('keydown', event => {
    event.preventDefault()
    const { code, shiftKey } = event
    const keyContent = this.keyboardData
        .flat()
        .find(elem => elem.code === code)
    this.setActiveKey(keyContent)
})
...
Enter fullscreen mode Exit fullscreen mode

keyboardData is 2D array (array with arrays). So we did it flat -- 1D, and find full key info by code. Then pass it to the method setActiveKey.

You can test it out: keydown and @click now returns almost the same value.

Move shiftKey to state

For now, we get shiftKey only with keydown, can't get shiftKey on @click. To make keydown and @click events equivalent, let's create a new keyboard state: shiftKey. So we'll have the ability to manipulate shiftKey not only from keyboard keydown, but also from mouse/tap screen events.

Add to the end of each keyboardData/lang.js a new row with 2 buttons:

en.js, ru.js, ar.js

    ...,
 [
    {
        code: 'ShiftLeft',
        label: 'Shift'
    },
    {
        code: 'ShiftRight',
        label: 'Shift'
    }
]
Enter fullscreen mode Exit fullscreen mode

Result

Image description

Add a new state to

Keyboard.js data()

{
    ...
    shiftKey: false
}
Enter fullscreen mode Exit fullscreen mode

Add 2 keyboard event listeners, that change the new app state

Keyboard.js mounted()

...
window.addEventListener('keydown', event => {
    if (event.key === 'Shift') {
        this.shiftKey = true
    }
})

window.addEventListener('keyup', event => {
    if (event.key === 'Shift') {
        this.shiftKey = false
    }
})
Enter fullscreen mode Exit fullscreen mode

Add shiftKey state to template, to test how it works

Keyboard.js template

...
<div>shiftKey: {{shiftKey}}</div>
...
Enter fullscreen mode Exit fullscreen mode

Result

Image description

When we hold shift on keyboard, state shiftKey is true even when activeKey faded. When we @click shift by mouse, shiftKey is false, even when shift is the activeKey.

We can't hold shift on the screen as on physical keyboard. So we need to set shiftKey by click on the screen button, and the app will think that we hold shift key. On the second click on shift the app will think, that we released the button.

For that, in Keyboard.js add a new method toggleShiftKey and pass it down to Key

Keyboard.js methods:

toggleShiftKey(){
    this.shiftKey = !this.shiftKey
}
Enter fullscreen mode Exit fullscreen mode

Keyboard.js template

<vue-key ... :toggleShiftKey="toggleShiftKey" />
Enter fullscreen mode Exit fullscreen mode

Key.js props

{
    ...
    toggleShiftKey: Function
}
Enter fullscreen mode Exit fullscreen mode

Now @click should call multiple methods, not only one @click="setActiveKey(keyContent)" as before. So we need to create an additional method calling all these methods. And call it from the template @click.

Key.js methods

methods: {
    keyClick(keyContent) {
        this.setActiveKey(keyContent)
        if (keyContent.code.includes('Shift')) {
            this.toggleShiftKey()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In Key.js template replace @click="setActiveKey(keyContent)" with @click="keyClick(keyContent)"

Key.js template

<div
    :class="['key', {active: activeKey.code === keyContent.code}]"
    @click="keyClick(keyContent)"
>
    <div class="main">{{main}}</div>
    <div class="shifted">{{shifted}}</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Result

Image description

shiftKey state works fine with keydown and @click. But we don't see that shift is holding on the keyboard.

Holding shift style

Add to styles.css an especial style for pressed (not released) shift buttons.

styles.css

.key.shiftKeyPressed {
    color: red;
    font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

To do that we need in the Key component the prop shiftKey. Pass it from Keyboard to Key

Keyboard.js template

<vue-key ... :shiftKey="shiftKey" />
Enter fullscreen mode Exit fullscreen mode

Key.js props

props: {
    ...
    shiftKey: Boolean,
},
Enter fullscreen mode Exit fullscreen mode

Add to computed 2 methods:

Key.js computed

isActive() {
    return this.activeKey.code === this.keyContent.code
},
isShift() {
    return this.keyContent.code.includes('Shift')
}
Enter fullscreen mode Exit fullscreen mode

Then use these new computed values in a template:

Key.js template

<div
    :class="[
                'key', 
                keyContent.code, 
                { active: isActive }, 
                { shiftKeyPressed: isShift && shiftKey && !isActive }
            ]"
    @click="keyClick(keyContent)"
></div>
Enter fullscreen mode Exit fullscreen mode

Style shiftKeyPressed will be applied to key only if:

  • it is a shift key (with code: ShiftLeft or ShiftRight),
  • keyboard state shiftKey is true -- the key is holding,
  • key is not active (we can't see red text on red background)

Result

Image description

Diffs in code 9.2

Entire code after the chapter

Top comments (0)