1

As part of becoming a better Vue programmer, I am trying to implement a popup similar to Popper with a clean and Vueish architecture. Here is a simple schematic that I came up with:

Heading

So basically there is a target component, which is the reference for the popup's position. The popup can be positioned above, below, right and left of the target, therefore I will need to have access to the target element in my popup. Also, the target can be an arbitrary component. It can be a simple button or span, but also something much more complex.

Then there is the popup itself, which will be put into a modal at the end of the body, It contains the actual content. The content again can be an arbitrary component.

I have a working implementation of the popup, but the basic structure seems to be far from perfect. I am using two slots, one for the target element and one for the content.

Here is what I have come up with so far for the template:

<template>
  <div ref="targetContainer">
    <slot name="target"></slot>
  </div>
  <teleport to="body">
    <div v-show="show" class="modal" ref="modal">
      <div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
        <slot name="content"></slot>
      </div>
    </div>
  </teleport>
</template>

There are several issues with this that I am not really happy with.

  1. Using the popup is not very simple

When using this popup in another component, two <template> tags are rquired. This is ungly and not very intuitive. A very simple use case looks like this:

 <modal :show="showPopup" @close="showPopup=false">
    <template v-slot:target>
      <button @click="showPopup=true"></button>
    </template>
    <template v-slot:content>
      <div>Hello World!</div>
    </template>
  </modal>
  1. The target is wrapped in another <div>

This is done to get access to the target element, that I need for the layout. In mounted() I am referencing the target element like this:

let targetElement = this.$refs.targetContainer.children[0];

Is this really the best way to do this? I would like to get rid of the wrapping <div> element, which just asks for unintended side effects.

The best solution would be to get rid of one slot and somehow reference the target element in another way because I only need its layout information, it does not have to be rendered inside the popover component.

Can someone point me in the right direction?

2
  • 1
    What you said last is true, you can pass your target's #id via prop on your popper component, i.e. <modal target="myTarget" /> then in your element access it as document.getElementById("#" + this.target). I must say creating a popper from scratch will be very challenging, good luck :D Commented Dec 31, 2020 at 20:04
  • Thanks, this lead me to an even better approach. I can just pass the ref of a component in the parent component as targetRef prop. Then I can access the target element through this.$parent.$refs[this.targetRef] This completely solves my issues and I can even avoid using ids! Commented Jan 1, 2021 at 8:31

1 Answer 1

0

Here is my solution, which was inspired by a comment on my question and which I think is worth sharing.

Instead of putting the target element into a slot, I am now passing its ref as a prop, which makes things much cleaner.

The popover component's template now looks like this.

<template>
  <teleport to="body">
    <div v-show="show" class="modal" ref="modal">
      <div ref="popover" class="popover" :style="{top: popoverTop + 'px', left: popoverLeft + 'px'}">
        <slot ref="content"></slot>
      </div>
    </div>
  </teleport>
</template>

I has a targetRefprop, so the component can be simply used like this:

<div ref="myTargetElement" @click="isPopupVisible=true">
</div>
<modal :show="isPopupVisible" @close="isPopupVisible=false" targetRef="myTargetElement">
    <!-- popup content goes here -->
</modal>

And after mounting I can access the target element like this:

let targetElement = this.$parent.$refs[this.targetRef];

I like this solution a lot. However, ideas, advice or words of caution are still highly welcome.

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

7 Comments

It's frustrating when you guys insert code with no highlighting. Hard to read :(
@Chris This can certainly be said in a nicer way, but you are correct. Just added highlighting.
I find @Chris's comment on point and very informative. If you think about it, Chris was nice enough to let you know the lack of highlighting can be frustrating. Which is something you clearly had no idea about, otherwise you would have used highlighting in the first place. Could you point out what exactly about his remark you found not nice enough and give an example of how he should have formulated his nice intent?
@tao most people have emotional and rational responses. The fact that the comment is informative (rational), and has nothing to do with it not being nice (emotional). "Frustrating" is negative emotional language. ":(" likewise. "You guys" implies that there are two categories of people, one which includes the OP which are below the other that includes the commenter. That"s a lot of negative cues in a one-liner. Possible alternative: "Please add highlighting to your code to make it more readable". Conveys the same information without the same emotional weight.
I am absolutely amazed by your deduction abilities. I would have never guessed "you guys" refers to two lots of people and that one lot is above or below the other one... Above... how? What instrument did you use to determine which group is above? Another fascinating detail is that you were able to determine that the commenter is part of a group. Which part of "you guys" revealed this aspect? Also, do you think this group could be a group of one? Your process is so magical. Thanks for taking the time to enlighten me.
However, I have to admit that I don't agree with you. To me, "Please add highlighting to your code to make it more readable" has a different meaning and message than (roughly) "lack of highlighting can be frustrating". And triggers different responses from me. The first one has a lower ability to convince me to take action. Whereas the second one, by underlying the negative emotional response, is more likely to make me correct the issue.
I agree, lack of highlighting can be frustrating is also fine. I guess it's the you guys that triggered the OP, me, and possibly others. I'm not sure to what extend you're being ironic, but you guys is plural hence it designates a group which doesn't include the writer (otherwise he would say we), when in reality we want to address the op and not make assumption about which group he belongs to. A bit like people like you can be offensive (depending on the context). Anyways you're right my explanations are clumsy, I guess people's perception vary and it's ok to disagree. Take care :)

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.