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>
Whenever you need some HTML to repeat in Vue, use v-for. In the example below I'm using it with component
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/vue#3.1.4/dist/vue.global.js"></script>
<div id='app'>
<my-form v-for="form in forms" :key="form.id" :form="form"></my-form>
</div>
Related
I have a group of objects that are rendered with v-for.
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control">
<button #click="addNewAnswer" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
#input="answers[answerCount - 1].answer = $event.target.value"
type="text"
class="input"
>
</p>
</div>
These are the models I use to render
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
And this is the code for adding a new line to enter the answer
addNewAnswer() {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers[this.answerCount - 1].isEntered = true;
this.answerCount += 1;
},
I need to add an is-static class to the input after adding the entered response. How do I do this correctly?
If I add this to the input element
:class="{ 'is-static':answers[answerCount - 1].isEntered }"
Then the class will not be added to the past element as it should be, because I will be referring to an already new object of the array answer. I couldn't think of any alternatives
As per your problem statement, No need to manage a separate answerCount variable. As you are iterating answers array in the top most element. You can access it's object properties directly using dot(.) notation.
Suggestions :
Replace #input="answers[answerCount - 1].answer = $event.target.value" with v-model="answer.answer"
Pass answer.id as a param in addNewAnswer method and then update the property value of the passed object id.
Live Demo :
const app = Vue.createApp({
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
methods: {
addNewAnswer(answerId) {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers.forEach(obj => {
if (obj.id === answerId) {
obj.isEntered = true;
}
})
},
}
})
app.mount('#app')
.is-static {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control" >
<button #click="addNewAnswer(answer.id)" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
v-model="answer.answer"
type="text"
class="input"
:class="{ 'is-static': answer.isEntered }"
>
</p>
</div>
</div>
Try with v-model:
const app = Vue.createApp({
data() {
return {
answers: [
{
id: 0,
answer: '',
isEntered: false,
}
],
answerCount: 1,
}
},
methods: {
addNewAnswer() {
const newAnswer = {
id: Date.now(),
answer: '',
isEntered: false,
};
this.answers.push(newAnswer);
this.answers[this.answerCount - 1].isEntered = true;
this.answerCount += 1;
},
}
})
app.mount('#demo')
.is-static {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="answer in answers" :key="answer.id" class="field has-addons">
<p class="control" >
<button #click="addNewAnswer" class="button is-primary">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>Добавить</span>
</button>
</p>
<p class="control">
<input
v-model="answer.answer"
type="text"
class="input"
:class="{ 'is-static':answer.isEntered }"
>
</p>
</div>
{{ answers }}
</div>
I'm relatively new to Vue JS. So I have this case on Form Submit.
This is what I've submitted in Console Log:
{
amount: [
monday: "123",
tuesday: "97438"
],
day_of_week: "perday",
started_at: "2022-09-14"
}
And what it received in Laravel Backend when I dump the data is this:
^ array:3 [
"started_at" => "2022-09-14"
"day_of_week" => "perday"
"amount" => []
]
So My Question Is, what did I do wrong? How is the "amount" part not included on the backend? It already looks right on the console.log. I do appreciate help here. I reckon I did a mistake on how to write the model name in the array part inside HTML, but still, I do not know how to do it right.
THANK YOU IN ADVANCE 🙏🏼🙏🏼🙏🏼
This is how I submit my form:
HTML Vue Page:
<div class="card-body p-9">
<div class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Type of Rate</div>
</div
<div class="col-xl-9 fv-row">
<VueMultiselect v-model="day_of_week" label="name" track-by="name" placeholder="Select Type" :options="type_of_day" :close-on-select="true">
<template slot="singleLabel" slot-scope="{ type_of_day }"><strong>{{ type_of_day.name }}</strong></template>
</VueMultiselect>
</div>
</div>
<div class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Started Date</div>
</div
<div class="col-xl-9 fv-row">
<div class="position-relative d-flex align-items-center">
<input v-model="ticketing.started_at" class="form-control form-control-solid ps-12" name="date" placeholder="Pick Start date" id="kt_datepicker_1" />
</div>
</div>
</div>
<div v-if="day_of_week">
<div v-if="day_of_week.value == 'allday'" class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Entrance Rate</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.allday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
</div>
<div v-if="day_of_week.value == 'perday'" class="row mb-8">
<div class="col-12 mb-8">
<div class="fs-6 fw-bold mt-2 mb-3">Entrance Rate Per Day of Week</div>
</div>
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Monday</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.day.monday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Tuesday</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.day.tuesday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end pb-6 px-9">
<span #click="storeNewRate" class="btn btn-primary">Create New Rate</span>
</div>
</template>
This Is the script Part
<script>
import { onMounted, toRaw } from "vue";
import useTicketing from "../../../composable/ticketing";
export default {
data() {
const { storeTicketing, ticketing } = useTicketing()
const storeNewRate = async () => {
let type = this.day_of_week.value
let submittedAmount = []
type == 'allday' ? submittedAmount = this.amount.allday : submittedAmount = this.amount.day
this.ticketing.day_of_week = this.day_of_week.value
this.ticketing.amount = submittedAmount
console.log('submit: ', toRaw(ticketing))
await storeTicketing({...ticketing})
}
return {
type_of_day: [
{
"name": "All Days of Week",
"value": "allday"
},
{
"name": "Rate per Day",
"value": "perday"
},
],
started_at: '',
day_of_week: '',
amount: {
allday: '',
day: []
},
storeNewRate,
ticketing
}
}
}
</script>
And I have Separate file for handling the API:
export default function usePlan() {
const ticketing = reactive({});
const router = useRouter();
let toastr = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 5000
})
const storeTicketing = async (data) => {
let errors = ''
try {
let response = await axios.post('/api/ticketing/create', data);
ticketing.value = response.data;
await toastr.fire("Success", "New Rate Has Been Created!", "success");
} catch (e) {
if(e.response.status === 422) {
for(const key in e.response.data.message) {
errors = e.response.data.message[key][0];
toastr.fire("Failed", errors, "error");
}
errors = ''
}
}
}
return {
storeTicketing,
ticketing
}
}
🙏🏼
I am new to React.js and I was creating a notes takign website using React.js and Bootstrap. I was saving the notes data in local storage for simplicity but I will upgrade this app to save the notes data in Firebase. However, when I was trying to load the notes from local storage and showing them in the a div element of id 'notes' it gives error that: TypeError: Cannot set property 'innerHTML' of null
I want to run the load notes function after all the html or every thing else is loaded.
When I run this function using button click listener after all the HTML is laoded it works.
The code is:
import React from 'react';
import './Home.css';
function Home() {
const loadNotes = () => {
let notes = localStorage.getItem('notes');
let titles = localStorage.getItem('titles');
let notesObj;
let titlesObj;
if (notes == null) {
notesObj = [];
}
else {
notesObj = JSON.parse(notes);
}
if (titles == null) {
titlesObj = [];
}
else {
titlesObj = JSON.parse(titles);
}
let html = '';
notesObj.forEach(function (element, index) {
html += `<div class="noteCard my-2 mx-2 card" style="width: 18rem;" id = ${index}>
<div class="card-body">
<h5 class="card-title">Note</h5>
<p class="card-text">${element}</p>
<button id = ${index} onclick = 'deleteNote(this.id)' class="btn btn-primary"><i class="fa fa-trash" style="margin-right: 10px;"></i>Delete Note</button>
</div>
</div>`
});
titlesObj.forEach(function (er, i) {
html = html.replace('<h5 class="card-title">Note</h5>', `<h5 class="card-title">${er}</h5>`);
});
let notesElm = document.getElementById('notes');
if (notesObj.length != 0) {
notesElm.innerHTML = html;
}
else {
notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
}
console.log('Notes shown.')
}
const addNote = () => {
let addTxt = document.getElementById('addTxt');
let notes = localStorage.getItem('notes');
let addTitle = document.getElementById('addTitle');
let titles = localStorage.getItem('titles');
let notesObj;
let titlesObj;
if (notes == null) {
notesObj = [];
}
else {
notesObj = JSON.parse(notes);
}
if (titles == null) {
titlesObj = [];
}
else {
titlesObj = JSON.parse(titles);
}
notesObj.push(addTxt.value);
titlesObj.push(addTitle.value);
localStorage.setItem('notes', JSON.stringify(notesObj));
localStorage.setItem('titles', JSON.stringify(titlesObj));
addTxt.value = '';
addTitle.value = '';
loadNotes();
console.log("Note added.")
}
return (
<div className="home">
<style type="text/css">
{`
.btn {
margin-right: 10px;t
}
.home__mainTitle {
margin-top: 60px;
}
`}
</style>
<div class="container my-3">
<h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
<div class="card">
<div class="card-body" id = 'editor'>
<h5 class="card-title">Add title</h5>
<div class="form-group">
<input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
</div>
<h5 class="card-title">Add notes</h5>
<div class="form-group">
<textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
</div>
<button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
<button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
</div>
</div>
<h1 className='home__notesTitle'>Your Notes</h1>
<hr/>
<div id="notes" class="row container-fluid">
{/* <!-- <div class="noteCard my-2 mx-2 card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Note 1</h5>
<p class="card-text"></p>
Delete Note
</div>
</div> --> */}
{ loadNotes() }
</div>
</div>
</div>
);
}
export default Home;
Thanks and sorry from any bad mistakes in code or description of problem.
By trying to access the DOM and set innerHTML directly, you're sort of fighting against some of the general principals of React.
In this specific case, it's failing because the div doesn't actually exist in the DOM when you first try to mutate it.
Take a look at this very partial refactor:
import React, { useEffect, useState } from 'react';
import './Home.css';
function Home() {
const [notes, setNotes] = useState([]);
const [titles, setTitles] = useState([]);
useEffect(() => {
setNotes(JSON.parse(localStorage.getItem('notes')) ?? []);
setTitles(JSON.parse(localStorage.getItem('titles')) ?? []);
},[]);
const addNote = () => {
let addTxt = document.getElementById('addTxt');
let addTitle = document.getElementById('addTitle');
let newTitles = [...titles, addTitle.value];
let newNotes = [...notes, addTxt.value];
localStorage.setItem('notes', JSON.stringify(newNotes));
localStorage.setItem('titles', JSON.stringify(newTitles));
setNotes(newNotes)
setTitles(newTitles)
addTxt.value = '';
addTitle.value = '';
console.log("Note added.")
}
return (
<div className="home">
<style type="text/css">
{`
.btn {
margin-right: 10px;t
}
.home__mainTitle {
margin-top: 60px;
}
`}
</style>
<div class="container my-3">
<h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
<div class="card">
<div class="card-body" id = 'editor'>
<h5 class="card-title">Add title</h5>
<div class="form-group">
<input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
</div>
<h5 class="card-title">Add notes</h5>
<div class="form-group">
<textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
</div>
<button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
<button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
</div>
</div>
<h1 className='home__notesTitle'>Your Notes</h1>
<hr/>
<div id="notes" class="row container-fluid">
{notes.map((note,index) => {
return <div class="noteCard my-2 mx-2 card" style={{width: '18rem'}} id={index}>
<div class="card-body">
<h5 class="card-title">{titles[index]}</h5>
<p class="card-text">{note}</p>
<button id={index} onclick='deleteNote(this.id)'
class="btn btn-primary"><i class="fa fa-trash" style={{marginRight: "10px;"}}></i>Delete Note</button>
</div>
</div>
})}
{notes.length === 0 ? <h4>Nothing to show here.</h4> : null}
</div>
</div>
</div>
);
}
export default Home;
Note how I'm using useState to store the notes and titles. Documentation: https://reactjs.org/docs/hooks-state.html
The useEffect is called when the component is mounted and loads the data from localStorage. https://reactjs.org/docs/hooks-effect.html
Then, in the body of the render, instead of calling loadNotes and trying to mutate a DOM that doesn't exist yet, I just map the notes and titles into the rendered content.
Note that this is not a complete refactor yet. For example, you may want to add listeners to your text area to keep track of the content automatically rather than pulling the content with document.getElementById. Also, delete hasn't been implemented yet, the test for localStorage content in useEffect is pretty minimal, etc. But, it's enough to get you started.
This error occurs because loadNotes() is wrapped inside notes div. So, you can check if the notes div is created in first place with if condition.
if (notesElm) {
if (notesObj.length != 0) {
notesElm.innerHTML = html;
}
else {
notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
}
}
Below is the working code:
import React from 'react';
import './Home.css';
function Home() {
const loadNotes = () => {
let notes = localStorage.getItem('notes');
let titles = localStorage.getItem('titles');
let notesObj;
let titlesObj;
if (notes == null) {
notesObj = [];
}
else {
notesObj = JSON.parse(notes);
}
if (titles == null) {
titlesObj = [];
}
else {
titlesObj = JSON.parse(titles);
}
let html = '';
notesObj.forEach(function (element, index) {
html += `<div class="noteCard my-2 mx-2 card" style="width: 18rem;" id = ${index}>
<div class="card-body">
<h5 class="card-title">Note</h5>
<p class="card-text">${element}</p>
<button id = ${index} onclick = 'deleteNote(this.id)' class="btn btn-primary"><i class="fa fa-trash" style="margin-right: 10px;"></i>Delete Note</button>
</div>
</div>`
});
titlesObj.forEach(function (er, i) {
html = html.replace('<h5 class="card-title">Note</h5>', `<h5 class="card-title">${er}</h5>`);
});
let notesElm = document.getElementById('notes');
if (notesElm) {
if (notesObj.length != 0) {
notesElm.innerHTML = html;
}
else {
notesElm.innerHTML = `<h4>Nothing to show here.</h4>`;
}
}
console.log('Notes shown.')
}
const addNote = () => {
let addTxt = document.getElementById('addTxt');
let notes = localStorage.getItem('notes');
let addTitle = document.getElementById('addTitle');
let titles = localStorage.getItem('titles');
let notesObj;
let titlesObj;
if (notes == null) {
notesObj = [];
}
else {
notesObj = JSON.parse(notes);
}
if (titles == null) {
titlesObj = [];
}
else {
titlesObj = JSON.parse(titles);
}
notesObj.push(addTxt.value);
titlesObj.push(addTitle.value);
localStorage.setItem('notes', JSON.stringify(notesObj));
localStorage.setItem('titles', JSON.stringify(titlesObj));
addTxt.value = '';
addTitle.value = '';
loadNotes();
console.log("Note added.")
}
return (
<div className="home">
<style type="text/css">
{`
.btn {
margin-right: 10px;t
}
.home__mainTitle {
margin-top: 60px;
}
`}
</style>
<div class="container my-3">
<h1 class='home__mainTitle'>Welcome to Magic Notes</h1>
<div class="card">
<div class="card-body" id = 'editor'>
<h5 class="card-title">Add title</h5>
<div class="form-group">
<input className='home__formInput' type="text" class="form-control" id="addTitle" rows="3" placeholder="Title"></input>
</div>
<h5 class="card-title">Add notes</h5>
<div class="form-group">
<textarea class="form-control" id="addTxt" rows="3" placeholder="Notes"></textarea>
</div>
<button class="btn btn-primary" onClick={ addNote } id='addBtn'><i class="fa fa-plus-square"></i>Add Note</button>
<button class="btn btn-primary" id='clearAllBtn'><i class="fa fa-eraser"></i>Clear All</button>
</div>
</div>
<h1 className='home__notesTitle'>Your Notes</h1>
<hr/>
<div id="notes" class="row container-fluid">
{/* <!-- <div class="noteCard my-2 mx-2 card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Note 1</h5>
<p class="card-text"></p>
Delete Note
</div>
</div> --> */}
{ loadNotes() }
</div>
</div>
</div>
);
}
export default Home;
So it's all in the title, I'll put my code right here:
{% extends "list/base.html" %}
{% block content %}
<main class="site-main" style="background-color: black;">
<!--Banner Area-->
<section class="site-banner pb-5" style="background-color: black; overflow: hidden; position: relative; color: white;">
<div class="container">
<h3>Liste</h3>
<p class="total" style="color: black; display: none;">{{ total }}</p>
{% for movie in movies %}
<div class="c">
<img src="https://via.placeholder.com/1000x1481" alt="Avatar" class="image">
<div class="overlay">
<a data-modal-target="#cont" class="icon" title="User Profile" style="display: block;">
<i class="fa fa-play" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="cont{{movie.id}}" id="cont{{movie.id}}">
<div class="row">
<div class="col-lg-4">
<img src="https://via.placeholder.com/1000x1481" alt="Avatar" class="image">
</div>
<div class="col-lg-8">
<div class="modal-header">
<div class="title">{{ movie.title }}</div>
<a data-close-button class="close-button">×</a>
</div>
<span>Sorti en {{ movie.date_released|date:"Y" }}</span> <span>avec {{ movie.author}}</span> <span class="id">{{movie.id}}</span>
<p>{{movie.synopsis}}</p>
</div>
</div>
</div>
{% endfor %}
<div id="ov"></div>
</div>
</section>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
const openModalButtons = document.querySelectorAll('[data-modal-target]');
const closeModalButtons = document.querySelectorAll('[data-close-button]');
const overlay = document.getElementById('ov');
var d = document.querySelector('.total').innerText;
function popup() {
for (i=1, c = d; i<c; i++) {
const cont = addEventListener(document.querySelector(".cont"+i));
console.log(cont);
cont.style.background = "red";
cont.style.color = "white";
cont.style.width = "75%";
cont.style.transform = "translate(-50%, -50%) scale(0)";
cont.style.position = "fixed";
cont.style.top = "50%";
cont.style.left = "50%";
cont.style.padding = "20px";
cont.style.zindex = "10";
cont.style.overflow = "hidden";
cont.style.maxWidth = "80%";
cont.style.borderRadius = "30px";
const contAct = document.querySelectorAll(".cont"+i+".active");
contAct.style.transform = "translate(-50%, -50%) scale(1)";
contAct.style.display = "block";
openModalButtons.forEach(button => {
button.addEventListener('click', () => {
const modal = document.querySelector(button.dataset.modalTarget);
openModal(modal);
})
})
overlay.addEventListener('click', () => {
const modals = document.querySelectorAll(cont+".active");
modals.forEach(modal => {
closeModal(modal);
})
})
closeModalButtons.forEach(button => {
button.addEventListener('click', () => {
const modal = button.closest(cont);
closeModal(modal);
})
})
document.addEventListener("keydown", function(event) {
if (event.keyCode == 27) {
const modals = document.querySelectorAll(cont+".active");
modals.forEach(modal => {
closeModal(modal);
})
}
});
function openModal(modal) {
if (modal == null) return
modal.classList.add('active');
overlay.classList.add('active');
}
function closeModal(modal) {
if (modal == null) return
modal.classList.remove('active');
overlay.classList.remove('active');
}
}
}
</script>
{% endblock content %}
When I add to my div, "{{ movie.id}}" to make cont1, cont2... in my javascript script, it adds to my div, the desired css properties except that it doesn't do anything and I don't understand why.
I hope you'll be able to help me, if you have more info, don't hesitate to ask me.
here is the problem
const cont = addEventListener(document.querySelector(".cont"+i));
const cont = document.querySelector(".cont"+i);
I am building an e-commerce website using vue.js and an api as backend,
i have a root component called Main.vue
which had a navigation of router-link and a body of router-view.
in a route called Cart.vue when a user updated quantity of some product i need Cart.vue to $emit an event to the root component Main.vue to trigger a function called getCartTotal()
hint Cart.vue is not a child component of Main.vue
Main.vue :
<template>
<div>
<div>
<div v-on:getCartAgain="getCartTotal()" id="top-header" class="has-background-ishtari">
<div class="container" style="padding:5px 0">
<div class="is-hidden-mobile" style="font-size: 13px">
<div
class="has-text-white has-text-right"
style="display: flex;align-items: center;justify-content: flex-end"
>
<i class="icon material-icons" style="margin-right: 0px;">attach_money</i>
<span style="margin-right:15px">Best Deals</span>
<i class="icon material-icons" style="margin-right: 5px;">low_priority</i>
<span style="margin-right: 15px">Free & Easy Returns</span>
<i class="icon material-icons" style="margin-right: 5px;">local_shipping</i>
<span>Free Delivery (OVER $100)</span>
</div>
</div>
</div>
<div class="container" style="padding:10px 0">
<div style="display: flex;justify-content: space-between;align-items: center;">
<div id="header-logo" #click="openHomePage()">
<img src="../assets/images/logo-ishtari.png" class width="140" />
</div>
<div style="flex-grow: 2;margin:0 40px">
<p class="control is-expanded">
<input
id="header-search"
class="input is-radiusless"
style="height: 35px;"
type="text"
placeholder="What are you looking for?"
/>
</p>
</div>
<div
class="has-text-white"
style="display: flex;align-items: center;justify-content: space-between"
>
<div style="display: flex;align-items: center;padding-right:10px">
<span>Login Or SignUp</span>
<i class="icon material-icons">arrow_drop_down</i>
</div>
<div
id="cart-container"
style="display: flex;align-items: center;padding-left: 15px;border-left:1px solid rgba(255,255,255,0.5)"
#click="openCartPage()"
>
<span style="margin-right:5px">Cart</span>
<span>
<i class="icon material-icons">shopping_cart</i>
<span
class="has-background-ishtari-blue is-paddingless"
:class="this.cartCount.length == 0 ? 'button is-loading' : ''"
id="cart-total"
>{{this.cartCount}}</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<vue-page-transition name="fade-in-right">
<router-view></router-view>
</vue-page-transition>
</div>
</template>
<script>
import VueCookies from "vue-cookies";
export default {
data() {
return {
showNav: false,
cartCount: "",
readyToken: false
};
},
created() {
this.checkToken();
},
mounted() {
this.getCartTotal();
},
methods: {
openCartPage() {
this.$router.push({ name: "cart" }).catch(err => {
return err;
});
},
openHomePage() {
this.$router.push({ name: "home" }).catch(err => {
return err;
});
},
checkToken() {
if (!VueCookies.isKey("token")) {
let requestBody = {
client_id: "shopping_oauth_client",
client_secret: "shopping_oauth_secret"
};
let requestHeader = {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic c2hvcHBpbmdfb2F1dGhfY2xpZW50OnNob3BwaW5nX29hdXRoX3NlY3JldA",
"Access-Control-Allow-Origin": "*",
"Cache-Control": null,
"X-Requested-With": null
};
window.axios
.post(window.main_urls["get-token"], requestBody, {
headers: requestHeader
})
.then(response => {
VueCookies.set("token", response.data.access_token);
});
} else {
console.log(VueCookies.get("token"));
}
},
getCartTotal() {
console.log("here");
let requestHeader = {
Authorization: "Bearer " + VueCookies.get("token"),
"Access-Control-Allow-Origin": "*",
"Cache-Control": null,
"X-Requested-With": null
};
window.axios
.get(window.main_urls["get-cart"], { headers: requestHeader })
.then(response => {
if (response.data.error === "Cart is empty") {
console.log(response.data);
this.cartCount = 0;
} else {
this.cartCount = response.data.data.products.length.toString();
}
});
}
}
};
</script>
<style>
</style>
And Cart.vue :
<template>
<div class="has-background-ishtari-grey">
<div class="container">
<div>
<section v-if="this.loading || this.emptyCart" class="hero is-fullheight-with-navbar">
<div
v-if="this.loading"
class="button is-loading hero is-fullheight-with-navbar has-background-ishtari-grey"
style="border: none"
>Please Wait</div>
<div v-if="! this.loading && this.emptyCart" class="hero-body">
<div class="container has-text-centered">
<i class="material-icons has-text-grey" style="font-size: 80px">shopping_cart</i>
<h1 class="title has-text-grey has-text-weight-bold is-4">Your Shopping Cart Is Empty</h1>
<h2 class="subtitle title has-text-grey is-6">what are you waiting for</h2>
<br />
<button
#click="goHome()"
class="button is-ishtari-blue is-outlined has-text-weight-bold is-7"
>START SHOPPING</button>
</div>
</div>
</section>
</div>
<div class="section" v-if="! this.loading && !this.emptyCart">
<div class="columns is-bordered" id="cart-products-container">
<div class="column is-9" style="margin-right:20px">
<h1
class="subtitle has-text-weight-bold is-4"
>My Cart ({{this.cartData.products.length}} items)</h1>
<img
src="https://storage.googleapis.com/noon-cdn-res/rn/banners/en_disclaimer-cart-desktop.gif"
style="margin-bottom:15px"
/>
<div
class="cart-product-row has-background-white"
style="padding:10px 15px"
v-for="product in cartData.products"
:key="product.product_id"
>
<div
class="columns padding-top:20px"
:class="product.stock ? '' : 'has-background-danger'"
>
<div class="image is-128x128 column is-narrow">
<img :src="product.thumb" />
</div>
<div
class="column"
style="display:flex;flex-direction:column;justify-content:space-between"
>
<p
class="has-text-grey subtitle is-7 is-marginless"
style="margin-bottom:10px !important"
>{{product.model}}</p>
<p class="has-text-weight-bold" style="font-size:14px">{{product.name}}</p>
<p>
<i #click="product.quantity = 0;updateCartQuantity(product.key,0)" class="material-icons has-text-grey" id="cart-delete">delete</i>
</p>
</div>
<div
class="column is-narrow"
style="padding-left:15px;display:flex;flex-direction:column;justify-content:center"
>
<p class="has-text-weight-bold">{{product.price}}</p>
</div>
<div
class="column is-narrow"
style="display:flex;flex-direction:column;justify-content:center"
>
<div class="field has-addons">
<p class="control">
<a
#click="product.quantity = Number(product.quantity) - 1;updateCartQuantity(product.key,product.quantity)"
class="button"
>-</a>
</p>
<p class="control">
<input
class="input has-text-centered"
type="text"
placeholder="0"
style="width:80px"
readonly
:value="product.quantity"
:ref="product.product_id"
/>
</p>
<p class="control">
<a
class="button"
#click="product.quantity = Number(product.quantity) + 1;updateCartQuantity(product.key,product.quantity)"
>+</a>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="column is-narrow" style=" align-self:flex-start;margin-top:10px">
<div style="border:1px solid #eee; background:#f7f9fe; padding:20px 15px">
<div class="field has-addons">
<p class="control">
<input class="input is-ishtari-green" type="text" placeholder="Coupon Code" />
</p>
<p class="control">
<a class="button is-ishtari-green">Apply</a>
</p>
</div>
<div class="columns">
<div class="column">
<p class="has-text-weight-bold">Order Summary</p>
</div>
</div>
<div class="columns"></div>
<div v-for="total in cartData.totals" :key="total.code">
<div class="columns">
<div class="column">
<p>{{total.title}}</p>
</div>
<div class="column is-narrow">
<p>{{total.text}}</p>
</div>
</div>
</div>
</div>
<button
class="button is-ishtari-blue has-text-weight-bold"
style="display:block;width:100%;margin-top:10px"
>CHECKOUT NOW</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import VueCookies from "vue-cookies";
export default {
data() {
return {
cartData: [],
emptyCart: false,
loading: true
};
},
created() {
this.getCartContent();
},
methods: {
goHome() {
this.$router.push({ name: "home" }).catch(err => {
return err;
});
},
getCartContent() {
let requestHeader = {
Authorization: "Bearer " + VueCookies.get("token")
};
window.axios
.get(window.main_urls["get-cart"], { headers: requestHeader })
.then(response => {
if (response.data.error === "Cart is empty") {
this.emptyCart = true;
this.loading = false;
} else {
this.loading = false;
this.cartData = response.data.data;
}
});
},
updateCartQuantity(pkey, pquan) {
this.loading = true;
let requestHeader = {
Authorization: "Bearer " + VueCookies.get("token")
};
let requestBody = {
key: pkey.toString(),
quantity: pquan.toString()
};
window.axios
.put(window.main_urls["get-cart"], requestBody, {
headers: requestHeader
})
.then(response => {
if (response.data.success == "1") {
this.getCartContent();
this.$emit("getCartAgain");
}
});
},
}
};
</script>
<style scoped la>
.cart-product-row {
border-bottom: 1px solid #eee;
padding: 10px 0;
}
.cart-product-row:last-of-type {
border-bottom: none;
}
#cart-delete {
font-size: 20px;
cursor: pointer;
}
#cart-delete:hover{
transform: scale(1.05);
}
</style>
I need the function called updateCartQuantity() in Cart.vue to trigger the function called getCartTotal() in Main.vue
Vuex may be a bit more than you need here. You can just use the pub/sub technique by creating a simple file that exports a new vue instance, you can then listen to events emitted on that Vue instance:
//bus.js
import Vue from 'vue'
export default new Vue()
Then in your Main.vue you can import that:
import EventBus from '#/path/to/bus'
And in your created hook you can set up a listener:
created() {
EventBus.$on('refresh_cart_total', this.getCartTotal)
}
And you can $emit that event from your Cart.vue when you need to. Once again, import the bus:
import EventBus from '#/path/to/bus'
Then in your Cart.vue call it whenever you need:
EventBus.$emit('refresh_cart_total')
Now you have a bi-directional pub/sub system in place and you don't need to introduce Vuex for a simple task such as this.
Bonus
To keep things DRY you could also implement constants within your bus.js, such as:
export const REFRESH_CART_TOTAL = 'refresh_cart_total'
And now you can use import * as CART_CONSTANTS from '/path/to/bus' and use this method:
EventBus.$on(CART_CONSTANTS.REFRESH_CART_TOTAL, this.getCartTotal)
and:
EventBus.$emit(CART_CONSTANTS.REFRESH_CART_TOTAL)