PBL - Input/Output with HTML
HTML5 Input
Idea is simple. HTML input is simply declared by:
<input type="">
Basically, we just create some input element via HTML, and allow user input.
HTML Input Validation
This, as the name suggests, is just checking input data and ensuring it abides by a criteria. I’ve been doing a lot of Rust lately (which is notorious for its safety and general practices regarding data sanitization), and this is really just preemptive error handling – a self coined term. Case in point, we’re just checking to make sure inputs can actually be operated on before operating on them, lest we leave more leeway for bugs.
<script>
function myFunction() {
// Get the value of the input field with id="numb"
let x = document.getElementById("numb").value;
// If x is Not a Number or less than one or greater than 10
let text;
if (isNaN(x) || x < 1 || x > 10) {
text = "Input not valid";
} else {
text = "Input OK";
}
document.getElementById("demo").innerHTML = text;
}
</script>
HTML DOM
Now it gets interesting. Here we actually start accessing the HTML via JS (wow!). You’ll probably most frequently see this in the form of: document.getElementById()
(as indicated in the previous code too).
CSSE Implementations
Since this is pretty broad topic, I’ve had WAY too many instances of use to count, but I’ll put some of my more interesting applications.
Playing Sounds in Platformer4x
// Play a sound by its ID
static playSound(id) {
const sound = document.getElementById(id);
sound.play();
}
This was my first introduction mini-project to the Platformer4x game, but it was a cool activity to learn some OOP principles. This specific static method was stored under the GameEnv.js
class, which handles fundamentals features of the game.
The method it uses to play sound is not by pure JS (I suspect this would be some levels harder, but doable), but rather by accessing audio files loaded into the HTML document.
We can load documents in like this:
<!-- index.md -->
<!--Audio for Everlong by Foo Fighters (Winter) -->
<audio id="everlong" src="/lucas_2025/assets/audio/everlong.mp3" preload="auto"></audio>
The id
field assigns an ID; the src
field links the location of the file; and we call it from JS by using that ID reference to access the HTML object from the JS end of the code.
Using this knowledge, I then wrote some more static methods for audio:
// Play a sound by its ID in a loop
static loopSound(id) {
const sound = document.getElementById(id);
sound.loop = true;
sound.play();
}
// Stop all sounds
static stopAllSounds() {
const sounds = document.getElementsByTagName('audio');
for (let sound of sounds) {
sound.pause();
sound.currentTime = 0;
}
}
The final implementation was “epic” music while playing Platformer4x levels.
Interactive Demos for Local Storage
<div>
<button onclick="saveData()">Save Data</button>
<button onclick="loadData()">Load Data</button>
<pre id="output" style="border: 1px solid #ccc; padding: 10px; background-color:rgb(15, 15, 15);"></pre>
</div>
<script>
function saveData() {
localStorage.setItem("cat", "meow");
alert("Data saved!");
}
function loadData() {
const data = localStorage.getItem("cat");
document.getElementById("output").innerText = data ? `Stored data: ${data}` : "No data found";
}
</script>
I added this code for a demo of local storage in a lesson we gave. Code here actually does 2 of the 3 topcis mentioned here: input and output (but no validation hah). Idea behind this code was to quickly demo how local storage works and writing data to the HTML directly to show outputs. This is frequently used in other lessons I created.
Drawing explosion placeholders in Platformer4x using DOM objects
// Set up explosions as HTML DOM objects
// Will fix this later with actual explosion img
const explosionX = Math.random() * GameEnv.innerWidth;
const explosionY = 0.65 * GameEnv.innerHeight;
const explosion = document.createElement('div');
explosion.style.position = 'absolute';
explosion.style.left = `${explosionX}px`;
explosion.style.top = `${explosionY}px`;
explosion.style.width = '100px';
explosion.style.height = '100px';
explosion.style.backgroundColor = 'red';
explosion.style.opacity = 0;
explosion.style.transition = 'opacity 1s ease-in-out';
document.body.appendChild(explosion);
// Fade in the explosion
setTimeout(() => {
explosion.style.opacity = 1;
}, 100);
// Flash the explosion and check player position
setTimeout(() => {
explosion.style.opacity = 0;
const playerX = GameEnv.PlayerPosition.playerX;
const playerY = GameEnv.PlayerPosition.playerY;
const distance = Math.sqrt(Math.pow(playerX - explosionX, 2) + Math.pow(playerY - explosionY, 2));
if (distance < 100) {
this.kill(GameEnv.player);
}
}, 1100);
// Remove explosion after some time
setTimeout(() => {
document.body.removeChild(explosion);
}, 2000);
Code here is probably self-explanatory, but we’re just drawing red boxes on the screen by appending it to the HTML document body. Not exactly the best practice for a game like platformer, but I digress.
My Implementations
I wanted to write some Rust code, so I started a project to send requests the our Synergy grade server and fetch grades. My original boilerplate half-written code looked like this before I moved to using Tauri with Rust (connecting a webapp backend and frontend for fetching grades):
use reqwest::{Client, Error, header};
use std::collections::HashMap;
use std::env;
use scraper::{Html, Selector};
use reqwest::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, REFERER, USER_AGENT, COOKIE, SET_COOKIE, HeaderValue, HeaderMap};
use reqwest::cookie::{Jar, CookieStore};
use std::sync::Arc;
use flate2::read::GzDecoder;
use std::io::Read;
use ua_generator;
#[tokio::main]
async fn main() -> Result<(), Error> {
let cookie_jar = Arc::new(Jar::default());
//let client = Client::builder().cookie_store(true).build()?; // Session handling enabled
let client = Client::builder().cookie_provider(Arc::clone(&cookie_jar)).build()?;
let login_url = "https://sis.powayusd.com/PXP2_Login_Student.aspx?regenerateSessionId=true";
let launchpad_url = "https://sis.powayusd.com/PXP2_LaunchPad.aspx";
let gradebook_url = "https://sis.powayusd.com/PXP2_GradeBook.aspx?AGU=0";
// Get credentials from environment variables
let username = env::var("SYNERGY_USERNAME").unwrap();
let password = env::var("SYNERGY_PASSWORD").unwrap();
// Prepare form data for login
let mut form_data = HashMap::new();
form_data.insert("ctl00$MainContent$username", username);
form_data.insert("ctl00$MainContent$password", password);
form_data.insert("ctl00$MainContent$Submit1", "Login".to_string());
// Send login request
let login_res = client.post(login_url).form(&form_data).send().await?;
if !login_res.status().is_success() {
println!("Login failed! Status: {}", login_res.status());
return Ok(());
}
println!("Login successful!");
// Ensure session by visiting LaunchPad
let launchpad_res = client.get(launchpad_url).header(USER_AGENT, "Mozilla/5.0").send().await?;
if !launchpad_res.status().is_success() {
println!("LaunchPad error! Status: {}", launchpad_res.status());
return Ok(());
}
println!("LaunchPad accessed successfully.");
let cookies = match launchpad_url.parse() {
Ok(url) => {
if let Some(cookies) = cookie_jar.cookies(&url) {
cookies.to_str().unwrap_or("").to_string()
} else {
String::new()
}
},
Err(e) => {
println!("Failed to parse URL: {}", e);
return Ok(());
}
};
println!("Cookies: {:?}", cookies);
let user_agent = ua_generator::ua::spoof_ua();
// Log the request details
println!("Sending request to GradeBook URL: {}", gradebook_url);
println!("User-Agent: {}", user_agent);
println!("Cookies: {}", cookies);
let gradebook_res = client.get(gradebook_url)
.header(USER_AGENT, user_agent)
.header(COOKIE, cookies)
.send()
.await?;
// let user_agent = ua_generator::ua::spoof_ua();
// // Fetch the GradeBook page with additional headers
// let gradebook_res = client
// .get(gradebook_url)
// .header(USER_AGENT, user_agent)
// //.header(USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0") // Mimics a real browser
// .header(ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") // Accept header
// .header(ACCEPT_ENCODING, "gzip, deflate, br, zstd") // Accept-Encoding header
// .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") // Accept-Language header
// .header(COOKIE, "ASP.NET_SessionId=mmqkenlyqyo4ygnjgapugdb4; SERVERID=parweb2|Z6aMv|Z6aGP")
// //.header(COOKIE, session_cookie) // Session cookie
// .send()
// .await?;
let status = gradebook_res.status();
let content_encoding = gradebook_res.headers().get(header::CONTENT_ENCODING).cloned();
let body_bytes = gradebook_res.bytes().await?;
println!("Content-Encoding: {:?}", content_encoding);
if status.is_success() {
println!("Gradebook fetched successfully!");
let decompressed_body = if content_encoding == Some(header::HeaderValue::from_static("gzip")) {
println!("Decompressing GZIP...");
let body = body_bytes.as_ref();
let mut decoder = GzDecoder::new(body);
let mut decompressed_body = String::new();
match decoder.read_to_string(&mut decompressed_body) {
Ok(_) => decompressed_body,
Err(e) => {
println!("Failed to decompress GZIP: {}", e);
return Ok(());
}
}
} else {
println!("No decompression needed.");
body_bytes.into_iter().map(|byte| byte as char).collect()
};
println!("{}", decompressed_body);
let selector = match Selector::parse(".gb-class-row") {
Ok(selector) => selector,
Err(e) => {
println!("Failed to parse selector: {}", e);
return Ok(());
}
};
let document = Html::parse_document(&decompressed_body);
let divs = document.select(&selector);
for div in divs {
let text: Vec<_> = div.text().collect();
println!("Find div: {}", text.join(" "));
}
} else {
println!("Gradebook request failed! Status: {}", status);
}
Ok(())
}
This code turned out the be fruitless, but I did use a bit of JS and HTML for webapp. The webapp, however, ran REALLY slow and froze whenever I sent the web requests even while I was multithreading. I tried to get the Python task (which I used for the requests instead of Rust because I was having problems with cookies) to run as a background task, but couldn’t figure out how. Nonetheless, it was a great learning experience.