JQuery event is being overtaken by another - javascript

I have a form which in fact consists of two forms. Each form is a reservation form. There are two dropdowns in both forms - destination from and destination to. There is an even handler, which calls AJAX to get possible destinations to when destination from is being selected/changed.
Another event handler (round trip checkbox) fills second form dropdowns by switching destinations from first form.
So if the first form has:
destination one: France
destination two: Austria
Then, if round trip is checked, the second form is immediately filled:
destination one: Austria
destination two: France
The problem is that this two events don't cooperate correctly.
When this code is executed:
id_form_1_destination_from.val(destination_to_0.val());
id_form_1_destination_to.val(destination_from_0.val());
id_form_1_destination_from.change();
id_form_1_destination_to.change();
The first line calls another handler which fills second form (this is the only case when it's not needed). Since it's AJAX, the second line overtakes this AJAX, so now, the second form is correctly filled (switched destinations from first form), but when AJAX is done, it changes the selection of the destination two field.
Is there a way how to avoid this? For example to turn off the event handler or better make JQuery wait until the AJAX is done and then continues. I can't just do .off() on destination to field because I use select2 plugin.
Here is my JQuery:
$(document).ready(function () {
var destination_from_0 = $("#id_form-0-destination_from");
var destination_to_0 = $('#id_form-0-destination_to');
var ride_two = $('#ride_two');
$('.class-destination-from').on('change', function () {
destination_from_changed.call(this);
});
$("#id_round_trip").on('change', function () {
if (($('#id_round_trip').is(':checked')) ) {
var id_form_1_destination_from =$('#id_form-1-destination_from');
var id_form_1_destination_to = $('#id_form-1-destination_to');
ride_two.show('fast');
//id_form_1_destination_from.off();
id_form_1_destination_from.val(destination_to_0.val()).change();
//id_form_1_destination_from.on();
//id_form_1_destination_from.change();
id_form_1_destination_to.val(destination_from_0.val()).change();
}else{
ride_two.hide('fast');
ride_two.find(':input').not(':button, :submit, :reset, :checkbox, :radio').val('').change();
ride_two.find(':checkbox, :radio').prop('checked', false).change();
}
});
$('.class-destination-to').on('change', destination_to_changed);
});
function destination_to_changed() {
var destination_id = $(this).val();
var arrival_container = $(this).siblings('.arrival-container');
var departure_container = $(this).siblings('.departure-container');
if (destination_id == '') {
return;
}
$.ajax({
url: '/ajax/is-airport/' + destination_id + '/',
success: function (data) {
if (data.status == true) {
arrival_container.hide("slow");
departure_container.show("slow");
}
if (data.status == false) {
departure_container.hide("slow");
arrival_container.show("slow");
}
arrival_container.change();
departure_container.change();
}
})
}
function destination_from_changed() {
var destination_id = $(this).val();
if (destination_id == '') {
return;
}
var ajax_loading_image = $('#ajax-loading-image');
var destination_to = $(this).siblings('.class-destination-to');
destination_to.empty();
ajax_loading_image.show();
$.ajax({
url: '/ajax/get-destination-to-options/' + destination_id + '/',
async:false, // ADDED NOW - THIS HELPED BUT IT'S NOT NECESSARY EVERYTIME
success: function (data) {
ajax_loading_image.hide();
destination_to.append('<option value="" selected="selected">' + "---------" + '</option>');
$.each(data, function (key, value) {
destination_to.append('<option value="' + key + '">' + value + '</option>');
});
destination_to.change();
}
})
}

If i'm understanding correctly, you have a concurrency issue. You basically want your first ajax call to be terminated before calling the second right?
I don't see any ajax request in your code but I think the paramter async: false, might be what you need.
Check the documentation: http://api.jquery.com/jquery.ajax/
Hope it helps

You definitely have a classic "race condition" going on here.
Since the AJAX calls seem fairly unrelated to one another, you might need to add some code on the JavaScript side so that potentially "racing" situations cannot occur. For example, to recognize that a combo is "being populated" if you've issued an AJAX request to populate it but haven't gotten the response back yet. You might disable certain buttons.
Incidentally, in situations like this, where two (or more ...) forms are involved, I like to try to centralize the logic. For example, there might be "a singleton object" whose job it is to know the present status of everything that's being done on or with the host. A finite state machine (FSM) (mumble, mumble ...) works very well here. This object might broadcast events to inform "listeners" when they need to change their buttons and such.

You need to cancel the first AJAX request before you start the second. From this SO question:
Abort Ajax requests using jQuery
var xhr;
function getData() {
if (xhr) { xhr.abort(); }
xhr = $.ajax(...);
}

Related

URL check with JS

I have two scripts, first of them clicks on the button and after that browser opens a new window, where i should click on the other button by the second script, is it possible to run them both at the same time, I mean like unite those scripts together?
function run() {
var confirmBtn = document.querySelector(".selector,anotherSelector ");
}
after this new window appears and here`s the second part of my script
var rooms = document.querySelectorAll(" .btn-a-offers");
console.log(rooms);
for (var room = 0; room < rooms.length; room++) {
rooms[room].click();
}
var prices = document.querySelectorAll(" .li-right-side>strong");
console.log(prices);
for (var price = 0; price < price.length; price++) {
}
var prices = [];
document.querySelectorAll(".new-pa-hotelsoffers .li-right-side > strong").forEach(function(price) {
prices.push(parseFloat(price.innerHTML.replace(/[^0-9.]/g, "")))
})
console.log(
Math.min(...prices).toFixed(2)
)
My English is not that good so I want to be sure that I explained everything right, second script must be executed in the new window, that opens after first script
Depending on the logical dependancy of your application and the use of the functions, you could execute the second function in a document.ready function on the second page.
Example:
<script>
//jQuery
$( document ).ready(function() {
secondFunction();
});
//Pure JS
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
r(function() {
secondFunction();
});
</script>
However, if the page is to act independantly, and the second function is only to respond upon the execution of the first function, then that solution would not be the one you are looking for.
In the case where the function has to act entirely dependant on the use of the first function you could parse a value in the URL (better known as a GET variable) and check if that value is set.
Example:
<script>
functionOne() {
window.location.href = '/your_page.php?click=1';
}
</script>
Then on your second page you need to retrieve the GET variable.
<?php
$clicked = $_GET['click'];
?>
You can then perform a check to see if the variable has been set and fire your function upon that logic.
<?php
if($clicked != "") {
echo '
<script>
functionTwo();
</script>';
}
?>
Another way of doing it could be by the use of AJAX and have the other function execute in the AJAX' success function. That way you can eliminate the use of the GET variable, which is visible in the URL.
Example:
<script>
functionOne() {
$.ajax({
type : "POST", //or GET
url : "/your_page.php",
data : {
//parse your POST variable data if any
// variable : value,
// anotherVairable : anotherValue
// [....]
},
success: function (html) {
//Success handling
secondFunction();
}
})
}
</script>
Note that the AJAX used in the example is jQuery AJAX, so if you want to use some AJAX logic involving this structure, you'll need to include a jQuery library.
You should pass some parameter in the URL query like this:
// first-script.js
openNewWindow('http://example.com?run-second-script=1') // openNewWindow is fake function, just for demo
// second-script.js
if (window.location.search.includes('run-second-script=1')) { ... your code here ...}

ajaxComplete with getJSON causing loop

I am using ajaxComplete to run some functions after dynamic content is loaded to the DOM. I have two separate functions inside ajaxComplete which uses getJSON.
Running any of the functions once works fine
Running any of them a second time causes a loop cause they are using getJSON.
How do I get around this?
I'm attaching a small part of the code. If the user has voted, clicking the comments button will cause the comments box to open and close immediately.
$(document).ajaxComplete(function() {
// Lets user votes on a match
$('.btn-vote').click(function() {
......
$.getJSON(path + 'includes/ajax/update_votes.php', { id: gameID, vote: btnID }, function(data) {
......
});
});
// Connects a match with a disqus thread
$('.btn-comment').click(function() {
var parent = $(this).parents('.main-table-drop'), comments = parent.next(".main-table-comment");
if (comments.is(':hidden')) {
comments.fadeIn();
} else {
comments.fadeOut();
}
});
});
Solved the problem by checking the DOM loading ajax request URL
$(document).ajaxComplete(event,xhr,settings) {
var url = settings.url, checkAjax = 'list_matches';
if (url.indexOf(checkAjax) >= 0) { ... }
}

Error Loading AJAX on Document Ready Using JQuery

I'm trying to create a PHP page that periodically updates values of several elements on the page. I'm using a host that limits my hits per day, and each hit to any page they're hosting for me counts against my total. Therefore, I'm trying to use JQuery/AJAX to load all of the information that I need from other pages at one time.
I'm calling the following index.php. This method achieves the desired affect exactly the way I want it, but results in three hits (dating.php, dgperc.php, and pkperc.php) every two seconds:
var focused = true;
$(window).blur(function() {
focused = false;
});
$(window).focus(function() {
focused = true;
});
function loadData() {
if (focused) {
var php = ["dating", "dgperc", "pkperc"];
$.each(php, function(index, value) {
$('#'+this).load(this+'.php');
});
}
}
$(document).ready(function() {
loadData();
});
setInterval(function() {
loadData();
}, 2000);
I'm calling the following index1.php. This is where I'm at as far as a method that only results in one hit every two seconds. My workaround is that I have combined the three php pages that I was loading into one, dating1.php. I load this into a div element, #cache, all at once. This element is set to hidden using CSS, and then I just copy its inner HTML into the appropriate elements:
var focused = true;
$(window).blur(function() {
focused = false;
});
$(window).focus(function() {
focused = true;
});
function loadData() {
if (focused) {
var php = ["dating", "dgperc", "pkperc"];
$('#cache').load('dating1.php');
$.each(php, function(index, value) {
$('#'+this+'1').html($('#'+this).html());
});
}
}
$(document).ready(function() {
loadData();
});
setInterval(function() {
loadData();
}, 2000);
Dating1.php will produce different outputs every time it's run, but here is an example of the output:
<span id = "dating">4 years, 7 months, 3 weeks, 10 seconds ago.</span>
<span id = "dgperc">21.9229663059</span>
<span id = "pkperc">22.2121099923</span>
On document ready, index1.php does not function properly: the #cache element isn't filled at all, so the other elements don't get filled either. However, after two seconds, the loadData() function runs again, and then the #cache element is filled correctly, and so are the other elements. For some reason, this isn't a problem on my index.php page at all, and I'm not sure why there's a difference here.
How can I get #cache to load the first time so that the page loads correctly? Or is there a better way to do this?
Each AJAX call is basically a page visit in the background. Like telling your assistant three different times to get you one coffee. Or telling them one to get you three coffees.
If you don't want to combine your three PHP pages into one - thus keeping code separate and easier to maintain. Consider creating one "cache.php" script and inside it:
cache.php:
$outputData = array('dating' => false, 'dgperc' => false, 'pkperc' => false);
foreach($outputData as $file => &$data)
{
//buffer output
ob_start();
//run first script (be smart and file_exists() first)
include_once($file . '.php');
$data = ob_get_clean();
}
//output JSON-compliant for easy jQuery consumption
echo json_encode($outputData);
Then in your javascript:
function loadData() {
if (focused) {
//call ajax with json and fill your spans
$.ajax({
async: true,
cache: false,
dataType: 'json',
success: function(data, textStatus, jqxhr) {
$('#dating').html(data.dating);
$('#dgperc').html(data.dgperc);
$('#pkperc').html(data.dgperc);
// NOTE... do a console.dir(data) to get the correct notation for your returned data
},
url: 'cache.php'
});
}
You are calling cache.php once every two seconds, saving on the 3-hits of calling the php files individually. Using a middle-man file you keep your scripts separate for maintainability.

jquery Select2 prevent selecting in ajax response

I want to prevent from adding a category to the Select2 element if it fails creating the row first in my db. The action is not prevented when i call ev.preventDefault(); Nothing happens.. what is wrong?
$('#sel2').select2({
placeholder: 'Enter categories',
minimumInputLength: 3,
multiple: true,
ajax: {
url: 'async/get_categories.php',
dataType: 'json',
quietMillis: 250,
data: function (term, page) {
return {
q: term,
};
},
results: function (data, page) {
return {
results: data.items
};
},
cache: true
},
formatResult: format,
formatSelection: format
}).on('select2-selecting', function(e) {
console.log(e);
if (e.val == 4) {
// if category id equals 4
// do not add this category to select 2
// e.preventDefault();
// the above works just fine and its just for testing
}
// Is something wrong here?
var ev = e;
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: ev.val
},
success: function(response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
ev.preventDefault();
// the above is not working
}
},
error: function() {
alert('Failed to assign category!');
}
});
});
The AJAX request is made asynchronusly, so by the time it has finished the element has already been added. Even though you are calling ev.preventDefault(), it is too late for it to make a difference. So this leaves you with two options:
Make the request synchronusly, which will allow preventDefault to make the difference.
Make the request asynchronusly, and manually remove the element if it fails.
Both options have their pros and cons, and it's up to you to decide which option you go with.
Making the request synchronusly
Pros
The value will never be added if the request fails.
Works well in cases where the element cannot be added quite often.
Cons
Blocks the UI - So the user is potentially left with an unresponsive page while the request is made.
Making the request asynchronusly
Pros
Does not block the UI.
Works well in cases where elements typically can be added.
Cons
The value will always show up for the user, even if it fails later.
You must manually unset the new option.
What's important to consider here is the user experience of both options. When making synchronus requests, it's not uncommon for the browser to stop relaying events - which gives the illusion that the UI has locked up and the page has gone unresponsive. This has the benefit of ensuring that the value never shows up if it isn't allowed. But if users typically can add the elements, it also has the downside of complicating the most common use case.
If users can usually add elements, then it is a better experience to add the element while the request is being made, and then notifying the user later (while removing the element) if there was an issue. This is very common is web applications, and you can see it being used in many places, such as the Twitter and Facebook like buttons (where requests usually work), as well as places on Stack Overflow.
There is a way to get around this with version4 of the select2 library.
on select2:selecting we cancel the preTrigger event. Which will stop the select2:select event. We do our ajax call. On success we then get out Select2 instance then call the trigger of the Observer that way it by passes overwritten trigger method on your select2 instance.
The call method needs your select2 instance as the context so that the existing listeners are available to call.
var sel = $('#sel');
sel.select2(config);
sel.on('select2:selecting', onSelecting);
function onSelecting(event)
{
$.ajax({
type: 'POST',
url: 'async/create_profile_category.php',
data: {
profile_id: '1',
category_id: event.params.args.data.id
},
success: function(event, response) {
console.log(response);
if (response.error === false) {
// category assigned successfully
// get select2 instance
var Select2 = $users.data('select2');
// remove prevented flag
delete event.params.args.prevented;
// Call trigger on the observer with select2 instance as context
Select2.constructor.__super__.trigger.call(Select2, 'select', event.params.args);
} else {
// failed to assign category
// so i want now to prevent from adding to select2
console.log('should not add this category');
}
}.bind(null, event),
error: function() {
alert('Failed to assign category!');
}
});
event.preventDefault();
return false;
}
here how I did it for yii2 Select2 integrated into Gridview:
'pluginEvents' => [
'select2:selecting' => "
function(event)
{
var select2 = $('#types-" . $model->id . "');
select2.select2('close');
$.post('update',{id: " . $model->id . ", type_id: event.params.args.data.id})
.done (function(response)
{
select2.val(event.params.args.data.id);
select2.trigger('change');
})
.fail(function(response)
{
krajeeDialog.alert('Error on update:'+response.responseText);
});
event.preventDefault();
return false;
}",
],
it allows to asynchoronous update data in the grid using select2 and ajax and return it to previous value if there was an error on updating.

jQuery: How to apply a function to all elements including some which are loaded later via Ajax?

I have a simple jQuery function that resizes text areas, and I want it to apply to all text areas.
For the most part, this works great:
$(document.ready(function(){$("text_area").resizer('250px')});
However, because it is only called once when the document is ready, it fails to catch text areas that are later added onto the page using Ajax. I looked at the .live() function, which seems very close to what I'm looking. However, .live() must be bound to a specific event, whereas I just need this to fire once when they're done loading (the onLoad event doesn't work for individual elements).
The only thing I can get working is a really obtrusive inclusion of the JavaScript call directly into the Ajax. Is that the recommended way to be doing this?
Edit: Here is the rails source code for what it does for Ajax requests:
$('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) {
var link = $(this);
if (!allowAction(link)) return false;
if (link.attr('data-remote') != undefined) {
handleRemote(link);
return false;
} else if (link.attr('data-method')) {
handleMethod(link);
return false;
}
});
// Submits "remote" forms and links with ajax
function handleRemote(element) {
var method, url, data,
dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType);
if (element.is('form')) {
method = element.attr('method');
url = element.attr('action');
data = element.serializeArray();
// memoized value from clicked submit button
var button = element.data('ujs:submit-button');
if (button) {
data.push(button);
element.data('ujs:submit-button', null);
}
} else {
method = element.attr('data-method');
url = element.attr('href');
data = null;
}
$.ajax({
url: url, type: method || 'GET', data: data, dataType: dataType,
// stopping the "ajax:beforeSend" event will cancel the ajax request
beforeSend: function(xhr, settings) {
if (settings.dataType === undefined) {
xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
}
return fire(element, 'ajax:beforeSend', [xhr, settings]);
},
success: function(data, status, xhr) {
element.trigger('ajax:success', [data, status, xhr]);
},
complete: function(xhr, status) {
element.trigger('ajax:complete', [xhr, status]);
},
error: function(xhr, status, error) {
element.trigger('ajax:error', [xhr, status, error]);
}
});
}
So in my particular case, I've got a link, that has data-remote set to true, which points to a location that will return JavaScript instructing a form containing a text area to be appended to my document.
A simple way to do this would be to use ajaxComplete, which is fired after every AJAX request:
$(document).ajaxComplete(function() {
$('textarea:not(.processed)').resizer('250px');
});
That says "every time an AJAX request completes, find all textarea elements that don't have the processed class (which seems to be added by the resizer plugin -- terrible name for its purpose!) and call the resizer plugin on them.
You may be able to optimise this further if we could see your AJAX call.
Generally speaking, I would do it this way..
$.ajax({
type : "GET",
url : "/loadstuff",
success: function(responseHtml) {
var div = $("#containerDiv").append(responseHtml);
$("textarea", div).resizer("250px");
}
});
Wondering if you could use .load for this. For example:
$('text_area').load(function() {
$("text_area").resizer('250px');
});

Categories

Resources