Vue: Props to slots

Created on 22 Jan 2017  Â·  7Comments  Â·  Source: vuejs/vue

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>

feature request

Most helpful comment

FYI: this can now be accomplished using provide / inject

All 7 comments

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

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.

@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)

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.

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>

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);
  },
};

FYI: this can now be accomplished using provide / inject

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gkiely picture gkiely  Â·  3Comments

lmnsg picture lmnsg  Â·  3Comments

aviggngyv picture aviggngyv  Â·  3Comments

franciscolourenco picture franciscolourenco  Â·  3Comments

bdedardel picture bdedardel  Â·  3Comments