Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): support passing down scopedSlots with v-bind #7765

Closed
wants to merge 2 commits into from

Conversation

@yyx990803
Copy link
Member

commented Mar 7, 2018

Allow passing down scoped slots using v-bind:

<child v-bind="{ scopedSlots: $scopedSlots }">
</child>

close #7178, close #7351

@yyx990803

This comment has been minimized.

Copy link
Member Author

commented Mar 8, 2018

Btw, this is still open to discussion, since the syntax isn't entirely consistent.

You can do v-bind="{ ref: 'foo', slot: 'bar' }" which makes sense because ref and slot can be bound individually as attributes, but scopedSlots is not.

Internally ref and slot are treated specially because they are "reserved" attributes and thus merged into the VNode's data as a root-level property.

So alternatively, we can expose a special convention for v-bind: if a property starts with $, it will always be merged as a root-level property into the VNode's data.

This then allows the usage:

<child v-bind="{ $scopedSlots } />

In addition, if you know the underlying VNode data interface, you can also do low-level stuff without dropping into render functions:

<child v-bind="{
  $on: {
    someEvent: someMethod
  },
  $nativeOn: {
    click: someMethod
  }
}"/>
@ktsn

This comment has been minimized.

Copy link
Member

commented Mar 14, 2018

What about adding a dedicated directive for merging vnode data? Since v-bind is used to be used for passing parent component data to child component attribute/props, it is a bit weird if it can modify underlying vnode data, IMHO.

If we have such directive we can simply write the vnode data as the same shape as in render function (named it v-vnode as a placeholder 😉 ):

<child v-vnode="{
  scopedSlots: $scopedSlots,
  on: {
    someEvent: someMethod
  },
  nativeOn: {
    click: someMethod
  }
}"/>

We may write directive argument like v-bind:

<child v-vnode:scoped-slots="$scopedSlots" />

This also has a benefit that we may able to write template type checker for that as we can simply utilize Vue's typescript typings. (FYI: The current WIP implementation of template type checker is here)

@dennythecoder

This comment has been minimized.

Copy link

commented Mar 14, 2018

The specific directive proposed by @ktsn seems a little awkward to me, but I think it would make sense to separate the behavior to improve readability.

@bigianb

This comment has been minimized.

Copy link

commented May 22, 2018

What's the status of this?

@kellym

This comment has been minimized.

Copy link

commented Jun 7, 2018

So is there a consensus on using a different tag name or not? If so, what should it be called?

Otherwise, this is clearly a hangup for a lot of people using Vue (googling led me to 5-6 different issues on the same topic) and it'd be nice to either give this a thumbs up or have some direction in what needs to be done to get this shipped.

As an added note, the current "solution" of using a render function is pretty silly, especially when templates get complex and/or are already written and are getting new components added to them.

@ClickerMonkey

This comment has been minimized.

Copy link

commented Jun 13, 2018

I wouldn't recommend this solution, but I needed this functionality now - so I implemented @yyx990803 's change as a plugin:

var BindScopedSlotsPlugin = {
  install: function(Vue, options) {
    Vue.prototype._b = function bindObjectProps (
      data,
      tag,
      value,
      asProp,
      isSync
    ) {
      if (value) {
        if (value === null || typeof value !== 'object') {
          warn(
            'v-bind without argument expects an Object or Array value',
            this
          );
        } else {
          if (Array.isArray(value)) {
            var res = {};
            for (var i = 0; i < value.length; i++) {
              if (value[i]) {
                Vue.util.extend(res, value[i]);
              }
            }
            value = res;
          }
          var hash;
          var loop = function ( key ) {
            if (
              key === 'class' ||
              key === 'style' ||
              key === 'scopedSlots' || // new code
              Vue.config.isReservedAttr(key)
            ) {
              hash = data;
            } else {
              var type = data.attrs && data.attrs.type;
              hash = asProp || Vue.config.mustUseProp(tag, type, key)
                ? data.domProps || (data.domProps = {})
                : data.attrs || (data.attrs = {});
            }
            if (!(key in hash)) {
              hash[key] = value[key];

              if (isSync) {
                var on = data.on || (data.on = {});
                on[("update:" + key)] = function ($event) {
                  value[key] = $event;
                };
              }
            }
          };

          for (var key in value) loop( key );
        }
      }
      return data;
    };
  }
};

