Create an element dynamically in Vue.js - javascript

I want to create a dynamic HTML template on click of a canvas.
This is what I tried:
var btn = document.createElement("div");
btn.classList.add("custom-signature-btn-row");
btn.classList.add("d-flex");
btn.innerHTML = `
<div class="btn-grid d-flex mr-2">
<span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
<button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
Signature
</button>
<div class="signature-dropdow" v-bind:class="{ active: isActive }">
<ul>
<li>
<div class="md-layout-item">
<div class="md-field">
<label for="movie">Assigned to Anyone</label>
<select name="movie" class="md-select" id="movie">
<option class="md-option" value="fight-club">Fight Club</option>
<option class="md-option" value="godfather">Godfather</option>
</select>
</div>
</div>
</li>
</ul>
</div>
</div>
`;
document.addEventListener('click', function(event) {
if (event.target && event.target.classList.contains("pdf-canvas")) {
console.log(event.target.parentNode);
event.target.parentNode.appendChild(btn);
}
});
The problem with above code is v-bind and v-on won't work with this approach.

You can programmatically render the component.
First, move the element as a component, e.g:
Button.vue
<template>
<div class="custom-signature-btn-row d-flex btn-grid d-flex mr-2">
<span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
<button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
Signature
</button>
<div class="signature-dropdow" v-bind:class="{ active: isActive }">
<ul>
<li>
<div class="md-layout-item">
<div class="md-field">
<label for="movie">Assigned to Anyone</label>
<select name="movie" class="md-select" id="movie">
<option class="md-option" value="fight-club">Fight Club</option>
<option class="md-option" value="godfather">Godfather</option>
</select>
</div>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
active: false,
toggleSignature: {
type: Function,
default: () => {}
}
}
}
</script>
On the canvas click handler, programmatically create a vue Button instance, render, and mount it into the target element.
import Vue from 'vue';
import Button from './Button.vue';
...
document.addEventListener('click', function(event) {
if (event.target && event.target.classList.contains("pdf-canvas")) {
const ButtonClass = Vue.extend(Button);
const buttonInstance = new ButtonClass({
propsData: {
isActive: this.isActive, // pass any data you need here
toggleSignature: this.toggleSignature, // callback
}
});
buttonInstance.$mount();
event.target.parentNode.appendChild(buttonInstance.$el);
}
});

You can refactor your code into an ordinary Vue component:
Vue.component('dynamic-btn', {
data: () => ({
isActive: true
}),
methods: {
toggleSignature() {
console.log('Clicked!')
}
},
template: `
<div class="custom-signature-btn-row d-flex">
<div class="btn-grid d-flex mr-2">
<span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
<button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
Signature
</button>
<div class="signature-dropdow" v-bind:class="{ active: isActive }">
<ul>
<li>
<div class="md-layout-item">
<div class="md-field">
<label for="movie">Assigned to Anyone</label>
<select name="movie" class="md-select" id="movie">
<option class="md-option" value="fight-club">Fight Club</option>
<option class="md-option" value="godfather">Godfather</option>
</select>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
`
});
new Vue({
el: '#app',
data: () => ({
btnCount: 1,
}),
});
Vue.config.productionTip = false;
Vue.config.devtools = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="btnCount++">Create Component</button>
<dynamic-btn v-for="(btn, i) in btnCount" :key="i" />
</div>
This way v-bind and v-on will work as normal.
Using SFC syntax:
<template>
<div class="custom-signature-btn-row d-flex">
<div class="btn-grid d-flex mr-2">
<span class="element-icon drag-icon md-icon sign-btn">drag_indicator</span>
<button class="md-button md-primary md-theme-default button-custom-regular" v-on:click="toggleSignature()">
Signature
</button>
<div class="signature-dropdow" v-bind:class="{ active: isActive }">
<ul>
<li>
<div class="md-layout-item">
<div class="md-field">
<label for="movie">Assigned to Anyone</label>
<select name="movie" class="md-select" id="movie">
<option class="md-option" value="fight-club">Fight Club</option>
<option class="md-option" value="godfather">Godfather</option>
</select>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DynamicBtn',
data: () => ({ isActive: true }),
methods: {
toggleSignature() {
console.log('Clicked!')
}
}
}
</script>
<template>
<div>
<button #click="btnCount++">Create Component</button>
<DynamicBtn v-for="(btn, i) in btnCount" :key="i" />
</div>
</template>
<script>
import DynamicBtn from './DynamicBtn';
export default {
name: 'Parent',
components: { DynamicBtn },
data: () => ({
btnCount: 1,
}),
}
</script>

