update data in slot vuejs - javascript

hi im using vuejs with laravel project
and this is my vuejs code
Vue.component('search_and_select',{
template:
'<div>'+
'<slot :test_text="test_text"></slot>'+
'</div>',
data:function(){
return {
test_text:"test text",
}
},
methods:{
},
props:{
},
});
new Vue({
el:'.user_search_and_select',
data:{
},
});
and this is my html code
<div is='search_and_select'>
<div slot-scope="{test_text}">
#{{test_text}}
<input type='text' v-model='test_text' />
</div>
</div>
till now everything working so good
but if i keyup <input type='text' v-model='test_text' /> the test_text dont change still the same
so how can i change in slot and change in parent component too
thanks a lot ..

You have to expose a method to the slot for updating the value. This means you won't be able to use v-model because you will need to handle :value and #input separately now.
<slot :test_text="test_text" :update_test_text="update_test_text"></slot>
methods: {
update_test_text(value) {
this.test_text = value
}
}
Now you can use the component like this:
<search_and_select>
<div slot-scope="{ test_text, update_test_text }">
<input
type="text"
:value="test_text"
#input="update_test_text($event.target.value)"
>
</div>
</search_and_select>

Related

Creating and showing complex JSON object dynamically using VueJS component and HTML

I am developing a Vuejs application within which I have a field extension. For this field, users can provide the values and this field expands dynamically (both vertically and horizontally) based on the user-provided values.
I am using the recursive component ExtensionComponent to display and store the values. But it's not working as expected for me.
The field name extensions displays, initially an Add Extension. With on click of the button, a bootstrap modal will be displayed which has 3 fields: namespace (text), localname (text), datatype(dropdown: string/complex). If the datatype is string then a simple text field will be displayed. However, if the datatype is complex then another button should be displayed and on click of the button again the same bootstrap modal is displayed with fields and the process continues. So the created JSON based on this will expand horizontally and vertically.
I am able to create the Vue component to show the recursive data but I am a bit confused about the addition of the values to extensionList array dynamically. Can someone please help me with this issue?
Following is my Test.vue page which will display the Extensions and Add Button:
<template>
<div>
<span>Extensions</span>
<button class="btn btn-primary" #click="createExtensions">
Add Another
</button>
<ExtensionComponent :extension-list="$store.state.extensionList" />
<TestModal v-if="$store.state.showModal" />
</div>
</template>
<script>
import TestModal from '#/components/TestModal.vue'
export default {
computed: {
TestModal
},
methods: {
// Method to create extensions and add
createExtensions () {
this.$store.commit('toggleExtensionModal')
}
}
}
</script>
<style>
</style>
Following is my Bootstrap modal (TestModal.vue) which will be displayed whenever Add Another button is clicked (initially and for complex):
<template>
<b-modal
id="Extension"
title="Add Another Element"
size="lg"
width="100%"
:visible="$store.state.showModal"
>
<b-form id="AddExtension" #submit.prevent="submitExtension">
<div class="form-group">
<label for="message-text" class="col-form-label">Namespace URI:</label>
<input
v-model="extension.namespace"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Local Name:</label>
<input
v-model="extension.localName"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label
for="AddExtensionDataType"
class="col-form-label"
>Data Type:</label>
<b-form-select v-model="extension.dataType" class="form-control">
<b-form-select-option value="string">
String
</b-form-select-option>
<b-form-select-option value="complex">
Complex
</b-form-select-option>
</b-form-select>
</div>
</b-form>
<template #modal-footer="{ cancel }">
<b-btn #click="cancel">
Cancel
</b-btn>
<b-btn variant="primary" type="submit" form="AddExtension">
OK
</b-btn>
</template>
</b-modal>
</template>
<script>
export default {
data () {
return {
extension: {
namespace: '',
localName: '',
dataType: 'string'
},
showModal: false
}
},
methods: {
submitExtension () {
// Call vuex store to save information
this.$store.commit('addExtension', this.extension)
// Hide modal after submitting modal
this.$store.commit('toggleExtensionModal')
}
}
}
</script>
<style>
</style>
Following is my ExtensionComponent.vue which will display data recursively:
<template>
<div>
<div
v-for="extension in extensionList"
:key="extension.ID"
class="form-inline"
>
<span>{{ extension.namespace + ":" + extension.localName }}</span>
<input
v-if="extension.dataType == 'string'"
type="text"
#input="$emit('AddExtensionText', {$event, id: extension.ID})"
>
<ExtensionComponent v-if="extension.dataType == 'complex'" :extension-list="extension" #AddExtensionText="AddExtensionText($event)" />
<button
v-if="extension.dataType == 'complex'"
#click="AddComplextExtension(extension.ID, extension)"
>
Add another
</button>
</div>
</div>
</template>
<script>
import ExtensionComponent from '#/components/ExtensionComponent.vue'
export default {
components: {
ExtensionComponent
},
props: {
extensionList: Array,
extension: Object
},
methods: {
AddComplextExtension (extensionID, extension) {
this.$store.commit('modules/ExtensionDataStore/showExtensionModal')
},
AddExtensionText ({ value, id }) {
const i = this.extensionList.findIndex(el => el.ID === id)
this.$set(this.extensionList, i, value)
}
}
}
</script>
Following is my index.js Vuex store:
export const state = () => ({
extensionID: 0,
extensionList: [],
showModal: false
})
export const mutations = {
toggleExtensionModal (state) {
// Function to show/hide the extension Modal
state.showModal = !state.showModal
},
addExtension (state, extension) {
console.log(extension)
extension.ID = state.extensionID
state.extensionList.push(extension)
state.extensionID++
}
}
export const actions = {}
Following is the front-end I have:
I want to create JSON and show it something like this:
As we can see if the value is complex then the field is created dynamically and displayed in a parent-child relationship way. I am a bit confused about how to establish the parent-child relationship within complex JSON and show them to the user. Someone,Ad

