ToroLoco
ToroLoco

Reputation: 501

How to create javascript form elements dynamically

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>

Upvotes: 0

Views: 198

Answers (1)

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37793

  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>

Upvotes: 2

Related Questions