I would like the user to be able to record an action that they have carried out. I grouped the actions by category and then using two select menus and JS the user is only showed the actions from the category that they have selected. There is also a quantity input that is generated depending on which action is selected.
My issue is that when I submit the form, I get the error:
Uncaught TypeError: Cannot set property 'onchange' of null
The select box and the functionality implemented by the JS work until the form is submitted.
index.js
document.addEventListener("DOMContentLoaded", () => {
// First select
let cat_select = document.getElementById("id_post_cat");
cat_select.onchange = () => handleCatChange(cat_select.value);
// Second select
let desc_select = document.getElementById("id_post_action");
desc_select.onchange = () => handleDescChange(desc_select.value);
});
const handleCatChange = (cat) => {
let quantity_div = document.getElementById("quantity_div");
quantity_div.innerHTML = "";
// Fetching the actions in the category selected and populating second select
fetch(`/action/${cat}`)
.then((response) => response.json())
.then((data) => {
let desc_select = document.getElementById("id_post_action");
let optionHTML = "<option>---------</option>";
data.actions.forEach((action) => {
optionHTML +=
'<option value"' + action.id + '">' + action.desc + "</option>";
});
desc_select.innerHTML = optionHTML;
})
.catch((err) => console.log(err));
};
const handleDescChange = (desc) => {
let quantity_div = document.getElementById("quantity_div");
quantity_div.innerHTML = "";
let time_actions = [
"Public transport instead of driving",
"Walk/cycle instead of drive",
];
let quant_actions = [
"Unplug unused electrical items",
"Repurpose a waste object",
"Use a reusable bag",
"Buy an unpackaged item",
"Buy a locally produced item",
"Buy a second hand item",
"Buy an object in bulk",
"Use a refillable bottle/to-go mug",
"Drink a tap beer instead of bottled beer",
];
if (time_actions.includes(desc)) {
formAdder("Distance* (km)");
} else if (quant_actions.includes(desc)) {
formAdder("Quantity*");
}
};
const formAdder = (label_content) => {
let quantity_div = document.getElementById("quantity_div");
// Label
let label = document.createElement("label");
label.innerHTML = `${label_content}`;
label.setAttribute("for", "id_post_quantity");
label.classList += "requiredField";
// Input
let input = document.createElement("input");
input.setAttribute("id", "id_post_quantity");
input.setAttribute("name", "post_quantity");
input.setAttribute("required", "");
input.classList += "form-control";
quantity_div.append(label, input);
};
views.py
#login_required
def record(request):
if request.method == 'POST':
form = NewPostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.poster = request.user
if request.POST.get('id_post_quantity'):
post.post_quantity = request.POST.get('id_post_quantity')
post.save()
return HttpResponseRedirect(reverse('index'))
return render(request, 'my_app/record.html', {
'form': NewPostForm
})
record.html
<form action="{% url 'record' %}" method="post">
{% csrf_token %}
{{form.post_cat|as_crispy_field}}
{{form.post_action|as_crispy_field}}
<div class="form-group" id="quantity_div">
</div>
<button class="btn btn-success" type="submit">Post</button>
</form>
A pointer in the right direction would be greatly appreciated thank you.
It seems the onchange was being called on every page that was loaded. That meant that, when redirecting to the index page (where the select boxes don't exist), the getElementById was returning a null value etc.
I changed this by adding conditionals to check if the elements are present on the page:
document.addEventListener("DOMContentLoaded", () => {
// First select
let cat_select = document.getElementById("id_post_cat");
if (!!cat_select) {
cat_select.onchange = () => handleCatChange(cat_select.value);
}
// Second select
let desc_select = document.getElementById("id_post_action");
if (!!desc_select) {
desc_select.onchange = () => handleDescChange(desc_select.value);
}
});
Is there a more elegant/correct way of solving the same problem?
for me it got fixed when i changed on submit event to btn click event.
Related
Set user id to input box
As a user when I click get user posts button that userid should be retrieved from input text box in order to be displayed in browsers's console.log()
Actual Resulst
on browser console.log() id is printed before i click the button
*** Codes***
//click handler to get clicked user id
const getUserPosts = (id) =>{
console.log(id)
}
//getData('users') has a list of ten users
getData('users').then(data=>data.forEach(({name, email, id})=>{
var div = newTag('div')
document.getElementsByClassName('user')[0].appendChild(div);
let listName = newTag('h2')
let listEmail = newTag('p')
let userPostBtn = newTag('button')
let input = newTag('input')
// userPostBtn.setAttribute('onclick', e=>getUserPosts())
userPostBtn.innerHTML = 'Get User’s Posts'
input.setAttribute('name', 'data')
div.appendChild(listName)
div.appendChild(listEmail)
div.appendChild(userPostBtn)
div.appendChild(input)
listEmail.innerHTML = email
listName.innerHTML=name
input.value = id
}))
Try this and let me know
const getUserPosts = (id) =>{
console.log(id)
}
function newTag(tagName) {
return document.createElement(tagName);
}
function getData() {
fetch('https://jsonplaceholder.typicode.com/users', {method: 'get'}).
then(data => data.json()).
then(data => {
console.log(data)
data.forEach(({name, email, id})=>{
var div = newTag('div')
document.getElementsByClassName('user')[0].appendChild(div);
let listName = newTag('h2')
let listEmail = newTag('p')
let userPostBtn = newTag('button')
let input = newTag('input')
userPostBtn.innerHTML = 'Get User’s Posts'
userPostBtn.addEventListener('click', function(e) {
getUserPosts(id)
})
input.setAttribute('name', 'data')
div.appendChild(listName)
div.appendChild(listEmail)
div.appendChild(userPostBtn)
div.appendChild(input)
listEmail.innerHTML = email
listName.innerHTML=name
input.value = id
})}).catch(e => {
console.log(e)
})
}
getData()
<div class="user"></div>
If you give your input an id at the time of creation (id could be anything as long as its unique): input.id = 'inputfieldid'; then you can get the value from the input in the event listener
const getUserPosts = (id) =>{
const input = document.getElementById('inputfieldid');
console.log(input.value);
}
try changing onclick event to be like this:
userPostBtn.onclick = (e)=>getUserPosts();
I am trying to update my global array, but it remains null after I submit a text value(.name) through a submit button.
Please tell me how I can keep track of text values in my global array. Thank you.
var display_name = [];
document.addEventListener('DOMContentLoaded', () =>{
document.querySelector("#form1").onsubmit = () => {
let name = document.querySelector(".name").value;
display_name.push(name);
};
});
When the form is submitted, a new page is loaded. It loads the URL in the action property of the form. So, your variable goes away.
If you don't want that to happen, prevent the form from being submitted with preventDefault.
For example ...
const name_list = [];
window.addEventListener('DOMContentLoaded', (e) => {
const names = document.querySelector(`.names`);
const add_button = document.querySelector(`.names--add_button`);
names.addEventListener('submit', e => e.preventDefault());
add_button.addEventListener('click', (e) => {
const name = document.querySelector(`.names--name`);
const collected = document.querySelector(`.names--collected`);
name_list.push(name.value);
collected.innerHTML += `<li>${name.value}</li>`;
name.value = ``;
name.focus();
});
});
body { background: snow; }
<form class="names" action="#" method="post">
<label>Name: <input type="text" name="name" class="names--name"></label>
<button class="names--add_button">Add To List</button>
<div>Names Collected:</div>
<ul class="names--collected">
</ul>
</form>
I am see at the moment it's working perfect. but you want add value every time when you click the button. so just changed the type of your
<button type="submit"> to <button type="button">
because when you click on submit page automatically reload in html, an the 2nd thing you need to change your event from onsubmit to onclick and your button to it instead of your form.
var display_name = [];
document.addEventListener('DOMContentLoaded', () =>{
document.querySelector("#button1").onclick = () => {
let name = document.querySelector(".name").value;
display_name.push(name);
};
});
I have a page, where I need to filter through all kinds of data (served as JSON variable) using just pure javascript. Everything works nicely until I try to implement a datalist where the contents of that list are updated in realtime, based on user input.
var data = [{name:"Paul"},{name:"Alex"},{name:"Laura"}] // This is just example object
function updateSearch() {
let search = document.getElementById('search').value;
let options = document.getElementById('searchList');
options.innerHTML = "";
if (search.length >= 2) {
search = search.toLowerCase();
for (let d of data) {
if (d.name.toLowerCase().search(search) === 0) {
options.innerHTML += `
<option>${d.name}</option>
`;
}
}
}
}
<datalist id='searchList'></datalist>
<input type="search" id="search" list='searchList' onchange="updateSearch()">
The goal is to not show the full list of names until at least 2 characters are entered, but it just won't update until the user clicks out focuses back to search input.
One solution to achieve what you require would be to replace the event type that updateSearch() is bound to from onchange to onkeyup:
const data = [
{ name : "Foo" },
{ name : "Bar" },
{ name : "Bing" },
{ name : "Bong" },
{ name : "Boo!" }];
function updateSearch() {
let search = document.getElementById('search').value;
let options = document.getElementById('searchList');
options.innerHTML = "";
if (search.length >= 2) {
search = search.toLowerCase();
for (let d of data) {
if (d.name.toLowerCase().search(search) === 0) {
options.innerHTML += `
<option>${d.name}</option>
`;
}
}
}
}
<datalist id='searchList'></datalist>
<!-- Update to onkeyup -->
<input type="search" id="search" list='searchList' onkeyup="updateSearch()">
Doing this will cause the datalist to interactivly update as the user types. Hope that helps!
So I am trying to make an edit function for a favorites bar. Editing one box is okay, but when I try to edit a different box, all the boxes that I clicked on previously gets edited as well. Here is a jsfiddle with the complete code: https://jsfiddle.net/1exrf9h8/1/
I am trying to understand why my editFavorite function is updating multiple boxes and not just one.
function clickEdit(input, title, url, plus, editIcon, anchorEdit, editBtn)
{
let i = editIcon.length - 1;
editIcon[i].addEventListener("click", function(event){
input.style.display = "block";
title.value = plus[i + 1].textContent;
url.value = anchorEdit[i].href;
console.log(i);
console.log(anchorEdit[i]);
editFavorite(anchorEdit[i], url, title, input, editBtn);
});
}
function editFavorite(changed, url, title, input, editBtn)
{
editBtn.addEventListener("click", function(){
changed.href = url.value;
changed.textContent = title.value;
input.style.display = "none";
});
}
There is a few problems in your logic, architecture and use of the event handler, Let's give it a shot in a more OOP way so you can actually make it to work and understand what is going on.
Every single favorite is an object by itself, that can spawn and update itself.
function favorite(newTitle, newUrl) {
this.element = container.appendChild(document.createElement("div"));
this.title = this.element.appendChild(document.createElement("h2"));
this.url = this.element.appendChild(document.createElement("h2"));
this.update = (newTitle, newUrl) => {
this.title.textContent = newTitle;
this.url.textContent = newUrl;
}
this.createButton = () => {
button = this.element.appendChild(document.createElement("button"));
button.append("Edit");
button.addEventListener("click", () => {
let titleInput = document.getElementById("title").value;
let urlInput = document.getElementById("url").value;
this.update(titleInput, urlInput);
})
}
this.update(newTitle, newUrl);
this.createButton();
}
Then let's have a simple form where we can take inputs, using the same for editing, and creating a new favorites.
<input id="title" type="text" name="title" placeholder="Title">
<input id="url" type="text" name="url" placeholder="Url">
<button id="submit">Create New</button>
Now the actual submit logic.
document.getElementById("submit").addEventListener("click", () => {
let titleInput = document.getElementById("title").value;
let urlInput = document.getElementById("url").value;
if (!titleInput.length || !urlInput.length) return;
let newFavorite = new favorite(titleInput, urlInput);
container.appendChild(newFavorite.element);
});
https://jsfiddle.net/p50L27us/48/
The problem is caused by editFavorite function. when you call editFavorite function automatically starts new listener. Evey click start new one.
The solution is " ,{once : true} "
function editFavorite(changed, url, title, input, editBtn)
{
editBtn.addEventListener("click", function(){
changed.href = url.value;
changed.textContent = title.value;
input.style.display = "none";
},{once : true});
}
I want to put an empty select on an html page.
When the users clicks on this select, I want to load items from a web service.
I did manage to make it work with a bootstrap button but not with a basic html select.
There is very little information to go on from your question... But generally you could use an AJAX call to dynamically fill in the options.
$("select").click(function(){
$.getJSON(url, function(data){
options = JSON.parse(data);
$.each(options, function(index, value){
$("<option/>").appendTo("select").val(value).append(value);
});
});
});
This is a very similar solution to the one CodeAt30 suggested, but without the jQuery dependency:
// fake web service
const getSelectOptions = () => new Promise((resolve, reject) => {
resolve(['option 1','option 2', 'option 3', 'option 4']);
});
const populateWithOptions = () => {
//abort if select box has already been populated
if(document.querySelector('#s1 option')) return;
const selectElement = document.querySelector('#s1');
getSelectOptions().then(options => {
options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.innerHTML = option;
selectElement.appendChild(optionElement);
}
);
});
};
const handleSelect = () => {
const selectElement = document.querySelector('#s1');
alert('user selected ' + selectElement.value);
}
window.populateWithOptions = populateWithOptions;
window.handleSelect = handleSelect;
<div>
<select id="s1" onmouseover="populateWithOptions()" onclick="populateWithOptions()" onchange="handleSelect()">
</select>
</div>