onclick function is running only once - javascript

I am using an edit button to edit the posts but when I am clicking the button the onclick function executes and it works perfectly like it edits the post and updates the post content(not from backend).
But the problem is when I click the edit button again the onclick function is not running.
My HTML code :-
<div class="post">
<b>{{post.author}}</b>
<input type="hidden" name="postid" value={{post.id}}>
<!-- <br><br> -->
<p>{{post.content}}</p>
{% csrf_token %}
<textarea name="edit_content" id="edit_content" cols="50" rows="5"></textarea>
{% if user.is_authenticated %}
{% if post.author == user %}
<button class="edit" onclick="edit(this)">Edit</button>
{% endif %}
{% endif %}
<p><small class="text-muted">{{post.created_on}}</small></p>
Here textarea display is set to 'none'.
My javascript code :-
function edit(ele){
var parent = ele.parentNode;
console.log(ele.parentNode);
var post = parent.querySelector('p');
var textarea = parent.querySelector('textarea');
var id = parent.querySelector('input');
post.style.display = 'none';
textarea.style.display = 'block';
ele.innerHTML = "Save";
ele.id = "save";
ele.disabled = true;
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
textarea.onkeyup = () => {
if( textarea.value.length > 0){
ele.disabled = false;
}
else{
ele.disabled = true;
}
}
var save = document.getElementById('save');
save.onclick = () => {
fetch("http://127.0.0.1:8000/edit", {
method : "PUT",
headers: {'X-CSRFToken': csrftoken},
body : JSON.stringify({
"postdata" : textarea.value,
"id" : id.value
})
}).then(() => {
post.innerHTML = textarea.value;
// textarea.value = '';
post.style.display = "block";
textarea.style.display = "none";
ele.innerHTML = "Edit";
save.removeAttribute('id');
});
}
}

I suggest you keep the two buttons, edit and save, separated and you hide/show one of them according to the current state.
In this demo I slightly rewrote your code so that the initial state is read only showing off the <p> with text content. When you'll press the edit button, the paragraph will be hidden and the textarea with its content will show up giving you the opportunity to edit the text. At the same time the edit/save buttons will flip their visibility so that at this point when you'll press save the reverse action will be performed just after successfully calling the web api.
This way you have two separated elements you can style independently and two different functions for the corresponding click events (save and edit).
As an added bonus this code doesn't deal with ids so that it could scale with multiple posts on the same page. The csrf hidden field would be the only exception.
function save(target){
const parent = target.parentNode;
const post = parent.querySelector('p');
const textarea = parent.querySelector('textarea');
const id = parent.querySelector('input');
const edit = parent.querySelector('button.edit');
const url = "http://127.0.0.1:8000/edit";
const csrftoken = "";
fetch(url, {
method: "PUT",
headers: {
'X-CSRFToken': csrftoken
},
body: JSON.stringify({
"postdata": textarea.value,
"id": id.value
})
})
//I used finally instead of then to deal with the fact that the api url will fail
.finally(() => {
post.innerText = textarea.value;
post.style.display = "block";
textarea.style.display = "none";
target.style.display = 'none';
edit.style.display = 'block';
});
}
function edit(target) {
const parent = target.parentNode;
const post = parent.querySelector('p');
const textarea = parent.querySelector('textarea');
const id = parent.querySelector('input');
const save = parent.querySelector('button.save');
post.style.display = 'none';
textarea.style.display = 'block';
textarea.value = post.innerText;
target.style.display = 'none';
save.style.display = 'block';
}
button.save{
display: none;
cursor: pointer;
}
button.edit{
display: block;
cursor: pointer;
}
textarea.edit_content{
display: none;
}
<div class="post">
<b>{{post.author}}</b>
<input type="hidden" name="postid" value={{post.id}}>
<p>{{post.content}}</p>
<textarea name="edit_content" class="edit_content" cols="50" rows="5"></textarea>
<button class="edit" onclick="edit(this);">Edit</button>
<button class="save" onclick="save(this);">Save</button>
<p><small class="text-muted">{{post.created_on}}</small></p>
<input type="hidden" name="csrfmiddlewaretoken" value="bogus">
</div>

