3

I have some logic in the watch prop in one of my Vue component's that switches between elements in sequential order upon press the down arrow key (key code 40). Its not total clutter at the moment however longterm it will become extremely inefficient. Heres the structure:

data () {
 return {
  firstElActive: true,
  secondElActive: false,
  thirdElActive: false
  ...
 }

},

props: {
 nextEl: {
  type: Boolean,
  default: false,
  required: true
 }
},

watch: {
 nextEl: function (value) {
  if (this.nextEl) {
   if (this.firstElActive) {
    this.firstElActive = !this.firstElActive;
    this.secondElActive = !this.secondElActive;
    ... // other specific logic
   }
   else if (this.secondElActive) {
    this.secondElActive = !this.secondElActive;
    this.thirdElActive = !this.thirdElActive;
    ... // other specific logic
   }
   ... // so forth
  }
 }
}

As you can probably assess, this will get pretty bad, pretty quick. I have Lodash bootstrapped globally (window._ = require('lodash')), and would like to utilize it... I'm just in a quandary as to which method(s) will refactor this most efficiently. Suggestions?

5
  • So your nextEl boolean will be toggling false/true and every time it goes true you want to "move forward" the active el? Commented Mar 5, 2018 at 22:44
  • Typically you just set an activeElement and hide/show based on that. When your key is pressed, all you would do is determine which element is active next and set it. Then it's trivial to handle any number of elements. lodash is really not necessary. Commented Mar 5, 2018 at 22:58
  • 2
    Here is a quickie example that needs some work but gives the gist. codepen.io/Kradek/pen/bLPBEb?editors=1010 Commented Mar 5, 2018 at 23:05
  • right, however what if i wanted to do specific logic before each increment of activeElement depending on its value? @Bert Commented Mar 5, 2018 at 23:16
  • I don't understand the problem. Do whatever you want before setting the activeElement in onKeyDown. Commented Mar 5, 2018 at 23:17

1 Answer 1

1

Instead of using many boolean data properties use an active index. Increment or decrement this active index while going up or down.

new Vue({
  name: 'example',

  data() {
    return {
      items: [
        { id: 0, value: 'item 1'}, 
        { id: 1, value: 'item 2'}, 
        { id: 2, value: 'item 3'},
      ],
      activeIndex: 0,
      arrowUpKeyCode: 38,
      arrowDownKeyCode: 40,
    };
  },

  computed: {
    currentItem() {
      return this.items[this.activeIndex];
    },
  },

  methods: {
    bindEvents() {
      document.addEventListener('keydown', this.onKeyDown);
    },

    unbindEvents() {
      document.removeEventListener('keydown', this.onKeyDown);
    },

    onPrev() {
      console.log(`on prev (key code ${this.arrowUpKeyCode}) ${this.currentItem.value}`);
    },

    onNext() {
      console.log(`on next (key code ${this.arrowDownKeyCode}) ${this.currentItem.value}`);
    },

    goPrev() {
      if (this.activeIndex > 0) {
        this.activeIndex -= 1;
        this.onPrev();
      }
    },

    goNext() {
      if (this.activeIndex < this.items.length - 1) {
        this.activeIndex += 1;
        this.onNext();
      }
    },

    onKeyDown(ev) {
      if (this.arrowUpKeyCode === ev.keyCode) this.goPrev();
      else if (this.arrowDownKeyCode === ev.keyCode) this.goNext();
    },
  },

  mounted() {
    this.bindEvents();
  },

  beforeDestroy() {
    this.unbindEvents();
  },
}).$mount('#example');
.active { background-color: dodgerblue; }
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<div id="example">
  <ul>
    <li v-for="(item, index) in items" 
        :key="item.id" 
        :class="{ active: index === activeIndex }">
      {{ item.value }}
    </li>
  </ul>
</div>

Sign up to request clarification or add additional context in comments.

6 Comments

correct me if I'm wrong, but doesn't $emit get sent to the parent of the component? what if i wanted to specific logic stemming from the component itself?
Since I saw you used props I mistakenly supposed you wanted to communicate with a parent component, if that's not the case you can just call another method from the component itself. I will update my answer.
one more thing! what if i don't want to show all items at once, only if its active or the activeIndex === item.id ?
Use the v-if directive like so: demo
You can use the currentItem computed property instead of using v-for as well: demo.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.