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!!
Related
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>
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
I am trying to add an event listener to my "degree section div" but it is not working nor am I getting any errors. I have tried multiple ways of traversing the DOM to reach the "degree-section" div but to no avail.
Any kind of help is welcome and appreciated
Code:
let city = document.querySelector('#city');
let searchbtn = document.querySelector('.search-btn');
let city_name = document.querySelector('.city-name');
let temp = document.querySelector('.temp');
let feels_like = document.querySelector('.feels-like');
let humidity = document.querySelector('.humidity');
let locationIcon = document.querySelector('.weather-icon');
let checkbox = document.getElementById('celcius');
let weather_sec = document.querySelector('.weather-info');
let degree_section = weather_sec.firstElementChild;
let degree_section_span = degree_section.getElementsByTagName('span')[0];
//let wind = document.querySelector('.wind');
async function getUrl(city) {
try {
let theUrl = url + city + '&appid=' + apiKey;
let response = await fetch(theUrl, {
mode: 'cors'
})
let data = await response.json();
//Get data from api and change html content based on the recieved data
let temp_data = data.main.temp
temp.textContent = temp_data;
let feels_like_data = data.main.feels_like;
feels_like.textContent = feels_like_data + "K";
let humidity_data = data.main.humidity;
humidity.textContent = humidity_data;
let {
icon
} = data.weather[0];
locationIcon.innerHTML = `<img src="icons/${icon}.png">`;
//change K to C
degree_section.addEventListener('click', () => {
//logging a message just to check if it is working
console.log("c")
})
} catch (err) {
let error = document.createElement('span')
error.className = "error";
error.textContent = "Location does not exist"
let top_center_div = document.querySelector('.top-center')
top_center_div.appendChild(error)
city_name.textContent = "No city found"
}
}
searchbtn.addEventListener('click', (e) => {
let cityName = city.value;
city_name.textContent = cityName
console.log(cityName)
getUrl(cityName)
})
<body>
<div class="loc-container">
<div class="location">
<h1 class="city-name">City</h1>
<div class="weather-icon"><img src="icons/unknown.png" /></div>
</div>
</div>
<div class="weather-info">
<div class="degree-section">
<h2 class="temp">0.0</h2>
<span>K</span>
</div>
<div class="info-section">
<div class="info-flex">
<h3 class="feels-like">0K</h3>
<h4>Feels Like</h4>
</div>
<div class="info-flex">
<h3 class="humidity">0</h3>
<h4>Humidity</h4>
</div>
<div class="info-flex">
<h3 class="wind">0</h3>
<h4>Wind</h4>
</div>
</div>
</div>
<div class="top-center">
<div class="form">
<input type="text" name="city" id="city" required>
<label for="city" class="label-name"><span class="search-name">Search City...</span></label>
</div>
<!-- <i class="fas fa-search search-btn"></i> -->
<i class="material-icons search-btn" style="font-size: 35px;">search</i>
</div>
<script src="weather.js"></script>
</body>
This is what "data" looks like
{"coord":{"lon":72.8479,"lat":19.0144},"weather":[{"id":711,"main":"Smoke","description":"smoke","icon":"50d"}],"base":"stations","main":{"temp":303.14,"feels_like":303.45,"temp_min":301.09,"temp_max":303.14,"pressure":1014,"humidity":45},"visibility":2500,"wind":{"speed":3.09,"deg":120},"clouds":{"all":20},"dt":1638773692,"sys":{"type":1,"id":9052,"country":"IN","sunrise":1638754125,"sunset":1638793848},"timezone":19800,"id":1275339,"name":"Mumbai","cod":200}
Thank you in advance!
I believe the problem is with
let degree_section_span = degree_section.getElementsByTagName('span')[0];
since it selects the wrong element. Try changing it to
let degree_section_span = weather_sec.querySelector('.check');
and see if it works. You can also change the variable name to something more appropriate, while you're at it.
EDIT:
I think this is what you're trying to do. For the sake of siplicity , I removed everything not related to temp:
let target = weather_sec.querySelector("div.check"),
temp_data = data.main.temp;
temp.textContent = temp_data;
target.addEventListener('click', () => {
cel = parseInt(temp_data) - 273.15;
temp.textContent = cel.toFixed(2);
temp.nextElementSibling.textContent = "C";
});
So after 48hrs of debugging I finally figured out what is wrong. If you see in my HTML I have a div.top-center at the bottom. And due to some dimension issues in my css file the height of div.top-center spanned the entire page so essentially all of my divs were being wrapped inside div.top-center so even if I assigned a click event to my div.degree-section the target was always div.top-center and that is why the click event was not showing the proper output.
//javasciprt code to display the comment api
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
const title = json.title;
const body = json.body;
document.getElementById("printTitle").innerHTML = title;
document.getElementById("printBody").innerHTML = body;
});
//html code supporting this js file
<div >
TITLE
<span id="printTitle">
</span>
</div>
<div class="news-content-1">
BODY
<span id="printBody">
</span>
</div>
#Faizan Lambe,
Please see the below javascript snippet for your query.
let list = document.querySelector('#list');
function loadData(){
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
for(var i = 0; i < json.length; i++) {
var obj = json[I];
addList(obj);
}
});
}
// Add Lists
function addList(obj){
let li = document.createElement('li');
let p = document.createElement('p')
let t = document.createTextNode(obj.title);
p.appendChild(t);
li.appendChild(p);
list.appendChild(li);
}
-----html----
in html, I kept list tag as below,
<ul id="list">
</ul>
Using the addlist function, adding the title to this list.
Please check at your side, and mark as answered if it solves your problem.
I am working on a wikipedia viewer (https://codepen.io/rwiens/pen/YLMwBa) which is almost done but I have 2 problems:
I cannot submit my search results when I press enter. I have added an event listener and can console.log "hello: but I cannot call the searchWiki function.
When I do a new search the results are appended to the bottom pf my old results.
I've searched the web for the last half day and am stuck. Any help would be appreciated.
<div class="container">
<div class="banner text-center align-items">
<h1>Wiki Search</h1>
<p>Search for articles on Wikipedia</p>
</div>
<form action="" class="text-center">
<input type="search" id="search-box" placeholder="Search Here">
<div class="buttons">
<input type="button" onclick="searchWiki()" id="search-
button" value="Search">
<input type="submit" value="Feel Lucky?">
</div>
</form>
<div class="articles">
<ul id="results">
</ul>
</div>
</div>
<script type="test/javascript">
const searchBox = document.getElementById('search-box');
const sButton = document.getElementById('search-button');
const results = document.getElementById('results');
window.onload = function() {
searchBox.focus();
};
const searchWiki = () => {
const keyword = searchBox.value;
fetch("https://en.wikipedia.org/w/api.php?
&origin=*&action=opensearch&search=" + keyword + "&limit=5", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ query: event.currentTarget.value })
})
.then(response => response.json())
.then((data) => {
console.log(data);
build(data);
});
}
const build = (data) => {
let title = data[1];
let description = data[2];
let url = data[3];
for(let x = 0; x < 5; x++){
console.log(title);
const item = `<a href="${url[x]}" target="#">
<li>
<h5>${title[x]}</h5>
<p>${description[x]}.</p>
</li>
</a>`;
results.insertAdjacentHTML("beforeend", item);
}
}
searchBox.addEventListener("keyup", function(event) {
if (event.key === "Enter") {
searchWiki;
}
});
</script>
You are not calling searchWiki as function. Call it like this searchWiki();
Also you need to remove the form tag. Because you have button type elements in it , it is by default submitting your form on enter press.
Also clear results div before appending new data like this
results.innerHTML = ""
for(let x = 0; x < 5; x++){
console.log(title);
const item = `<a href="${url[x]}" target="#">
<li>
<h5>${title[x]}</h5>
<p>${description[x]}.</p>
</li>
</a>`;
results.insertAdjacentHTML("beforeend", item);
}
Check updated codepen
when I put searchWiki I am still not calling the search unfortunately. also, when i add results.innerHTML = "" my search only comes back with one result.
You need to add an event listener for the form submit. In that you need to cancel the event ( event.preventDefault() ).
Empty your results as #NanditaAroraSharma pointed out (best before calling build function)
Solved it. Removed the form as it was trying to send me to another page.
<div class="text-center">
<input type="search" id="search-box" placeholder="Search Here">
<div class="buttons">
<input type="button" onclick="searchWiki()" id="search-
button" value="Search">
<input type="button"
onclick="location.href='https://en.wikipedia.org/wiki/Special:Random';"
value="Feel Lucky?">
</div>
for building the html i took part of it out of the for loop.
const build = (data) => {
let title = data[1];
let description = data[2];
let url = data[3];
results.innerHTML = "";
for(let x = 0; x < 5; x++){
console.log(title);
const item = `<a href="${url[x]}" target="#">
<li>
<h5>${title[x]}</h5>
<p>${description[x]}.</p>
</li>
</a>`;
results.innerHTML += item;
}
}