Home > other >  Show on click / hide on blur wrapper component
Show on click / hide on blur wrapper component

Time:11-11

I have several widgets that I'd like to toggle on click/blur/submit.

Let's take a simple example with an input (Vue 2 style)

Input.vue

<template>
  <input
    ref="input"
    :value="value"
    @input="input"
    @blur="input"
    @keyup.escape="close"
    @keyup.enter="input"
  />
</template>

<script>
export default {
  props: ['value'],
  methods: {
    input() {
      this.$emit("input", this.$refs.text.value);
      this.close();
    },
    close() {
      this.$emit("close");
    },
  }
}
</script>

ToggleWrapper.vue

<template>
  <div @click="open = true">
    <div v-if="open">
      <slot @close="open = false"></slot> <!-- Attempt to intercept the close event -->
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      open: false,
    }
  },
}
</script>

Final usage:

<ToggleWrapper>
  <Input v-model="myText" @submit="updateMyText" />
</ToggleWrapper>

So when I click on ToggleWrapper it appears, but if I close it, it doesn't disappear because it's not getting the close event.

Should I use scoped events ?
How can I intercept the close event by adding the less possible markup on the final usage ?

CodePudding user response:

I think it makes sense to use a scoped slot to do this. But you can also try this kind of solution.

Input.vue

<template>
        <input
                ref="input"
                :value="value"
                @input="input"
                @blur="input"
                @keyup.escape="close"
                @keyup.enter="input"
        />
</template>

<script>
export default {
        props: ['value'],
        methods: {
                input() {
                        this.$emit("input", this.$refs.text.value);
                        this.close();
                },
                close() {
                        this.$parent.$emit('close-toggle')
                },
        }
}
</script>

ToggleWrapper.vue

<template>
        <div @click="open = true">
                Click
                <div v-if="open">
                        <slot></slot> <!-- Attempt to intercept the close event -->
                </div>
        </div>
</template>

<script>
export default {
        data() {
                return {
                        open: false,
                }
        },
        created() {
                this.$on('close-toggle', function () {
                        this.open = false
                })
        }
}
</script>

CodePudding user response:

In a Vue3 style, I would use provide and inject (dependency injection). This solution leaves the final markup very light and you still have a lot of control, see it below :

Final usage :

<script setup>
  import { ref } from 'vue'
  import ToggleWrapper from './ToggleWrapper.vue'
  import Input from './Input.vue'

  const myText = ref('hi')
  
  const updateMyText = ($event) => {
    myText.value = $event
  }
</script>

<template>
  <ToggleWrapper>
    <Input :value="myText" @submit="updateMyText" />
  </ToggleWrapper>
  <p>value : {{myText}}</p>
</template>

ToggleWrapper.vue

<template>
  <div @click="open = true">
    <div v-if="open">
      <slot></slot> 
    </div>
    <span v-else>Open</span>
  </div>
</template>

<script setup>
    import { provide, inject, ref } from 'vue'
    const open = ref(false)
  
  provide('methods', {
    close: () => open.value = false
  })
</script>

Input.vue

<template>
  <input
    :value="value"
    @input="input"
    @blur="close"
    @keyup.escape="close"
    @keyup.enter="submit"
  />
</template>

<script setup>
  import { inject, ref } from 'vue'
  const props = defineProps(['value'])
  const emit = defineEmits(['close', 'input', 'submit'])
  const methods = inject('methods')
  
  const value = ref(props.value)
  
  const input = ($event) => {
    value.value = $event.target.value
    emit("input", $event.target.value);
  }
  
  
  const close = () => {
    methods.close()
    emit('close')
  }
  
  const submit = () => {
    emit('submit', value.value)
    close()
  }
</script>

See it working here

  • Related