4

I am trying to switch from jQuery to Vue.js and I am a little bit stuck with this. I have 3 buttons on the page. When I click on a button, I want all the other buttons to change the background color to green and the button that was clicked - change it's color to black.

It was just 2 lines of code with jQuery but I can't figure out how to accomplish it with Vue.js. There also doesn't seem to be a this keyword for Vue.js.

Also, at this point, I'd like to just apply raw css background-color property instead of applying a class.

Here is my jQuery code - very simple

<div class="main-content-area">    
  <div class="btn">Click me!</div>
  <div class="btn">Click me!</div>
  <div class="btn">Click me!</div>    
</div>
const Example = new Vue({
  el: '.main-content-area',
  methods: {
    addEventListeners() {
      $(document).ready(function() {
        $(".btn").click(function() {
          $(".btn").css("background-color", "green"); // Make all buttons green
          $(this).css("background-color", "black"); // Make the clicked button black
        });
      });
    }
  },
  mounted: function() {
    this.addEventListeners();
  }
})

With Vue.js - I got only this far...

<div class="main-content-area">    
  <div class="btn" @click="changeColor">Click me!</div>
  <div class="btn" @click="changeColor">Click me!</div>
  <div class="btn" @click="changeColor">Click me!</div>    
</div>
const Example = new Vue({
  el: '.main-content-area',
  methods: {
    changeColor() {
      //  Change color to green for all .btn elements
      //  and change  color for clicked .btn to black
    }
  })

2 Answers 2

1

use component for buttons :

HTML :

<div class="main-content-area">

    <my-custom-button component-type="my-custom-button" ></my-custom-button>
    <my-custom-button component-type="my-custom-button"></my-custom-button>
    <my-custom-button component-type="my-custom-button"></my-custom-button>

</div>

JavaScript :

Vue.component("my-custom-button",{
        template : '<div class="btn" :style="buttonStyles" @click="activeThisButton" >Click me!</div>',

    data(){
        return {
        isActive : false,
      }
    },

    computed : {
        buttonStyles(){
        return {
            backgroundColor : this.isActive  ? 'green' : '',
        }
      }
    },

    methods : {
        activeThisButton(){
        // call inactiveAllButtons on parent to deselect all buttons
        this.$root.inactiveAllButtons();
        // active current button
        this.isActive = true;
      }
    }
})

const Example = new Vue
({

    el: '.main-content-area',

    methods : {
        // filter children and find buttons ( by component-type property )
        // and inactive all .
        inactiveAllButtons(){
        var buttons = this.$children.filter(function(child) {
            return child.$attrs['component-type'] === 'my-custom-button';
        });
        for(var i = 0 ; i < buttons.length ; i++ ){
          buttons[i].isActive = false;
        }
      }
    }

});

jsfiddle demo

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

4 Comments

It doesn't deselect all other buttons when clicked on a button.
This is a very bad approach. You need to keep the state in the parent component in this case and never use $children or $root. If you use any of these, now your components are tightly coupled with the containing component and this breaks idea of vue.js as a framework
@Archeg so we should check there is method on parent and if there is, call it . please post your idea if you have a better one
@Zoha Sorry, I still don't think this is a good idea, and I would really advise you not to write code that uses $children or $root, unless really necessary. Please see my answer.
1

This is a better approach, without using unsafe $root and $children.

<template>
  <div class="hello">
    <button class="btn" @click="activeButton = 0" v-bind:style="{'background-color':buttonColor[0]}">Click me!</button>
    <button class="btn" @click="activeButton = 1" v-bind:style="{'background-color':buttonColor[1]}">Click me!</button>
    <button class="btn" @click="activeButton = 2" v-bind:style="{'background-color':buttonColor[2]}">Click me!</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      activeButton: 0
    };
  },
  computed: { 
    buttonColor: function() {
      let result = [];
      for (var i = 0; i< 3; i++){
        if (this.activeButton == i){
          result.push('black');
        } else {
          result.push('green');
        }
      }

      return result;
    }
  }
};
</script>

Demo: https://codesandbox.io/s/8kz9y0rjj9

You could also wrap button in a separate component, as @Zoha suggested, but given that you did not ask for it, I did not do that. This would allow to hide buttonColor implementation in the component.

Also please note that using classes is much preferable and cleaner approach: No need for ugly buttonColor computed function.

<template>
  <div class="hello">
    <button class="btn" @click="activeButton = 0" v-bind:class="{'greenBtn':true, 'blackBtn': activeButton == 0}">Click me!</button>
    <button class="btn" @click="activeButton = 1" v-bind:class="{'greenBtn':true, 'blackBtn': activeButton == 1}">Click me!</button>
    <button class="btn" @click="activeButton = 2" v-bind:class="{'greenBtn':true, 'blackBtn': activeButton == 2}">Click me!</button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      activeButton: 0
    };
  },
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
 .greenBtn {
   background-color: green
 }

 .blackBtn {
   background-color: black
 }
</style>

4 Comments

thank you for sharing this @Archeg . this is good idea. but can you clear me why we should not use $root or $children in vue. i have several projects witch i used them. and there was no problem yet :)
@Zoha The idea is that every component should know nothing of the environment it is running in (and should require nothing), except from the properties that are fed to the component explicitly. Your component has an implicit requirement of the parent to have inactiveAllButtons() method. The components are the building blocks that are supposed to be used anywhere w/o context. If your building blocks require some implicit rules, they are no longer components and become much harder to work with. Instead, you should use props and $emit()
@Zoha Unfortunately I can't find you a good read article about this (I'm just bad at finding good reads), but I'm pretty sure if you ask a question here on SO why component should know nothing of parent, somebody will write a better explanation. Just look at $parent property documentation: vuejs.org/v2/api/#parent Use $parent and $children sparingly - they mostly serve as an escape-hatch. Prefer using props and events for parent-child communication.
@Zoha Also I'd like to point out that this does not mean that you should change your code right away. If it works - it is fine. I'm just trying to follow best practice when answering on SO because people learn here, and it is extremely hard to get bad practice out of people's minds once they got used to it

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.