Get list of slot names from Vue template - javascript

I'm creating an interactive style guide for some custom components we've created at my job. It goes through the list of globally registered components and then displays their props. The user can then edit the prop values and see how it affects the rendered component.
Right now I need to figure out how to allow interaction with slots for certain components we have. For example, we have our own button component that looks something like this:
<template>
<button :class="customClasses">
<slot></slot>
</button>
</template>
I have a componentPreviewRenderer component that shows a rendering of our custom components. It looks something like this:
export default {
props: {
component: String, //The name of the global component to render
props: Object //List of component props
},
render (createElement): {
return createElement(
this.component,
{
props: this.props
}
);
}
}
I need to (1) find out if the component's template I'm rendering has slots and (2) get a list of the slot names so I can pass it into the createElement() function and let the user edit the slot values. For example, for the button component they should be able to edit the "default" slot which controls the text appearing on the button.
I've already looked at this article but I'd like to be able to get the slot names directly from the Vue component I'm rendering instead of having to parse through the .vue file. I tried things like Vue.component(this.component).$slots but $slots is undefined. Does anyone know how I would get the slot names for the component being rendered?

Related

Vue how to access props of parent component - the importer not a wrapper

Overall goal: from a child Vue component, get access to the component that it is imported and used within - which is not always the same as $parent. This should be done by only making changes within the child component (if possible).
We have PinButton Vue component meant to add a "pinning" functionality to other components. This component is used within many other components and we want to be able to access the parent props so they can be saved and rendered on a different page of "pinned content" by passing those props back into the parent component.
Note: I know this would be possible by manually passing the parent props down into the component (<pin-button :parent-props="$props" />), but we're trying to avoid having to do this every time the component is used.
A minimal reproduction of this with a single parent and child component will show that you can access parent props using $parent.$props. However, when the child component is nested as slot content of some other component within the parent, then the child will get the props of the wrapper component - not the component in which it is imported and actually used.
Sandbox reproduction - I want to get the props for ParentComponent from within ChildComponent. The expected value is shown by passing the props along (what I'm trying to avoid) and the actual value is the props of the SlotWrapper component, which doesn't import ChildComponent so I wouldn't consider it the true parent, but it is the direct parent element in the <template>
Update:
Seems like the suggested solution for "arbitrarily deep" access is provide/inject, but this would still seem to require changing all components that use the <pin-button />
To answer your question directly, you can access ParentComponent from ChildComponent via the "context" the component is rendered within:
// ChildComponent.vue
computed: {
expectedProps() {
return this.$vnode.context.$props
}
}
But this might be an "XY" kind of problem; I'm sure there's a better design solution for what you're trying to achieve.

How to access and render a component in an array dynamically, based on a selected index?

I have an array of objects that have a key Component, which points to its respective component.
I'm able to map over the array and display all components like this,
dict.map(({ Component }) => <Component />
And that works, however I'm trying to conditionally render specific components. In my case, I'm trying to access the Component property at index 1, however it doesn't seem to work. The way I'm trying to access it is something like, dict[displayComponentAtIndex].Component, where displayComponentAtIndex is part of the state.
How can I render the component the way I want to?
You should change your dict.js to
export default [{ Component: (<Component1></Component1>) }, { Component: <Component2></Component2> }];
As you need component and not object to render.

Is there a way to call a child-component's function from another child?

I'm contributing to an open-sourced React/Redux application built using ES6 JavaScript. I'm fairly new to React/Redux so I'm having some trouble.
I have a parent class that's rendering two different React components. The first component contains some input fields regarding events (called NewShift). The second component is a calendar that renders these events (called Index).
Once a user fills out the input fields and presses a button in the first component, I want to be able to re-render the calendar in the second component. If the re-render function is in the calendar component, how do I call it from the input fields component (both children).
React re-renders components whenever component state is changed. Should you be using just React, this would mean passing changed values up to the parent component's state to force a re-render.
However, Redux makes this easier on you, as it's 'Store' functions as a global state. You can force a re-render by changing appropriate variables within the store.
Given your situation: the button should get this within it's onClick attribute:
onClick={() => dispatchNewCalendarInfo(payload)}.
dispatchNewCalenderInfo should also be imported by the component:
import { dispatchNewCalendarInfo } from './redux/path/to/post/actions.js'; and connected to it: export default connect(()=>({}), { dispatchNewCalendarInfo })(Component);. Note, you need to also import connect from 'react-redux' for this.
And, of course, dispatchNewCalendarInfo should be present in the actions.js path, and accepted by the store reducer. This dispatch should alter information that the calendar is connected to, which will force it to update and re-paint.
If you're not using Redux there's another path you can take. Instead of having the function that takes input be in NewShift, have new shift receive the function as a prop from the parent.
So in your NewShift component you would have something like onClick={this.props.submitCalanderInfo()}
The submitCalanderInfo function would be part of the parent component. You would probably want this new info to be saved into the state of the parent component, and then then use that state to update the props on the calendar or Index component. So Index might look something like this:
<Index shiftData={this.state.shiftData} />

Vue.js component within component, manipulate parent data

ive seen afew answers that sort of answer my question but not fully, so let me explain what I want to do.
We use a global #app div within the layout of our website, which is a Laravel project. So all pages will be the same main Vue instance, due to this i'm separating key functionality into components.
So, the first example is just a simple Tab component, this either separates any children into tabs, or accepts some data which the single child component then renders.
So below i'm injecting some data from another component, this ajax component literally just does an ajax call, and makes the data available within it's slot.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
#{{ row['example' }}
</div>
</div>
</tabs>
</div>
</ajax>
Now this is all well and good, to a point, but this falls down with the below code. This contains a component which will allow the used to drag and drop elements, it re-arranges them by literally moving the data around and letting Vue handle the DOM changes.
This will of course work fine within it's own data which you have injected in, but when you change the data within the component below this then clears this child component.
<ajax endpoint="/api/website/{{ $website->id }}/locations/{{ $location->slug }}/get-addresses">
<div>
<tabs :injected="data">
<div>
<div v-for="row in data">
<draggable :injected="row">
<div>
<div v-for="item">
#{{ item }}
</div>
</div>
</draggable>
</div>
</div>
</tabs>
</div>
</ajax>
I need to find a way to make any changes to this data apply to the parent data, rather than the data passed into the child components.
What is the best practice to do this!?
Edit 1
Basically, I need any child component's manipulate the data within the ajax component. The children within ajax could change, or there could be more, so I just need them all to do this without knowing what order or where they are.
It is hard to come up with specifics on this one, but I am going to try to put you in the right direction. There are three ways to share data between components.
1) Passing down data via props, emitting data up via custom events
The passing down of data via props is a one-way street between the parent and child components. Rerendering the parent component will also re-render the child and data will be reset to the original state. See VueJS: Change data within child component and update parent's data.
2) Using a global event-bus
Here you create an event bus and use this to emit the data to different components. All components can subscribe to updates from the event bus and update their local state accordingly. You initiate an event bus like this:
import Vue from 'vue';
export const EventBus = new Vue();
You send events like this:
import { EventBus } from './eventbus.js'
EventBus.$emit('myAwsomeEvent', payload)
And you subscribe to events like this:
import { EventBus } from './eventbus.js'
EventBus.$on('myAwsomeEvent', () => {
console.log('event received)
})
You still need to manage state in the components individually. This is a good start with an Event bus: https://alligator.io/vuejs/global-event-bus/
3) Using Vuex
Using Vuex extracts the component state into the Vuex store. Here you can store global state and mutate this state by committing mutations. You can even do this asynchonously by using actions. I think this is what you need, because your global state is external to any components you might use.
export const state = () => ({
resultOfAjaxCall: {}
})
export const mutations = {
updateAjax (state, payload) {
state.resultOfAjaxCall = payload
}
}
export const actions= {
callAjax ({commit}) {
const ajax = awaitAjax
commit('updateAjax', ajax)
}
}
Using vuex you keep your ajax results separated from your components structure. You can then populate your state with the ajax results and mutate the state from your individual components. This way, it doesn't matter whether you recall ajax, or destroy components since the state will always be there. I think this is what you need. More info on Vuex here: https://vuex.vuejs.org/

VueJS XHR inside reusable component

Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.

Categories

Resources