0

I have a vuejs page and I am creating DOM inputs dynamically and I want to bind them with a set of 3 form variables to catch the data. I don't know how many sets of inputs they will be created from the beginning to initialize the form entries. The form is created with useForm on the setup() section of Vue. Below are snips of the code.

<template>
    <app-layout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                {{ title }}
            </h2>
        </template>
        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                    <div class="audio_edit">
                        <form @submit.prevent="form.post('/pppaudio/newProcessedAudio')">
                            <div class="pp_title">
                                <label style="font-weight: bold">
                                    Title:
                                </label>
                                <label for="title">{{ title }}</label>
                            </div>
                            <div class="pp_audio">
                                <div id="waveform"></div>
                                <div id="wave-timeline"></div>
                                <div class="playButton">
                                    <Button data-action="play" type="button">
                                        <span id="play">
                                            <i class="glyphicon glyphicon-play"></i>
                                            Play
                                        </span>
                                        <span id="pause" style="display: none">
                                            <i class="glyphicon glyphicon-pause"></i>
                                            Pause
                                        </span>
                                    </Button>
                                </div>
                            </div>
                            <div class="pp_transcript flex flex-col">
                                <label style="font-weight: bold">
                                    Transcript:
                                </label>
                                {{ transcript }}
                            </div>
                            <div id="region_block" ref="reg_block" >
                                <div class="region">
                                    <div id="copyTextButton" class="copyButton">
                                        <Button v-on:click="getText" type="button">Copy Marked Text</Button>
                                    </div>
                                    <div class="pp_new_transcript flex flex-col">
                                        <label style="font-weight: bold">
                                            New Transcript:
                                        </label>
                                        <textarea id="selectedText" rows="5" class="selectedTextInput h-full" v-model="form.selectedText" />
                                    </div>
                                    <div class="pp_start-stop">
                                        <label for="start" style="font-weight: bold">
                                            Start:
                                        </label>
                                        <input id="start" v-model="form.start" disabled/>
                                        <label for="stop" style="font-weight: bold">
                                            Stop:
                                        </label>
                                        <input id="stop" v-model="form.stop" disabled/>
                                    </div>
                                    <div id="submit_button">
                                        <Button>Submit</Button>
                                    </div>
                                </div>
                            </div>

                            <div id="return_back">
                                <Button v-on:click="goBack" type="button">Back</Button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </app-layout>
</template>

<script>
import AppLayout from "../../Layouts/AppLayout";
import Label from "../../Jetstream/Label";
import Button from "../../Jetstream/Button";
import {InputFacade} from 'vue-input-facade';
import WaveSurfer from 'wavesurfer.js';
import RegionPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js';
import TimeLine from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.js';
import {useForm} from '@inertiajs/inertia-vue3';
import Vue from 'vue'