How to show HTML from this array Vue JS

I'm new to Vue JS and I'm trying to pass an HTML table using this array, I have a dropdown where I select the option I want and then it shows it, but I can't figure out how can I put HTML in there, when I do it prints code instead. A little help would be greatly appreciated.
This is the HTML file:
<div id="app">
<div>
{{pickedValue}}
</div>
<picker v-model="pickedValue"></picker>
</div>
This is the JS file and I want to put an HTML table inside list:["c","d","e"]
console.clear()
Vue.component("picker",{
props:["value"],
data(){
return {
list:["c","d","e"],
currentValue: this.value,
selectedValue: ""
}
},
template:`
<div>
<select #change="currentValue = $event.target.value" v-model="selectedValue">
<option value="">Select</option>
<option v-for="item in list" :value="item" :key="item">{{item}}</option>
</select>
</div>
`,
watch:{
currentValue(newValue){
if (!this.list.includes(newValue))
this.selectedValue = ""
this.$emit('input', newValue)
}
}
})
new Vue({
el:"#app",
data:{
pickedValue: null
}
})
You should use v-html instead. Be careful when using this because if "c", "d", "e" are user inputs, it could expose your application to XSS attacks:
<div id="app">
<div v-html="pickedValue"></div>
<picker v-model="pickedValue"></picker>
</div>
https://v2.vuejs.org/v2/api/#v-html

Can't re-render html in vee-validate component

