Skip to content

Props to slots #4766

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

Closed
dorseth opened this issue Jan 22, 2017 · 7 comments
Closed

Props to slots #4766

dorseth opened this issue Jan 22, 2017 · 7 comments

Comments

@dorseth
Copy link

dorseth commented Jan 22, 2017

Hi, I wonder if it would be good to have something like this. (maybe it's already achievable via current Vue functional, but I didn't find it)
Imagine I have a 12 rows Grid component that has props of xs, sm,md and lg, has a single slot and
looks like this:

<Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>

What I want is to have something like a modifier for the wrapped components, so it would be possible to do something like this

<ElementsList xs='1' sm='2' lg='3' passToSlot='true'>
     <Grid>Content</Grid>
     <Grid>Content</Grid>
     <Grid>Content</Grid>
</ElementsList>

The ElementsList takes the props, transforms them and passes to Grid accordingly.

So after the component passes props to slot I have something like this:

<ElementsList xs='1' sm='2' lg='3' passToSlot='true'>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>
@posva
Copy link
Member

posva commented Jan 22, 2017

You could write this by yourself:

<ElementsList xs='1' sm='2' lg='3' passToSlot='true'>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>
 <Grid xs='12' sm='6' lg='3'>
   slot content here
</Grid>

Since all the values are directly available there

If you really want to have the same values while updating only at one place, you should be able to achieve this with scoped slots (https://vuejs.org/v2/guide/components.html#Scoped-Slots) or by simply referring to the $parent variable only if Grid is meant to be used inside an ElementList
Here's an example for reference: http://jsfiddle.net/posva/wg13cfh0/

As you pointed out, it should be doable with a render function too but I didn't dig into it.

Since the behaviour you're asking for is an implicit version of something that can already be achieved, I doubt it makes sense to implement it

@dorseth
Copy link
Author

dorseth commented Jan 22, 2017

Yep, it looks like a solution for now, though it would be nice to have something that looks simple on the front, the example you have kindly provided:

  <element-list :xs="xs" :sm="sm">
    <template scope="props">
      <grid :xs="props.xs" :sm="props.sm"></grid>
      <grid :xs="props.xs" :sm="props.sm"></grid>
    </template>
  </element-list> 

The 'simple' syntax that would be (maybe), more readable

  <element-list :xs="xs" :sm="sm" passProps='true'>
      <grid></grid>
      <grid></grid>
  </element-list> 

I actually don't know how complex it is to 'find' the children
elements with the according props in the slot and pass them the parent-defined props,
so if it is not that hard - that would be really awesome.

@posva
Copy link
Member

posva commented Jan 22, 2017

@dorseth we prefer the explicit way. You can always rely on the $parent for an implicit way but it means your component depends on its parent (not reusable)

@yyx990803
Copy link
Member

You can achieve what you want by using render function, and iterate through the VNodes in this.$slots.default, find nodes with vnode.tag === 'grid' add appropriate props to them. This requires you to be a bit familiar with the VNode structure, but there isn't anything extra that Vue needs to expose to allow that.

@dorseth
Copy link
Author

dorseth commented Jan 25, 2017

Sorry to bump it up, here is a quick and a very dirty solution without the render function of what I've been looking. Maybe it will help someone to write their own cleaner lib for that :)

<template>
    <Grid xs='12' :mod='wrapper'>
        <Grid v-for='n in fheight' :extra='extra' >
            <Grid :xs='fxs' :md='fmd' :lg='flg' v-for='a in fwidth' :extra='childExtra'  :mod='same'>
                 <slot :name='"cell_"+n + "_" + a'>
               
                 </slot>
            </Grid>
        </Grid>
   </Grid> 
</template>
<script>
import match from 'jquery-match-height'
import Grid from './grid.vue'
export default {
    props: {
        width: {
            default: '1'
        },
        height: {
            default: '1'
        },
        xs: {
            type: String
        },
        md: {
            type: String
        },
        lg: {
            type: String
        },
        elems: {
            default: '1'
        },
        extra: {
            type: String,
            default: ''
        },
        childExtra:{
        	type: String,
            default: ''
        },
        same: {
            type: String,
            default: ''
        },
        wrapper: {
            type: String,
            default: ''
        }
    },
    components: {
        Grid
    },
    created() {
        if (this.xs !== '5', '7', '8', '9', '10') {
            this.fxs = (12 / parseInt(this.xs)) + '';
        }
        if (this.md !== '5', '7', '8', '9', '10') {
            this.fmd = (12 / parseInt(this.md)) + '';
        }
        if (this.lg !== '5', '7', '8', '9', '10') {
            this.flg = (12 / parseInt(this.lg)) + '';
        }

        if(!this.md){
        	this.fmd = this.fxs;
        }

        if(!this.lg){
        	this.flg = this.fxs;
        }
    },
    mounted(){
    	if (this.same.length > 1) {	
        	$('.'+this.same).matchHeight({byRow: false});
        }	
    },


    data() {
        return {
            fwidth:  parseInt(this.width),
            fheight: parseInt(this.height),
        }
    }
}
</script>

@Stupidism
Copy link

I accomplished this throught a customized context:

// Form.js
export default {
  props: {
    showRequiredColumn: Boolean,
    noLeftPadding: Boolean,
  },
  expose() {
    return {
      showRequiredColumn: this.showRequiredColumn,
      noLeftPadding: this.noLeftPadding,
    };
  },
  mixins: [
    exposer,
  ],
}
// Field.js
<template>
  <label class="text-field" :class="{'no-left-padding': noLeftPadding}">
    <span v-if="showRequiredColumn" class="required-marker">*</span>
    <input>
  </label>
</template>
<script>
import { consumer } from '@/mixins/context';

export default {
  consume() {
    return {
      showRequiredColumn: 'showRequiredColumn',
      noLeftPadding: 'noLeftPadding',
    };
  },
};
</script>
// mixins/context.js

// Copied from HerringtonDarkholme/vue-advanced-programming
// Renamed and transplanted for mpvue
// https://github.com/HerringtonDarkholme/vue-advanced-programming/blob/master/context/index.js
import _ from 'lodash';
import _get from 'lodash/fp/get';

const getOptions = _get('$vnode.componentOptions.Ctor.options');

const $consume = (vm, { token, all }) => {
  let parent = vm;
  const ret = [];
  while (parent) {
    const $context = parent.$context;
    if ($context && _.has($context, token)) {
      const value = _.get($context, token);
      if (!all) return value;
      ret.push(value);
    }
    parent = parent.$parent;
  }
  return all ? ret : undefined;
};

const exposeContext = (vm) => {
  const options = getOptions(vm);
  if (!options.expose) return;
  options.computed.$context = () => options.expose.call(vm, vm);
};

const consumeContext = (vm) => {
  const options = getOptions(vm);
  if (!options.consume) return;
  const consumeMap = _.isFunction(options.consume) ? options.consume() : options.consume;

  options.computed = options.computed || {};
  _.forEach(consumeMap, (token, key) => {
    options.computed[key] = () => $consume(vm, { token });
  });
};

export const exposer = {
  beforeCreate() {
    exposeContext(this);
  },
  beforeUpdate() {
    exposeContext(this);
  },
};

export const consumer = {
  beforeCreate() {
    consumeContext(this);
  },
  beforeUpdate() {
    consumeContext(this);
  },
};

@indirectlylit
Copy link

FYI: this can now be accomplished using provide / inject

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

No branches or pull requests

5 participants