Leveraging keypress() for managing all Vue interactions

I am in the process of converting a game from Javascript/jQuery to Vue. This particular game is entirely controlled by keyboard input, with no mouse interaction involved.

Players navigate through the game using the "up" and "down" arrow keys to cycle through buttons, the "enter" key to select options, and the "back" arrow key to go back.

When a player selects an option, relevant data is stored in a "game" array so that during gameplay, the correct game and players can be displayed.

My question is, what would be the most effective way to implement this functionality in Vue?

I've come across information suggesting that using jQuery for DOM manipulation in a Vue project is not ideal. However, I'm unsure how to achieve the same functionality as keypress() does in jQuery across all pages without it.

This is the current structure of the page (designed to work with Javascript/jQuery):

// Initial page seen by the player
<div class="page1">
    <div class="button button_selected" data-link="choosegame">Choose a game</div>
    <div class="button" data-link="highscores">Highscores</div>
    <div class="button" data-link="settings">Settings</div>
</div>

// If "Choose a game" is selected, display this page:
<div class="choosegame">
    <div class="button" data-link="chooseplayers" data-options="{game:'checkers',players:'2',playersmax:'2'}">Checkers</div>
    <div class="button" data-link="chooseplayers" data-options="{game:'bingo',playersmin:'2',playersmax:'4'}">Bingo</div>
    <div class="button" data-link="chooseplayers" data-options="{game:'scrabble',players:'2',playersmax:'2'}">Scrabble</div>
</div>

// If a specific game is selected (e.g., checkers), display this page:
<div class="chooseplayers">
    <div class="button" data-link="playgame" data-options="{player:'Jimmy'}">Jimmy</div>
    <div class="button" data-link="playgame" data-options="{player:'Billy'}">Billy</div>
    <div class="button" data-link="playgame" data-options="{player:'Arnold'}">Arnold</div>
</div>

// After players are selected, show this page:
<div class="playgame">
    PLAYING!
</div>

Answer №1

Below is a comprehensive example that addresses your query. Due to its length, feel free to seek clarification by asking questions.

This code snippet sets up an event listener on the window object and manages it using Vue logic.

new Vue({
  el: '#app',
  data: {
    buttons: [
      {
        'choosegame': 'Choose a game',
        'highscores': 'Highscores',
        'settings': 'Settings'
      },
      {
        'Checkers': { game: 'checkers', players: '2', playersmax: '2' },
        'Bingo': { game: 'bingo', playersmin: '2', playersmax: '4' },
        'Scrabble': { game: 'scrabble', players: '2', playersmax: '2' }
      },
      {
        'Jimmy': 'Jimmy',
        'Billy': 'Billy',
        'Arnold': 'Arnold'
      },
    ],
    page: 0, // currentPage
    selectedButton: 'choosegame',
    game: null, // chosen game and player
    player: null
  },
  methods: {
    handleKeyPress: function (e) {
      const keyCode = String(e.keyCode || e.code || e.keyIdentifier);
      if (keyCode === '13') { // enter
        if (this.page < 3) {
          if (this.page === 1) this.game = this.selectedButton;
          if (this.page === 2) this.player = this.selectedButton;
          if (this.page === 0 && this.selectedButton !== 'choosegame') {
            console.log('not implemented yet. choose a game instead');
          } else {
            this.page++;
            this.selectedButton = Object.keys(this.currentButtons)[0];
          }
        }
      } else if (keyCode === '38' || keyCode === '40') { // navigate up or down
        const buttons = this.buttons[this.page];
        const pageKeys = Object.keys(buttons);
        const currIndex = pageKeys.findIndex(key => this.selectedButton === key);
        const newIndex = (keyCode == 38) // up
          ? (currIndex === 0)
            ? pageKeys.length - 1
            : currIndex - 1
          : (keyCode == 40) // down
            ? (currIndex === (pageKeys.length - 1))
              ? 0
              : currIndex + 1
            : currIndex;
        this.selectedButton = pageKeys[newIndex]
      }
    }
  },
  computed: {
    currentButtons: function () {
      return this.buttons[this.page] || [] // current buttons object 
    }
  },
  mounted: function () {
    // add an event listener for keypress
    window.addEventListener('keypress', this.handleKeyPress)
  }
});
.button_selected {
  background-color: red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <div v-if="page == 0">
    <div class="button"
      v-for="(button, index) in currentButtons" 
      :key="index" 
      :class="{'button_selected': index === selectedButton}">{{ button }}
    </div>
  <div v-if="page == 1 || page == 2">
    <div class="button"
      v-for="(button, index) in currentButtons" 
      :key="index" 
      :class="{'button_selected': index === selectedButton}">{{ index }}
    </div>
  </div>
  <div v-if="page == 3">
    You made it here! {{ player }} is going to play {{ game }}
  </div>
</div>

Answer №2

Utilizing key events on any DOM element with focus is a possibility. Typically, this applies to input fields; however, if you enable focus on a div (by setting its tabindex attribute to -1) and set the focus on it (or have focus on an element within it that doesn't interrupt event bubbling), then keyboard events can be managed.

One strategy is as follows: I establish a keyBus where the main app dispatches events to the bus based on the pressed key. The component (menu, game, etc.) receives the bus as a prop and reacts to the events.

Regrettably, key events can lead to scrolling, which may be unavoidable. It's advisable to keep all overflows hidden.

new Vue({
  el: '#app',
  data: {
    keyBus: new Vue()
  },
  methods: {
    handleEnter() {
      this.keyBus.$emit('enter');
    },
    handleUp() {
      this.keyBus.$emit('up');
    },
    handleDown() {
      this.keyBus.$emit('down');
    }
  },
  components: {
    menuComponent: {
      template: '#menu-template',
      props: ['keyBus'],
      data() {
        return {
          ups: 0,
          downs: 0,
          enters: 0
        }
      },
      mounted() {
        this.keyBus.$on('up', () => ++this.ups);
        this.keyBus.$on('down', () => ++this.downs);
        this.keyBus.$on('enter', () => ++this.enters);
      }
    }
  },
  mounted() {
    this.$el.focus();
  }
});
#app {
  height: 300px;
  width: 300px;
  background-color: #eee;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app" tabindex="-1" @keyup.enter="handleEnter" @keyup.up="handleUp" @keyup.down="handleDown">
  <menu-component :key-bus="keyBus">
  </menu-component>
</div>

<template id="menu-template">
<div>
Ups: {{ups}} <br>
Downs: {{downs}} <br>
Enters: {{enters}} <br>
</div>
</template>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to utilize RxJs for streaming HostListener events?

Although I've found plenty of resources on binding Angular HostListeners, I'm curious about using RxJs to stream it instead: @HostListener('document:click', ['$event']) handleClick(event: Event) { // etc } I want to cre ...

Fixed position toolbar within a container positioned beside a dynamically sized sidebar navigation

I'm trying to figure out how to create a fixed toolbar that fills the remaining width of the page when a side nav is collapsible. Setting the width to 100% causes it to overflow the page due to the dynamic nature of the side nav. Using calc() isn&apos ...

Stop a hacker from obtaining the usernames from a system

Our forgot password page has been identified with a security issue that needs attention: ISS-0003938 Web Inspect Open Medium Suspicious Files Found in Recursive Directory ****** Remove any unnecessary pages from the web server If any files are nec ...

Is this included in the total number of reads?

Following my query with CollectionsGroup, I attempted to retrieve the information of the parent's parent like this: db.collectionGroup('teams').where('players', 'array-contains', currentUser.uid).get().then(function(snaps ...

Is it possible for browsers to handle PUT requests using multipart/form data?

Is it common for HTML forms to not support HTTP PUT requests when submitted from certain browsers like Google Chrome? <form id="#main-form" action="http://localhost:8080/resource/1" method="put" enctype=" ...

``The error 'Uncaught SyntaxError' is thrown during the production build of

I am facing an issue with the production build of my Vuejs application. When I use the npm run build command to generate the production build and then use serve -s dist to deploy it, everything works fine except for one parameterized path (product) in Vue ...

Combine the promises from multiple Promise.all calls by chaining them together using the array returned from

I've embarked on creating my very own blogging platform using node. The code I currently have in place performs the following tasks: It scans through various folders to read `.md` files, where each folder corresponds to a top-level category. The dat ...

I'm having trouble with the colors not displaying correctly on my NextJS Tailwind navbar

I've encountered an issue with my navbar where none of the specified colors are being displayed. The code snippet for the navbar is as follows: Codes: 'use client' import Head from 'next/head'; import Link from 'next/link&apo ...

Effortlessly retrieving the id attribute from an HTML tag using jQuery

Currently, I am encountering an issue with a code snippet that is designed to extract the value from an HTML tag. While it successfully retrieves a single word like 'desk', it fails when attempting to do so for an ID consisting of two or more wor ...

Adjust the appearance of matSelect when the selection menu is activated

What is the best way to adjust mat-select properties when its options are open? <mat-select class="selector"> <mat-option><mat-option> </mat-select> .selector:focus { color: green; } I attempted using focus, but ...

Exploring the World of D3.js with an Interactive Example

Struggling to grasp D3, I'm having difficulty executing the circle example. http://mbostock.github.com/d3/tutorial/circle.html I aim to run the part where the circles change colors and sizes. I simply copied and pasted the example but can't fi ...

Steps for removing an item from an array using lodash

I have an array containing objects and I am looking to remove specific items based on certain conditions. Can someone guide me on how to achieve this using the lodash map function? Example: [{a: 1}, {a: 0}, {a: 9}, {a: -1}, {a: 'string'}, {a: 5 ...

Could the slow loading time of the React site be attributed to an overload of static assets?

As a ML professional diving into frontend development, I have recently incorporated various fixed assets such as images into the assets folder for React. However, I've noticed that my website is running slower than expected. Do you believe that these ...

Displaying results of data from a cross domain request

I have some test code related to WordPress displayed below. jQuery(document).ready(function($) { var link = 'http://000.00.00.00/cgi-bin/test.cgi'; $("#sub").click(function() { $.ajax({ type:'POST', url: link, ...

Version 5.3 of Laravel combined with Vue 2

Working with Vue 2 in a fresh Laravel 5.3 project, I'm faced with a challenge regarding binding a URL in the Laravel format. I've extracted some configuration rows from a database and my goal is to iterate over them to display the data, followed ...

What is the best way to retrieve the name of an element or component using JavaScript

I'm currently working on a webpage that includes ASP.NET panels and JavaScript which retrieves all components present on the page: var items = Sys.Application.getComponents(); My goal is to obtain the name/ID of each element stored in the 'item ...

Invoking a function within another function in Python

I'm struggling with optimizing my database queries by using methods to avoid repeating code. However, I've encountered an issue where calling methods within other defined methods is not working as expected. Below is the error message I am receiv ...

Struggling with rendering components in REACT?

I'm encountering an issue with rendering the Butcher Shop component. I can't seem to pinpoint what's causing it to be null or undefined. Can someone help me identify the mistake? Nothing is showing up on the DOM and I keep getting this error ...

Using the v-for directive to create sequential lists

I am struggling to display pairs of data from an object based on category using nested v-for loops. The object, categoryArray, contains entries such as {stage 1, red}, {stage 1, blue}, {stage 2, orange}, {stage 3, brown}, {stage 2, green. My desired displ ...

Error encountered when executing the npm run dev script

Attempting to follow a tutorial, I keep encountering an error related to the script. I've tried restarting the tutorial to ensure I didn't overlook anything, but the same issue persists. Working on a Mac using pycharm. In the tutorial (from Ude ...