4
votes

I am working on an admin app in Vuejs with Vuetify, and I have three fields in a form for a user to select a hex color value. To make it easier for the user, I have implemented a color picker based off of this codepen.

Here is the ColorPickerButton component:

<template>
    <div ref="colorpicker" class="color-picker-outer">
      <span class="color-picker-inner" v-bind:style="{ 'background-color': colorValue}" @click="togglePicker"></span>
            <chrome-picker :value="colors" @input="updateFromPicker" v-if="displayPicker" />
    </div>
</template>

<script>
import { Chrome } from 'vue-color'

export default {
    props: {
        fieldName: String,
        initColor: string
    },
  components: {
      'chrome-picker': Chrome
  },
  data() {
    return {
      colors: {
                hex: '#000000',
            },
            colorValue: this.initColor,
            displayPicker: false,
    }
  },
  mounted() {
        this.setColor(this.color || '#3121e0');
  },
    methods: {
        setColor(color) {
            this.updateColors(color);
            this.colorValue = color;
        },
        updateColors(color) {
            if(color.slice(0, 1) == '#') {
                this.colors = {
                    hex: color
                };
            }
            else if(color.slice(0, 4) == 'rgba') {
                var rgba = color.replace(/^rgba?\(|\s+|\)$/g,'').split(','),
                    hex = '#' + ((1 << 24) + (parseInt(rgba[0]) << 16) + (parseInt(rgba[1]) << 8) + parseInt(rgba[2])).toString(16).slice(1);
                this.colors = {
                    hex: hex,
                    a: rgba[3],
                }
            }
        },
        showPicker() {
            document.addEventListener('click', this.documentClick);
            this.displayPicker = true;
        },
        hidePicker() {
            document.removeEventListener('click', this.documentClick);
            this.displayPicker = false;
        },
        togglePicker() {
            this.displayPicker ? this.hidePicker() : this.showPicker();
        },
        updateFromInput() {
            this.updateColors(this.colorValue);
        },
        updateFromPicker(color) {
            this.colors = color;
            if(color.rgba.a == 1) {
                this.colorValue = color.hex;
            }
            else {
                this.colorValue = 'rgba(' + color.rgba.r + ', ' + color.rgba.g + ', ' + color.rgba.b + ', ' + color.rgba.a + ')';
            }
        },
        documentClick(e) {
          var el = this.$refs.colorpicker,
                target = e.target;
            if(el !== target && !el.contains(target)) {
                this.hidePicker()
            }
            this.$emit('update-color', this.colorValue, this.fieldName)
        }
  },
  watch: {
        colorValue(val) {
            if(val) {
                this.updateColors(val);
                this.$emit('input', val);
                //document.body.style.background = val;
            }
        }
    }
}
</script>

<style scoped>
  div.color-picker-outer {
    width: 55px;
    height: 50px;
    display: inline-block;
    background-color: #EEE;
    position: relative;
  }

  .color-picker-inner {
    width: 30px;
    height: 30px;
    position: relative;
    top: 10px;
    left: 13px;
    display: inline-block;
  }

    .vc-chrome {
    position: absolute;
    top: 0px;
    left: 55px;
    z-index: 9;
  }
</style>

and here is how I call it from the parent TenantTemplateEdit.vue component.

              <v-layout row>
                <v-flex xs4>
                  <v-text-field
                    v-bind="fields.alertBackgroundColor"
                    v-model="templateModel.alertBackgroundColor"
                    placeholder="#4A4A4A"
                  />
                </v-flex>
                <v-flex xs2>
                  <ColorPickerButton
                    v-bind:field-name="'alertBackgroundColor'"
                    v-bind:init-color="templateModel.alertBackgroundColor"
                    v-on:update-color="getUpdatedColor">
                  </ColorPickerButton>
                </v-flex>
                <!-- Alert Text Color -->
                <v-flex xs4>
                  <v-text-field
                    v-bind="fields.alertTextColor"
                    v-model="templateModel.alertTextColor"
                    placeholder="#4A4A4A"
                  />
                </v-flex>
                <v-flex xs2>
                  <ColorPickerButton
                    v-bind:field-name="'alertTextColor'"
                    v-bind:init-color="templateModel.alertTextColor"
                    v-on:update-color="getUpdatedColor"
                  ></ColorPickerButton>
                </v-flex>
              </v-layout>

The issue I'm struggling with is setting the initial color for the span.color-picker-inner element when the data is changed. and the ColorPickerButton component is called from TenantTemplateEdit. I have verified that the initColor prop is being properly passed and is available in ColorPickerButton, but what I have is not getting to my background-color attribute in the template.

What do I need to change to get background-color set on initial load?

1
How do you pass initColor from TenantTemplateEdit? for example something like: :init-color="SomeColorCode" or what?ajafari
@click="togglePicker()" should be @click="togglePicker" ( forum.vuejs.org/t/…), as you don't pass any arguments to the function.muka.gergely
Do you get errors? Did you inspect element and see that style indeed doesn't get applied? Did you log colorValue to see if it has correct string value in the first place? Can you reproduce on codepen? (if by "background-color attribute" you mean style property)Traxo
@ajafari I updated the code above.wonder95
@muka.gergely Thanks, fixed.wonder95

1 Answers

1
votes

If you correct some typos and such (like string instead of String, no parameters at v-bind, clearing out the mounted() hook in the picker template) in the code, it should work.

