I am using firebase cloud firestore to hold several candidates' names in an election. I am trying to use JavaScript to create a form that shows each candidates' name in radio buttons. I have the script connected to my form, but the radio buttons don't show. It shows the name, but not the button. The form is in a popup modal. There are no errors in the console.
Here is my code:
const splCanList = document.querySelector('#SPLInput');
const setupSPLCans = (data) => {
let html = '';
if (data.length) {
data.forEach(doc => {
const SPLCan = doc.data();
const li = `
<input type="radio" name="SplElection" id="${SPLCan.name}" value="${SPLCan.name}" style="display:block;">${SPLCan.name}
<br>
`;
html += li
});
splCanList.innerHTML = html;
} else {
splCanList.innerHTML = html;
};
};
There are a lot of missing pieces here but from what I can tell this should work.
You didn't execute the setupSPLCans function anywhere and I had to fill in the gaps what the data shape is.
But what I did change that may have been bugs in your code is:
I moved the html declaration up one block so that's accessible to your else clause
The material CSS you used comes with radios being hidden automatically. I added some CSS to counter that
const splCanList = document.querySelector('#SPLInput');
const setupSPLCans = (data) => {
let html = ''; // <-- moved this up a line
if (data.length) {
data.forEach(doc => {
const SPLCan = doc.data();
const li = `
<input type="radio" name="${SPLCan.name}" id="${SPLCan.name}" value="${SPLCan.name}" style="display:block;">${SPLCan.name}
<br>
`;
html += li
});
splCanList.innerHTML = html;
} else {
splCanList.innerHTML = html;
};
};
setupSPLCans([ // <-- made some assumptions based on your code
{
data: () => ({ name: 'name1' }),
},
{
data: () => ({ name: 'name2' }),
},
]);
#SPLInput [type="radio"]:not(:checked), [type="radio"]:checked {
position: static;
opacity: 1;
pointer-events: initial;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<div id="SPLInput"></div>
Related
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 storing the todo-items on an array of objects. I want to map the array to get the HTML elements. I am able to do this using innerHTML but I want to do it with createElement.
I am expecting <li><span>TEXT</span></li> output but I am getting only span. It seems append is not working.
HTML code :
<div class="todo">
<h1>Todo</h1>
<form name="todo--form">
<input type="text" name="todo--input" />
<button
type="submit"
name="todo--submit"
class="p-2 bg-slate-500 rounded"
>
Add
</button>
</form>
<ul class="todo--list"></ul>
</div>
JS Code :
const addTodoForm = document.querySelector(`[name="todo--form"]`);
const todoList = document.querySelector('.todo--list');
const todos = [];
// function getLabel() {}
function handleSubmit(e) {
e.preventDefault();
const text = this.querySelector(`[name="todo--input"]`).value;
const item = {
text: text,
finished: false,
};
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
}
addTodoForm.addEventListener('submit', handleSubmit);
I am getting only <span><span> as output.
When the <form> is submitted there's only one item being appended to list at a time so there's no need to build a one item array. BTW event handlers (functions that are bound to an event) don't return values like a normal function can, but there are indirect ways to get values from them (like from your OP code, that array outside of the function pushing objects).
Details are commented in example
// Reference <form>
const form = document.forms.todo;
// Reference <ul>
const list = document.querySelector('.list');
// Define array for storage
let tasks = [];
// Bind "submit" event to <form>
form.onsubmit = handleSubmit;
// Event handlers passes Event Object by default
function handleSubmit(e) {
// Stop default behavior during a "submit"
e.preventDefault();
// Reference all form controls
const IO = this.elements;
/*
Define {item}
Add current timestamp to object {item}
Add <input> text to {item}
Add {item} to [tasks]
Clear <input>
*/
let item = {};
item.log = Date.now();
item.task = IO.data.value;
tasks.push(item);
IO.data.value = '';
/*
Create <time>
Assign timestamp <time>
Convert timestamp into a readable date and time
Add formatted datetime as text to <time>
*/
const log = document.createElement('time');
log.datetime = item.log;
let date = new Date(item.log);
log.textContent = date.toLocaleString();
/*
Create <output>
Add text of <input> to <output>
*/
const txt = document.createElement('output');
txt.value = item.task;
/*
Create <button>
Add type, class, and the text: "Delete" to <button>
*/
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'delete';
btn.textContent = 'Delete';
// Create <li>
const li = document.createElement('li');
// Append like crazy
li.append(log);
li.append(txt);
li.append(btn);
list.append(li);
console.log(tasks);
}
html {
font: 300 2ch/1.2 'Segoe UI';
}
input,
button {
font: inherit;
}
output {
display: inline-block;
margin: 0 8px 8px;
}
button {
cursor: pointer;
}
.as-console-row::after {
width: 0;
font-size: 0;
}
.as-console-row-code {
width: 100%;
word-break: break-word;
}
.as-console-wrapper {
max-height: 30% !important;
max-width: 100%;
}
<form id='todo'>
<fieldset>
<legend>ToDo List</legend>
<input id='data' type="text" required>
<button class="p-2 bg-slate-500 rounded">Add</button>
<ul class="list"></ul>
</fieldset>
</form>
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newLi;
});
In this block of code, I believe the newEl variable being created isn't correct. Try changing newLi.append(newSpan) to newLi.appendChild(newSpan)
This is also listed below
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.appendChild(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
Thanks for all the answers.
Upon reading on the documentation of append and appendChild, it seems elements cannot be saved into a variable upon using these methods.
I just need to return the append li element.
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newLi.setAttribute('data-index', i);
const todoTextNode = document.createTextNode(todo.text);
newSpan.appendChild(todoTextNode);
newLi.appendChild(newSpan);
return newLi;
});
In my website i am listing movies and tv series that users can share their comments on them. Users can add comments, but when it comes to receiving comments, an undefined value is returned. (I am trying to get comments from movieComment. movieComment store comments for the movie)
var show = qs["movieId"];
/* show variable is giving my movie's ID and it is 1 */
btnComment.addEventListener('click', (e) => {
var movieComment = document.getElementById('textComment').value;
push(child(firebaseRef, 'Movies/' + show + '/movieComment/'), movieComment) {
movieComment: movieComment
};
});
function AddItemsToTable2(comment) {
const comments = `
<td>Alan Smith</td>
<td><i class="fa fa-star" style="color:rgb(91, 186, 7)"></i></td>
<td>${comment}<h6>[May 09, 2016]</h6></td>
`;
html = comments;
body2.innerHTML += html;
}
}
function AddAllItemsToTable2(TheComments) {
body2.innerHTML = "";
TheComments.forEach(element => {
AddItemsToTable2(element.movieComment);
});
}
function getAllDataOnce2() {
var show = qs["movieId"];
get(child(firebaseRef, 'Movies/' + show + '/movieComment')).then((snapshot) => {
var comments = [];
comments.push(snapshot.val());
console.log(comments);
AddAllItemsToTable2(comments);
});
}
window.onload = (event) => {
getAllDataOnce2();
};
Console.log(movies)
Undefined error:
Focusing on this function:
function AddAllItemsToTable2(TheComments) {
body2.innerHTML = "";
TheComments.forEach(element => {
AddItemsToTable2(element.movieComment);
});
}
The TheComments object here is a Record<string, string>[]:
TheComments = [{
"-Mstwhft8fKP6-M2MRIk": "comment",
"-Mstwj5P2TD_stgvZL8V": "a comment",
"-MstwjxvmkNAvWFaIejp": "another comment"
}]
When you iterate over this array, you end up with an element object that doesn't have a movieComment property, which is why when you feed it to AddItemsToTable2 you get undefined.
To fix this, you need to change the way you assemble the TheComments object:
function AddAllItemsToTable2(TheComments) { // TheComments: ({id: string, text: string})[]
body2.innerHTML = "";
TheComments.forEach(commentObj => AddItemsToTable2(commentObj.text));
}
function getAllDataOnce2() {
const show = qs["movieId"];
get(child(firebaseRef, 'Movies/' + show + '/movieComment'))
.then((snapshot) => {
const comments = [];
snapshot.forEach(childSnapshot => {
comments.push({
id: childSnapshot.key, // store this for linking to database/anchors
text: childSnapshot.val()
});
});
console.log(comments);
AddAllItemsToTable2(comments);
});
}
As another point, beware of XSS risks when using innerHTML and use innerText where possible for any user generated content. Also, you should wrap your comment content in a table row so comments are concatenated properly.
function AddItemsToTable2(commentObj) {
const commentEle = document.createElement('span');
commentEle.id = `comment_${commentObj.id}`;
commentEle.innerText = commentObj.text;
const commentRowHTML = `
<tr>
<td>Alan Smith</td>
<td><i class="fa fa-star" style="color:rgb(91, 186, 7)"></i></td>
<td>${commentEle.outerHTML}<h6>[May 09, 2016]</h6></td>
</tr>`;
body2.innerHTML += commentRowHTML;
}
function AddAllItemsToTable2(TheComments) {
body2.innerHTML = "";
TheComments.forEach(commentObj => AddItemsToTable2(commentObj));
}
With the above code block, you can now add #comment_-MstwjxvmkNAvWFaIejp to the end of the current page's URL to link to the "another comment" comment directly similar to how StackOverflow links to comments.
Why when you are searching for something else is deleting the previous contents ?For example first you search for egg and show the contents but then when you search for beef the program deletes the egg and shows only beef.Thank you for your time code:
const searchBtn = document.getElementById('search-btn');
const mealList = document.getElementById('meal');
const mealDetailsContent = document.querySelector('.meal-details-content');
const recipeCloseBtn = document.getElementById('recipe-close-btn');
// event listeners
searchBtn.addEventListener('click', getMealList);
mealList.addEventListener('click', getMealRecipe);
recipeCloseBtn.addEventListener('click', () => {
mealDetailsContent.parentElement.classList.remove('showRecipe');
});
// get meal list that matches with the ingredients
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
let html = "";
if(data.meals){
data.meals.forEach(meal => {
html += `
<div class = "meal-item" data-id = "${meal.idMeal}">
<div class = "meal-img">
<img src = "${meal.strMealThumb}" alt = "food">
</div>
<div class = "meal-name">
<h3>${meal.strMeal}</h3>
Get Recipe
</div>
</div>
`;
});
mealList.classList.remove('notFound');
} else{
html = "Sorry, we didn't find any meal!";
mealList.classList.add('notFound');
}
mealList.innerHTML = html;
});
}
Beacuse you are using innerHTML , if you want to save the previous contents you should use append or innerHTML + = .
Because everytime you make a search, the html var is populated with new data.
if you move the 'html' variable to the root scope, this should get you there:
// get meal list that matches with the ingredients
let html = ""; // <-- place it outside the function body
function getMealList(){
let searchInputTxt = document.getElementById('search-input').value.trim();
fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?i=${searchInputTxt}`)
.then(response => response.json())
.then(data => {
// let html = ""; // <-- remove it from here
if(data.meals){
data.meals.forEach(meal => {
I'm trying to load and show a CSV in my HTML page using JS. I am able to show the data, but now I want to add the option to add an agree/disagree or check mark/x mark at the end of each row.
So for example, the first row will show some data, and after I looked at it, I will decide if I agree with it or not. After I click agree, the whole row's variable will be set to "true" for example.
So this is my HTML:
<html>
<body>
<input type="file" id="fileSelect" />
<div id="status">Waiting for CSV file.</div>
<table id="csvOutput"></table>
<script src="script.js"></script>
</body>
</html>
This is my script.js:
function buildHeaderElement (header) {
const headerEl = document.createElement('thead')
headerEl.append(buildRowElement(header, true))
return headerEl
}
function buildRowElement (row, header) {
const columns = row.split(',')
const rowEl = document.createElement('tr')
for (column of columns) {
const columnEl = document.createElement(`${header ? 'th' : 'td'}`)
columnEl.textContent = column
rowEl.append(columnEl)
}
return rowEl
}
function populateTable (tableEl, rows) {
const rowEls = [buildHeaderElement(rows.shift())]
for (const row of rows) {
if (!row) { continue }
rowEls.push(buildRowElement(row))
}
tableEl.innerHTML= ''
return tableEl.append(...rowEls)
}
function readSingleFile ({ target: { files } }) {
const file = files[0]
const fileReader = new FileReader()
const status = document.getElementById('status')
if (!file) {
status.textContent = 'No file selected.'
return
}
fileReader.onload = function ({ target: { result: contents } }) {
status.textContent = `File loaded: ${file.name}`
const tableEl = document.getElementById('csvOutput')
const lines = contents.split('\n')
populateTable(tableEl, lines)
status.textContent = `Table built from: ${file.name}`
}
fileReader.readAsText(file)
}
window.addEventListener('DOMContentLoaded', _ => {
document.getElementById('fileSelect').addEventListener('change', readSingleFile)
})
I can't figure out how to add the option I'm looking for at the end of each row, as well as how to mark each row as it's own variable.
To extend your code: after rowEl.append(columnEl), you could call a method to append a hard-coded TD/ TH and also append a checkbox.
As the columns are dynamically created, there is no way of knowing which columns store the unique key(s). Therefore you could store the entire row as a comma separated string as an attribute on the checkbox. Although you'll need to be careful the row data is not too long.
function buildRowElement(rowData, header) {
const columns = rowData.split(',')
const rowEl = document.createElement('tr')
for (column of columns) {
const columnEl = document.createElement(`${header ? 'th' : 'td'}`)
columnEl.textContent = column
rowEl.append(columnEl)
}
rowEl.append(provideColumnAgree(rowData, header))
return rowEl
}
function provideColumnAgree(rowData, header) {
const columnAgree = document.createElement(`${header ? 'th' : 'td'}`)
if (header) {
columnAgree.textContent = 'Agree?';
}
else {
const checkboxAgree = document.createElement(`input`)
checkboxAgree.setAttribute("type", "checkbox");
checkboxAgree.setAttribute("data-row", rowData);
columnAgree.append(checkboxAgree)
}
return columnAgree
}
A working example: https://jsfiddle.net/f74pxkor/