Related

JavaScript :Ajax - Symfony 6 : trying to send form with plain js ajax

I have a little problem or a big one, I don't know. I'm trying to send a form with ajax with Symfony, and native JavaScript, but I don't really know how. I managed to do ajax with GET request to try to find a city (which is included in this form).
So I've got my form, I also want to send 2 arrays 1 for images (multiple images with different input) the input are created with js via CollectionType::class, then I'm putting my images in array which I want to send.
And the other array is for the city I want my product to be in. I've got an input text and via ajax it's searching city then by clicking on the city I've got a function putting it on an array.
but now I find difficulties trying to send it everything I found on the web mostly uses jQuery.. but I want to learn JavaScript so I believe I have to train with native first.
so I tried to send my form, but nothing happened when I submit it, not even an error, it just reload the page, and in my console I've got a warning for CORB issues I think it's due to my browser blocking my request because something is wrong in it?
I'm trying to find a way to send it and save it in my database.
so here's the code:
{% extends "base.html.twig" %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-lg-6 mx-auto mt-5">
{{form_start(form)}}
{{form_errors(form)}}
{{form_row(form.title)}}
{{form_row(form.description)}}
{{form_row(form.surface)}}
{{form_row(form.piece)}}
{{form_row(form.type)}}
<div class="container">
<div class="select-btn">
<span class="btn-text d-flex">
<input type="text" oninput="getData(this.value)" class="rel" name="ville" id="">
<span class="arrow-dwn">
<i class="fa-solid fa-chevron-down"></i>
</span>
</span>
</div>
<ul class="list-items js-result"></ul>
</div>
<button type="button" class="btn btn-primary btn-new opacity-100" data-collection="#foo">ajouter une image</button>
<div id="foo" class="row" data-prototype="{{include ("include/_Addimage.inc.html.twig", {form: form.image.vars.prototype})|e("html_attr")}}" data-index="{{form.image|length > 0 ? form.image|last.vars.name +1 : 0}}">
{% for image in form.image %}
<div class="col-4">
{{ include ("include/_Addimage.inc.html.twig", {form: image}) }}
</div>
{{form_errors(form.image)}}
{% endfor %}
</div>
<div class="col-4 mt-5">
{{form_row(form.submit)}}
</div>
{{form_widget(form._token)}}
{{form_end(form, {render_rest: false})}}
</div>
</div>
</div>
{% endblock %}
here the code of my JavaScript, everything is in my twig file, because as you will see I added eventListener on some input, I didn't see a better way maybe someone can correct me.
{% block javascripts %}
<script type="text/javascript">
/////////////// GET INPUT TEXT VALUE AND SHOW A LIST OF CITIES
function getData(text) {
const param = new URLSearchParams();
param.append('text', text);
const url = new URL(window.location.href);
fetch(url.pathname + "?" + param.toString() + "&ajax=1", {
header: {
"X-Requested-With": "XMLHttpRequest"
}
})
.then(response => response.json())
.then(data => {
handle_result(data);
});
}
////////////////////////////// CREATE MY OPTIONS WITH CITIES NAME
function handle_result(response)
{
let result_div = document.querySelector(".js-result");
let str = "";
for (let i = response.length - 1; i >= 0; i--) {
str += "<option" + ' ' + "onclick=" + "addTag(this.value)" + ' ' + "class=" + "item" + ' ' + "value=" + response[i].ville + ">" + response[i].ville + "</option>";
}
result_div.innerHTML = str;
};
// //////////////////////////// ADD THE CITY NAME IN A CONTAINER WHEN I USER CLICK ON IT
const selectBtn = document.querySelector(".select-btn");
const rel = document.querySelector(".rel");
items = document.querySelectorAll(".item");
rel.addEventListener("click", () => {
selectBtn.classList.toggle("open");
});
function createTag(label) {
const div = document.createElement('div');
div.setAttribute('class', 'tag');
const span = document.createElement('span');
span.innerText = label;
const closeBtn = document.createElement('i');
closeBtn.setAttribute('data-item', label);
closeBtn.setAttribute('onclick', 'remove(this)');
closeBtn.setAttribute('class', 'material-icons');
closeBtn.innerHTML = 'close';
div.appendChild(span);
div.appendChild(closeBtn);
return div;
}
btnText = document.querySelector(".btn-text");
let tags = [];
function addTags()
{
reset();
for (let a of tags.slice().reverse()) {
const tag = createTag(a);
selectBtn.prepend(tag);
}
}
function addTag(value) {
input = document.querySelector('.rel');
console.log(input);
if (tags.includes(value)) {
alreadyExist(value);
}
else {
tags.shift();
tags.push(value);
addTags();
}
input.value = "";
}
function alreadyExist(value) {
const index = tags.indexOf(value);
tags = [
... tags.slice(0, index),
... tags.slice(index + 1)
];
addTags();
}
function reset() {
document.querySelectorAll('.tag').forEach(function (tag) {
tag.parentElement.removeChild(tag);
})
}
function remove(value) {
const data = value.getAttribute('data-item');
const index = tags.indexOf(data);
tags = [
... tags.slice(0, index),
... tags.slice(index + 1)
];
addTags();
}
//////////////////////////////////////////////////////// CREATING IMAGE ARRAY TO SEND WITH AJAX REQUEST ?
images = [];
function image_to_array(value) {
if(!images.includes(value))
{
images.push(value);
}else{
return false;
}
}
const form =
{
title: document.getElementById('product_form_title'),
description: document.getElementById('product_form_description'),
surface: document.getElementById('product_form_surface'),
piece: document.getElementById('product_form_piece'),
type: document.getElementById('product_form_type'),
}
const submit = document.getElementById('submit', () => {
const request = new XMLHttpRequest();
const url = new URL(window.location.href);
const requestData =
`
title=${form.title.value}&
description=${form.description.value}&
surface=${form.surface.value}&
piece=${form.piece.value}&
image=${JSON.stringify(images)}&
type=${JSON.stringify(tags)}&
ville=${tags}
`;
fetch(requestData , url.pathname ,{
header: {
"X-Requested-With": "XMLHttpRequest"
}
})
request.addEventListener('load', function(event) {
console.log(requestData);
});
request.addEventListener('error', function(event) {
console.log(requestData);
});
});
////////////////////////////// CREATE NEW FILE INPUT
const newItem = (e) => {
const collectionHolder = document.querySelector(e.currentTarget.dataset.collection);
const item = document.createElement('div');
item.classList.add('col-4');
item.innerHTML = collectionHolder.dataset.prototype.replace(/__name__/g, collectionHolder.dataset.index);
item.querySelector('.btn-remove').addEventListener('click', () => item.remove());
collectionHolder.appendChild(item);
collectionHolder.dataset.index ++;
}
document.querySelectorAll('.btn-new').forEach(btn => btn.addEventListener('click', newItem));
</script>
{% endblock %}
here my controller but I don't think it is the issue, I didn't finish it since I'm quite lost on the js part
class AdminController extends AbstractController
{
#[Route('/admin/create_product', name: 'create_product', methods: ['POST', 'GET'])]
public function createProduct(EntityManagerInterface $em, SluggerInterface $slugger, Request $request, LieuxRepository $villeRepo, SerializerInterface $serializer): Response
{
$product = new Product;
$ville = new Lieux;
$form = $this->createForm(ProductFormType::class, $product);
$form->handleRequest($request);
$list = $villeRepo->findAll();
$query =$request->get('text');
if($request->get('ajax')){
return $this->json(
json_decode(
$serializer->serialize(
$villeRepo->handleSearch($query),
'json',
[AbstractNormalizer::IGNORED_ATTRIBUTES=>['region', 'departement', 'products']]
), JSON_OBJECT_AS_ARRAY
)
);
}
if($request->isXmlHttpRequest())
{
if ($form->isSubmitted() && $form->isValid()) {
$product->setCreatedAt(new DateTime());
$product->setUpdatedAt(new DateTime());
$product->setVille($form->get('ville')->getData());
$product->setType($form->get('type')->getData());
$em->persist($product);
$em->flush();
}
}
return $this->render(
'admin/create_product.html.twig',
['form' => $form->createView() ]
);
}
hope it's clear, thank you

How to loop over dynamically generated input fields?

I am generating some input fields dynamically on my page, and I want to grab inputs from them to store in localStorage if this way works if not? suggest a way around, how can this be done? also how can i add a event listener to submit button ? followings are code have a look at it and give some suggestions/improvisations.
..
HTML
<div id="warnMessage"></div>
<div class="add"></div>
<div class="inputs">
<input
type="text"
maxlength="1"
id="inputValue"
/>
<button class="btn" type="button">+</button>
</div>
javascript
const div = document.querySelector(".add");
const add = document
.querySelector(".btn")
.addEventListener("click", addingInps);
function addingInps() {
const inputValue = parseInt(document.getElementById("inputValue").value);
if (isNaN(inputValue)) {
document.getElementById("warnMessage").innerText = "Enter Again";
document.getElementById("inputValue").value = "";
} else {
const form = document.createElement("form");
form.method = "post";
form.action = "#";
for (let i = 0; i < inputValue; i++) {
const inp = document.createElement("input");
inp.type = "text";
inp.maxLength = "12";
inp.required = true;
inp.className = "inp";
const br = document.createElement("br");
form.appendChild(br.cloneNode());
form.appendChild(inp);
form.appendChild(br.cloneNode());
div.appendChild(form);
document.querySelector("#inputValue").style.display = "none";
}
const sub = document.createElement("button");
sub.className = "subButton";
sub.type = "button";
sub.value = "button";
sub.textContent = "Submit"
form.appendChild(sub);
}
}
You are loop through an input ...not an array or nodelist.
It cant work
I think it would be easier if you appended an ID with every new input field you made
for(let i=0;i < inputValue;i++){
// create your element ipt
ipt.setAttribute("id","autogenerated_" + i);
}
and grab value based on id
document.getElementById("autogenerated_x").value();
about setting an event listener, I can't think any other way of the classic
btn.addEventListener("click", function(e){
// your functionality
});

Posting Comment to Backend

Attempting to post a comment on a portrait correctly using a JavasScript Frontend with a Rails backend. The comment is posting, however on the frontend its showing it incorrectly. When the user creates a comment, the comment does show however it always shows on the first portrait comment section when its posted. However once you re-fresh the page it shows under the correct portrait. I assume this is something to do with the way I am posting it to my backend in my POST. This is what my current portraits code looks like used to build the portraits...
const buildPortrait = (portrait) => {
let div = document.createElement('div')
div.className = 'card'
div.id = portrait.id
div.innerHTML = `
<i class="far fa-window-close fa-1x" id="delete"></i>
<img src= ${portrait.attributes.img_url} class="profile" alt="Avatar" >
<div class="container">
<h5 class='description'>Caption: ${portrait.attributes.description}</h5>
<form data-portrait=${portrait.id} class="comment-form">
<input
class="comment-input"
type="text"
name="comment"
placeholder="Add a comment..."
/>
<button class="comment-button" type="submit">Post</button>
</form>
<div class="likes-section">
<button class="like-button"> ${portrait.attributes.like} likes ♥</button>
</div>
</div>
`
cardContainer.appendChild(div)
listenForLikes(portrait)
commentSection(portrait)
listenForComment(portrait)
listenForEditComment(portrait)
listenForDelete(portrait)
}
Below is the code I am attempting to use to build the comment and the have the POST correctly...
//create comments
function commentSection(portrait){
const newUl = document.createElement('ul')
newUl.className = 'comments'
portrait.attributes.comments.map(comment => {
let li = document.createElement('li')
li.textContent = comment.content
newUl.appendChild(li)
const editBtn = document.createElement('button')
// editBtn.className = 'edit-button'
editBtn.dataset.commentId = comment.id
editBtn.innerHTML = `
<i class="fas fa-pen-square"></i>`
// li.appendChild(editBtn)
})
const currentCard= document.getElementById(portrait.id)
const description = currentCard.querySelector('.description')
description.after(newUl)
}
//event listen for comments
function listenForComment(portrait){
const portraitComment = document.getElementById(portrait.id)
const commentForm = portraitComment.querySelector('.comment-form')
commentForm.addEventListener('submit', (e)=> {
e.preventDefault()
postComments(e)
commentForm.reset()
})
}
//fetch comments
function postComments(e){
console.log()
data = {
content: e.target[0].value,
portrait_id: e.target.dataset.portrait
}
console.log(data)
fetch(`http://localhost:3000/comments`,{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: "application/json"
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(json => {
const ul = document.querySelector('ul')
const li = document.createElement('li')
li.textContent = json["data"].attributes.content
console.log(ul)
ul.appendChild(li)
})
}
Any advice is gratefully appreciated!!
Thanks!!

Can't add onclick listener to dynamically created element

I'm trying to implement a searchbar to search adresses and show the selected place on a map.
When I type in the searchbar the results are showing in a dropdown, now I want to edit my map and other stuff when I click on a result.
But I can't manage to add a listener to the elements (which are dynamically created when I got the results of the search), no matter what I try (addEventListener, JQuery, href) when I inspect the elements on my browser no listener is attached to any of them.
Here is my html :
<div class="container p-3 text-center text-md-left clearfix">
<h1>Smart-bornes</h1>
<p>Je localise les smart-bornes les plus proches</p>
<div class="input-group dropdown mx-auto w-75 float-md-left">
<input id="localisation_barreRecherche" class="form-control" type="text" placeholder="Rechercher un lieu"/>
<button id="localisation_boutonRecherche" class="btn btn-light">Rechercher</button>
<div id="localisation_dropdownRecherche" class="dropdown-menu w-100"></div>
</div>
</div>
<div class="container p-3">
<div id="map"></div>
</div>
And my JS:
function initBarreRecherche(){
let barreRecherche = document.getElementById('localisation_barreRecherche');
let boutonRecherche = document.getElementById('localisation_boutonRecherche');
let dropdownResultats = document.getElementById('localisation_dropdownRecherche');
barreRecherche.onkeyup = function (event) {
console.log('onkeyup');
if(event.key === 'Enter'){
}else if(event.key === 'keydown'){
}else if(barreRecherche.value !== '') {
rechercheAdr(barreRecherche.value);
$(dropdownResultats).show();
}else{
$(dropdownResultats).hide();
}
};
boutonRecherche.onclick = function () {
}
}
function rechercheAdr(entree){
console.log('recherche');
return new Promise((resolve, reject) => {
fetch(rechercheURL + entree)
.then(resp => {
return resp.text();
})
.then(adressesJSON => {
let adresses = JSON.parse(adressesJSON);
let dropdownResultats = document.getElementById('localisation_dropdownRecherche');
//dropdownResultats.style.width = '100%';
dropdownResultats.style.zIndex = '10000';
let resultats = document.createElement('ul');
resultats.className = 'list-group';
if(adresses.length > 0){
for(let [key, adr] of Object.entries(adresses)){
let result = document.createElement('li');
result.className = 'list-group-item list-group-item-action';
result.href = 'javascript : resultOnClick(adr)';
result.style.paddingLeft = '5px';
result.style.paddingRight = '5px';
result.style.cursor = 'pointer';
//result.style.overflow = 'hidden';
result.innerText = adr.display_name;
result.addEventListener('click', () => console.log('onclick'));
resultats.appendChild(result);
}
}else{
console.log('aucun résultat');
let msgAucunResultat = document.createElement('li');
msgAucunResultat.style.paddingLeft = '5px';
msgAucunResultat.style.paddingRight = '5px';
msgAucunResultat.innerText = 'Aucun résultat';
resultats.appendChild(msgAucunResultat);
}
dropdownResultats.innerHTML = resultats.innerHTML;
console.log(adresses)
})
})
}
manually call initBarreRecherche() at the end of response.
.then(adressesJSON => {
///....
this.initBarreRecherche()
})
You are using jquery so instead of all 'onClick' syntax, or addEventListener, you can attach click handler like:
$(dropdownResultats).on('click', 'li', event => {
alert('some li clicked')
})

Creating two divs with a button

I am creating a simple note editor that has two divs a heading and a body. I'm trying to add a new note by creating the two divs with a button. Such that when you click the button the new divs will be created with texts appended to it through localStorage. When I click the button none of the divs is added. here is the html
<div id ="heading" contenteditable="true">
</div>
<div id="content" contenteditable="true">
</div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
here is the js
document.getElementById("heading").innerHTML = localStorage['title'] || 'Heading goes here';
document.getElementById('content').innerHTML = localStorage['text'] || 'Body of text editor';
setInterval(function () {
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}, 1000);
function addNote() {
const heading = document.createElement('div');
const content = document.createElement('div');
heading.id = "heading";
content.id = "content";
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}
your are missing to append the created elements to the dom (https://www.w3schools.com/jsref/met_node_appendchild.asp) :
function addNote() {
var heading = document.createElement('div');
var content = document.createElement('div');
heading.id = "heading";
content.id = "content";
document.getElementById("myDivs").appendChild(heading);
document.getElementById("myDivs").appendChild(content);
}
and you will need to have an div with id myDivs
<div id="myDivs"></div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
greetings
There are many problems in your code.
Proper way to set localStorage is via localStorage.setItem(key, val)
You need to append your DIVs somewhere on your HTML page.
Also you need to handle your setTimeout properly.
Please find solution below
HTML
<button type="button" onclick="addNote()" name="button">Add new Note</button>
JavaScript
var intervalObj = null;
function addNote() {
if(intervalObj) {
clearInterval(intervalObj)
}
const heading = document.createElement('div');
heading.setAttribute('contenteditable', true)
heading.id = "heading";
const content = document.createElement('div');
content.setAttribute('contenteditable', true)
content.id = "content";
heading.innerHTML = window.localStorage.getItem('title') || 'Heading goes here';
content.innerHTML = window.localStorage.getItem('text') || 'Body of text editor';
document.getElementsByTagName("body")[0].append(heading);
document.getElementsByTagName("body")[0].append(content);
intervalObj = setInterval(function () {
localStorage.setItem('title', document.getElementById('heading').innerHTML);
localStorage.setItem('text', document.getElementById('content').innerHTML);
}, 1000);
}
Instead of using the setInterval function you can load the data using an onload function and update to local storage whenever the AddNote button is clicked.
Modify the html as follows:
<body onload="load()">
<div id ="heading" contenteditable="true">
</div>
<div id="content" contenteditable="true">
</div>
<button type="button" onclick="addNote()" name="button">Add new Note</button>
</body>
And also you can modify your JS file as follows
function load() {
var headingText = localStorage['title'] || 'Heading goes here';
var bodyText = localStorage['text'] || 'Body of text editor';
document.getElementById("heading").innerHTML = headingText;
document.getElementById('content').innerHTML = bodyText;
}
function addNote() {
localStorage['title'] = document.getElementById('heading').innerHTML;
localStorage['text'] = document.getElementById('content').innerHTML;
}

Categories

Resources