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

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

Related

Tiptap with Bubble Menu Extension

I am using Livewire/Alpinejs stack and have also installed tiptap editor. So far following this link the editor works along with it's basic functionality/buttons. What I want is to add the Bubble Menu extension in the game. I have installed the package and also following the docs in this link but it's not working (after selecting a word bubble menu wont appear). Here is the code below.
import { Editor } from '#tiptap/core'
import StarterKit from '#tiptap/starter-kit'
import BubbleMenu from '#tiptap/extension-bubble-menu'
window.setupEditor = function (content) {
return {
editor: null,
content: content,
init(element) {
this.editor = new Editor({
element: element,
extensions: [
StarterKit,
BubbleMenu.configure({
element: document.querySelector('#menu'),
}),
],
content: this.content,
onUpdate: ({ editor }) => {
this.content = editor.getHTML()
}
})
this.$watch('content', (content) => {
// If the new content matches TipTap's then we just skip.
if (content === this.editor.getHTML()) return
/*
Otherwise, it means that a force external to TipTap
is modifying the data on this Alpine component,
which could be Livewire itself.
In this case, we just need to update TipTap's
content and we're good to do.
For more information on the `setContent()` method, see:
https://www.tiptap.dev/api/commands/set-content
*/
this.editor.commands.setContent(content, false)
})
}
}
}
<div
x-data="setupEditor($wire.entangle('{{ $attributes->wire('model')->value() }}').defer)"
x-init="() => init($refs.editor)"
wire:ignore
{{ $attributes->whereDoesntStartWith('wire:model') }}
>
<template>
<div id="menu">
<div x-if="editor">
<button #click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
</button>
</div>
</div>
</template>
<div x-ref="editor"></div>
</div>
I needed to change and put the id to the parent element (template).
<template x-if="editor" id="menu">
<div>
<div>
<button #click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold
</button>
</div>
</div>
</template>

update data in slot vuejs

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>

On a Keydown.enter event in input element within a child component is also calling a method that is defined in Parent Component

I have a parent component where user can select skills from a range of options and a child component where user can add their own skill if its not available on the parent component.
The issue is in child component, when a user enters skill into an input element on which I have an #keydown.enter event defined to call a method, to take the input and push it to an array and that all works. The only problem is when keydown.enter event is fired it's also calling a method that is defined in the parent component which changes the state of the options element.
// parent component
<div class="card-body">
<p class="card-subtitle font-weight-bold mb-1">Select Skills</p>
<button
v-for="skill in skills"
:key="skill.id"
:class="[skill.state ? skillSelectedState : skillNotSelectedState]"
class="btn btn-sm m-2" #click="addSkill(skill)"
:value="skill.category">
{{skill.skills}}
</button>
<clientInput></clientInput> // child component
</div>
<script>
import clientInput from './ClientSkillInput.vue'
export default {
data() {
return {
skills: [], // getting skills from an axios call
selectedSkills: [],
}
}
}
methods: {
addSkill(skill) { // this is the method getting called
if (!skill.state) {
this.selectedSkills.push(skill.skills);
skill.state = true;
} else {
let position = this.selectedSkills.indexOf(skill.skills);
this.selectedSkills.splice(position, 1);
// skill.state = false;
}
},
}
// child component
<template>
<div class="form-group mt-2">
<label class="d-block">Not what you're looking for?</label>
<div class="customWraper">
<div class="userSkillbox d-inline bg-secondary"> // will be using v-for to loop on userProvidedSkills and show the user inputs
Rrby on rails
<button class="userSkillboxBtn btn-sm border-0 text-white"> // to remove the input item
<i class="fas fa-times"></i>
</button>
</div>
<input v-model="userInput" type="text" class="d-inline border-0" placeholder="Type to add different skill" #Click="">
</div>
</div>
</template>
<script>
export default {
data() {
return {
isEditable: true,
userInput: '',
userProvidedSkills: [],
}
},
methods: {
addUserInput() {
this.userProvidedSkills.push(this.userSkill);
this.userSkill = '';
}
}
}
</script>
It is not clear where you’re adding the keydown event, but there 2 possible solutions:
1.Use a event modifier on the input to stop propagation
<input #keydown.enter.stop
2.Use the self event modifier on the parent component button
<button
v-for="skill in skills"
#click.self="addSkill(skill)"
:value="skill.category">
{{skill.skills}}
More about event modifiers here

Dynamic component insertion using VueJs, Cordava and Javascript

Using vuejs and Cordova, I could not dynamicaly insert/create a new v-select component.
My goal is to duplicate and insert a copy an existing v-select when my "Add" button gets clicked.
The code of my page is:
<v-select
:items="exerciceList"
v-model="selectedExercice"
label="pick an exercise"
v-validate="'required'"
data-vv-name="select"
required>
</v-select>
</div>
<div id="selectsContainer"></div>
<v-btn flat icon id="btn" v-on:click="newExercice()">
My (not working) click event listener:
methods: {
newExercice: function () {
var container = document.getElementById('selectsContainer');
var select = document.getElementById('exo');
// select.items = this.exerciceList;
select.removeAttribute('id');
select.style.display = 'block';
container.appendChild(select);
}
},
The item with the id expo's code:
<v-select
id="exo"
style="display: none;"
:items="exerciceList"
v-model="selectedExercice"
label="Choisissez un exercice"
v-validate="'required'"
data-vv-name="select"
required>
Thank you for helping me improve my implementation.
What Michael S has said is correct, you do not want to manipulate the DOM directly, causes many issues. Probably the best solution (quick and easy anyway) would be to import that additional v-select component and then add it dynamically with a simple list or in this case an integer increase.
If you're wanting to get a little more customized with it, you can create an object and add and remove styles, attr, etc. from it dynamically the same way this option would work but turning item into an array of objects. then proceed to add an object to it whenever the button is clicked.
<v-select
:items="exerciceList"
v-model="selectedExercice"
label="pick an exercise"
v-validate="'required'"
data-vv-name="select"
required>
</v-select>
</div>
<div id="selectsContainer"
v-for="(item, idx) in items">
<v-select
id="item.id" <!-- or use idx -->
style="item.style" <!-- or use any string statement -->
...etc
>
</div>
<v-btn flat icon id="btn" v-on:click="newExercice()">
...{
components: [componentName],
data: return {
items: 0,
...
}
methods: {
newExercice: ()=>{
this.items++
}
}
...}

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!

Categories

Resources