Vue.use( BindScopedSlotsPlugin );

The syntax is as mentioned initially: v-bind="{ scopedSlots: $scopedSlots }"

@joernroeder

This comment has been minimized.

Copy link

commented Jun 16, 2018

@ClickerMonkey thanks for building the plugin! there is a tiny typo: if (arr[i]) needs to be replaced with if (value[i])

@ClickerMonkey

This comment has been minimized.

Copy link

commented Jul 3, 2018

Thanks! I have a smaller simpler version:

    Vue.prototype._b = (function (bind) {
      return function(data, tag, value, asProp, isSync) {
        if (value && value.$scopedSlots) {
          data.scopedSlots = value.$scopedSlots;
          delete value.$scopedSlots;
        }
        return bind.apply(this, arguments);
      };
    })(Vue.prototype._b);

And you just need to add v-bind="{$scopedSlots}" to the component.

@Owumaro Owumaro referenced this pull request Sep 1, 2018
@RedShift1

This comment has been minimized.

Copy link

commented Oct 20, 2018

Check out this solution too: https://stackoverflow.com/a/52823029/2050460

test: add slot test case (#8344)
Added another test to ensure default slot contents are preserved as well as slots passed to children.

@yyx990803 yyx990803 added this to Todo in 2.6 Dec 5, 2018

@yyx990803 yyx990803 moved this from Todo to Backlog in 2.6 Dec 6, 2018

@ggirodda

This comment has been minimized.

Copy link

commented Dec 10, 2018

I edited the @ClickerMonkey script to merge the parent and the current $scopedSlots, maybe it can be useful for someone

import Vue from "vue"

Vue.prototype._b = (function(bind) {
  return function(data, tag, value, asProp, isSync) {
    if (value && value.$scopedSlots) {
      data.scopedSlots = {
        ...data.scopedSlots,
        ...value.$scopedSlots
      }
      delete value.$scopedSlots
    }
    return bind.apply(this, arguments)
  }
})(Vue.prototype._b)

@yyx990803 yyx990803 moved this from Backlog to Todo in 2.6 Dec 26, 2018

@yyx990803 yyx990803 moved this from Todo to In progress in 2.6 Dec 26, 2018

@yyx990803

This comment has been minimized.

Copy link
Member Author

commented Dec 28, 2018

So I don't think we will land this, because:

  1. I'm not entirely satisfied with the consistency of the change. Feels like a dirty special case.

  2. There is already a way to make it work in userland.

  3. This is probably the most important reason: we want to avoid landing changes in v2 that would then have to be broken in v3. In v3, slots and scoped slots are being unified under $slots, and slots itself is becoming a reserved prop like key and ref. This means in v3 you can just do:

<foo :slots="$slots"></foo>

Moreover, we have other changes in v3 that makes passing things down to child components easier (RFC in early 2019).

@yyx990803 yyx990803 closed this Dec 28, 2018

2.6 automation moved this from In progress to Done Dec 28, 2018

@ClickerMonkey

This comment has been minimized.

Copy link

commented Dec 29, 2018

Glad to hear about v3. Thanks Evan for everything!

serebrov added a commit to serebrov/emoji-mart-vue that referenced this pull request Feb 28, 2019

Use high level scoped slots inside the NimblePicker component.
Started with the granular slots (inside the Search / Preview
components), but it appears that fallback content is not rendered
in the nested slots in vue 2.5.
It works in 2.6 and should be further improved in 3.0,
see vuejs/vue#7765 (comment)

KaelWD added a commit to vuetifyjs/vuetify that referenced this pull request Jun 10, 2019

feat(VMenu): allow v-bind="data" from scoped slot
adds a shim for bindObjectProps as in vuejs/vue#7765
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
9 participants
You can’t perform that action at this time.