Pass a component inside a vue-grid-layout item - vue.js

Please help me understand how to pass an imported single file vue component inside a vue-grid-layout item.
I know how to pass simple html, like here:
https://jsfiddle.net/gmsa/jw2mmmpq/1/
But I need to insert other components with buttons and axios calls, etc
HTML:
<template>
<div class="hello">
<grid-layout
:layout="layout"
:col-num="12"
:row-height="30"
:is-draggable="true"
:is-resizable="true"
:vertical-compact="true"
:margin="[10, 10]"
:use-css-transforms="true"
>
<grid-item v-for="item in layout"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i">
<div v-html="item.c">
</div>
<Test></Test>
</grid-item>
</grid-layout>
</div>
</template>
JS:
import VueGridLayout from 'vue-grid-layout';
import Test from './test.vue';
import Test from './test2.vue';
let a = `This needs to be another component, like Test`;
let b = `This needs to be another component, like Test2`;
var testLayout = [
{"x":0,"y":0,"w":2,"h":2,"i":"0", "c": a},
{"x":2,"y":0,"w":2,"h":4,"i":"1", "c": b}
];
export default {
name: 'HelloWorld',
props: {
msg: String
},
components: {
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem,
Test,
Test2
},
data: function (){
return {
layout: testLayout
}
}
}
Cannot find any ideas here:
https://github.com/jbaysolutions/vue-grid-layout

You want dynamic components, which can be done using the <component :is="component"></component> syntax. If all of the items you wish to display are components, you could do something like this:
<grid-item v-for="item in layout"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i">
<component :is="item.c"></component>
</grid-item>
And the JavaScript:
import VueGridLayout from 'vue-grid-layout';
import Test from './test.vue';
import Test2 from './test2.vue';
export default {
components: {
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem,
Test,
Test2
},
data: function (){
return {
layout: [
{"x":0,"y":0,"w":2,"h":2,"i":"0", "c": 'Test'}, // component name used but you could also use a reference to the component
{"x":2,"y":0,"w":2,"h":4,"i":"1", "c": 'Test2'}
];
}
}
}
If only some of the items will be components and some are plain HTML, you could perhaps flag which are components in the layout array:
layout: [
{"x":0,"y":0,"w":2,"h":2,"i":"0", "c": 'Test', isComponent: true},
{"x":2,"y":0,"w":2,"h":4,"i":"1", "c": '<h1>Hello World</h1>', isComponent: false}
];
And then conditionally render the component or plain HTML in the grid-item slot.
<grid-item v-for="item in layout"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i">
<template>
<component v-if="item.isComponent" :is="item.c"></component>
<div v-else v-html="item.c"></div>
</template>
</grid-item>

Related

Vue3 dynamic render

