I have two scripts handling my multistep form. The first handles the visibility of each step (input/selector boxes) within the form along with the visibility of the pagination buttons (prev, next).
This looks like:
const previousButton = document.querySelector('#prev')
const nextButton = document.querySelector('#next')
const submitButton = document.querySelector('#submit')
const tabTargets = document.querySelectorAll('.tab')
const tabPanels = document.querySelectorAll('.tabpanel')
const isEmpty = (str) => !str.trim().length
let currentStep = 0
// Validate first input on load
validateEntry()
// Next: Change UI relative to current step and account for button permissions
nextButton.addEventListener('click', (event) => {
event.preventDefault()
// Hide current tab
tabPanels[currentStep].classList.add('hidden')
tabTargets[currentStep].classList.remove('active')
// Show next tab
tabPanels[currentStep + 1].classList.remove('hidden')
tabTargets[currentStep + 1].classList.add('active')
currentStep += 1
validateEntry()
updateStatusDisplay()
})
// Previous: Change UI relative to current step and account for button permissions
previousButton.addEventListener('click', (event) => {
event.preventDefault()
// Hide current tab
tabPanels[currentStep].classList.add('hidden')
tabTargets[currentStep].classList.remove('active')
// Show previous tab
tabPanels[currentStep - 1].classList.remove('hidden')
tabTargets[currentStep - 1].classList.add('active')
currentStep -= 1
nextButton.removeAttribute('disabled')
updateStatusDisplay()
})
function updateStatusDisplay() {
// If on the last step, hide the next button and show submit
if (currentStep === tabTargets.length - 1) {
nextButton.classList.add('hidden')
previousButton.classList.remove('hidden')
submitButton.classList.remove('hidden')
validateEntry()
// If it's the first step hide the previous button
}
else if (currentStep == 0) {
nextButton.classList.remove('hidden')
previousButton.classList.add('hidden')
submitButton.classList.add('hidden')
// In all other instances display both buttons
}
else {
nextButton.classList.remove('hidden')
previousButton.classList.remove('hidden')
submitButton.classList.add('hidden')
}
}
function validateEntry() {
let input = tabPanels[currentStep].querySelector('.form-input')
// Start but disabling continue button
nextButton.setAttribute('disabled', true)
submitButton.setAttribute('disabled', true)
// Validate on initial function fire
setButtonPermissions(input)
// Validate on input
input.addEventListener('input', () => setButtonPermissions(input))
// Validate if bluring from input
input.addEventListener('blur', () => setButtonPermissions(input))
}
function setButtonPermissions(input) {
if (isEmpty(input.value)) {
nextButton.setAttribute('disabled', true)
submitButton.setAttribute('disabled', true)
}
else {
nextButton.removeAttribute('disabled')
submitButton.removeAttribute('disabled')
}
}
For the first input box, i have a Jquery autocomplete which pulls data from a json file and presents it as a select option.
$(document).ready(function() {
$.ajax({
dataType: "json",
type: 'Get',
contentType: 'application/json; charset=utf-8',
xhrFields: {
withCredentials: true
},
crossDomain: true,
cache: true,
url: "AvailableFeatures.json",
success: function(data) {
var array = $.map(data.Table, function(item) {
return {
label: item.label,
value: item.label
}
});
$("#txtfeature").autocomplete({
source: array,
minLength: 3,
open: function() {},
close: function() {},
focus: function(event, ui) {},
select: function(event, ui) {},
change: function(event, ui) {
if (!ui.item) {
$("#txtfeature").val("");
}
}
});
},
error: function(data) {}
});
});
I had been using the change event to limit the content of the input box to an item selected and if a user clicks elsewhere and the data isn't as per the selected option, it empties the content of the input box.
The problem I have is that if a user types random text in the box and simply hits continue on the form, it blanks the field but continues on to the next input box.
How can i prevent this from occurring?
Should i even have two scripts handling this form/autocomplete?
I'm sure there's a more efficient way of doing what i'm trying to achieve but i'm a novice in this space and trying to learn whilst trying to build upon an idea.
Any help would be much appreciated!
Related
I have UI with a list of users in a table. Each entry / user has two buttons. One of the buttons is a 'check' button.
<button type="submit" id="$id_from_db" class="bg-aqua btnCheckUser">Check</button>
When a check button is clicked for a particular user a POST request is sent to a server. While the request is loading all buttons should be disabled, the button which started the request should change its class from bg-puple to bg-aqua
and its text from Check to Checking.... If the request returns an error (which is indicated by result having the value 1) the button should change its class from bg-aqua to bg-danger. If there is no error the class should change from bg-aqua to bg-navy and the text from Checking... to User Okay.
I'm able to achieve this but it does not only affect the button which started the request but all buttons.
This is my code:
$(document).on("click", ".btnCheckUser", function() {
var Item_Number = $(this).attr("Item_Number");
/************************** MANIPULATE BUTTONS *******************************************************************************/
$(this).removeClass('bg-purple').addClass('bg-aqua');
$(this).html('Checking ...');
$(this).attr("disabled", true);
$('.btnViewUser').attr("disabled", true);
/******************************* PROCESS AJAX **************************************************************************/
var value = {
Item_Number: Item_Number
};
$.ajax({
url: "./plugins/serverside/CheckUserStatus",
type: "POST",
data: value,
success: function(data, textStatus, jqXHR) {
var data = jQuery.parseJSON(data);
if (data.result == 1) {
$(this).removeClass('bg-aqua').addClass('bg-danger');
$("#CheckUser").html('Failed!');
$('.btnCheckUser').attr("disabled", false);
$('.btnViewUser').attr("disabled", false);
setTimeout(function() {
var table = $('#User_Table').DataTable();
table.ajax.reload(null, false);
}, 3500);
} else if (data.result == 2) {
//------------------------ IF User Okay -------------------------------------------------------------------------------------------------------------------
$("#CheckUser").removeClass('bg-aqua').addClass('bg-navy');
$("#CheckUser").html('User Okay');
$('.btnCheckUser').attr("disabled", false);
$('.btnViewUser').attr("disabled", false);
}
How can I fix this and only affect the button which started the request, but still disable all buttons when a request is loading?
JSFiddle
I stripped the code down to the parts which matter for the button logic. I'm using a dummy API and I randomly generate the result value as I have no access to the actual API you're using.
$(() => $('.btnCheckUser').on('click', ({ target }) => clickHandler(target)));
function clickHandler(button) {
// Disable all buttons when one of them is clicked
$('.btnCheckUser').prop('disabled', true);
$('.btnViewUser').prop('disabled', true);
// Remove all non standard classes, then add 'loading' class and text
$(button).removeClass('bg-purple').removeClass('bg-navy').removeClass('bg-danger').addClass('bg-aqua');
$(button).text('Loading...')
$.ajax({
url: 'https://reqres.in/api/users?delay=3',
success: raw => {
// Random result value
const result = Math.random() > 0.5 ? 1 : 2;
if (result === 1) {
// Something went wrong, add 'danger' class and text
$(button).addClass('bg-danger');
$(button).text('Failed!')
} else if (result === 2) {
// Everything went fine, add 'success' class and text
$(button).addClass('bg-navy');
$(button).text('Success!');
}
},
error: () => {
// Add 'danger' class if something goes wrong
$(button).addClass('btn-danger');
},
complete: () => {
// Enable all buttons as we got a response
$('.btnCheckUser').prop('disabled', false);
$('.btnViewUser').prop('disabled', false);
// Remove 'loading' class as we got a response
$(button).removeClass('bg-aqua');
}
});
}
This should give you the expected behavior.
hello there this is truly easy what you have to do is
function TheClickerFunction(){
$("body").on("click",".YourButton",function(){
//this will only affect the clicked element
$(this).attr("disabled",true);
});
}
$(document).ready(function(){
TheClickerFunction();
});
/*
i think creating a global counter and increment it on each element would b better*/
var GlobalCounter= 0;
function DynamicBuilder(){
var stringBuilder="";
for(var i = 0; i< lengthOfYourElement ; i++){
GlobalCounter+=1;
stringBuilder+="<input type='submit' id='myButton"+GlobalCounter+"'></input>";
}
$("body").append(stringBuidler);
}
in this way each time a click is made
$(".yourClass").click(function(){
var elementID = $(this).attr("id");
//here you can do what ever you want with its id
})
*/
I made a little application in JavaScript with the Flickr API. It returns the ten most recent photos of a specific tag.
When Searching for a tag it brings one the photos, everything fine till here. At the next search it keeps the old photos and just adds new ones. It was planned to "delete" the older photos. Visit: https://jsfiddle.net/79uueuso/
var thread = null;
function findTags(input) {
console.log(input);
var flickerAPI = "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?";
// Get the vaules of the Flickr-API
$.getJSON(flickerAPI, {
tags: input, // input = value of the input text field
tagmode: "all",
format: "json"
})
// get 10 pictures
.done(function (data) {
$.each(data.items, function (i, item) { // put them in a <img> with thumbnail of bootstrap / append them to the <div> with id="images"
$("<img class='img-thumbnail'>").attr("src", item.media.m).appendTo("#images");
if (i === 9) {
return false;
}
});
});
}
// Get the Value of the text input field while you type!
// The function reads the value 500ms after your last typing (.keyup)
$('#tag').keyup(function () {
clearTimeout(thread);
var $this = $(this);
thread = setTimeout(function () {
findTags($this.val())
}, 750);
});
Originally it was like this, Visit: https://jsfiddle.net/ckdr2Lwx/
(function() {
var flickerAPI = "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?";
$.getJSON(flickerAPI, {
tags: "Schloss Heidelberg",
tagmode: "any",
format: "json"
})
.done(function (data) {
$.each(data.items, function (i, item) {
$("<img>").attr("src", item.media.m).appendTo("#images");
console.log("Hallo die " + [i] + ".");
if (i === 9) {
return false;
}
});
});
})();
I solved my problem. I added a function which removes the input value, after that when there is no input value the recent photos will be deleted and the search does not start from scratch. First you have to type something in the input field. Here goes the additional code I used.
// when pressing backspace once, the whole input field gets cleared.
$('html').keyup(function(e){
if(e.keyCode == 8) {
var photo = document.getElementById("tag");
photo.value = "";
var photoDel = $(".img-thumbnail"); // All pictures of the last search will be deleted.
photoDel.remove();
}
});
if(input == ""){ // The search only works now when the input field is not empty-
return false;
}else {
added on top of
var flickerAPI = "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?";
// Get the vaules of the Flickr-API
I've got a piece of code that populates a dropdown based on the selection of another dropdown. This propogates through 4 dropdowns so:
selection -> selection based on 1 -> selection based on 1 & 2 -> selection based on 1, 2, & 3.
My issue is that sometimes the selection only comes up as 1 item, making it such that the user cannot "fire" the onchanged event. As such, upon population, I want to set the selected index to 0. Easy peasy, right?
So here's my code (edited to include full javascript function):
function FillProject() {
var BusinessAreaDropDown = document.getElementById("BusinessAreaDropDown");
var BusinessAreaSelection = BusinessAreaDropDown.options[BusinessAreaDropDown.selectedIndex].text
var GenericProjectDropDown = document.getElementById("GenericProjectDropDown");
if (GenericProjectDropDown.selectedIndex == -1) {
GenericProjectDropDown.selectedIndex = 0;
}
var GenericProjectSelection = GenericProjectDropDown.options[GenericProjectDropDown.selectedIndex].text
#* Get list from C# for GenericProjects matching *#
$.ajax({
url: '/Home/FillProject',
type: "GET",
dataType: "JSON",
data: { BusinessArea: BusinessAreaSelection, GenericProject: GenericProjectSelection },
error: function (error) {
alert(error);
},
success: function (Projects) {
$("#ProjectDropDown").html("");
$.each(Projects, function (i, project) {
$("#ProjectDropDown").append(
$('<option></option>').val(project).html(project));
});
}
})
}
This generates an error. If I place an alert after the if statement, it says that the selected index is still -1. Here's where stuff gets weird. If I place an alert INSIDE the if statement it pops up saying that the selected index is -1 before changing, 0 after changing, and actually changes the selectedIndex to 0, causing the code to work. If I remove the alert statement, it goes back to failing. What's happening here?
Examples:
Works->
if (GenericProjectDropDown.selectedIndex == -1) {
alert(GenericProjectDropDown.selectedIndex);
GenericProjectDropDown.selectedIndex = 0;
}
Works->
if (GenericProjectDropDown.selectedIndex == -1) {
GenericProjectDropDown.selectedIndex = 0;
alert(GenericProjectDropDown.selectedIndex);
}
Fails ->
if (GenericProjectDropDown.selectedIndex == -1) {
GenericProjectDropDown.selectedIndex = 0;
}
Solution! Thanks to #charlietfl
I had some ajax that was running in a previous code chunk populating the dropdown and it wasn't finixshed before running the second script because it was asynchronous. By adding:
$.ajax({
url: '/Home/FillProject',
type: "GET",
dataType: "JSON",
async: false, //this line
data: { BusinessArea: BusinessAreaSelection, GenericProject: GenericProjectSelection },
error: function (error) {
alert(error);
},
success: function (Projects) {
$("#ProjectDropDown").html("");
$.each(Projects, function (i, project) {
$("#ProjectDropDown").append(
$('<option></option>').val(project).html(project));
});
The issue was resolved!
I'm using typeahead to pull names and then updating a hidden field with the id respective to the name. In my form the name can be left blank, filling it is not a requirement.
Is there a way to add custom error checking to the typeahead function? for instance, I start filling out the form and type/select a user, then I change my mind and want to leave it blank. by this point, the user id is already added to the hidden field, so even if i leave the name field blank, the form will be passed with the user id in it.
I guess I can always do error checking on submit, but am curious if there is a way to add to the native api.
my code is:
$('#name_field').typeahead({
minLength: 3,
source: function (query, process){
$.get('/url/getsource', {query: query}, function (data) {
names = [];
map = {};
$.each(data, function (i, name) {
map[name.uName] = name;
names.push(name.uName);
});
process(names);
});
},
matcher: function (item) {
if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
return true;
}
},
sorter: function (items) {
return items.sort();
},
highlighter: function (item) {
var regex = new RegExp( '(' + this.query + ')', 'gi' );
return item.replace( regex, "<strong>$1</strong>" );
},
updater: function (item) {
selectedID = map[item].id;
$('#id_field').val(selectedID);
return item;
}
});
I was hoping I can add something like
$('#name_field').typeahead({
myCustom: function (){
if($('#name_field').val() == ''){
$('#id_field').val('');
},
rest,
of the,
function
});
You could bind a function to the blur event of the user field. So as soon as the user makes any changes to another form element if it is left blank you clear the typeahead field.
$('#user-field').blur(function (e) {
if(this.val() === ''){
$('#typeahead-field').val(''); // Clear typeahead
}
});
I have an application running with jquery-1.5.2.min.js. It works fine in IE9, 8, 7, FF and Chrome.
But there's this problem. I have a JavaScript function in a custom .js file using jQuery that rules the behaviour of a hidden field. Whenever a button is clicked, the hidden field is turned into a jQuery Autocomplete control, and loads the Autocomplete information through an Ajax call. The function is like this:
$.ajax({
type: "POST",
url: action,
dataType: "json",
data: "{type: '" + control + "', param:" + params + "}",
contentType: "application/json; charset=utf-8",
success: function (data) {
var dataTable = data;
$(selector).autocomplete({
minLength: 2,
source: dataTable,
open: function (e, ui) {
var options = $(this).data('autocomplete');
options.menu.element.find('a').each(function () {
var this_ = $(this);
var regex = new RegExp(options.term, "gi");
this_.html(this_.text().replace(regex, function (matched) {
return autocompleteTemplate.replace('%s', matched);
}));
});
},
focus: function (event, ui) {
$(selector).val(ui.item.label);
return false;
},
change: function (event, ui) {
if (!ui.item) {
var options = $(this).data('autocomplete');
var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex($(this).val()) + "$", "i"),
valid = false;
options.menu.element.find('a').each(function () {
if ($(this).text().match(matcher)) {
valid = true;
return false;
}
});
if (!valid) {
if (control == "ProjectType") {
$('#selector').val("...");
$('#selector').attr('disabled', 'disabled');
$('#another.selector').val("");
}
// Remueve los valores inválidos.
$(this).val("");
$(selector).val("");
$(selector).data("autocomplete").term = "";
return false;
}
}
if (control == "ProjectType") {
$('#selector').val("");
}
},
select: function (event, ui) {
$(selector).val(ui.item.label);
$(hidden).val(ui.item.value);
if (control == "ProjectType") {
Autocomplete("ProjectSubType", action, ui.item.value);
// This is a function that changes the CSS for another HTML control
ProjectSubType(false);
}
return false;
}
});
}
});
So, whenever I change the browser type from IE8 to IE7 or IE9, or from IE7 to IE8 or IE9, after activating this field, the following exception is thrown from jquery-1.5.2.min.js
Runtime error from Microsoft JScript: Cannot get value of property
'type': the object is null or undefined
FYI:
The AJAX calls work. The autocomplete works properly and fires the events it has to fire when completed, in the order they have to be fired.
There is another control that fires another AJAX event (filling a jqGrid) which produces no mistake.
The conditional clause that you see in the code, "if (control == "ProjectType")", is meant to allow another control to turn into an Autocomplete if this control being used has an Autocomplete option filled in. Otherwise, it is disabled, as you can see (I changed its name to '#selector'). This also works properly: if you fill in a value in that Autocomplete, the other control is filled with the options needed.
Thanks
UDP
The function that calls the AJAX function is the following:
function SetSearchMenu(url, local) {
$('#advancedSearch').hide();
$('#advSearch').click(function () {
if ($('#advancedSearch').css("display") == "none") {
$('#advancedSearch').show();
$('#generalSearch').val("...");
$('#generalSearch').attr('disabled', 'disabled');
ProjectSubType(true);
}
else {
$('#dAdvancedSearch').hide();
$('#General').val("");
$('#General').removeAttr('disabled');
}
if (alreadyOpen == false) {
Autocomplete("SelectorOne", url, null);
Autocomplete("ProjectType", url, null);
Autocomplete("Selector", url, local);
alreadyOpen = true;
}
});
}
The parameters url and local are sent from the $(document).ready() function, and are filled with an #Url.Action() in string format and another variable hardcoded to one.