I found lots of libraries that somehow marry an external library (and their DOM elements) with Vue.js. All of them though seem to only add child elements to the Vue.js-managed DOM node.
I wrote Vue-Stripe-Elements to make the use of the new Stripe V3 easier but struggled to mount Stripes elements to the Vue component.
The obvious way would be a .vue component like this:
<template>
</template>
<script>
export default {
// could also be `mounted()`
beforeMount () {
const el = Stripe.elements.create('card')
el.mount(this.$el)
}
}
</script>
This would work if Stripe only adds children to the element it is mounted too but it looks like Stripe instead transcludes or replaces the given DOM node. Stripe of course also doesn't support any VNodes.
My current solution to the problem is to create a real DOM node and add it as a child:
<template>
</template>
<script>
export default {
mounted () {
const dom_node = document.createElement('div')
const el = Stripe.elements.create('card')
el.mount(dom_node)
this.$el.appendChild(el)
}
}
</script>
It works but it feels like I'm fighting against Vue.js here and I might create odd side effects here. Or am I just doing what other appending libraries do manually and it is the best way to go?
Is there an "official" way to do this?
Thanks in advance for any helpful comment about it.
Stripe Elements Vuejs 2
Use refs to get DOM elements in vuejs.
HTML
<div ref="cardElement"></div>
JS
mounted() {
const stripe = Stripe('pk');
const elements = stripe.elements();
const card = elements.create('card');
card.mount(this.$refs.cardElement);
}
I faced the same problem, so the method mounted is correct to add, but for the big applications where u call a specific vuejs i got the error
"please make sure the element you are attempting to use is still mounted."
HTML Snippet :
<div style="min-height:100px;">
<div class="group">
<h4><span class="label label-default"> Enter Card Details</span> </h4>
<label class="cardlabel">
<span>Card number</span>
<div id="card-number-element" class="field"></div>
<span class="brand"><i class="pf pf-credit-card" id="brand-icon"></i></span>
</label>
<label class="cardlabel">
<span>Expiry date</span>
<div id="card-expiry-element" class="field"></div>
</label>
<label class="cardlabel">
<span>CVC</span>
<div id="card-cvc-element" class="field"></div>
</label>
</div>
</div>
Vue.js
(function () {
let stripe = Stripe('keyhere');
elements = stripe.elements(),
cardNumberElementStripe = undefined;
cardExpiryElementStripe = undefined;
cardCvcElementStripe = undefined;
var style = {
base: {
iconColor: '#666EE8',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: 'Helvetica Neue',
fontSize: '15px',
'::placeholder': {
color: '#CFD7E0',
},
},
};
var purchase= new Vue({
el: '#purchase',
mounted() {
cardNumberElementStripe = elements.create('cardNumber', {
style: style
});
cardExpiryElementStripe = elements.create('cardExpiry', {
style: style
});
cardCvcElementStripe = elements.create('cardCvc', {
style: style
});
cardNumberElementStripe.mount('#card-number-element');
cardExpiryElementStripe.mount('#card-expiry-element');
cardCvcElementStripe.mount('#card-cvc-element');
cardNumberElementStripe.on('change', function (event) {
// Switch brand logo
if (event.brand) {
if (event.error) { setBrandIcon("unknown"); } else { setBrandIcon(event.brand); }
}
//setOutcome(event);
});
function setBrandIcon(brand) {
var brandIconElement = document.getElementById('brand-icon');
var pfClass = 'pf-credit-card';
if (brand in cardBrandToPfClass) {
pfClass = cardBrandToPfClass[brand];
}
for (var i = brandIconElement.classList.length - 1; i >= 0; i--) {
brandIconElement.classList.remove(brandIconElement.classList[i]);
}
brandIconElement.classList.add('pf');
brandIconElement.classList.add(pfClass);
}
var cardBrandToPfClass = {
'visa': 'pf-visa',
'mastercard': 'pf-mastercard',
'amex': 'pf-american-express',
'discover': 'pf-discover',
'diners': 'pf-diners',
'jcb': 'pf-jcb',
'unknown': 'pf-credit-card',
}
},
created() {
//on the buttn click u are calling this using v-on:click.prevent="payment"
payment: function (e) {
stripe.createToken(cardNumberElementStripe).then(function (result) {
debugger;
if (result.token) {
cardId = result.token.id;
// $("#paymentform").get(0).submit();
} else if (result.error) {
errorElement.textContent = result.error.message;
return;
}
});
}
}
Related
I have been getting an inconsistent error popping up while interacting with controls my audio player using wavesurfer.js.
Here is the error:
I'm not sure but i think it is to do with the way i have the wavesurfer import inside my useEffect. This was the only way i could get the wavesurfer container to render correctly without throwing a different error.
Here is my component:
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useState } from "react";
import styles from "../../styles/audioPlayer.module.css";
let WaveSurfer = null;
const AudioPlayer = () => {
// Get the current song and song data from the Redux store
const songCurrent = useSelector((state) => state.songCurrent);
const songData = useSelector((state) => state.songData);
// Get the audioUrl from the songData array
const audioUrl = songCurrent ? songData[songCurrent].songUrl : "";
const artistName = songCurrent ? songData[songCurrent].screenName : "";
const songName = songCurrent ? songData[songCurrent].songName : "";
// State to store the WaveSurfer instance and the current volume
const [wavesurfer, setWaveSurfer] = useState(null);
function timeCalculator(value) {
minute = Math.floor(value / 60);
second = Math.floor(value - minute * 60);
if (second < 10) {
second = "0" + second;
}
return minute + ":" + second;
}
useEffect(() => {
if (!audioUrl) return;
// Destroy the Wavesurfer instance if it already exists
if (wavesurfer) {
wavesurfer.destroy();
}
// Import Wavesurfer
import("wavesurfer.js").then((WaveSurfer) => {
// Initialize Wavesurfer
const wavesurfer = WaveSurfer.default.create({
container: "#wave",
waveColor: "#261b1b",
progressColor: "#f44336",
height: 40,
length: 1000,
scrollParent: false,
hideScrollbar: true,
skipLength: 30,
cursorWidth: 0,
barWidth: 3,
barHeight: 1,
barGap: 1,
});
// Load audio file
wavesurfer.load(audioUrl);
console.log("Audio URL:", audioUrl); // Log the audio URL
// Store the Wavesurfer instance in state
setWaveSurfer(wavesurfer);
wavesurfer.on("ready", function () {
// Initialize DOM elements
/* const duration = document.querySelector("#duration");
const current = document.querySelector("#current"); */
const playPause = document.querySelector("#playPause");
const skipBack = document.querySelector("#skipBack");
const skipForward = document.querySelector("#skipForward");
const volumeOn = document.querySelector("#volumeon");
const volumeSlider = document.querySelector("#volumeSlider");
wavesurfer.on("ready", function () {
wavesurfer.play();
});
//change play button to pause on playing
wavesurfer.on("play", function (e) {
if (playPause) {
playPause.classList.remove("fa-play");
playPause.classList.add("fa-pause");
}
});
//change pause button to play on pause
wavesurfer.on("pause", function (e) {
if (playPause) {
playPause.classList.add("fa-play");
playPause.classList.remove("fa-pause");
}
});
if (playPause) {
playPause.addEventListener("click", function (e) {
wavesurfer.playPause();
});
}
if (skipBack) {
skipBack.addEventListener("click", function (e) {
wavesurfer.skipBackward();
});
}
if (skipForward) {
skipForward.addEventListener("click", function (e) {
wavesurfer.skipForward();
});
}
//Add volumeOn button
if (volumeOn) {
volumeOn.addEventListener("click", function (e) {
wavesurfer.setMute(!wavesurfer.getMute());
});
}
//Add volumeSlider
if (volumeSlider) {
volumeSlider.addEventListener("input", function (e) {
wavesurfer.setVolume(e.target.value / 100);
});
}
});
});
}, [audioUrl]);
return (
<div className={styles.audioContainer}>
<div className={styles.innerContainer}>
<div className={styles.controlContainer}>
<div className={styles.controls}>
<div className={styles.buttons}>
<i className="fa-solid fa-backward-step fa-2x" id="skipBack"></i>
<i className="fa-solid fa-play fa-2x" id="playPause"></i>
<i
className="fa-solid fa-forward-step fa-2x"
id="skipForward"
></i>
</div>
</div>
<div className={styles.volume}>
<input
id="volumeSlider"
className="volume-slider"
type="range"
name="volume-slider"
min="0"
max="100"
defaultValue="75"
></input>
</div>
</div>
<div className={styles.songInfo}>
<div className={styles.artistName}>{artistName}</div>
<div className={styles.songName}>{songName}</div>
</div>
<div className={styles.waveContainer}>
<div id="wave"></div>
</div>
</div>
</div>
);
};
export default AudioPlayer;
Most of the javascipt is pretty standard implementation.
Any of you had this error before or can advise on this error please.
The audioUrl is fed to the component via redux.
Thanks in advance.
Wavesurfer uses WEB Apis that are not accessible during the server render.
When using some libraries with NextJS, you may run into this issue, since NextJS pre-renders the page on the server. On the server, there is no window object, no WebAudio API and many other things.
By placing these browser-only libraries in the useEffect they are ran after the component mounts on the client (browser).
useEffect hook is not ran on the server.
How can I Watch for any changes like classes or text change inside a div in Vue 3.
from
<p class="font-bold">My text</p>
to:
<p class="font-bold color-red">My updated text.</p>
I have tried the Vue 3 Watch method but the Watch method is not looking for changes inside a div.
watch(myDiv, (newValue, oldValue) => {
// not working for changes inside a myDiv.
})
To watch some properties you need to bind it first, like <p :class="classes">{{ text }}</p>, also, you can use mutationObserver:
const {ref, onMounted, onBeforeUnmount} = Vue
const app = Vue.createApp({
data() {
return {
text: 'My text',
classes: 'font-bold'
};
},
watch: {
text(newValue, oldValue) {
console.log(newValue)
},
classes(newValue, oldValue) {
console.log(newValue)
}
},
methods: {
addClass() {
this.classes = 'font-bold color-red'
}
},
setup() {
let observer = null
let target = ref(null)
let options = {subtree: true, childList: true, attributes: true}
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
} else if (mutation.type === 'attributes') {
console.log(`The ${mutation.attributeName} attribute was modified.`);
}
}
};
onMounted(() => {
target = document.querySelector(".mydiv")
observer = new MutationObserver(callback);
observer.observe(target, options);
});
onBeforeUnmount(() => observer.disconnect())
},
})
app.mount('#demo')
.color-red {
color: red;
}
.font-bold {
font-weight: 700;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<p class="mydiv" :class="classes">{{ text }}</p>
<input v-model="text" />
<button #click="addClass">class</button>
</div>
In Vue3 you can use refs in template. Docs
<p ref="myDiv" class="test">{{text}}</p>
Now you can define them in template. myDiv.value will hold DOM element
const myDiv = ref(null)
const text= computed(() => {
if(myDiv.value.classList ... rest of your logic for checking classes)
return "Text"
else return "Updated Text"
})
you can create a SFC component with your paragraph and catch every change with a hook onUpdated
onUpdated(() => {
console.log('updated');
});
example: https://stackblitz.com/edit/vue-5qtyjg
I am using vuejs 2.6.14 and am running into the following issue :
Modified data from child component updates data in parent component aswell, without any use of $emit in code.
This is the opposite of the usual "how to update data in child from parent / how to update in parent from child"
Here is my code in greater details:
I have a parent component named Testing.vue, passing a JSON object ("userData") to a child,
GeneralData.vue.
This is what the code looks like for the parent :
<template>
<div id="testing-compo">
<div style="margin-top: 1rem; margin-bottom: 1rem; max-width: 15rem">
<label
class="sr-only"
for="inline-form-input-username"
style="margin-top: 1rem; margin-bottom: 1rem"
>Account settings for :</label
>
<b-form-input
v-model="username"
id="inline-form-input-username"
placeholder="Username"
:state="usernameIsValid"
></b-form-input>
</div>
<b-button class="button" variant="outline-primary"
#click="callFakeUser">
Populate fake user
</b-button>
<GeneralData :userData="user" />
</div>
</template>
<script>
export default {
name: "Testing",
components: {
GeneralData,
},
data() {
return {
user: null,
username: null,
};
},
computed: {
usernameIsValid: function () {
if (this.username != null && this.username.length >= 4) {
return true;
} else if (this.username != null) {
return false;
}
return null;
},
},
methods: {
async callFakeUser() {
userServices.getFakeUser().then((res) => {
this.user = res;
console.log(this.user);
});
},
</script>
A very simple testing component that calls userServices.getFakeUser(), which asynchronously returns a JSON object.
For the child :
<template>
<div id="general-compo">
<!-- AGE -->
<div class="mt-2">
<label for="text-age">Age</label>
<div>
<b-form-input
v-model="userAge"
placeholder="+18 only"
class="w-25 p-1"
type="number"
>
</b-form-input>
</div>
</div>
<!-- LANGUAGES -->
<div class="mt-2">
<label for="lang-list-id">Language(s)</label>
<div
v-for="langKey in userLangsCount"
:key="langKey"
style="display: flex; flex-direction: row"
>
<b-form-input
readonly
:placeholder="userLangs[langKey - 1]"
style="max-width: 50%; margin-top: 0.5rem"
disabled
></b-form-input>
**This form is set to read only, for display purposes only**
<b-button
variant="outline-danger"
#click="removeLang(langKey)"
style="margin-top: 0.5rem; margin-left: 1rem"
>Remove</b-button
>
**This button removes a language from the userLangs array by calling removeLang(langKey)**
</div>
<div style="display: flex; flex-direction: row">
<b-form-input
v-model="userCurrentLang"
list="langlist-id"
placeholder="Add Language"
style="max-width: 50%; margin-top: 0.5rem"
></b-form-input>
<datalist id="langlist-id">
<option>Manual Option</option>
<option v-for="lang in langList" :key="lang.name">
{{ lang.name }}
</option>
</datalist>
<b-button
:disabled="addLangBtnDisabled"
variant="outline-primary"
#click="addLang()"
style="margin-top: 0.5rem; margin-left: 1rem"
>Add</b-button
>
</div>
</div>
</div>
</template>
<script>
import langList from "../assets/langList";
export default {
name: "GeneralData",
components: {},
props: {
userData: Object,
},
data() {
return {
userAge: null,
langList: langList,
userLangs: [],
userCurrentLang: null,
};
},
watch: {
//Updating tabs with fetched values
userData: function () {
this.userLangs = this.userData.general.langs;
this.userAge = this.userData.general.age
},
},
computed: {
**userGeneral is supposed to represent the data equivalent of userData.general, it is therefore computed from the user input, its value is updated each time this.userAge or this.userLangs changes**
userGeneral: function () {
//user data in data() have been filled with userData values
return {
age: this.userAge,
langs: this.userLangs,
};
},
**returns the amount of languages spoken by the user to display them in a v-for loop**
userLangsCount: function () {
if (this.userLangs) {
return this.userLangs.length;
}
return 0;
},
**gets a list of languages name from the original JSON list for display purposes**
langNameList: function () {
let namelist = [];
for (let i = 0; i < this.langList.length; i++) {
namelist.push(langList[i].name);
}
return namelist;
},
**returns true or false depending on whether entered language is in original list**
addLangBtnDisabled: function () {
for (let i = 0; i < this.langList.length; i++) {
if (this.userCurrentLang == langList[i].name) {
return false;
}
}
return true;
},
},
methods: {
addLang() {
this.userLangs.push(this.userCurrentLang);
this.userCurrentLang = null;
},
removeLang(key) {
this.userLangs.splice(key - 1, 1);
},
}
}
</script>
Here is what the data looks in the vuejs dev tool inside the browser after having updated this.user in Testing.vue:
Data in Testing.vue :
user : {
general:{"age":22,"langs":["French"]}
}
Data in GeneralData.vue :
userData : {
general:{"age":22,"langs":["French"]}
}
userAge : 22
userLangs : ["French"]
userGeneral :
{
general:{"age":22,"langs":["French"]}
}
So far so good right?
Well here is where the issues happen, if I change the age field in my form, userAge gets incremented, userGeneral.age gets update, but userData.general.age doesnt. Which is expected as userGeneral.age is computed out of this.userAge, and userData is a prop so it shouldn't be mutated as a good practice (and not method sets userData.general.age = xxx anyways).
HOWEVER, if i hit the Remove button next to French in the language list, this.userLangs gets updated as it should and is now [], this.userGeneral.langs gets updated to [] aswell as it is computed directly from the former. And userData.general.langs ... gets updated to [] aswell which really makes no sense to me.
Worse, in the parent, Testing.vue, user.general.langs is now set to [] aswell.
So somehow, this.userLangs updated the prop this.userData, AND this prop has updated it's original sender user in the parent component, although no $emit of any kind has been involved.
I do not want this to happen as I dont think it's supposed to happen this way and is therefore an hasard, but also because i want to setup a 'save' button later on allowing the user to modify his values all at once.
What i've tried : setting all kinds of .prevent, .stop on the #click element on the Remove / Add buttons, in the method called itself, adding e.preventDefault (modifying addLang and removeLang to send the $event element aswell), none of those attemps have solved anything.
Hopefully I didnt implement the .prevent part correctly, and someone can help me block this reverse-flow annoying issue.
Solution to the problem here is that lang is an array that is passed as a reference thus a mutation is bubbled up to the parent.
Instead of assigning the original array we can just assign a copy of that
userData: function () { this.userLangs = [...this.userData.general.langs]; this.userAge = this.userData.general.age }
I'm trying to implement the Syncfusion rich text editor reactjs widget in my site. I've slightly modified the demo code to be a standalone class like this:
import { render } from 'react-dom';
import React, { Component } from 'react';
import { addClass, removeClass, Browser } from '#syncfusion/ej2-base';
import { RichTextEditorComponent, Toolbar, Inject, Image, Link, HtmlEditor, Count, QuickToolbar, Table } from '#syncfusion/ej2-react-richtexteditor';
import { createElement } from '#syncfusion/ej2-base';
import * as CodeMirror from 'codemirror';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css.js';
import 'codemirror/mode/htmlmixed/htmlmixed.js';
class RichTextEd extends Component {
constructor(props) {
super(props);
// Rich Text Editor items list
this.items = ['Bold', 'Italic', 'Underline', 'StrikeThrough',
'FontName', 'FontSize', 'FontColor', 'BackgroundColor',
'LowerCase', 'UpperCase', '|',
'Formats', 'Alignments', 'OrderedList', 'UnorderedList',
'Outdent', 'Indent', 'SuperScript', 'SubScript', '|',
'CreateTable', 'CreateLink', 'Image', '|', 'ClearFormat', 'Print',
'SourceCode', 'FullScreen', '|', 'Undo', 'Redo'
];
//Rich Text Editor ToolbarSettings
this.toolbarSettings = {
items: this.items
};
}
mirrorConversion(e) {
this.textArea = this.rteObj.contentModule.getEditPanel();
let id = this.rteObj.getID() + 'mirror-view';
let mirrorView = this.rteObj.element.querySelector('#' + id);
let charCount = this.rteObj.element.querySelector('.e-rte-character-count');
if (e.targetItem === 'Preview') {
this.textArea.style.display = 'block';
mirrorView.style.display = 'none';
this.textArea.innerHTML = this.myCodeMirror.getValue();
charCount.style.display = 'block';
}
else {
if (!mirrorView) {
mirrorView = createElement('div', { className: 'e-content' });
mirrorView.id = id;
this.textArea.parentNode.appendChild(mirrorView);
}
else {
mirrorView.innerHTML = '';
}
this.textArea.style.display = 'none';
mirrorView.style.display = 'block';
this.renderCodeMirror(mirrorView, this.rteObj.value);
charCount.style.display = 'none';
}
}
renderCodeMirror(mirrorView, content) {
this.myCodeMirror = CodeMirror(mirrorView, {
value: content,
lineNumbers: true,
mode: 'text/html',
lineWrapping: true,
});
}
handleFullScreen(e) {
let sbCntEle = document.querySelector('.sb-content.e-view');
let sbHdrEle = document.querySelector('.sb-header.e-view');
let leftBar;
let transformElement;
if (Browser.isDevice) {
leftBar = document.querySelector('#right-sidebar');
transformElement = document.querySelector('.sample-browser.e-view.e-content-animation');
}
else {
leftBar = document.querySelector('#left-sidebar');
transformElement = document.querySelector('#right-pane');
}
if (e.targetItem === 'Maximize') {
if (Browser.isDevice && Browser.isIos) {
addClass([sbCntEle, sbHdrEle], ['hide-header']);
}
addClass([leftBar], ['e-close']);
removeClass([leftBar], ['e-open']);
if (!Browser.isDevice) {
transformElement.style.marginLeft = '0px';
}
transformElement.style.transform = 'inherit';
}
else if (e.targetItem === 'Minimize') {
if (Browser.isDevice && Browser.isIos) {
removeClass([sbCntEle, sbHdrEle], ['hide-header']);
}
removeClass([leftBar], ['e-close']);
if (!Browser.isDevice) {
addClass([leftBar], ['e-open']);
transformElement.style.marginLeft = leftBar.offsetWidth + 'px';
}
transformElement.style.transform = 'translateX(0px)';
}
}
actionCompleteHandler(e) {
if (e.targetItem && (e.targetItem === 'SourceCode' || e.targetItem === 'Preview')) {
this.rteObj.sourceCodeModule.getPanel().style.display = 'none';
this.mirrorConversion(e);
}
else {
setTimeout(() => { this.rteObj.toolbarModule.refreshToolbarOverflow(); }, 400);
}
}
render() {
return (<div className='control-pane'>
<div className='control-section' id="rteTools">
<div className='rte-control-section'>
<RichTextEditorComponent id="toolsRTE" ref={(richtexteditor) => { this.rteObj = richtexteditor; }} showCharCount={true} actionBegin={this.handleFullScreen.bind(this)} actionComplete={this.actionCompleteHandler.bind(this)} maxLength={2000} toolbarSettings={this.toolbarSettings}>
<div>
<p>The Rich Text Editor is WYSIWYG ("what you see is what you get") editor useful to create and edit content, and return the valid <a href='https://ej2.syncfusion.com/home/' target='_blank'>HTML markup</a> or <a href='https://ej2.syncfusion.com/home/' target='_blank'>markdown</a> of the content</p> <p><b>Toolbar</b></p><ol><li> <p>Toolbar contains commands to align the text, insert link, insert image, insert list, undo/redo operations, HTML view, etc</p></li><li><p>Toolbar is fully customizable </p></li></ol> <p><b>Links</b></p><ol><li><p>You can insert a hyperlink with its corresponding dialog </p></li><li><p>Attach a hyperlink to the displayed text. </p></li><li><p>Customize the quick toolbar based on the hyperlink </p> </li></ol><p><b>Image.</b></p><ol><li><p>Allows you to insert images from an online source as well as the local computer </p> </li><li><p>You can upload an image</p></li><li><p>Provides an option to customize quick toolbar for an image</p></li></ol><img alt="Logo" src="https://ej2.syncfusion.com/react/demos/src/rich-text-editor/images/RTEImage-Feather.png" style={{ width: '300px' }}/>
</div>
<Inject services={[Toolbar, Image, Link, HtmlEditor, Count, QuickToolbar, Table]}/>
</RichTextEditorComponent>
</div>
</div>
</div>);
}
}
export default RichTextEd;
I'm then using it in my component with
<RichTextEd />
The editor doesn't render as expected, and there's an error reported about the ref being used in strict mode:
Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of RichTextEditorComponent which is inside StrictMode. Instead, add a ref directly to the element you want to reference.
I think I understand that the problem is with the way the ref is set in the render method, but I'm unclear on where the this.rteObj is defined initially, and further how to make this work as expected from a parent component.
Greetings from Syncfusion support,
We have tried the shared code blocks of the RichTextEditor rendered from parent component, which works fine and we haven't faced any issues. Can you please check the below working example.
Sample: https://stackblitz.com/edit/react-render-parent-component?file=index.js
I have a simple code where I have got input fields for the user and the user can click on a button to render more input fields, and I want to store those input fields.
<div id='wordslist'>
<div class='announcement'>Voer hieronder uw woorden in</div>
<div class='wordsdiv'>
<div id='onedictspec' v-for='arrayspot in wordupload.wordcount'>
<input type='text' class='inputwordtext' v-model='arrayspot[wordupload.wordcount][0]'>
<input type='text' class='inputwordtext' v-model='arrayspot[wordupload.wordcount][1]'>
</div>
</div>
<div id='morewords'>
<button class='morewordsbtn active hover' v-on:click='morewords'>More words</button>
</div>
Vuejs
data{
wordupload: {
wordcount: 20,
wordinput: [],
}
}
methods: {
morewords: function () {
this.wordupload.wordcount += 30
}
}
In short it renderes wordupload.wordcount number of #wordsdiv, and I am tring to give the input of those wordsdiv a spot in the wordinput array. But I can't v-model their value to the spot if it doesn't exist. Any ideas on what would be the best way to store those dynamically rendered input values would be much appreciated.
Are you looking for something like this?
https://jsfiddle.net/2nn58fa8/1/
Vue.component('wordlist', {
data: function() {
return {
wordupload: {
wordcount: 20,
wordinput: [],
}
}
},
created: function() {
this.wordupload.wordinput.length = this.wordupload.wordcount;
},
methods: {
morewords: function() {
this.wordupload.wordcount += 30;
this.wordupload.wordinput.length = this.wordupload.wordcount;
},
checkwords: function() {
console.log(this.wordupload.wordinput);
}
}
});
var vue = new Vue({
el: '#app'
});