I have a list of components and I want to set a config for them from outside,
For example:
const myConfig = [
{
name: 'example',
renderer: () => (<button #click="clickHanlder">Click me!</button>)
},
...
];
And for my component I want to use myConfig as shown below:
<template>
<div class="example">
<template v-for="(item, index) in myConfig" :key="index">
My Button:
<div class="example-2">{{ item.renderer() }}</div>
</template>
</div>
</template>
Please pay attention that I don't want to use slots
How I can do it?
You could achieve that by using h render function as shown in the following example :
<template v-for="(item, index) in myConfig" :key="index">
My Button:
<div class="example-2">
<component :is="item.renderer" />
</div>
</template>
<script setup lang="ts">
import { h } from 'vue';
const myConfig = [
{
name: 'example',
renderer: h('button', {
onClick:clickHanlder
},'Click me !'),
},
];
function clickHanlder(){
console.log('click btn');
}
</script>

How to pass class attribute to child component element

how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.

import dynamically fontawesome icons in Vue

I have a problem rendering icons dynamically. I use v-for to get all the data from the object array. Also, I have a second array where I save the name of the icons I worked with. However, when the first array is looping, the second array (icons) doesn't move.
I tried to create a method that maps the data from the first and second array to create a new array. But nothing happens.
My code:
Component.vue
<template>
<div class="items">
<div class="item" v-for="(param, index) in params" :key="index">
<font-awesome-icon :icon="['fab', 'temp']" :temp="getIcon" :key="index" class="fab fa" />
<h3 class="skills-title">{{ param.name }}.</h3>
<p style="display: none">{{ param.description }}.</p>
</div>
</div>
</template>
<script>
export default {
name: "PxSkillCard",
data() {
return {
params: [],
icons: ["laravel", "wordpress-simple"],
};
},
methods: {
getIcon() {
let temp = this.params.map((aux, index) => {
return [aux, this.icons[index]];
});
},
},
};
</script>
And I separated the fontawesome file in a apart module
fontawesome.js
import Vue from "vue";
import { library } from "#fortawesome/fontawesome-svg-core";
import {
faLaravel,
faWordpressSimple
} from "#fortawesome/free-brands-svg-icons";
import { faPlus } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/vue-fontawesome";
library.add(
faLaravel,
faWordpressSimple
);
Vue.component("font-awesome-icon", FontAwesomeIcon);
The final result is:
What about with my code (or my logic)?
You are already looping through everything in your template, there's no need to loop again in your function.
Something like this should work.
<template>
<div class="items">
<div class="item" v-for="(param, index) in params" :key="index">
<font-awesome-icon :icon="['fab', icons[index]]" :key="index" class="fab fa" />
<h3 class="skills-title">{{ param.name }}.</h3>
<p style="display: none">{{ param.description }}.</p>
</div>
</div>
</template>
<script>
export default {
name: "PxSkillCard",
data() {
return {
params: [],
icons: ["laravel", "wordpress-simple"],
};
},
};
</script>
This assume, both arrays are the same size and the data in params and icons are in the correct order.

How to disable vue draggable placeholder

I am currently doing a website builder, where user can drag and drop to add element.
The drag and drop works well, but what i want is, how can i disable/hide the drop placeholder in the target container ?
As show in the image, whenever I hover on a container, it will show a copy of my dragging element by default, which I don't want.
Here is my code :
<template>
<div style="display : flex;">
<div id="dragArea">
<draggable
class="dragArea list-group"
:list="list1"
:group="{ name: 'item', pull: 'clone', put: false }"
:clone="cloneItem"
#change="log"
>
<div class="list-group-item" v-for="element in list1" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
<div id="dropArea">
<draggable class="dragArea list-group" :list="list2" group="item" #change="log">
<div class="list-group-item" v-for="element in list2" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
</div>
</template>
Script :
<script>
import draggable from "vuedraggable";
let idGlobal = 8;
export default {
name: "custom-clone",
display: "Custom Clone",
order: 3,
components: {
draggable,
},
data() {
return {
hover : false,
list1: [
{ name: "cloned 1", id: 1 },
{ name: "cloned 2", id: 2 },
],
list2: [
]
};
},
methods: {
log: function(evt) {
window.console.log(evt);
},
cloneItem({ name, id }) {
return {
id: idGlobal++,
name: name
};
},
},
};
</script>
On each of your <draggable> components within your <template>, you can set the ghost-class prop to a CSS class that hides the drop placeholder (ie. "ghost", or "dragging element" as you called it) using display: none; or visibility: hidden;.
For example:
In your <template>:
<draggable ghost-class="hidden-ghost">
and in the <style> section of your Vue Single File Component, or in the corresponding stylesheet:
.hidden-ghost {
display: none;
}
Working Fiddle
The ghost-class prop internally sets the SortableJS ghostClass option (see all the options here). The ability to modify these SortableJS options as Vue.Draggable props is available as of Vue.Draggable v2.19.1.

Vue.js how can i add dynamic component to the dom

in my app i need to add components to the dom after fetching data from an api
i have a component called Carousel and a component called Home
so i don't know how many carousels i need in my Home component
the question is : how can i add components using for loop from inside methods:{}
My Code :
Home.vue
<template>
<div>
<template
v-for="widget in widgets"
v-bind:is="Carousel">
{{ widget }}
</template>
</div>
</template>
<script>
import Carousel from './widgets/Carousel.vue'
export default {
components: {
Carousel
},
data() {
return {
page_content: [],
widgets: [],
}
},
created() {
this.getHomeContent();
},
methods:
{
addWidget() {
this.widgets.push('Carousel')
},
getHomeContent() {
window.axios.get(window.main_urls["home-content"]).then(response => {
this.page_content = JSON.parse(JSON.stringify(response.data));
this.addWidget()
});
}
}
}
</script>
Carousel.vue
<template>
<div>
<div :class="this.type == 'full' ? '' : 'container'">
Slider
</div>
</div>
</template>
<script>
export default
{
name:'Carousel',
props:[
'type',
'showTitle',
'dots',
'controls',
'data'
],
methods:
{
}
}
</script>
How to create as much carousels is i need using loop
the above code just give me a string 'carousel'
i want the component to be rendered
Here is a working example of dynamic components with properties
<div>
<template v-for="item in widgets">
<component :is="item.name" :name="item.name" v-if="item.name==='carousel'"></component>
</template>
</div>
The data in widgets
widgets: [
{name: 'carousel'},
{name: 'carousel'},
{name: 'test'},
{name: 'carousel'},
]
then it will create three components.
I created a test component like this to check this
components: {
'carousel': {
template: "<div>Dynamic component {{name}}</div>",
props: ["name"]
}
},