export default {
    setup() {
        const form = useForm({
            title: null,
            selectedText: null,
            start: null,
            stop: null,
        })

        return {form}
    },
    components: {Button, Label, AppLayout, InputFacade},
    methods: {
        getText() {
            this.newTranscript = window.getSelection().toString();
            this.form.selectedText = this.newTranscript;
            this.form.title = this.title;
        },
        copyValues(start, stop) {
            this.form.start = start;
            this.form.stop = stop;
        },
        goBack() {
            let formData = new FormData();
            formData.append('id', localStorage.id);

            axios.post('/pppaudio/goBack',
                formData, {
                    headers: {
                        'Content-Type': 'multipart/form-data'
                    }
                }
            ).then( (response) => {
                if (response.data.status === 'success') {
                    window.location.href = response.data.url;
                }
            }).catch(error => {
                console.log("ERRRR:: ", error.response.data);
            });
        }
    },
    mounted() {
        if (localStorage.title) {
            this.title = localStorage.title;
        }
        if (localStorage.path) {
            this.path = localStorage.path;
        }
        if (localStorage.transcript) {
            this.transcript = localStorage.transcript;
        }

        const wavesurfer = WaveSurfer.create({
            container: '#waveform',
            waveColor: 'violet',
            progressColor: 'purple',
            scrollParent: true,
            plugins: [
                RegionPlugin.create({
                    dragSelection: true
                }),
                TimeLine.create({
                    container: '#wave-timeline'
                })
            ]
        });

        wavesurfer.load('../storage/preprocessed-audio/' + this.path);

        let play = false;

        wavesurfer.on('region-click', (region, e) => {
            e.stopPropagation();
            // Play on click, loop on shift click
            if (play) {
                wavesurfer.pause();
                play = false;
            } else {
                region.play();
                play = true;
            }
            // console.log(this.form.title);
           this.copyValues(region.start, region.end);
        });

        wavesurfer.on('region-created', (region, e) => {
            console.log(region.id.substring(10))
            let regionNode = document.createElement("div")
            regionNode.className = "region"
            let regionTitle = document.createElement("div")
            regionTitle.className = "regionTitle"
            regionTitle.innerHTML = region.id
            regionTitle.style.fontWeight = "bold"
            regionNode.appendChild(regionTitle)
            let color = () => Math.random() * 256 >> 0;
            let col = `${color()}, ${color()}, ${color()}`
            regionTitle.style.color = 'rgb('+col+')';
            region.color = 'rgba('+ col + ', 0.1)';
            let copyButtonDiv = document.createElement("div")
            copyButtonDiv.className = "copyTextButton"
            let copyButton = document.createElement("Button")
            copyButton.className = "inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring focus:ring-gray-300 disabled:opacity-25 transition"
            let copyButtonText = document.createTextNode("Copy Marked Text")
            copyButton.appendChild(copyButtonText)
            copyButton.addEventListener('click', this.getText)
            let cpBtnType = document.createAttribute("type")
            cpBtnType.value = "button"
            copyButton.setAttributeNode(cpBtnType)
            copyButtonDiv.appendChild(copyButton)
            regionNode.appendChild(copyButtonDiv)
            let newTranscriptDiv = document.createElement("div")
            newTranscriptDiv.className = "pp_new_transcript flex flex-col"
            let newTransLabel = document.createElement("label")
            newTransLabel.innerText = "New Transcript:"
            newTransLabel.style.fontWeight = "bold"
            let selectTextArea = document.createElement("textarea")
            selectTextArea.id = "selectedText"
            selectTextArea.className = "selectedTextInput h-full"
            let selectTextAreType = document.createAttribute("rows")
            selectTextAreType.value = "5"
            selectTextArea.setAttributeNode(selectTextAreType)
            newTranscriptDiv.appendChild(newTransLabel)
            newTranscriptDiv.appendChild(selectTextArea)
            regionNode.appendChild(newTranscriptDiv)
            let startStopDiv = document.createElement("div")
            startStopDiv.className = "pp_start-stop"
            let startLabel = document.createElement("label")
            startLabel.innerText = "Start:"
            startLabel.style.fontWeight = "bold"
            let startLabelType = document.createAttribute("for")
            startLabelType.value = "start"
            startLabel.setAttributeNode(startLabelType)
            let startInput = document.createElement("input")
            startInput.id = "start"
            let startInputType = document.createAttribute("disabled")
            startInput.setAttributeNode(startInputType)
            let stopLabel = document.createElement("label")
            stopLabel.innerText = "Stop:"
            stopLabel.style.fontWeight = "bold"
            let stopLabelType = document.createAttribute("for")
            stopLabelType.value = "stop"
            stopLabel.setAttributeNode(stopLabelType)
            let stopInput = document.createElement("input")
            stopInput.id = "stop"
            let stopInputType = document.createAttribute("disabled")
            stopInput.setAttributeNode(stopInputType)
            startStopDiv.appendChild(startLabel)
            startStopDiv.appendChild(startInput)
            startStopDiv.appendChild(stopLabel)
            startStopDiv.appendChild(stopInput)
            regionNode.appendChild(startStopDiv)
            let submitBtnDiv = document.createElement("div")
            submitBtnDiv.id = "submit_button"
            let submitButton = document.createElement("Button")
            let submitButtonText = document.createTextNode("Submit")
            submitButton.className = "inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring focus:ring-gray-300 disabled:opacity-25 transition"
            submitButton.appendChild(submitButtonText)
            submitBtnDiv.appendChild(submitButton)
            regionNode.appendChild(submitBtnDiv)
            // this.$set()


            document.querySelector('#region_block').appendChild(regionNode)
        })

        let playButton = document.querySelector('#play');
        let pauseButton = document.querySelector('#pause');
        wavesurfer.on('play', function() {
            playButton.style.display = 'none';
            pauseButton.style.display = '';
        });
        wavesurfer.on('pause', function() {
            playButton.style.display = '';
            pauseButton.style.display = 'none';
        });

        playButton.addEventListener('click', function () {
            wavesurfer.play()
        })

        pauseButton.addEventListener('click', function () {
            wavesurfer.pause()
        })

    },
    data() {
        return {
            title: '',
            path: '',
            transcript: '',
            selectedText: '',
            newTranscript: '',
            start: '',
            stop: ''
        }
    }
}
</script>