Vue internally pre-compiles all the templates into "render functions", so they can build the Virtual DOM tree. During this process all the "v-on" and "v-bind" magic happens - that's why it doesn't work when you simply add new node to the DOM directly
What you can do is write your own render function - Vue provides you with the "createElement" function(AKA "h" function, so-called by convention). There you can specify all the Children, Classes, select the HTML tag, handle events etc.
Or a bit simpler solution will be the Dynamic components (<component v-bind:is="myCompName">), probably in combination with "v-html" to handle the "dynamic" part

Related

Vue 3 Composition Api - Find object and render

I have a problem. It's my first Vue.js project and I need help to solve the following problem. First I get a response from my API, then I get a list of projects and I want to find the project with the same ID as the url parameter. When I try to open the view my console logs the following error:
TypeError: Cannot read property 'title' of undefined
However, it then renders the right project into the template.
Code:
<template>
<div id="wrapper">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"
integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA=="
crossorigin="anonymous"
/>
<Sidebar></Sidebar>
<div id="content">
<Navbar></Navbar>
<div id="headline">
<ul>
<li>
<h1>Projekt Details</h1>
<Popup></Popup>
</li>
</ul>
</div>
<div id="grid" class="module-grid module-grid-2">
<div class="card">
<div class="card-head">
<div>
<h3>Meta Daten</h3>
</div>
<div></div>
</div>
<div class="card-body">
<ul v-if="filtered_projects != null">
<li>
<div class="list-info">
<p>Projektnummer: {{ filtered_projects.id }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Autor: {{ filtered_projects.author }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Firma: {{ filtered_projects.company }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Erstellt am:
{{
new Date(filtered_projects.created_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Letzte Änderung am:
{{
new Date(filtered_projects.updated_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-head">
<div>
<h3>Projekt Übersicht</h3>
</div>
<div></div>
</div>
<div class="card-body">
<form class="edit-form" #submit.prevent="submitProject()">
<label for="title">Überschrift*</label>
<input
v-model="filtered_projects.title"
name="title"
id="title"
class="input"
type="text"
required
maxlength="16"
/>
<label for="text">Text*</label>
<textarea
v-model="filtered_projects.text"
name="text"
id="text"
class="input"
type="text"
required
rows="6"
/>
<label for="finish">Abgeschlossen</label>
<input
v-model="filtered_projects.finish"
name="finish"
id="finish"
class="input w-auto"
type="checkbox"
/>
<button v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" type="submit" class="second-btn btn">
Aktualisieren
</button>
</form>
<button v-on:click="deleteProject()" v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" class="btn delete-btn">Löschen</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed, reactive } from "vue";
import { useStore } from "vuex";
import { useRoute, useRouter } from "vue-router";
import Sidebar from "../components/Sidebar.vue";
import Popup from "../components/Popup.vue";
import Navbar from "../components/Navbar.vue";
export default {
name: "ProjectDetails",
components: {
Sidebar,
Popup,
Navbar,
},
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
store.dispatch("company_projects/getProjectList");
store.dispatch("user_info/getUserInfo");
store.dispatch("companies/getCompaniesList");
const state = reactive({
query: route.params.id,
company_projects: computed(
() => store.getters["company_projects/getProjectlist"]
),
user_info: computed(
() => store.getters["user_info/getUserInfo"]
),
companies: computed(
() => store.getters["companies/getCompanieslist"]
),
user_auth_data: computed(
() => store.getters["auth/getAuthData"]
),
});
const filtered_projects = computed(() => state.company_projects.find(obj => {
return obj.id == parseInt(state.query)
}))
async function submitProject() {
await store
.dispatch("company_projects/submitProject", {
id: state.company_project.id,
company: state.company_project.company,
author: state.company_project.author,
title: state.company_project.title,
text: state.company_project.text,
finish: state.company_project.finish,
})
.catch((err) => {
console.log(err);
});
}
async function deleteProject() {
await store
.dispatch("company_projects/deleteProject", {
id: state.company_project.id,
})
.catch((err) => {
console.log(err);
});
router.push("/project");
}
return {
state,
submitProject,
deleteProject,
filtered_projects,
route,
};
},
};
</script>
Do you know a way to solve it better than me?
Thank you very much.
Since you are dispatching the company_projects/getProjectList action (which I suppose is responsible for fetching data from the server) directly in the setup, it is very likely that at the moment your component will render for the 1st time, the company_projects/getProjectList getter (and consequently the state.company_projects computed) will return an empty array and thus filtered_projects is undefined (find)
Which means the part of your template which needs filtered_projects to have a value should be protected with v-if
<form class="edit-form" #submit.prevent="submitProject()" v-if="filtered_projects">

How to pass Books array length to the parent component from child component in vue.js?

I am developing one page which is responsible from displaying books and the response is coming from backend API, DisplayBooks.vue component is imported inside a Dashboard.vue (parent-component).inside DisplayBooks.vue it contains books [] array , i want to pass this length to the Dashboard.vue component ,How to acheive this thing please help me to fix this thing..
Dashboard.vue
<template>
<div class="main">
<div class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img src="../assets/education.png" alt="notFound" class="education-image" />
</div>
<ul class="nav navbar-nav">
<li>
<p class="brand">Bookstore</p>
</li>
</ul>
<div class="input-group">
<i #click="handlesubmit();" class="fas fa-search"></i>
<div class="form-outline">
<input type="search" v-model="name" class="form-control" placeholder='search...' />
</div>
</div>
<ul class="nav navbar-nav navbar-right" id="right-bar">
<li><a> <i class="far fa-user"></i></a></li>
<p class="profile-content">profile</p>
<li #click="shownComponent='Cart'"><a><i class="fas fa-cart-plus"></i></a></li>
<p class="cart-content" >cart <span class="length" v-if="booksCount">{{booksCount}}</span></p>
</ul>
</div>
<div class="mid-body">
<!-- i want to display books length here -->
<h6>Books<span class="items">({{books.length}})</span></h6>
<select class="options" #change="applyOption">
<option disabled value="">Sort by relevance</option>
<option value="HighToLow">price:High to Low</option>
<option value="LowToHigh">price:Low to High</option>
</select>
</div>
<div v-if="flam==false">
<h2>Hello</h2>
</div>
<DisplayBooks v-show="flag==='noOrder' && shownComponent==='DisplayBooks'" #update-books-count="(n)=>booksCount=n" />
<Cart v-show=" shownComponent==='Cart'" />
<sortBooksLowtoHigh v-show="flag==='lowToHigh'" #update-books-count="(n)=>booksCount=n" />
<sortBooksHightoLow v-show="flag==='highToLow'" #update-books-count="(n)=>booksCount=n" />
</div>
</template>
<script>
import sortBooksLowtoHigh from './sortBooksLowtoHigh.vue'
import sortBooksHightoLow from './sortBooksHightoLow.vue'
import DisplayBooks from './DisplayBooks.vue'
import Cart from './Cart.vue'
export default {
components: {
DisplayBooks,
sortBooksLowtoHigh,
sortBooksHightoLow,
Cart
},
data() {
return {
shownComponent:'DisplayBooks',
booksCount:0,
flag: 'noOrder',
brand: 'Bookstore',
name: '',
flam: true,
visible: true,
}
},
methods: {
flip() {
this.flam = !this.flam;
},
applyOption(evt) {
if (evt.target.value === "HighToLow") {
this.flag = 'highToLow';
} else this.flag = 'lowToHigh';
},
}
}
</script>
DisplayBooks.vue
<template>
<div class="carddisplay-section">
<!-- <div class="mid">
<h6>Books<span class="items">({{books.length}})</span></h6>
</div> -->
<div v-for="book in books" :key="book.id" class="card book" >
<div class="image-section" #mouseover="book.hover = true" #mouseleave="book.hover = false">
<div class="image-container">
<img v-bind:src="book.file" />
</div>
</div>
<div class="title-section" >
{{book.name}}
</div>
<div class="author-section">
by {{book.author}}
</div>
<div class="price-section">
Rs. {{book.price}}<label class="default">(2000)</label>
</div>
<div class="buttons">
<div class="button-groups" v-if="!addedBooks.includes(book.id)">
<button type="submit" #click="handleCart(book.id);toggle(book.id);addedBooks.push(book.id)" class="AddBag">Add to Bag</button>
<button class="wishlist">wishlist</button>
</div>
<div class="AddedBag" v-else>
<button class="big-btn" #click="removeFromCart(book.id);addedBooks=addedBooks.filter(id=>id!==book.id)">Added to Bag</button>
</div>
</div>
<div class="description" v-if="book.hover">
<p class="hovered-heading">BookDetails</p>
{{book.description}}
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
mounted() {
service.userDisplayBooks().then(response => {
let data = response.data;
data.map(function(obj) {
obj.hover = false;
return obj;
});
this.books.push(...data);
return response;
})
},
data() {
return {
isActive:true,
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'Add to Bag',
buttonWishlist:'wishlist',
buttonAddedBag:'Added to Bag',
flag: true,
state: true,
addedBooks:[],
clickedCard: '',
books: [] // i want to display this array length
}
},
watch:{
addedBooks:{
handler(val){
this.$emit('update-books-count',val.length)
},
deep:true
}
},
methods: {
toggleClass: function(event){
this.isActive = !this.isActive;
return event;
},
toggle(id) {
this.clickedCard = id;
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handleCart(bookId){
let userData={
id: bookId,
}
service.userUpdateCart(userData).then(response=>{
return response;
}).catch(error=>{
alert("error while displaying Books");
return error;
})
},
removeFromCart(bookId){
let userData={
id:bookId,
}
service.userRemoveFromCart(userData).then(response=>{
return response;
}).catch(error=>{
alert("error while removing from cart");
return error;
})
}
}
}
</script>

How can I separate values from reusable checkbox in vuejs?

My question might not match to the problem that I am facing, So, I will explain it in detail here.
This is code for SeparateTechnology.vue. I have removed a few methods to make code shorter.
<template>
<div>
<div v-for="mss in ms" :key="mss.name">
<section class="container shadow-box techno-box" v-if="mss.name==selected">
<p>Review the software criteria below and ensure that you complete each section</p>
<h4>{{mss.name}}</h4>
<div class="row shadow-box">
<div class="col-sm-12 col-lg-6">
<p>Select all that apply</p>
<div class="dropdown">
<input
v-model.trim="inputValue"
class="dropdown-input"
type="text"
placeholder="Search for your Solution"
/>
<div class="dropdown-list" v-show="selected">
<div
v-show="itemVisible(item)"
v-for="item in mss.subMenu"
:key="item"
class="dropdown-item"
>
{{ item }}
<button
class="button button-mini button-dark button-rounded itemLabel"
#click="selectSolution(item,mss)"
>
<i class="icon-line-plus"></i>
</button>
</div>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6 rightList">
<div class="dropdown-list" v-show="selected" v-if="mss.selected_solutions!=''">
<div v-for="item in mss.selected_solutions" :key="item" class="dropdown-item">
{{ item }}
<button
class="button button-mini button-dark button-rounded deleteLabel"
#click="deleteSelectedItem(item,mss)"
>
<i class="icon-line-minus"></i>
</button>
</div>
</div>
<div v-else>
<div class="style-msg errormsg">
<div class="sb-msg">
<i class="icon-remove"></i>You have not selected any solutions.
</div>
</div>
</div>
<button
class="button button-mini"
#click="clearUserOptions(mss)"
v-if="mss.selected_solutions.length > 1"
>
<i class="icon-line-cross"></i>Clear all selection
</button>
</div>
<div style="padding:20px;"></div>
</div>
<div class="row">
<div class="col-sm-12 col-md-3 inputTitle">
<h5>Don't see it in the list above:</h5>
</div>
<input
class="col-sm-12 col-md-6"
type="text"
v-model="value"
#keydown.enter="getUserSolution(value,mss)"
placeholder="Enter your solution here.. "
/>
</div>
<div style="padding:20px;"></div>
<div class="row shadow-box">
<h5
class="col-sm-12"
>Identify how the software solution is leveraged within your organization</h5>
<div
v-for="item in mss.selected_solutions"
:key="item"
class="clearfix col-sm-12 col-md-6"
style="padding:20px"
>
<span v-if="mss.isDifferent=='campaign'">
<div class="card">
<h5 class="card-header">{{item}}</h5>
<CheckBox
:groups="campaignMangment"
name="campaignMangment"
:value="item"
classProp="col-sm-12"
#clicked-show-detail="clickedShowDetailModal"
/>
{{item}} and {{productSelected}}
</div>
</span>
</div>
</div>
<button class="btn btn-primary" #click="postUserDetails(mss)">Submit</button>
</section>
</div>
</div>
</template>
<script>
import CheckBox from "../../../components/checkbox/Checkbox";
export default {
name: "technology",
data() {
return {
usageValue: "",
value: "",
campainCheckedNames: [],
checked: "",
productSelected: [],
checkedValues: "",
exists: null,
inputValue: "",
campaignMangment: [
"Business to Customer (B2C)",
"Business to Business (B2B)",
"Customer to Customer (C2C)",
"Customer to Business (C2B)",
"E-Government",
"M-Commerce",
],
};
},
props: ["ms", "selected"],
//method to show all the solutions that contains the user
methods: {
clickedShowDetailModal: function (campainCheckedNames) {
console.log(campainCheckedNames);
this.productSelected.push(campainCheckedNames);
},
},
components: {
CheckBox,
},
};
</script>
This is CheckBox.vue
<template>
<div class="row col-mb-0">
<div
:class="classProp + ' checkbox-margin'"
v-for="singleSelector in groups"
:key="singleSelector"
>
<div>
<input
:id="singleSelector+groupId"
:value="singleSelector"
class="checkbox-style"
type="checkbox"
v-model="checkedValues"
#change="showDetailModal"
/>
<label :for="singleSelector +groupId" class="checkbox-style-3-label">{{singleSelector}}</label>
</div>
</div>
</div>
</template>
<script>
let groupId = 0;
export default {
props: {
groups: Array,
name: String,
classProp: String,
value: String,
},
data() {
return {
groupId: groupId++,
checkedValues: [],
inputs: {},
};
},
methods: {
showDetailModal: function (e) {
this.$set(this.inputs, this.value, e.target.value);
this.$emit("clicked-show-detail", this.inputs);
},
},
};
</script>
<style scoped>
.checkbox-margin {
margin-bottom: 10px;
}
</style>
Right Now, the line {{item}} and {{productSelected}} prints output like below as shown in screenshot
.
Problem: On every click/unclick of checkbox it adds an item to an array and not in the format I want. and if I select the checkbox on left only, it adds that item on right as well as shown in the screenshot above. It is due to the same array declaration, but I couldn't think more than that.
Expected Output: For every selected item, I want to print the list of selected checkboxes in an array format like below.
"selected_solutions": [{
"name": "Adobe Campaign",
"usage": [ "Business to Customer",...]
}, {
"name": "Marin Software",
"usage": ["M-Commerce",...]
}]
Even small hints or tips would be more than appreciated. I don't mind posting code on gist if required. Thank you.
With checkbox-components you want to emit "event.target.checked" likeso:
this.$emit('input', event.target.checked)
That way it'll behave like a checkbox and you can use a v-model on the parent:
<CheckBox v-model="item.checked" ...>
I do not use v-model inside the component and instead just bind :checked="value", but that might be up to preference. But using both :value and v-model could (should?) cause problems because v-model is actually a :value + emitter under the hood (or so I heard).
Anyway, here is my minimal reusable checkbox-wrapper:
<template>
<input type="checkbox" :checked="value" #change="handleChange" />
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'MyCheckbox',
props: {
value: { type: Boolean, default: false }
},
methods: {
handleChange(event) {
this.$emit('input', event.target.checked)
}
}
})
</script>
After that is done you can just filter on checked items:
computed: {
checkedSolutions(){
return this.ms[0].selected_solutions
.filter( solution => solution.checked );
}
}

Bind element inside a for loop Vue not working properly

In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</script>

Vue JS dynamic modal with components

in news.twig
{% extends 'layouts.twig' %}
{% block content %}
<section class="ls section_padding_bottom_110">
<div id="cart" class="container">
<cart
v-bind:materials="news"
type="news"
test="materials"
></cart>
<modal></modal>
</div>
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask" v-if="active" #click="close()">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<h3>${ item.name }</h3>
</div>
<div class="modal-body">
${ item.body }
<br>
modal #${ item.id }
</div>
<div class="modal-footer">
<button class="modal-default-button" #click="close()">
close
</button>
</div>
</div>
</div>
</div>
</transition>
</script>
</section>
{% endblock %}
I have 2 components and 1 Vue in my js.
var Hub = new Vue();
Vue.component(
'modal', {
template: '#modal-template',
delimiters: ['${', '}'],
data: function() {
return {
active: false,
item: {
id: '',
name: '',
body: ''
}
}
},
methods: {
open: function (item) {
this.active = true;
this.item = item;
},
close: function () {
this.active = false;
}
},
mounted: function() {
this.$nextTick(function () {
Hub.$on('open-modal', this.open);
Hub.$on('close-modal', this.close);
}.bind(this));
}
});
Vue.component('cart', {
props: {
materials: { type: Array, required: true},
type: { type: String, required: true}
},
computed: {
isPoints() {
return this.type == 'paymentPoints';
},
isNews() {
return this.type == 'news';
}
},
template : `
<div class="row masonry-layout isotope_container">
<div class="col-md-4 col-sm-6 isotope-item" v-for="item in materials">
<div class="vertical-item content-padding topmargin_80">
<div class="item-media">
<img v-bind:src="item.image" alt="">
<div class="media-links p-link">
<div class="links-wrap">
<i class="flaticon-arrows-2"></i>
</div>
<a v-if="!isNews" v-bind:href="item.image" class="abs-link"></a>
</div>
</div>
<button #click="openModal(item)" #keyup.esc="closeModal()">more</button>
<div class="item-content with_shadow with_bottom_border">
<span v-if="isNews" class="categories-links" style="font-size:20px;">
<a rel="category" href="#modal1" data-toggle="modal">
{{item.name}}
</a>
</span>
<p>{{item.body}}</p>
<div v-if="isPoints">
<hr class="light-divider full-content-divider bottommargin_10">
<div class="media small-icon-media topmargin_5">
<div class="media-left">
<i class="fa fa-map-marker grey fontsize_18"></i>
</div>
<div class="media-body">
{{item.adress}}
</div>
</div>
<div class="media small-icon-media topmargin_5">
<div class="media-left">
<i class="fa fa-phone grey fontsize_18"></i>
</div>
<div class="media-body">
{{item.telephone}}
</div>
</div>
</div>
<div v-if="isNews" class="text-center">
<hr class="light-divider full-content-divider bottommargin_10">
<span class="date">
<i class="flaticon-clock-1 grey"></i>
<time class="entry-date">
{{item.date}}
</time>
</span>
</div>
</div>
</div>
</div>
</div>
`
});
var vm = new Vue({
el: '#cart',
name: 'cart',
delimiters: ['${', '}'],
data: {
complated: [],
continuing: [],
planned: [],
points: [],
infoSlider: [],
news: []
},
methods: {
openModal: function (item) {
Hub.$emit('open-modal', item);
},
closeModal: function () {
Hub.$emit('close-modal');
}
},
created() {
axios.get(url).then(res => {
var proje = res.data.projects[0];
this.complated = proje.complated;
this.continuing = proje.continuing;
this.planned = proje.planned;
this.points = res.data.paymentPoints;
this.infoSlider = res.data.sliderİnfos;
this.news = res.data.news;
})
.catch(e => {
console.log(e);
})
}
});
When I click openModal(item) button give me error ;
Property or method "openModal" 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://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
I can not use the openModal function in component.
I can use the function in news.twig without any problems, but then I can not use the component. Can you help me?
You are using openModal in cart component, but that method is defined in root component.
According to Vue's documentation:
Everything in the parent template is compiled in parent scope;
everything in the child template is compiled in the child scope.
in my case need to define variable in vuejs
like this
<script>
export default {
name: "MegaMenu",
props: {
categories: Array,
},
}

Categories

Resources