Here's a working example (the inner color changes as you pick a new color, and it's set on initial load):

https://codesandbox.io/s/p9620jzoy7

I hope I correctly understood your problem and this snippet helps.

I paste the code here (the code is edited so it can be used in a sandbox environment):

// ColorPickerButton.vue

<template>
  <div ref="colorpicker" class="color-picker-outer">
    <span
      class="color-picker-inner"
      :style="{ 'background-color': colorValue}"
      @click="togglePicker"
    ></span>
    Child init: {{initColor}}
    Child color: {{colorValue}}
    <chrome-picker :value="colors" @input="updateFromPicker" v-if="displayPicker"/>
  </div>
</template>

<script>
import { Chrome } from "vue-color";

export default {
  props: {
    fieldName: String,
    initColor: String
  },
  components: {
    "chrome-picker": Chrome
  },
  data() {
    return {
      colors: {
        hex: "#000000"
      },
      colorValue: this.initColor,
      displayPicker: false
    };
  },
  mounted() {
    // actually there's no such as 'this.color'
    // in this template
    // this.setColor(this.color || "#3121e0");
  },
  methods: {
    setColor(color) {
      this.updateColors(color);
      this.colorValue = color;
    },
    updateColors(color) {
      if (color.slice(0, 1) === "#") {
        this.colors = {
          hex: color
        };
      } else if (color.slice(0, 4) === "rgba") {
        var rgba = color.replace(/^rgba?\(|\s+|\)$/g, "").split(","),
          hex =
            "#" +
            (
              (1 << 24) +
              (parseInt(rgba[0], 10) << 16) +
              (parseInt(rgba[1], 10) << 8) +
              parseInt(rgba[2], 10)
            )
              .toString(16)
              .slice(1);
        this.colors = {
          hex: hex,
          a: rgba[3]
        };
      }
    },
    showPicker() {
      document.addEventListener("click", this.documentClick);
      this.displayPicker = true;
    },
    hidePicker() {
      document.removeEventListener("click", this.documentClick);
      this.displayPicker = false;
    },
    togglePicker() {
      this.displayPicker ? this.hidePicker() : this.showPicker();
    },
    updateFromInput() {
      this.updateColors(this.colorValue);
    },
    updateFromPicker(color) {
      this.colors = color;
      if (color.rgba.a === 1) {
        this.colorValue = color.hex;
      } else {
        this.colorValue =
          "rgba(" +
          color.rgba.r +
          ", " +
          color.rgba.g +
          ", " +
          color.rgba.b +
          ", " +
          color.rgba.a +
          ")";
      }
    },
    documentClick(e) {
      var el = this.$refs.colorpicker,
        target = e.target;
      if (el !== target && !el.contains(target)) {
        this.hidePicker();
      }
      this.$emit("update-color", this.colorValue, this.fieldName);
    }
  },
  watch: {
    initColor: function(newVal, oldVal) {
      console.log(newVal);
      this.colorValue = newVal;
    }
  }
};
</script>

<style scoped>
div.color-picker-outer {
  width: 55px;
  height: 50px;
  display: inline-block;
  background-color: #EEE;
  position: relative;
}

.color-picker-inner {
  width: 30px;
  height: 30px;
  position: relative;
  top: 10px;
  left: 13px;
  display: inline-block;
}

.vc-chrome {
  position: absolute;
  top: 0px;
  left: 55px;
  z-index: 9;
}
</style>

The other template:

// TenantTemplateEdit.vue

<template>
  <v-layout row>
    <v-flex xs4>
      <v-text-field
        v-bind:field-name="fields.alertBackgroundColor"
        v-model="templateModel.alertBackgroundColor"
        placeholder="#4A4A4A"
      />
      Parent: {{templateModel.alertBackgroundColor}}
    </v-flex>
    <v-flex xs2>
      <ColorPickerButton
        v-bind:field-name="'alertBackgroundColor'"
        v-bind:init-color="templateModel.alertBackgroundColor"
        v-on:update-color="getUpdatedColor"
      ></ColorPickerButton>
    </v-flex>
    <!-- Alert Text Color -->
    <v-flex xs4>
      <v-text-field
        v-bind:field-name="fields.alertTextColor"
        v-model="templateModel.alertTextColor"
        placeholder="#4A4A4A"
      />
    </v-flex>
    <v-flex xs2>
      <ColorPickerButton
        v-bind:field-name="'alertTextColor'"
        v-bind:init-color="templateModel.alertTextColor"
        v-on:update-color="getUpdatedColor"
      ></ColorPickerButton>
    </v-flex>
  </v-layout>
</template>
<script>
import ColorPickerButton from "./ColorPickerButton";
export default {
  components: {
    ColorPickerButton
  },
  data() {
    return {
      fields: {
        alertBackgroundColor: "#00ff00",
        alertTextColor: "#ff0000"
      },
      templateModel: {
        alertBackgroundColor: "#00ff00",
        alertTextColor: "#ff0000"
      }
    };
  },
  methods: {
    getUpdatedColor(colorValue, fieldName) {
      this.fields[fieldName] = colorValue;
      this.templateModel[fieldName] = colorValue;
    }
  }
};
</script>

EDIT

I updated the sandbox (and the code here on SO) to work from the input field. I think it does everything it's supposed to do.