This application is the front end to a very basic database application. The front end assumes that the back end database would have fields as per the html form.
some concerns on db2form.js I have are:
some very specific html doc references in the javascript - eg document.forms.searchform.elements.search.innerText = "Search";
current_contact_idx global variable doesn't seem right.
As for the css file, that could probably be improved a lot.
Any feedback on this application would be very welcome.
The html page:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Itel Office</title> <link rel="stylesheet" href="style.css"> <script src="db2form.js"></script> </head> <body> <nav> <a href="">Contacts</a> <a href="call_identifier_pretty.html" target="_blank">Call Log</a> </nav> <section> <h1>Contacts</h1> <p>Enter text below and click Search button to find a contact</p> <form name="searchform" action="/cgi-bin/database.exe" method="POST"> <label for="rowid">ID: </label> <input id="rowid" type="text" name="rowid" value="" readonly disabled> <br> <label for="name">Name: </label> <input id="name" type="text" name="name" value=""> <br> <label for="company">Company: </label> <input id="company" type="text" name="company" value=""> <br> <label for="email">Email: </label> <input id="email" type="email" name="email" value=""> <br> <label for="ddi">Telephone: </label> <input id="ddi" type="tel" name="ddi" value=""> <br> <label for="mobile">Mobile: </label> <input id="mobile" type="tel" name="mobile" value=""> <br> <label for="switchboard">alt Telephone: </label> <input id="switchboard" type="tel" name="switchboard" value=""> <br> <label for="url">Web: </label> <input id="url" type="text" name="url" value=""> <br> <label for="address1">Address line 1: </label> <input id="address1" type="text" name="address1" value=""> <br> <label for="address2">Address line 2: </label> <input id="address2" type="text" name="address2" value=""> <br> <label for="address3">Address line 3: </label> <input id="address3" type="text" name="address3" value=""> <br> <label for="address4">Address line 4: </label> <input id="address4" type="text" name="address4" value=""> <br> <label for="postcode">Postcode: </label> <input id="postcode" type="text" name="postcode" value=""> <br> <label for="category">Category: </label> <input id="category" type="text" name="category" value=""> <br> <label for="notes">Notes: </label> <textarea id="notes" name="notes"></textarea> <br> <div class="buttons"> <button name="search" type="button" onclick="process(document.forms.searchform.elements.search.innerText)">Search</button> <button name="new" type="button" onclick="process('New')">New</button> <button name="edit" type="button" onclick="process('Edit')" disabled>Edit</button> <button name="save" type="button" onclick="process('Save')" disabled>Save</button> <button name="delete" type="button" onclick="process('Delete')" disabled>Delete</button> <button name="first" type="button" onclick="process('First')" disabled>First</button> <button name="next" type="button" onclick="process('Next')" disabled>Next</button> <button name="prior" type="button" onclick="process('Prior')"disabled>Prior</button> <button name="last" type="button" onclick="process('Last')" disabled>Last</button> </div> </form> <div id="status"> </div> </section> </body> </html>
The css file, style.css:
body{ background-color: #ffff00; } nav{ box-sizing:border-box; background-color:#409fff; /* blue we like */ display: inline-block; width: 20%; min-width: 125px; margin-right:15px; height:100vh; overflow: auto; } nav a{ display:block; line-height: 45px; height:45px; color: #FFFFFF; text-decoration: none; padding-left: 50px; margin:10px 0 10px 5px; } section{ display: inline-block; width:70%; height:100vh; overflow: auto; } h1{ color: #409fff; padding: 2px; margin: 0; } form { display: grid; grid-template-columns: 150px 1fr; border: 0; } label { grid-column: 1 / 2; margin: 0; padding:0; border: 0; } input{ grid-column: 2 / 3; margin: 0; padding:0; border: 0; border-radius: 5px; } /*input:focus{ background-color: #fcfab1; } */ textarea{ border-radius: 5px; height: 20px; } .buttons{ display: grid; grid-column: 2 / 3; grid-gap: 10px; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; }
The javascript file, db2form.js:
let current_contact_idx = -1; let records = null; function search_mode() { // now change button to say Search document.forms.searchform.elements.search.innerText = "Search"; document.forms.searchform.elements.new.disabled = false; document.forms.searchform.elements.edit.disabled = true; document.forms.searchform.elements.save.disabled = true; document.forms.searchform.elements.delete.disabled = true; document.forms.searchform.elements.first.disabled = true; document.forms.searchform.elements.next.disabled = true; document.forms.searchform.elements.prior.disabled = true; document.forms.searchform.elements.last.disabled = true; } function found_mode() { // now change button to say Cancel document.forms.searchform.elements.search.innerText = "Cancel"; document.forms.searchform.elements.new.disabled = false; document.forms.searchform.elements.edit.disabled = false; document.forms.searchform.elements.save.disabled = true; document.forms.searchform.elements.delete.disabled = false; document.forms.searchform.elements.first.disabled = false; document.forms.searchform.elements.next.disabled = false; document.forms.searchform.elements.prior.disabled = false; document.forms.searchform.elements.last.disabled = false; } function new_edit_mode() { // now change button to say Cancel document.forms.searchform.elements.search.innerText = "Cancel"; document.forms.searchform.elements.new.disabled = true; document.forms.searchform.elements.edit.disabled = true; document.forms.searchform.elements.save.disabled = false; document.forms.searchform.elements.delete.disabled = true; document.forms.searchform.elements.first.disabled = true; document.forms.searchform.elements.next.disabled = true; document.forms.searchform.elements.prior.disabled = true; document.forms.searchform.elements.last.disabled = true; } function server_response_callback_search(ajax) { let form_elements = document.forms.searchform.elements; if(ajax.responseText.length == 0) { cancel_step(form_elements); document.getElementById('status').innerHTML = "No record found for your search." return; } console.log("server_response_callback_search response type: " + ajax.getResponseHeader('content-type')); records = JSON.parse(ajax.responseText); if (records.contacts.length > 0) { current_contact_idx = 0; populate_field(records.contacts[current_contact_idx]); found_mode(); } else { current_contact_idx = -1; // reset to no record found search_mode(); // stay in search mode } // display message if (current_contact_idx == -1) { document.getElementById('status').innerHTML = "No record found which matches the criteria"; } else { document.getElementById('status').innerHTML = "Displaying record " + (current_contact_idx + 1).toString() + " of " + records.contacts.length; } } function server_response_callback_update(ajax, rowid) { console.log("server_response_callback_update response type: " + ajax.getResponseHeader('content-type')); let form_elements = document.forms.searchform.elements; search_mode(); // empty all input and textarea fields for (let element of form_elements) { if(element.type != 'hidden') { element.value = ""; } } document.getElementById('status').innerHTML = ajax.responseText;; } function server_response_callback_insert(ajax) { console.log("server_response_callback_insert response type: " + ajax.getResponseHeader('content-type')); let form_elements = document.forms.searchform.elements; search_mode(); // empty all input and textarea fields for (let element of form_elements) { if(element.type != 'hidden') { element.value = ""; } } document.getElementById('status').innerHTML = ajax.responseText; } // We need to display what it is that database.exe returns for these cases function server_response_callback_delete(ajax, rowid) { console.log("server_response_callback_delete response type: " + ajax.getResponseHeader('content-type')); let form_elements = document.forms.searchform.elements; search_mode(); // empty all input and textarea fields for (let element of form_elements) { if(element.type != 'hidden') { element.value = ""; } } document.getElementById('status').innerHTML = ajax.responseText; } function populate_field(element) { let formelements = document.forms.searchform.elements; // formelements is an array for (let i = 0; i < formelements.length; i++) { if (formelements[i].name in element) { formelements[i].value = element[formelements[i].name]; } else { formelements[i].value = ""; } } document.getElementById('status').innerHTML = "Displaying record " + (current_contact_idx + 1).toString() + " of " + records.contacts.length; } function edit_step() { new_edit_mode(); } function cancel_step(form_elements) { search_mode(); // empty all input and textarea fields for (let element of form_elements) { if(element.type != 'hidden') { element.value = ""; } } document.getElementById('status').innerHTML = ""; } function new_step(form_elements) { new_edit_mode(); // empty all input and textarea fields for (let element of form_elements) { if(element.type != 'hidden') { element.value = ""; } } document.getElementById('status').innerHTML = "Enter data for new contact, then click Save button to save to database"; } function extract_form_values(form_elements) { let query = ""; let first = "yes"; for (let element of form_elements) { if(["text", "textarea", "tel", "email"].includes(element.type)) { if(first == "no") { query += "&"; } first = "no"; query += element.name; query += "="; query += element.value; } } return query; } function save_step(form_elements) { let request_payload = extract_form_values(form_elements); if(request_payload.length == 0) { //alert("You need to enter some data to save to database"); document.getElementById('status').innerHTML = "You need to enter some data to save to database"; return; } // we determine whether to UPDATE or INSERT based on presence of rowid. // if a rowid assume updating an existing contact, otherwise a new contact if (document.forms.searchform.elements.rowid.value == "") { // go down INSERT route // remove rowid= from payload let pos = request_payload.indexOf("rowid=&"); if (pos != -1) { // remove string request_payload = request_payload.replace("rowid=&", ""); } request_payload += "&operation=INSERT"; console.log("sending query to database server: " + request_payload); // setup ajax callback to handle response ajax_post("/cgi-bin/database.exe", request_payload, server_response_callback_insert); } else { let rowid = parseInt(document.forms.searchform.elements.rowid.value, 10); request_payload += "&operation=UPDATE"; console.log("sending query to database server: " + request_payload); // setup ajax callback to handle response ajax_post("/cgi-bin/database.exe", request_payload, server_response_callback_update, rowid); } } function has_values(form_elements) { for (let element of form_elements) { if(["text", "textarea", "tel", "email"].includes(element.type) && element.name != "rowid" && element.value != "") { return true; } } return false; } function insert_step(form_elements) { // check user actually entered some data in fields if(!has_values(form_elements)) { console.log("attempting to insert but no values populated"); document.getElementById('status').innerHTML = "Enter contact details to add a new contact"; return; } let request_payload = extract_form_values(form_elements); if(request_payload.length == 0) { document.getElementById('status').innerHTML = "You need to enter some update a contact"; return; } request_payload += "&operation=INSERT"; console.log("sending query to database server: " + request_payload); // setup ajax callback to handle response ajax_post("/cgi-bin/database.exe", request_payload, server_response_callback_insert); } function search_step(form_elements) { let query = extract_form_values(form_elements); query += query.length == 0 ? "operation=SELECT" : "&operation=SELECT"; console.log("sending query to database server: " + query); // setup ajax callback to handle response ajax_post("/cgi-bin/database.exe", query, server_response_callback_search); } function ajax_post(url, request, callback, arg) { // setup ajax callback to handle response var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { callback(this, arg); } }; xhttp.open("POST", url, true); xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhttp.send(request); } function delete_step(form_elements) { if(form_elements.rowid.value == "") { const delete_msg = "Form not in correct state to delete a contact"; document.getElementById('status').innerHTML = delete_msg; alert(delete_msg); return; } let rowid = parseInt(form_elements.rowid.value, 10); // DELETE FROM table_name WHERE condition; let request = `rowid=${rowid}&operation=DELETE`; console.log("sending request to database server: " + request); let confirmation = confirm("Click Ok if you are absolutely sure you want to delete this contact from the database"); if (confirmation) { // setup ajax callback to handle response ajax_post("/cgi-bin/database.exe", request, server_response_callback_delete, rowid); } } function process(buttontext) { console.log(`buttontext=${buttontext}`); let form_elements = document.forms.searchform.elements; if (buttontext == "New") { new_step(form_elements); }else if (buttontext == "Edit") { edit_step(); } else if (buttontext == "Save") { save_step(form_elements); } else if (buttontext == "Search") { search_step(form_elements); } else if (buttontext == "Cancel") { cancel_step(form_elements); } else if (buttontext == "Delete") { delete_step(form_elements); } else if (buttontext == "First") { if (records.contacts.length != 0) { current_contact_idx = 0; populate_field(records.contacts[current_contact_idx]); } } else if (buttontext == "Next") { if (records.contacts.length > (current_contact_idx + 1)) { populate_field(records.contacts[++current_contact_idx]); } else { document.getElementById('status').innerHTML = "You are on the last record"; } } else if (buttontext == "Prior") { if (current_contact_idx > 0) { populate_field(records.contacts[--current_contact_idx]); } else { document.getElementById('status').innerHTML = "You are on the first record"; } } else if (buttontext == "Last") { if (records.contacts.length != 0) { current_contact_idx = records.contacts.length - 1; populate_field(records.contacts[current_contact_idx]); } } else { document.getElementById('status').innerHTML = "something has gone wrong - button text incorrectly set"; } } // user can press Enter key to invoke search, Esc key to cancel (go back to ready to search mode) document.onkeydown = function(evt) { evt = evt || window.event; var isEscape = false; var isEnter = false; if ("key" in evt) { isEscape = (evt.key === "Escape" || evt.key === "Esc"); isEnter = (evt.key === "Enter"); } else { isEscape = (evt.keyCode === 27); isEnter = (evt.keyCode === 13); } if (isEscape) { // only handle Escape if Cancel button enabled if(document.forms.searchform.elements.search.innerText == "Cancel") { process("Cancel"); } } else if (isEnter) { // only handle Enter if Search button enabled if(document.forms.searchform.elements.search.innerText == "Search") { process("Search"); } } };
/cgi-bin/database.exe
? The hacker in me gets all warm and fuzzy when it sees an executable exposed to the internet.\$\endgroup\$