Vue.js v2.6.11 / vee-validate v3.2.2
I have a button that will push new element to form.demand (data in vue app) on click event.
And if form.demand update, html in v-for should be updated.
After I wrap it in vee-validate component , it not works.
form.demand will update, but v-for won't.
I try to put same html in test-component, when form.demand update, v-for update too.
I can't figure out why...
following is my code:
HTML
<div id="content">
<test-component>
<div v-for="demand in form.demand">{{demand}}</div>
</test-component>
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in form.demand">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
</validation-provider>
</div>
javascript
Vue.component('validation-provider', VeeValidate.ValidationProvider);
Vue.component('validation-observer', VeeValidate.ValidationObserver);
Vue.component('test-component',{
template: `
<div>
<slot></slot>
</div>
`
})
var app = new Vue({
el: "#content",
data: {
form: {
demand: [],
},
},
methods: {
addDemand(){
this.form.demand.push({
name : "demand name",
count: 1
})
}
})
------------Try to use computed & Add :key----------------
It's still not work. I get same result after this change.
HTML
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in computed_demand" :key="index">
<!--.........omitted.........-->
</validation-provider>
Javascript
var app = new Vue({
el: "#content",
// .......omitted
computed:{
computed_demand() {
return this.form.demand;
}
},
})
I think I found the problem : import Vue from two different source. In HTML, I import Vue from cdn. And import vee-validate like following:
vee-validate.esm.js
import Vue from './vue.esm.browser.min.js';
/*omitted*/
validator.js
import * as VeeValidate from './vee-validate.esm.js';
export { veeValidate };
main.js
// I didn't import Vue from vue in this file
import { veeValidate as VeeValidate } from './validator.js';
Vue.component('validation-provider', VeeValidate.ValidationProvider);
HTML
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- at end of body -->
<script src="/static/javascripts/main.js" type="module"></script>
</body>
After I fix this( import vee-validate from cdn, or import Vue by ES6 module).
It works, although it still have infinite loop issue with vee-validate.
Sorry for I didn't notice that import vue from two different source.
Please provide a key in you v-for. see code below
<div v-for="(demand,index) in form.demand" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, make a computed property that will hold your form.demands array, like this one
computed: {
form_demands: function() {
return this.form.demand
}
}
then call this computed property in your v-for
<div v-for="(demand,index) in form_demands" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, use the vue forceUpdate method
import Vue from 'vue';
Vue.forceUpdate();
Then in your component, just call the method after you add demand
this.$forceUpdate();
It is recommended to provide a key with v-for whenever possible,
unless the iterated DOM content is simple, or you are intentionally
relying on the default behavior for performance gains.

Vue.js change model attached to a form upon clicking a list

I have an array of objects. These objects are loaded into a list in vue.js.
Aside from this list, I have a form that displays data from one of these objects. I want to, when clicking one of the list's elements, it will bind this specific object to the form and show its data.
How can do this in Vue.js?
My list code is:
<div id="app-7">
<ul id="food-list" v-cloak>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>
<li class="food">
<div class="food-header">
<img :src="'img/' + food.slug +'.png'">
<div class="food-title">
<p>{{food.name}} |
<b>{{food.slug}}</b>
</p>
<p>quantity: {{food.quantity}}</p>
</div>
<div class="food-load"> // load into form upon clicking this
</div>
</div>
</li>
</food-item>
</ul>
</div>
Since I do not have the code for the form, this is my best guess without clarification.
You can add a click handler to the item you want to be clicked. It will pass the value of the food item into the method.
<div class="food-load" #click="setFoodItem(item)">
</div>
And when that method is called, it can assign the clicked item to a data property. I'm not sure where your form is, and if it is in a different component. If it is in a child component, you would have to pass it in as a prop, or emit an event to pass it to a parent component.
data() {
return {
//create a reactive field to store the current object for the form.
foodItemForm: null
};
},
methods: {
//method for setting the current item for the form.
setFoodItem(item) {
this.foodItemForm = item;
}
}
Missing quite a bit of info in your sample code, your script is very important to see to make sense of what you would like to accomplish and where things might be going wrong.
Here's a quick list of the issue I came across with your code:
v-for refers to an individual food item as 'item', inside the loop you're trying to access properties as 'food'
You don't wrap your code in a component unless you're importing the component
When binding a value to 'v-bind:src' (or shorthand ':src') only pass the url, you should be specifying this in your script not inline.
You're better off using a button and the 'v-on:click' (or shorthand '#click') to load your selected food item into your form
You should also include your Javascript
Regardless, here's how I would handle this (took the liberty in filling in some blanks):
<template>
<div id="app">
<ul id="food-list">
<!--<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>-->
<li v-for="item in foodList" class="food">
<div class="food-header">
<img :src="item.slug" v-bind:alt="item.slug" width="250px" height="auto">
<div class="food-title">
<p>{{item.name}} | <b>{{item.slug}}</b></p>
<p>quantity: {{item.quantity}}</p>
</div>
<button class="food-load" #click="loadFoodItem(item.id)">Load Food Item</button>
</div>
</li>
<!--</food-item>-->
</ul>
<form v-if="activeFoodId != null" id="foodItemForm" action="#">
<h3>Food Form</h3>
<label for="food-id">Id:</label>
<input id="food-id" type="number" v-bind:value="foodList[activeFoodId].id"><br/>
<label for="food-slug">Slug:</label>
<input id="food-slug" type="text" v-bind:value="foodList[activeFoodId].slug"><br/>
<label for="food-name">Name:</label>
<input id="food-name" type="text" v-bind:value="foodList[activeFoodId].name"><br/>
<label for="food-quantity">Quantity:</label>
<input id="food-quantity" type="number" v-bind:value="foodList[activeFoodId].quantity">
</form>
</div>
</template>
<script>
export default {
name: 'app',
data: function () {
return {
activeFoodId: null,
foodList: [
{
id: 1,
slug: 'http://3.bp.blogspot.com/-QiJCtE3yeOA/TWHfElpIbkI/AAAAAAAAADE/Xv6osICLe6E/s320/tomato.jpeg',
name: 'tomatoes',
quantity: 4
}, {
id: 2,
slug: 'https://img.purch.com/rc/300x200/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzA2NS8xNDkvb3JpZ2luYWwvYmFuYW5hcy5qcGc=',
name: 'bananas',
quantity: 12
}, {
id: 3,
slug: 'https://media.gettyimages.com/photos/red-apples-picture-id186823339?b=1&k=6&m=186823339&s=612x612&w=0&h=HwKqE1MrsWrofYe7FvaevMnSB89FKbMjT-G1E_1HpEw=',
name: 'apples',
quantity: 7
}
]
}
},
methods: {
loadFoodItem: function (foodItemId) {
console.log(foodItemId)
this.activeFoodId = foodItemId
}
}
}
</script>
<style>
/# Irrelevant #/
</style>
Hope it helps!

Vuejs - How to communicate changes between parent and child in scoped slots

I'm trying to create a component which has two slots. The second slot is repeating based on the number of items in the first slot. I have achieved this using scoped slots, however, when the data is updated on the first slot, the second slot does not automatically update it until an event is triggered, eg: click of a button which calls a method.
Is there a way to make the second slot updates its view when the data is changed on the first slot?
Here is the example that I have:
Jsfiddle: https://jsfiddle.net/89vykm75/1/
new Vue({
el: '#app',
components: {
'repeat-for-each-item': {
data: function() {
return {
items: []
}
},
template: `<div>
<slot name="item" v-for="item in items" :item="item"></slot>
<button #click="addItem()">Add item</button>
<slot name="repeat" v-for="item in items" :item="item"></slot>
</div>
`,
methods: {
addItem() {
this.items.push({});
}
}
}
}
});
<div id="app">
<repeat-for-each-item>
<template slot="item" scope="props">
<div>
<input type="text" v-model="props.item.name">
</div>
</template>
<template slot="repeat" scope="props">
<div>
<label>
<span v-if="props.item.name">{{props.item.name}}:</span>
<span v-else>No Name:</span>
</label>
<input type="text">
</div>
</template>
</repeat-for-each-item>
</div>
I found a solution by calling a method on keyup.
Basically, I added #keyup event on the slot
<input type="text" v-model="props.item.name" #keyup="props.onchange()">
And on the component template, pass on the onchange method to the slot
<slot name="item" v-for="item in items" :item="item" :onchange="onchange"></slot>
And then have the onchange function to force the re-render
onchange:() => {
// hack to trigger changes
this.$set(this.items, 0, this.items[0]);
}
Here is the full working JsFiddle: https://jsfiddle.net/89vykm75/2/
I wonder if there is a cleaner solution?
You are falling into a Vue change detection caveat. The issue here is if you add a property to an object that didn't exist before when the object was added to the Vue data, then Vue cannot detect the change. Here is the problem:
this.items.push({})
You're adding an object with no properties, and then you bind v-model to the name property of that object, which does not exist. Vue cannot detect the change, and does not update the other items bound to that property.
If you instead did this:
this.items.push({name: null})
You will find your code works. Here is an updated fiddle.

Categories

Resources