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: add scoped slots option #507

Merged
merged 22 commits into from Apr 14, 2018
Merged

feat: add scoped slots option #507

merged 22 commits into from Apr 14, 2018

Conversation

38elements
Copy link
Contributor

@38elements 38elements commented Apr 1, 2018

This is related to #156 .
This adds scopedSlots option by replacing the renderSlot method.
It is supported at vue@2.5+.
It does not support template tag.

@38elements 38elements changed the title feat: add scoped slots option [WIP]feat: add scoped slots option Apr 1, 2018
@38elements 38elements mentioned this pull request Apr 1, 2018
@38elements 38elements changed the title [WIP]feat: add scoped slots option feat: add scoped slots option Apr 2, 2018
Copy link
Member

@eddyerburgh eddyerburgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I don't like monkey patching the _t helper but we can look at changing that in the future. For now we can go ahead with this. I've added some comments

it('mounts component scoped slots', () => {
const wrapper = mountingMethod(ComponentWithScopedSlots, {
scopedSlots: {
'item': '<p slot="item" slot-scope="props">{{props.index}},{{props.text}}</p>'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to include slot=item in the markup? We already identify it as the item slot in the scopedSlots object

@@ -113,6 +113,7 @@ interface MountOptions<V extends Vue> extends ComponentOptions<V> {
localVue?: typeof Vue
mocks?: object
slots?: Slots
scopedSlots?: string
stubs?: Stubs,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an object. Can you add a test for this in types/test?


describeWithShallowAndMount('scopedSlots', (mountingMethod) => {
if (vueVersion >= 2.5) {
it('mounts component scoped slots', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an itIf helper function in resouces/utils. Could you use that instead of if else statement

@38elements
Copy link
Contributor Author

Thank you for reviewing.
I changed this.

@@ -113,6 +113,7 @@ interface MountOptions<V extends Vue> extends ComponentOptions<V> {
localVue?: typeof Vue
mocks?: object
slots?: Slots
scopedSlots?: object
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can specify more detailed type Record<string, string>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing out.
I changed it.


Provide an object of scoped slots contents to the component. The key corresponds to the slot name. The value can be a template string.

There is two limitations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is two limitations > There are two limitations.


There is two limitations.

* This supports vue@2.5+.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is only supported in vue@2.5+


* This supports vue@2.5+.

* You can not set a `template` tag to top of `scopedSlots` option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot use <template> tag as the root element in thescopedSlots option.

Object.keys(scopedSlots).forEach((key) => {
const template = scopedSlots[key].trim()
if (template.substr(0, 9) === '<template') {
throwError('scopedSlots option does not support template tag.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the error message read:

the scopedSlots option does not support a tag as the root element.

vm._renderProxy._t = function (name, feedback, props, bindObject) {
const scopedSlotFn = vm.$_vueTestUtils_scopedSlots[name]
if (scopedSlotFn) {
props = { ...bindObject, ...props }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to set vm._renderProxy.props?

Copy link
Contributor Author

@38elements 38elements Apr 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scopedSlotFn needs vm._renderProxy.props.
If vm._renderProxy.prop is not setted, the render function can not use props.

with(this){return _c('p',{},[_v(_s(props.index)+","+_s(props.text))])}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug.
When slot-scope is other than props, this is not work.

@38elements
Copy link
Contributor Author

38elements commented Apr 7, 2018

Thank you for pointing out.
I changed them.

@38elements
Copy link
Contributor Author

There is a bug.
When slot-scope is other than props, this is not work.
I will fix it.

proxy[key] = vm._renderProxy[key]
}
})
proxy[slotScope] = props
Copy link
Contributor Author

@38elements 38elements Apr 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since when slot name and slot scope is same, vm._renderProxy.[<slot name>] is changed in _t function, proxy is created.

<ul>
  <slot name="foo"
    v-for="(item, index) in foo"
    :text="item.text"
    :index="index">
  </slot>
</ul>
function render() {
  with(this) {
    return _c('ul', [_l((foo), function (item, index) {
      return _t("foo", null, {
        text: item.text,
        index: index
      })
    })], 2)
  }
}

scopedSlotFn

function anonymous() {
  with(this){return _c('p',{},[_v(_s(foo.index)+","+_s(foo.text))])}
}

@Mattchewone
Copy link

Looking forward to this being merged.

Thanks for this.

if (key[0] === '_') {
proxy[key] = vm._renderProxy[key]
}
const helpers = ['_c', '_o', '_n', '_s', '_l', '_t', '_q', '_i', '_m', '_f', '_k', '_b', '_v', '_e', '_u', '_g']
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.keys(vm._renderProxy).concat(Object.keys(Vue.prototype)).forEach((key) => { may be better,
since it is abstruct.

Copy link
Member

@eddyerburgh eddyerburgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, this adds some much needed support 👍 . I've added a couple of comments

throwError('the scopedSlots option does not support a template tag as the root element.')
}
const domParser = new window.DOMParser()
const document = domParser.parseFromString(template, 'text/html')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rename this variable to avoid collisions with window.document?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it.

helpers.forEach((key) => {
proxy[key] = vm._renderProxy[key]
})
proxy[slotScope] = props
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Vue throw an error if there are two conflicting slot scopes? If not, we should throw an error here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is impossible to set a word that starts with "_" in Vue.

[Vue warn]: Property or method "_foo" is not defined on the instance but referenced during render. 
Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. 
See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant if you mounted with this option:

{
scopedSlots: {
  foo: '<div />',
  foo: '<input />'
}
}

The second scopedSlot would overwrite the first scopedSlots in _vueTestUtils_slotScopes. Although thinking about it, this is probably fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible to register the same key.

{ foo: 1, foo: 2 }
// => { foo: 2 }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️ Of course ;p

* You can not use `<template>` tag as the root element in the `scopedSlots` option.

* This does not support PhantomJS.
Please use [Puppeteer](https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change to:

You can use Puppeteer as an alternative.

@38elements
Copy link
Contributor Author

Thank you for pointing out.
I changed it.

@@ -61,7 +61,7 @@ export default function createInstance (

if (options.scopedSlots) {
if (window.navigator.userAgent.match(/PhantomJS/i)) {
throwError('the scopedSlots option does not support strings in PhantomJS. Please use Puppeteer, or pass a component.')
throwError('the scopedSlots option does not support in PhantomJS. Please use Puppeteer, or pass a component.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the scopedSlots option does not support in PhantomJS. Please use Puppeteer, or pass a component.
to

the scopedSlots option does not support PhantomJS. Please use Puppeteer, or pass a component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it.

Copy link
Member

@eddyerburgh eddyerburgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job 👍

@eddyerburgh eddyerburgh merged commit e6a54b2 into vuejs:dev Apr 14, 2018
@38elements
Copy link
Contributor Author

@eddyerburgh
Thank you for reviewing.

@38elements 38elements deleted the scopedslots branch April 15, 2018 04:26
@Haroenv
Copy link
Contributor

Haroenv commented Oct 22, 2018

@38elements, what was the reason <template> in scoped slot was not implemented?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants