How to make a dropdown autocomplete navigable by keyboard - javascript

Here, I have a nice autocomplete dropdown that, given a city typed by the user, the page will show all possible city-state-country triplets. For example the name Guadalajara would suggest
Guadalajara de Buga, Valle del Cauca Department, Colombia
Guadalajara, Castilla La Mancha, Spain
and
Guadalajara, Jalisco, Mexico
The code works perfectly from both the backend and the frontend, the only problem left is that the autosuggest drop down menu will only work with the mouse and not with the keyboard. The Up and Down arrow keys will not allow to navigate through the most appropriate choice and pressing esc doesn't cancel the menu.
I tried everything and I have no idea about what is missing to make this work with the keyboard as well. I would like to find a solution with pure vanilla JavaScript and that would involve the least possible changes in the rest of working code. Here the entire code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vanilla Javascript Cities Test</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma#0.8.2/css/bulma.min.css">
</head>
<body id="body">
<div class="title has-text-centered" id="main"></div>
<section id="form" class="section" onclick="clearCities()">
<div class="container">
<h1 class="title">Vanilla Cities Javascript</h1>
<!-- Row for City, State and Country -->
<div id="location">
<div class="field">
<div class="control">
<div id="cities-dropdown" class="dropdown">
<div class="dropdown-trigger">
<input name="cities" id="cities" onkeyup="getCities()" maxlength="50" class="input" type="text" placeholder="Enter a city" aria-haspopup="true" aria-controls="dropdown-menu3" -autocomplete="off" required
>
</div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div id="cities-dropdown-content" class="dropdown-content">
<!-- content -->
<a class="dropdown-item"></a>
</div>
</div>
</div> Clear
</div>
<span class="is-size-7 has-text-info">(If your location doesn't appear immediately, try to type slower).</span>
</div>
<!-- City Field -->
<div class="field">
<div class="control">
<input name="city" id="city" class="input" type="text" placeholder="City" required>
</div>
</div>
<!-- State Field -->
<div class="field">
<div class="control">
<input name="state" id="state" class="input" type="text" placeholder="State" required>
</div>
</div>
<!-- Country Field -->
<div class="field">
<div class="control">
<input name="country" id="country" class="input" type="text" placeholder="Country" required>
</div>
</div>
</div>
</div>
</section>
</body>
<script>
function getCities(){
var inputCity = document.getElementById('cities').value;
const city = changeCase(inputCity);
if(city.length <= 2){
return false;
}
// Create request to get cities locations
var request = new XMLHttpRequest();
request.addEventListener("load", transferComplete);
request.open("GET", "/cities/?cityname=" + city);
request.send();
// Called when transfer is complete
function transferComplete(event){
//alert(event.srcElement.response);
locations = JSON.parse(event.srcElement.response);
// Return false if no matching city was found
if(locations.length == 0){
return false;
}
// Append choices
dropContent = document.getElementById('cities-dropdown-content');
dropContent.innerHTML = "";
for (var i = 0; i < locations.length; i++) {
var link = document.createElement("a");
link.setAttribute("onclick", "setCity(" + JSON.stringify(locations[i].name) + "," + JSON.stringify(locations[i].state) + "," + JSON.stringify(locations[i].country) + ")");
link.setAttribute("class", "dropdown-item");
link.innerHTML = locations[i].name + ", " + locations[i].state.name + ", " + locations[i].country.name
dropContent.append(link);
}
document.getElementById("cities-dropdown").classList.add("is-active");
}
// document.getElementById("products-list").innerHTML = html;
}
function setCity(city, state, country){
document.getElementById('cities').value = city+', '+state.name+', '+country.name;
document.getElementById('city').value = city;
document.getElementById('state').value = state.name;
document.getElementById('country').value = country.name;
document.getElementById('cities-dropdown-content').innerHTML = "";
document.getElementById("cities-dropdown").classList.remove("is-active");
}
function clearCities(){
document.getElementById('cities-dropdown-content').innerHTML = "";
document.getElementById("cities-dropdown").classList.remove("is-active");
}
function deleteEntry(e){
e.preventDefault();
document.getElementById('cities').value = '';
}
function changeCase(inputCity){
return inputCity
.replace(/([a-z])([A-Z])/g, function (allMatches, firstMatch, secondMatch) {
return firstMatch + " " + secondMatch;
})
.toLowerCase()
.replace(/([ -_]|^)(.)/g, function (allMatches, firstMatch, secondMatch) {
return (firstMatch ? " " : "") + secondMatch.toUpperCase();
}
);
}
var eraser = document.getElementById("clear");
eraser.addEventListener('click', deleteEntry);
</script>
I hope to find the simpler and least invasive solution to complete a code that just have this left.

I would change your dropdown and input to the semantic html datalist element:
<label for="ice-cream-choice">Choose a flavor:</label>
<input list="ice-cream-flavors" id="ice-cream-choice" name="ice-cream-choice" />
<datalist id="ice-cream-flavors">
<option value="Chocolate">
<option value="Coconut">
<option value="Mint">
<option value="Strawberry">
<option value="Vanilla">
</datalist>
Then both search and keyboard accessibility should work more or less out of the box.
If you really don't want to do that, you have to attach event listeners (vanilla js) to the different elements of your dropdown and input field and write a rather long thing to be able to tab or arrow key your way down and up and back into the search field. I would say it's too much work compared to the reward.
Better to use some time to css-animate the datalist so it behaves a bit smoother and style it neatly.

Related

PHP code doesn't see input added through js

I have form that collect datas when SAVE button is pressed from three inputs. First two is already loaded on the site, but last appears when DVD-disk is selected in <select>. So PHP code see values from first two inputs, but not from the last one. I added name and id to all of them. Inputs are in the main container that is in form.
Expected output: echo ($DVDdisk) show data
Real output: Undefined index: DVDsize
let selector = document.getElementById("selector");
let main = document.getElementById("input-main-add");
let div = document.createElement('div');
let h2 = document.createElement('H2');
let input = document.createElement('input');
selector.addEventListener("change", (e) => {
if (selector.value == "DVD") {
div.classList.add('input-add-holder');
main.appendChild(div);
h2.textContent = 'Enter size:';
h2.style.display = 'inline-block';
div.appendChild(h2);
input.setAttribute("name", "DVDsize");
input.setAttribute("id", "DVDsize");
div.appendChild(input);
}
});
<form method="POST" action="add.php">
<button class="accept-btn" type="submit">SAVE</button>
<!-- + -->
<button class="decline-btn">CANCEL</button>
<!-- + -->
</div>
</div>
<div class="input-main-add" id="input-main-add">
<!-- + -->
<div class="input-add-holder">
<H2 style="display:inline-block">SKU: </H2>
<input class="something" name="SKU" id="SKU"></input>
</div>
<div class="input-add-holder">
<H2 style="display:inline-block">Name: </H2>
<input class="something" name="name" id="name"></input>
</div>
<div class="input-add-holder">
<H2 style="display:inline-block">Type Switcher: </H2>
<select name="selector" id="selector">
<option value="DVD" id="DVD" name="DVD">DVD-Disk</option>
<option value="book" id="book" name="book">Book</option>
<option value="furniture" id="furniture" name="furniture">Furniture</option>
</select>
</div>
</div>
</form>
PHP code:
<?php
$SKU = $_POST["SKU"];
$name = $_POST["name"];
$DVDsize = $_POST["DVDsize"];
echo ($SKU);
echo ($name);
echo ($DVDsize);
?>
Your JS listen for change event on Type Switcher select box that the selected value must be DVD-Disk but your default value of this select box is DVD-Disk which is already selected.
So, this event will never happens when you just load the page, fill form (without change select box) and submit.
If this event never happens, it means input name DVDSize will not rendered and not send to server. That's why your PHP doesn't see this input.
You have to manually trigger change event for select box once DOM ready.
let selector = document.getElementById("selector");
let main = document.getElementById("input-main-add");
let div = document.createElement('div');
let h2 = document.createElement('H2');
let input = document.createElement('input');
selector.addEventListener("change", (e) => {
if (selector.value == "DVD") {
div.classList.add('input-add-holder');
main.appendChild(div);
h2.textContent = 'Enter size:';
h2.style.display = 'inline-block';
div.appendChild(h2);
input.setAttribute("name", "DVDsize");
input.setAttribute("id", "DVDsize");
div.appendChild(input);
}
});
// manually trigger change event.
let selectTypeSwitcher = document.getElementById('selector');
if (selectTypeSwitcher) {
selectTypeSwitcher.dispatchEvent(new Event('change'));
}
<form method="POST" action="add.php">
<button class="accept-btn" type="submit">SAVE</button>
<!-- + -->
<button class="decline-btn">CANCEL</button>
<!-- + -->
</div>
</div>
<div class="input-main-add" id="input-main-add">
<!-- + -->
<div class="input-add-holder">
<H2 style="display:inline-block">SKU: </H2>
<input class="something" name="SKU" id="SKU"></input>
</div>
<div class="input-add-holder">
<H2 style="display:inline-block">Name: </H2>
<input class="something" name="name" id="name"></input>
</div>
<div class="input-add-holder">
<H2 style="display:inline-block">Type Switcher: </H2>
<select name="selector" id="selector">
<option value="DVD" id="DVD" name="DVD">DVD-Disk</option>
<option value="book" id="book" name="book">Book</option>
<option value="furniture" id="furniture" name="furniture">Furniture</option>
</select>
</div>
</div>
</form>
Run the code above while open network inspector and you will see DVDSize input send to the server.

Array of html inputs

I have a html form, where user need to enter the name and address of their office. The number of offices are dynamic.
I want to add an Add More button, so that users can enter the details of any number of offices.
My question is, how can I create an array of inputs where new elements can be added and removed using JavaScript. Currently, I'm doing it using js clone method, but I want an array, so that input data can easily be validated and stored to database using Laravel.
What I'm currently doing..
This is my HTML form where users have to enter the address of their clinic or office. I've taken a hidden input field and increasing the value of that field whenever a new clinic is added, so that I can use loop for storing data.
<div class="inputs">
<label><strong>Address</strong></label>
<input type="text" class="hidden" value="1" id="clinicCount" />
<div id="addresscontainer">
<div id="address">
<div class="row" style="margin-top:15px">
<div class="col-md-6">
<label><strong>Clinic 1</strong></label>
</div>
<div class="col-md-6">
<button id="deleteclinic" type="button" class="close deleteclinic"
onclick="removeClinic(this)">×</button>
</div>
</div>
<textarea name="address1" placeholder="Enter Clinic Address" class="form-control"></textarea>
<label class="text-muted" style="margin-top:10px">Coordinates (Click on map to get coordinates)</label>
<div class="row">
<div class="col-md-6">
<input class="form-control" id="latitude" type="text" name="latitude1" placeholder="Latitude" />
</div>
<div class="col-md-6">
<input class="form-control" id="longitude" type="text" name="longitude1" placeholder="Longitude" />
</div>
</div>
</div>
</div>
</div>
<div class="text-right">
<button class="btn btn-success" id="addclinic">Add More</button>
</div>
And my js code..
function numberClinic(){
//alert('test');
var i=0;
$('#addresscontainer > #address').each(function () {
i++;
$(this).find("strong").html("Clinic " + i);
$(this).find("textarea").attr('name','name'+i);
$(this).find("#latitude").attr('name','latitude'+i);
$(this).find("#longitude").attr('name','longitude'+i);
});
}
$("#addclinic").click(function(e){
e.preventDefault();
$("#addresscontainer").append($("#address").clone());
numberClinic();
$("#addresscontainer").find("div#address:last").find("input[name=latitude]").val('');
$("#addresscontainer").find("div#address:last").find("input[name=longitude]").val('');
$("#clinicCount").val(parseInt($("#clinicCount").val())+1);
});
function removeClinic(address){
if($("#clinicCount").val()>1){
$(address).parent('div').parent('div').parent('div').remove();
$("#clinicCount").val(parseInt($("#clinicCount").val())-1);
}
numberClinic();
}
This way, I think I can store the data to the database but can't validate the data. I'm using the laravel framework.
One way you could do this is by using the position of the input in the parent as the index in the array, then saving the value in the array every time each input is changed. Then you can just add and remove inputs.
Sample code:
var offices = document.getElementById('offices');
var output = document.getElementById('output');
var data = [];
var i = 0;
document.getElementById('add').addEventListener('click', function() {
var input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('placeholder', 'Office');
var button = document.createElement('button');
var index = i++;
input.addEventListener('keyup', function() {
for (var i = 0; i < offices.children.length; i++) {
var child = offices.children[i];
if (child === input) {
break;
}
}
// i is now the index in the array
data[i] = input.value;
renderText();
});
offices.appendChild(input);
});
document.getElementById('remove').addEventListener('click', function() {
var children = offices.children;
if (children.length === data.length) {
data = data.splice(0, data.length - 1);
}
offices.removeChild(children[children.length - 1]);
renderText();
});
function renderText() {
output.innerHTML = data.join(', ');
}
JSFiddle: https://jsfiddle.net/94sns39b/2/

On particular javascript function call HTML control's value changes to its default value

I have a form where I need such facility where user input some data in textfield and hits enter that time using jquery it should create new controls like new textfield, dropdown menu, textfield. and it works also, but it has a bug like when user input another data and hits enter that time value of previous controls, dropdown and textfield changes to its default.
Here is my code:
<script type="text/javascript">
function addMbo(value){
var div = document.getElementById('mboTable');
var keycode = (event.keyCode ? event.keyCode : event.which);
if(keycode == '13'){
event.preventDefault();
var divName = document.getElementById('mboName');
var divState = document.getElementById('mboState');
var divProgress = document.getElementById('mboProgress');
divName.innerHTML = divName.innerHTML + "<input class=form-control type=text name=mboName value='" + value + "' id=mboNamw/>"
divState.innerHTML = divState.innerHTML + "<select class=form-control > <option value=1>In Progress </option><option <value=2>Completed</option><option value=3>Cancled</option></select>"
divProgress.innerHTML = divProgress.innerHTML + "<input class=form-control type=text name=progress id=progress />"
document.getElementById('mboNameInput').value = null;
}
}
</script>
HTML code:
<div class="col-sm-4">
<div class="row">
<div class="col-sm-5">
<b>Objectives</b>
</div>
<div class="col-sm-4">
<b>Status</b>
</div>
<div class="col-sm-3">
<b>Compl. %</b>
</div>
</div>
<div class="row">
<div id="mboTable">
<div id="mboName" class="col-sm-5"></div>
<div id="mboState" class="col-sm-4"></div>
<div id="mboProgress" class="col-sm-3"></div>
</div>
</div>
<div class="row" >
<div class="col-sm-5">
<input type="text" id="mboNameInput" class="form-control " onkeydown="addMbo(this.value)" placeholder="Your MBO...">
</div>
</div>
</div>
here is jsfiddle
When you do innerHTML on any element, it will completely destroy what ever content already there inside the elment.
Rather you should appndChild as shown below,
var divName = document.getElementById('mboName');
var mboName = document.createElement("INPUT");
mboName.setAttribute("type", "text");
mboName.setAttribute("class", "form-control");
divName.appendChild(mboName );
you can do the related changes to above code and make your things work.
See below code to add select dropdown.
var divState = document.getElementById('mboState');
var selectData = [{name:"In Progress",value:"1"},{name:"Completed",value:"2"},{name:"Cancelled",value:"3"}];
var selectList = document.createElement("select");
selectList.class = "form-control";
divState.appendChild(selectList);
for (var i = 0; i < selectData.length; i++) {
var option = document.createElement("option");
option.value = selectData[i].value;
option.text = selectData[i].name;
selectList.appendChild(option);
}

Is there a way to simplify this into only a few lines of jQuery

I am trying to display input values of a form into their corresponding div/p tags. So whenever a user starts typing into an input field, that value will be written in the input box as well as in a p tag else where on the page.
I have my jQuery looking at every individual form field and displaying that info to an assigned p tag. Is there a way to write this code so I do not have to create multiple lines of code for each form field?
Write now it is checking for if there is a change in the form, and then seeing if the field has a value and if so displaying the information in the p tag, if not it makes the p tag empty.
He is what I have working now.
$('#webform-client-form-1').on('change',function(e){
/* Distributor Name INPUT */
var distributorNameInput=$('#edit-submitted-distributor-name').val();
if( !$("#edit-submitted-distributor-name").val() ) {
$(".distributor-name p").html("");
} else {
$(".distributor-name p").html("<strong>Distributor Name</strong> <br/>" + distributorNameInput);
};
/* Year INPUT */
var YearInput=$('#edit-submitted-year').val();
if( !$("#edit-submitted-year").val() ) {
$(".year p").html("");
}else {
$(".year p").html("<strong>Year</strong> <br/>" + YearInput);
};
/* General Information INPUT */
var generalinfoInput=$('#edit-submitted-general-information').val();
if( !$("#edit-submitted-general-information").val() ) {
$(".general-info").html("");
}else{
$(".general-info").html("<h2>General Information</h2> <p>" + generalinfoInput + "</p>");
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<form enctype="multipart/form-data" action="/" method="post" id="webform-client-form-1" accept-charset="UTF-8">
<p>
<label>Distributor Name*</label>
<input type="text" id="edit-submitted-distributor-name" name="submitted[distributor_name]" value=" " size="60" maxlength="128" class="form-text required">
</p>
<p>
<label for="edit-submitted-year">Year*</label>
<select id="edit-submitted-year" name="submitted[year]" class="form-select">
<option value="2015" selected="selected">2015</option>
<option value="2016">2016</option>
</select>
</p>
</form>
<div class="preview" id="preview">
<div class="distInfo">
<div class="distributor-name">
<p><strong>Distributor Name</strong> <br>Text will go here</p>
</div>
<div class="year">
<p><strong>Year</strong> <br>2015</p>
</div>
</div>
</div>
Instead of using IDs you can use classes and data attributes to pass input target element:
$(document).ready(function() {
$(document).on('change input paste', '.input', function() {
$($(this).data('target')).text($(this).val());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" class="input" data-target="#text-input" />
<select class="input" data-target="#select-input">
<option value=""></option>
<option value="A">A</option>
<option value="B">B</option>
</select>
<hr/>
<div id="text-input"></div>
<div id="select-input"></div>
you probably meaning something like inline if
/* Distributor Name INPUT */
var distributorNameInput = $('#edit-submitted-distributor-name').val();
$(".distributor-name p").html( distributorNameInput ? "<strong>Distributor Name</strong> <br/>" + distributorNameInput : "");
/* Year INPUT */
var YearInput = $("#edit-submitted-general-information").val();
$(".year p").html( YearInput ? "<strong>Year</strong> <br/>" + YearInput: "");
/* General Information INPUT */
var generalinfoInput = $('#edit-submitted-general-information').val();
$(".general-info").html( generalinfoInput ? "<h2>General Information</h2> <p>" + generalinfoInput : "");
The syntax for an inline if is
condition ? true code: false code
You can create an array of objects and iterate over it. This is great if you are thinking on adding new elements, because you will only need to add them to the array.
$('#webform-client-form-1').on('change',function(e){
var elements=new Array(
{valueToCheck:'edit-submitted-distributor-name',className:'distributor-name', label: 'Distributor Name'},
{valueToCheck:'edit-submitted-year',className:'distributor-name', label: 'Distributor Name'},
{valueToCheck:'edit-submitted-distributor-name',className:'distributor-name', label: 'Distributor Name'},
);
//check'em
elements.forEach(function(element){
var myValue=$("#"+element.valueToCheck).val();
$("."+element.className+" p").html( myValue ? "<strong>"+element.label+"</strong> <br/>" + myValue: "");
});
}

Text input from jquery not working

Hi I have a script that appends a set of constant string to a textarea it works fine if I click the button first but as soon as I input text on the textarea, the button does not append the constant string on the textarea if clicked again
here is my code for the click event:
$("#apply").on("click",function() {
var orange = $("#agent_option").val(),
lock = $("#agent_disallowed").val();
$("#textareaFixed").html(orange + " " + lock );
});
and Here is my html form:
<label for="agent_option" class="control-label">User-Agent :</label></div>
<div class="col-md-6">
<select id="agent_option" class="form-control input-sm">
<option value="all">All</option>
<option value="banana">Banana</option>
<option value="apple">Apple</option>
<option value="melon">Melon</option>
<option value="lynx">Lynx</option>
<option value="liger">Liger</option>
</select>
</div>
<div class="row">
<div class="col-md-4">
<label for="ax_disallowed" class="control-label">Disallow :</label></div>
<div class="col-md-6">
<input class="form-control input-sm" id="ax_disallowed" type="text" value="<?=ax_default_disallow;?>">
</div>
</div>
<div class="row">
<div class="col-md-4">
<button id="apply" class="btn btn-default">Register Player</button>
</div>
This is my textarea:
<form method="post" class="form-login">
<div class="form-group">
<textarea name="new_config" class="form-control" id="textareaFixed" cols="60" rows="16"><?=file_get_contents($open); ?></textarea>
</div>
</form>
please help me I tried google it but found irrelevant results. you guys are my only hope now :(
Change .html(...) to .val(...)
$("#apply").on("click",function() {
var orange = $("#agent_option").val(),
lock = $("#ax_disallowed").val();
$("#textareaFixed").val(orange + " " + lock );
});
You need to use append() instead of html()
Please try $("#textareaFixed").append(orange + " " + lock ); if you want to add the new text after the previous one.
If you use html(), it replaces the old stuff with the new one.
There isn't any input named as "agent_disallowed", you need to use correct id.
$("#apply").on("click",function() {
var orange = $("#agent_option").val();
var lock = $("#ax_disallowed").val();
$("#textareaFixed").html(orange + " " + lock );
});

Categories