Hi guys am having trouble with this, When I declare the elements outside the loop the script ends up being broken. I plan to use the elements in several functions. Tried a lot of times but didn't find a solution. Broke the project a few times while doing guess work. Also If there's any idea on how to shorten this or to make it work better please give me a heads up :)
async function loadTable() {
try {
while(dpTable.firstChild) dpTable.removeChild(dpTable.firstChild);
const result = await fetch('http://localhost:5000/users/student_data', { method: 'GET' });
const table = await result.json();
table.forEach((res) => {
//Create Table elements and then load results into the elements
const tr = document.createElement('tr');
const studentNo = document.createElement('td');
const name = document.createElement('td');
const surname = document.createElement('td');
const date_joined = document.createElement('td');
const fees_paid = document.createElement('td');
const fees_owing = document.createElement('td');
const is_owing = document.createElement('td');
const trash_btn = document.createElement('button');
const trash_Icon = document.createElement('i');
tr.id = 'Content';
studentNo.id = res.id;
name.id = 'fname';
surname.id = 'lname';
fees_paid.id = 'amount_paid';
fees_owing.id = 'amount_owing';
is_owing.id = 'debt';
//Enter the data from the response
studentNo.innerText = res.id;
name.innerText = res.fname;
surname.innerText = res.lname;
fees_paid.innerText = res.amount_paid;
fees_owing.innerText = res.amount_owing;
date_joined.innerText = res.date_joined;
is_owing.innerText = res.is_owing;
trash_btn.innerText = 'Delete';
//Add Classes for elements
studentNo.classList.add('trContent');
name.classList.add('trContent');
surname.classList.add('trContent');
fees_paid.classList.add('trContent');
fees_owing.classList.add('trContent');
date_joined.classList.add('trContent');
is_owing.classList.add('trContent');
trash_Icon.classList.add('fas');
trash_Icon.classList.add('fa-trash');
trash_Icon.classList.add('trash-btn');
//Append table row elements to main table
tr.append(studentNo);
tr.append(name);
tr.append(surname);
tr.append(fees_paid);
tr.append(fees_owing);
tr.append(date_joined);
tr.append(is_owing);
tr.append(trash_btn);
//Event Listeners
trash_btn.addEventListener('click', async (e) => {
if (window.confirm('Delete')) {
console.log('trash icon clicked');
const jsonReq = {};
jsonReq.id = res.id;
const result = await fetch('http://localhost:5000/users/student_data', {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(jsonReq),
});
alert('Deleted Record');
window.location.reload();
}
});
//Append the table as a child to the main element
dpTable.appendChild(tr);
});
} catch (e) {
console.log(`Cant load List ${e}`);
}
}
There are a couple of minor issues you need to remedy or perhaps even ignore
Element IDs must be unique so you can't use the same id for multiple cells. You could switch to using classNames instead
The buttons need to be inserted into cells, they can't be directly insterted into a row
You can cut this all down quite a bit by using an array of field names to iterate and using TableElement.insertRow() and TableRow.insertCell()
Something like:
const fields = ['studentNo','fname','lname','amount_paid','amount_owing','date_joined','is_owing'] ;
table.forEach((res) => {
const row = dpTable.insertRow();
// loop over fields array to create cell for each
fields.forEach((f) => {
const cell = row.insertCell();
cell.innerText = res[f];
cell.className = 'trContent';
});
const btnCell = row.insertCell();// cell for button
const trash_btn = document.createElement('button');
const trash_Icon = document.createElement('i');
trash_Icon.classList.add('fas', 'fa-trash', 'trash-btn');
trash_btn.append(trash_icon);
btnCell.append(trash_btn);// insert button in own cell
trash_btn.addEventListener('click', .....);
});
It is likely because you're declaring them as const which means they shouldn't be rewritten - but you have them in a loop which reassigns them and so causes an error. You can use let instead in this case
async function loadTable() {
try {
while(dpTable.firstChild) dpTable.removeChild(dpTable.firstChild);
const result = await fetch('http://localhost:5000/users/student_data', { method: 'GET' });
const table = await result.json();
let tr, studentNo, name, surname, date_joined, fees_paid, fees_owing, is_owing, trash_btn, trash_Icon
table.forEach((res) => {
//Create Table elements and then load results into the elements
tr = document.createElement('tr');
studentNo = document.createElement('td');
name = document.createElement('td');
surname = document.createElement('td');
date_joined = document.createElement('td');
fees_paid = document.createElement('td');
fees_owing = document.createElement('td');
is_owing = document.createElement('td');
trash_btn = document.createElement('button');
trash_Icon = document.createElement('i');
tr.id = 'Content';
studentNo.id = res.id;
name.id = 'fname';
surname.id = 'lname';
fees_paid.id = 'amount_paid';
fees_owing.id = 'amount_owing';
is_owing.id = 'debt';
//Enter the data from the response
studentNo.innerText = res.id;
name.innerText = res.fname;
surname.innerText = res.lname;
fees_paid.innerText = res.amount_paid;
fees_owing.innerText = res.amount_owing;
date_joined.innerText = res.date_joined;
is_owing.innerText = res.is_owing;
trash_btn.innerText = 'Delete';
//Add Classes for elements
studentNo.classList.add('trContent');
name.classList.add('trContent');
surname.classList.add('trContent');
fees_paid.classList.add('trContent');
fees_owing.classList.add('trContent');
date_joined.classList.add('trContent');
is_owing.classList.add('trContent');
trash_Icon.classList.add('fas');
trash_Icon.classList.add('fa-trash');
trash_Icon.classList.add('trash-btn');
//Append table row elements to main table
tr.append(studentNo);
tr.append(name);
tr.append(surname);
tr.append(fees_paid);
tr.append(fees_owing);
tr.append(date_joined);
tr.append(is_owing);
tr.append(trash_btn);
//Event Listeners
trash_btn.addEventListener('click', async (e) => {
if (window.confirm('Delete')) {
console.log('trash icon clicked');
const jsonReq = {};
jsonReq.id = res.id;
const result = await fetch('http://localhost:5000/users/student_data', {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(jsonReq),
});
alert('Deleted Record');
window.location.reload();
}
});
//Append the table as a child to the main element
dpTable.appendChild(tr);
});
} catch (e) {
console.log(`Cant load List ${e}`);
}
}
You can definitely simplify your code and make it more flexible into the process. The JSON data returned will have a set of keys - these can be used to build the HTML output without knowing, in advance, what they are. The beauty of that is the same code can display greatly different datasets very easily.
If you were to use the method create shown below to create the new DOM elements you greatly reduce the code needed.
<?php
#---------------------------------------------------
# to emulate whatever "users/student_data" returns.
# this is dummy data for example only.
#
set_include_path('c:/wwwroot/dbo/');
require 'db-conn-details.php';
require 'mysqli-conn.php';
/*
When we receive the AJAX request, identified by querystring,
prepare some sql statement and execute. Return the full
recordset in JSON format.
*/
if( isset( $_GET['fetch'] ) && $_GET['fetch']=='data' ){
ob_clean();
$sql='select * from `sport` limit 10';
$res=$db->query( $sql );
$json=json_encode( $res->fetch_all( MYSQLI_ASSOC ) );
exit( $json );
}
if( $_SERVER['REQUEST_METHOD']=='DELETE' ){
exit('DELETE');
}
?>
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title></title>
<style>
#data{ border:1px solid grey;display:table;padding:1rem;width:100%; }
</style>
<script>
// The endpoint used by the ajax query - in this example it is same page.
const url=location.href; //http://localhost:5000/users/student_data
const create=function(t,a,p){
/*
utility to add a new node with attributes and content.
t=type ~ the DOM node type, defaults to DIV
a=attrbutes to set
p=parent to append new node to.
*/
let el=(typeof(t)=='undefined'||t==null)?document.createElement('div'):document.createElement(t);
let _arr=['innerHTML','innerText','html','text'];
for(let x in a)if(a.hasOwnProperty(x)&&!~_arr.indexOf(x))el.setAttribute(x,a[x]);
if(a.hasOwnProperty('innerHTML')||a.hasOwnProperty('html'))el.innerHTML=a.innerHTML||a.html;
if(a.hasOwnProperty('innerText')||a.hasOwnProperty('text'))el.innerText=a.innerText||a.text;
if(p!=null )typeof(p)=='object'?p.appendChild(el):document.getElementById(p).appendChild(el);
return el;
};
async function loadTable() {
try {
// It was not clear where dbTable was defined - hence declared here
const dpTable=document.querySelector('table#data');
while( dpTable.firstChild ) dpTable.removeChild( dpTable.firstChild );
// As this example targets the same page I needed a way to return only db results - hence using querystring.
const result = await fetch( url + '?fetch=data' );
const json = await result.json();
// the callback that will process the button clicks. The button has a dataset attribute set
// that will use the res.id value from each JSON record.
const clickhandler=async function(e){
if( window.confirm('Delete') ) {
const jsonReq = {
id:this.dataset.id
};
const result = await fetch( url, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify( jsonReq ),
});
console.log( result );
alert('Record deleted');
window.location.reload();
}
};
json.forEach( res => {
// find all the keys from the JSON ~ one of which will be the ID for the record ( res.id )
let fields=Object.keys( res );
// generate new table row
let tr=dpTable.insertRow();
// iterate through all json keys - add new table cell with JSON data.
fields.forEach( field => {
create('td',{
'data-id':field,
text:res[field]
},tr);
});
// add the button for the row and assign listener - also assign the dataset attribute for the record ID.
let bttn=create('button',{ text:'Delete','data-id':res.id }, tr.insertCell() );
bttn.addEventListener( 'click', clickhandler );
// add the icon to the button
create('i',{ 'class':'fas fa-trash trash-btn' }, bttn );
});
} catch(e) {
console.log('Unable to load list: %o',e);
}
}
document.addEventListener('DOMContentLoaded',loadTable);
</script>
</head>
<body>
<table id='data'></table>
</body>
</html>
The sql query shown here in the demo returns the following data:
[
{"id":"1","tag":"3D Archery","description":"a form of archery in which the goal is to strike targets in the shape of animals at unknown distances.","enabled":"1"},
{"id":"2","tag":"3x3","description":"a variation of basketball played on a half court with just three players in each team.","enabled":"1"},
{"id":"3","tag":"Abseiling","description":"an adventure sport where the participants descend a steep formation using a rope. (not really a sport).","enabled":"1"},
{"id":"4","tag":"Acroski","description":"athletes on snow skis perform various choreographed\u00c2\u00a0routines (once called Ski Ballet)","enabled":"1"},
{"id":"5","tag":"Adventure Racing","description":"an event combining two or more endurance disciplines, such as Orienteering, Cross-Country Running, Mountain Biking, Paddling and Climbing. It is also called Expedition Racing.","enabled":"1"},
{"id":"6","tag":"Aerials","description":"a freestyle skiing discipline in which athletes ski along a take-off ramp, then perform various in-air tricks.","enabled":"1"},
{"id":"7","tag":"Aerobatics","description":"sport aerobatics involves aircraft maneuvers such as rolls, loops, stall turns (hammerheads), and tailslides.","enabled":"1"},
{"id":"8","tag":"Acrobatic Gymnastics","description":"team of gymnasts work together to perform acrobatic moves in combination with dance moves.","enabled":"1"},
{"id":"9","tag":"Aerobic Gymnastics","description":"another name for Sport Aerobics.","enabled":"1"},{"id":"10","tag":"Aeromodeling","description":"activity using remotely controlled flying model aircraft (not really a sport).","enabled":"1"}
]
And yielded the following output:
I have a method in views.py that adds user to the following list,
I'm trying to make it work whenever I click on the button "follow" that I have added in JavaScript
How do I make my Follow button in JavaScript go to my views.py method onclick ?
views.py method.
def follow(request, profile_to_follow):
try:
user_to_follow = User.objects.get(username=profile_to_follow)
user_to_following = Profile.objects.get(user=request.user.id)
except User.DoesNotExist:
return JsonResponse({"error": "Profile not found."}, status=404)
if request.method == "PUT":
user_to_following.following.add(user_to_follow)
return HttpResponse(status=204)
urls.py
# API Routes
path("posts", views.compose_post, name="compose_post"),
path("posts/all_posts", views.show_posts, name="show_posts"),
path("posts/<int:post_id>", views.post, name="post"),
path("profile/<str:profile>", views.display_profile, name="profile"),
path("<str:profile_posts>/posts", views.display_profile_posts, name="profile_posts"),
path("follow/<str:user_to_follow>", views.follow, name="follow"),
JavaScript
function load_user_info(user_clicked_on){
document.querySelector('#page-view').style.display = 'none';
document.querySelector('#posts-view').style.display = 'none';
document.querySelector('#show-posts').style.display = 'none';
document.querySelector('#load-profile').style.display = 'block';
fetch(`/profile/${user_clicked_on}`)
.then(response => response.json())
.then(profile => {
const profile_element = document.createElement('div');
const followers = document.createElement('div');
const following = document.createElement('div');
const follow_button = document.createElement('button');
follow_button.innerHTML += "Follow";
followers.innerHTML = 'Followers: ' + profile.followers;
following.innerHTML = 'Following: ' + profile.following;
profile_element.appendChild(followers);
profile_element.appendChild(following);
profile_element.appendChild(follow_button);
profile_element.classList.add('profile_element');
follow_button.addEventListener("click", () => {
follow_user(user_clicked_on)
});
document.querySelector('#user-profile').appendChild(profile_element);
});
document.querySelector('#user-profile').innerHTML = `<h3>${user_clicked_on.charAt(0).toUpperCase() + user_clicked_on.slice(1)} Profile</h3>`;
}
function follow_user(user_to_follow){
// to make this function work with the one in views.py
fetch(`/profile/${user_to_follow}`, {
method: 'PUT',
})
}
I am currently trying to build a fullstack application using javascript(node.js). My goal is to create a weather API with dummy data and everything was going well until I reached the end portion where I am trying to get the users search results to show up below the search box. I've tried using a fetch API from my main js file to retrieve data from my index.js file (which contains the server, imported data, etc.) but I continue to get an error message from the console stating that 'res' is not defined. 'res.data.city' is what I am using to acquire the input information from the user and render it to the screen but I keep getting told that it is not defined; I believe that it should be coming from the index.js file. Here is my code. (PLEASE be nice guys. I am a new developer and am just trying to better myself at programming.)
script.js file (main javascript file that is linked to html document)
const form = document.querySelector('form');
const textInput = document.getElementById('city');
let city = textInput.value;
textInput.addEventListener('input', (e) => {
city = e.target.value;
})
const getData = () => {
fetch('http://localhost:3000/weather/?city=' + city)
.then(res => {
return res.json();
})
.then(resData => {
console.log(resData)
}).catch((error) => {
console.log(error)
});
};
form.addEventListener('submit', (e) => {
e.preventDefault();
getData()
let location = document.querySelector('.cityName');
let celsius = document.querySelector('.celsius')
let fahrenheit = document.querySelector('.fahrenheit')
let error = document.querySelector('.error')
if(res.data.city) {
location.innerHTML = 'City: ' + res.data.city;
celsius.innerHTML = 'Temperature (C): ' + res.data['temperature (C)']
fahrenheit.innerHTML = 'Temperature (F): ' + res.data['temperature (F)'];
} else {
error.Message.innerHTML = 'SORRY! This city is not currently in our
database :{'
}
textInput.value = '';
})
index.js(file with the server running)
const express = require('express')
const app = express();
const cors = require('cors')
const fakeWeatherData = require('./data.js')
app.use(cors());
app.get('/weather', (req, res) => {
let cityName = req.query.city.toLowerCase();
for(let i = 0; i < fakeWeatherData.length; i++)
if(!cityName) {
res.send({"status": "error", "message": "Please enter a city name"})
} else if (cityName === fakeWeatherData[i].city.toLowerCase()) {
return res.send(fakeWeatherData[i])
}
res.send({"status": "error", "message": "Sorry! This city isn't in the database :{"})
})
index.html(main file that will be rendered to broswer)
<body>
<div class='main'>
<form>
<label for='city'>City:</label>
<div class='city-box'>
<input type='text' placeholder='Please enter a city' id='city' name='city' require>
</div>
<button type='submit'>CLICK</button>
</form>
</div>
<div class='error'></div>
<div class='weather'>
<h1 class='cityName'></h1>
<h4 class='celsius'></h4>
<h4 class='fahrenheit'></h4>
</div>
<script src="./js/script.js"></script>
</body>
</html>
#GaloisGirl is correct, res is not defined because you are not defining res within the scope of the anonymous function where you set the submit event listener.
Given that you're not already using async/await, seems best to stick with promise notation so it would look something like this:
form.addEventListener('submit', (e) => {
e.preventDefault();
let location = document.querySelector('.cityName');
let celsius = document.querySelector('.celsius')
let fahrenheit = document.querySelector('.fahrenheit')
let error = document.querySelector('.error')
getData()
.then((res) => {
if(res.data.city) {
location.innerHTML = 'City: ' + res.data.city
celsius.innerHTML = 'Temperature (C): ' + res.data['temperature (C)']
fahrenheit.innerHTML = 'Temperature (F): ' + res.data['temperature (F)']
} else {
error.Message.innerHTML = 'SORRY! This city is not currently in our database'
}
textInput.value = ''
})
})
And you'll also want to return the promise from getData
const getData = () => {
return fetch('http://localhost:3000/weather/?city=' + city)
...OTHER STUFF
I am fetching the user input data from textarea on button click. If a user put some hyperlink code inside textarea, the function automatically recognize and add rel=nofollow. Now I need to assign these new hyperlink code inside textarea again and replace the old one.
I am able to add rel=nofollow and able to alert it
function naming() {
var rel_attribute = document.getElementById('textareaCode').value;
var dom = new DOMParser().parseFromString(rel_attribute, 'text/html');
[...dom.getElementsByTagName('a')].forEach((a) => {
a.setAttribute('rel', 'nofollow');
alert(a.outerHTML);
});
}
<textarea id="textareaCode"></textarea>
Execute Function
Using replace()
function naming() {
var textarea = document.getElementById('textareaCode');
var rel_attribute = document.getElementById('textareaCode').value;
var dom = new DOMParser().parseFromString(rel_attribute, 'text/html');
[...dom.getElementsByTagName('a')].forEach((a) => {
let original = a.outerHTML
a.setAttribute('rel', 'nofollow');
textarea.value = textarea.value.replace(original, a.outerHTML)
});
}
<textarea id="textareaCode" cols=70 rows=5>
Execute Function
</textarea>
<br/>
Execute Function
Use the XMLSerializer interface to convert your DOMParser instance into HTML string, then affect your <textarea>'s value property with the HTML string.
You'll need to do some other processes if you want to only have the <body> tags' content.
function naming() {
const eArea = document.getElementById('textareaCode'),
dom = new DOMParser().parseFromString(eArea.value, 'text/html');
[...dom.getElementsByTagName('a')].forEach(a => a.setAttribute('rel', 'nofollow'));
// That's here!
eArea.value = new XMLSerializer().serializeToString(dom);
}
<textarea id="textareaCode"></textarea>
Execute Function
Please try out as follows,
let counter = 1;
function naming() {
var rel_attribute = document.getElementById('textareaCode').value;
var anchorTags = document.getElementsByTagName('a');
var dom = new DOMParser().parseFromString(rel_attribute, 'text/html');
[...dom.getElementsByTagName('a')].forEach((a) => {
a.setAttribute('rel', 'nofollow');
a.onclick = naming;
a.innerText = "Manish " + counter++;
for(let i=0; i < anchorTags.length; i++){
anchorTags[i].remove();
}
document.body.appendChild(a);
console.log(anchorTags);
});
}
<textarea id="textareaCode"></textarea>
Execute Function
I want to build a website which returns products from a database and when click on See More, should return product details from server in another html page. The problem is that when I click See More, productID%20=%20null :(.
productDetails = second html page.
productDetails = div - in index.html where products are returned from server
<script>
var productsUrlList = 'https://finalonlineshop.firebaseio.com/.json';
async function getProductsFromServer() {
var productsResponse = await fetch(productsUrlList)
var products = await productsResponse.json();
return products;
}
async function showProducts(productsPromise) {
var products = await productsPromise;
var generatedHTML = '';
var productsIDs = Object.keys(products);
productsIDs.forEach(productID => {
var product = products[productID];
generatedHTML += getGeneratedHTMLForProduct(productID, product);
});
document.getElementById('categories').innerHTML = generatedHTML;
}
function getGeneratedHTMLForProduct(productID, product) {
var generatedHTML = `
<div id = categoriesDiv>
<img class = "categoryImage" src = ${product.Image} />
<div class = "categoryName">${product.Name}</div>
<div class = "categoryPrice">$ ${product.Price}</div>
<br>
<button id = "seeMore" onclick = "seeMore('${productID}')">See
more</button>
</div>
`;
return generatedHTML;
}
function seeMore (productID) {
window.location = `./productDetails.html?productID = ${productID}`;//issue
}
function getProductIDFromUrl () {
var params = new URLSearchParams(window.location.search);
var productID = params.get('productID');
return productID;
}
async function getDetailsFromServer(productID) {
var detailsResponse = await fetch(`https://finalonlineshop.firebaseio.com/Products/details/${productID}.json`);
var details = await detailsResponse.json();
return details;
}
async function seeDetails(detailsPromise) {
var details = await detailsPromise;
var generatedHTML = `
<div id = "detailsAboutProduct">
<img src = "${details.Image}" /> //Cannot read property "Image" of null
<div>${details.Name}</div>
<div>${details.Details}</div>
<div>$ ${details.Price}</div>
<div>${details.Qty}</div>
<button id = "addToCart" onclick = "addToCart();">Add to
cart</button>
</div>
`;
document.getElementById('details').innerHTML = generatedHTML;
}
</script>
get rid of the spaces around the = in the URL, that gets encoded as %20. You should also use encodeURIComponent() to escape any special characters in the product ID.
function seeMore (productID) {
window.location = `./productDetails.html?productID=${encodeURIComponent(productID)}`;
}
Your query to your firebase app seems to be wrong.
you are fetching : https://finalonlineshop.firebaseio.com/Products/details/${productID}.json
which return null
but you have to fetch: https://finalonlineshop.firebaseio.com/${productID}.json
instead and it will return the right object