<style>
.audio_edit {
    padding: 10px;
}

.pp_title, .pp_audio, .copyButton, .pp_transcript, .pp_new_transcript, .pp_start-stop, #wave-timeline, #submit_button {
    padding-bottom: 10px;
}

.region {
    border-top: 3px solid black;
    border-bottom: 3px solid black;
    padding-top: 3px;
    margin-bottom: 3px;
}

.pp_new_transcript {
    display: flex;
    width: 100%;
}

.selectedTextInput {
    -webkit-box-flex:1;
    -webkit-flex:1;
    -ms-flex:1;
    flex:1;
    border: none;
}

</style>
4
  • 1
    v3.vuejs.org/guide/list.html#v-for-with-a-component Commented Jul 16, 2021 at 8:54
  • @MichalLevý I need to create the code block dynamically for each time is required each time. The only possibility that I can think with v-for is to create an array and inside to put objects with the entries that I need. Commented Jul 16, 2021 at 9:10
  • Yep, exactly... Commented Jul 16, 2021 at 9:13
  • @MichalLevý But one of the problems is that I cannot assign v-model dynamically to make the 2 way binding. The v-for I could use it only to present the data, but not I need to create input fields. Commented Jul 16, 2021 at 9:30

1 Answer 1

2
  1. Whenever you need some HTML to repeat in Vue, use v-for. In the example below I'm using it with component

  2. Using createElement in Vue is rarely needed and should be avoided. If you think about my example below, you should see that whole wavesurfer.on('region-created') part can be just replaced by just pushing new Region object into an array which is used in v-for

const app = Vue.createApp({
  data() {
    return {
      forms: [{
          id: 1,
          selectedText: 'Hello',
          start: 0,
          stop: 4
        },
        {
          id: 2,
          selectedText: 'world',
          start: 6,
          stop: 10
        },
      ]
    }
  }
})

app.component('my-form', {
  props: {
   form: {
     type: Object,
     required: true
   }
  },
  template: `
  <div class="region">
    <div id="copyTextButton" class="copyButton">
       <button type="button">Copy Marked Text</button>
    </div>
    <div class="pp_new_transcript flex flex-col">
        <label style="font-weight: bold">
            New Transcript:
        </label>
        <textarea id="selectedText" rows="5" class="selectedTextInput h-full" v-model="form.selectedText" />
    </div>
    <div class="pp_start-stop">
        <label for="start" style="font-weight: bold">
            Start:
        </label>
        <input id="start" v-model="form.start" disabled/>
        <label for="stop" style="font-weight: bold">
            Stop:
        </label>
        <input id="stop" v-model="form.stop" disabled/>
    </div>
    <div id="submit_button">
        <button>Submit</button>
    </div>
 </div>
  `
})

app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id='app'>
  <my-form v-for="form in forms" :key="form.id" :form="form"></my-form>
</div>

Sign up to request clarification or add additional context in comments.

6 Comments

This is close to what I would like to achieve, but I this is not how I create the Vuejs file. Check the edited Script that I have posted.
The form is not relevant here. I must use this (instead of Vue SFC) to be able to execute the code directly inside the browser without the compile step. What is relevant is the principle - how to use v-for to generate multiple component instances each bound to its own piece of data....
I don't understand the difference between the layout of the code. I don't create a new Vue app. I need to find how to combine my code with your example.
Your solution is partially working. I can get all fields presented at the DOM, but how I can catch each submit separately and also execute the copy text button for each region?
If you followed my example and extracted the markup into separate component as I did, the way to communicate back to parent component is by using events
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.