Comments (40)
Hi and thanks for the praise and bringing up this issue and the description of what you're experiencing.
You're right, that the Ajaxify call itself can cause unwanted recursion, but the call you're using seems to be alright:
<script>
let ajaxify = new Ajaxify();
</script>
I don't think that's the issue, because the syntax seems to be alright.
Thanks for the picture of your console.
It's rather obvious, that your system is somehow initiating an attempt to reload the whole (minified) Ajaxify library on each of such POSTs.
The Ajaxify plugin should never be reloaded at all.
Would you have a link to your site? I would be more than happy to try and help you debug this...
from ajaxify.
Hi, the alert is run only once and no duplicate init logs are created so it looks like the main problem is with AJAX response handling.
<script>
let ajaxify = new Ajaxify({
verbosity: 100,
canonical: true,
});
alert('once')
</script>
Here are some server responses (Laravel):
return response('<p>test</p>', 200); // this works (DOM not modified)
return response('<p>test</p>', 201); // this works (DOM not modified)
return response('<p>test</p>', 299); // this works (DOM not modified)
return response('<p>test</p>', 300); // this loops requests from ajaxify
return response('<p>test</p>', 399); // this loops requests from ajaxify
So the error occurs when status code is not in 200 - 299
range. The site is not online yet.
from ajaxify.
Hi again.
The alert()
is called only once, because any whole inline script containing the string:
- "new Ajaxify("
...is ignored completely.
That's to say, that if Ajaxify is called from an inline script, it should only contain the Ajaxify call - no more, no less.
Really, I think the problem is that the POST somehow attempts to reload the whole library, which causes a sort of "recursion", because the whole logic kicks in by mistake again...
Yes, I appreciate, if your site is not online yet, but please take into account as well that offline sites may behave differently...
from ajaxify.
Hi, I created this test server script, can be named as whatever, ex. test.php
:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
http_response_code(200); // 200, 404
echo "<p>test</p>";
exit;
}
?>
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/arvgta/[email protected]/ajaxify.min.js"></script>
<script>
let ajaxify = new Ajaxify({
verbosity: 100,
});
console.log('init')
</script>
</head>
<body>
<form method="POST" action="">
<button>Test</button>
</form>
</body>
</html>
You are right, when http_response_code(200)
and clicking at the button then it sends more and more requests (1, 2, 4, 8, ...) at each click: (console cleared after each click):
from ajaxify.
Alright, I really can't tell from the information so far.
Please remember when debugging, that the file
ajaxify.min.js
should only be loaded once on inital page load and never again after that.
So all you really have to examine from my point of view is, what is causing it to be reloaded in the first place...
Once reloaded, it is not foreseeable, what will happen next...
EDIT: I only saw your PHP script now - will have a look at it!
Regarding your PHP test script - why are you using:
action=""
within here:
<form method="POST" action="">
?
from ajaxify.
Yes, empty action will make it use the current url. Here it is explicitly set:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
http_response_code(200); // 200, 404
echo "<p>test</p>";
exit;
}
?>
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/arvgta/[email protected]/ajaxify.min.js"></script>
<script>
let ajaxify = new Ajaxify({
verbosity: 100,
});
</script>
</head>
<body>
<form method="POST" action="/test.php">
<button>Test</button>
</form>
</body>
</html>
No change.
from ajaxify.
Exactly. So that was your intention!
What is happening now?
from ajaxify.
Exactly the same. After each click, more requests are send at once. This is one issue. Another issue is that it tries to load POST request with GET.
from ajaxify.
Here I split it to two php scripts:
test.php
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/arvgta/[email protected]/ajaxify.min.js"></script>
<script>
let ajaxify = new Ajaxify({
verbosity: 100,
});
</script>
</head>
<body>
<form method="POST" action="/postonly.php">
<button>Test</button>
</form>
</body>
</html>
postonly.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
http_response_code(404); // 200, 404
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
error message
</body>
</html>';
exit;
}
?>
The problem is that ajaxify tries to load postonly.php with GET:
from ajaxify.
I agree with you that this is weird.
Maybe not the most straight forward use-case but still weird...
Here is the current code within Ajaxify for form handling:
class Frms { constructor() {
let fm = 0, divs = 0;
this.a = function (o, p) {
if (!Ay.s.forms || !o) return; //ensure data
if(o === "d") divs = p; //set divs variable
if(o === "a") divs.forEach(div => { //iterate through divs
Array.prototype.filter.call(qa(Ay.s.forms, div), function(e) { //filter forms
let c = e.getAttribute("action");
return(Ay.internal(c && c.length > 0 ? c : Ay.currentURL)); //ensure "action"
}).forEach(frm => { //iterate through forms
frm.addEventListener("submit", q => { //create event listener
fm = q.target; // fetch target
p = _k(); //Serialise data
var g = "get", //assume GET
m = fm.getAttribute("method"); //fetch method attribute
if (m.length > 0 && m.toLowerCase() == "post") g = "post"; //Override with "post"
var h, a = fm.getAttribute("action"); //fetch action attribute
if (a && a.length > 0) h = a; //found -> store
else h = Ay.currentURL; //not found -> select current URL
Ay.Rq("v", q); //validate request
if (g == "get") h = _b(h, p); //GET -> copy URL parameters
else {
Ay.Rq("is", true); //set is POST in request data
Ay.Rq("d", p); //save data in request data
}
Ay.trigger("submit", h); //raise pronto.submit event
Ay.pronto(0, { href: h }); //programmatically change page
q.preventDefault(); //prevent default form action
return(false); //success -> disable default behaviour
});
});
});
};
let _k = () => {
let o = new FormData(fm), n = qs("input[name][type=submit]", fm);
if (n) o.append(n.getAttribute("name"), n.value);
return o;
},
_b = (m, n) => {
let s = "";
if (m.iO("?")) m = m.substring(0, m.iO("?"));
for (var [k, v] of n.entries()) s += `${k}=${encodeURIComponent(v)}&`;
return `${m}?${s.slice(0,-1)}`;
}
}}
from ajaxify.
I think POST with status code other than 200-299 is important usecase. If anything goes wrong on the server then response text should be displayed and JS library should not try to send GET request again. Error codes such as 404 and 500 are used by Laravel and other frameworks with custom error pages.
In another pjax library https://github.com/MoOx/pjax I handled response in such a way that it displayed error response immediately because it allows to override function:
pjax.handleResponse = function(responseText, request, href, options) {
if (responseText && responseText.match("<html")) {
pjax._handleResponse(responseText, request, href, options);
} else {
window.scrollTo(0, 0);
document.write(responseText || request.responseText);
}
}
Weird thing is that this is similar to another issue I created in yet another library: PaperStrike/Pjax#342
from ajaxify.
Thanks for your sharp thinking - much appreciated!
I'm trying to have a look the last issue in PJAX you mentioned.
What does your gut feeling say is the problem in Ajaxify?
from ajaxify.
Yes, I can confirm that Ajaxify tries to jump to the URL directly in case of failing of any AJAX request.
Here is the current salient code pertaining to any AJAX request:
_lAjax = (hin, pre) => {
var ispost = Ay.Rq("is");
if (pre) rt="p"; else rt="c";
ac = new AbortController(); // set abort controller
rc++; // set active request counter
fetch(hin, {
method: ((ispost) ? "POST" : "GET"),
cache: "default",
mode: "same-origin",
headers: {"X-Requested-With": "XMLHttpRequest"},
body: (ispost) ? Ay.Rq("d") : null,
signal: ac.signal
}).then(r => {
if (!r.ok || !_isHtml(r)) {
if (!pre) {location.href = hin; _cl(); Ay.pronto(0, Ay.currentURL);}
return;
}
rsp = r; // store response
return r.text();
}).then(r => {
_cl(1); // clear only plus variable
if (!r) return; // ensure data
rsp.responseText = r; // store response text
return _cache(hin, r);
}).catch(err => {
if(err.name === "AbortError") return;
try {
Ay.trigger("error", err);
lg("Response text : " + err.message);
return _cache(hin, err.message, err);
} catch (e) {}
}).finally(() => rc--); // reset active request counter
},
I suppose this bit especially:
if (!r.ok || !_isHtml(r)) {
if (!pre) {location.href = hin; _cl(); Ay.pronto(0, Ay.currentURL);}
return;
}
is not enough error handling?
from ajaxify.
Hold on, that code is problematic anyway, because there seem to be two "jumps" happening at once:
location.href = hin
andAy.pronto(0, Ay.currentURL)
from ajaxify.
After having a closer look at your PJAX thread - I feel like simply leaving away the:
location.href = hin
...but am afraid that everything will fall over, when changing something that central?
from ajaxify.
Double checked against the last jQuery version of the plugin which had been reported to work really well:
It only does a:
location.href = hin
and nothing else.
I suspect the current code in the plain vanilla version does a "double jump"...
What would you advise me to prefer? ->
location.href = hin //breaks execution of Ajaxify
or
Ay.pronto(0, Ay.currentURL) //can maintain execution of Ajaxify
The former is safer because it worked in the old jQuery version.
The latter would be more exciting if it works.
from ajaxify.
There is var ispost = Ay.Rq("is");
maybe this flag could be used to differentiate GET requests from POSTS requests... Anyway, POST requests should really not be pre-fetched. Maybe lAjax
could be duplicated to lAjaxPreFetch
but I need to take better look at the code.
But I think it would be OK to create new config flag, like revisitPost
or something.
from ajaxify.
Much appreciated!
Ay.Rq("is") //can be used to differentiate request types - yes
The question is, whether the bug does not simply apply to all failed AJAX requests (!)
The following code makes sure only non-prefetching requests trigger the code described previously:
if (!pre) ...
My gut feeling says, we need only one of these:
location.href = hin //breaks execution of Ajaxify but is nice and safe
or
Ay.pronto(0, Ay.currentURL) //can maintain execution of Ajaxify, not as safe but potentially powerful
POST requests should really not be pre-fetched.
I agree - that has been reported elsewhere to be a newly introduced bug in the plain vanilla version.
(I thought it might be due to the user setup but if you have the same finding...)
Am calling it a day, too, thanks very much for bringing this up!
from ajaxify.
You are welcome.
}).then(r => {
if (!r.ok || !_isHtml(r)) {
if (!pre) {location.href = hin; _cl(); Ay.pronto(0, Ay.currentURL);}
return;
}
rsp = r; // store response
return r.text();
location.href = hin;
this will make browser to visit the fetched url. But then other two commands _cl(); Ay.pronto(0, Ay.currentURL);}
will not execute (or their execution is probably meaningless because page is changed with a browser load, https://stackoverflow.com/questions/36398482/why-does-setting-window-location-href-not-stop-script-execution).
On the other hand, Ay.pronto(0, Ay.currentURL);
will download the original URL (not the fetched one) and modify DOM.
Is there a reason that any of them should run if response is not OK or request is not HTML? Why to revisit any URL with GET?
Response.ok
The ok read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.
Sourse: https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
And _isHtml
checks headers.
I think that ajaxify should never repeat any request. Response is received and it should be handled and displayed if it is possible. So it should check if there is any text and just print it.
To make it backward compatible, maybe a new config option revisitOnError
with true default value could be created:
if (!r.ok || !_isHtml(r)) {
if (revisitOnError) {
if (!pre) {location.href = hin; _cl(); Ay.pronto(0, Ay.currentURL);}
return;
} else {
// handle displaying HTML (ex. 404 Not found styled page) or non HTML such as JSON, etc.
return;
}
}
// handle OK case
rsp = r; // store response
return r.text();
I think it is OK to not store response in history because browser do not store failed responses too.
Also response in case of !r.ok
must be handled in catch block or somewhere else because it is missing from the first then
because ... javascript https://stackoverflow.com/questions/36225862/handle-a-500-response-with-the-fetch-api
from ajaxify.
First of all, many thanks for your findings, which I all agree with.
For a start in here:
then(r => {
if (!r.ok || !_isHtml(r)) {
if (!pre) {location.href = hin;/* _cl(); Ay.pronto(0, Ay.currentURL);*/}
return;
}
rsp = r; // store response
return r.text();
}
...I've commented out this code:
_cl(); Ay.pronto(0, Ay.currentURL);
...and committed the change to this file for the moment:
...and enabled this file and tested it against 4nf.org briefly - everything still seems to work...
Maybe you can see, whether the change has any effect on your system?
from ajaxify.
Now there is not infinite loop, so this one issue is solved.
But wrong server error is displayed in effect. It should display error 404 or 500 or whatever error the server wants to display. But it displays always 405 because ajaxify will load (location.href
) the URL with GET, but that URL is POST only.
However the general problem is that ajaxify tries to repeat request in non-preload mode (so it is not expected to make requests without user invocation). This could generate false access logs on the server and cause other side effects. It is not such a big problem because GET is idempotent but wrong implemented server endpoints could be affected by this in a bad way. So I think revisits should be disabled or at least a config option such as revisitOnFailure
could be implemented with default true
value. In next major version it could be switched to default false
if no complains from ajaxify users :)
from ajaxify.
Btw I commented out the entire block and now handlers are not multiplied so next clicks do not generate more and more requests:
}).then(r => {
if (!r.ok || !_isHtml(r)) {
// if (!pre) {location.href = hin; _cl(); Ay.pronto(0, Ay.currentURL);}
return;
}
rsp = r; // store response
return r.text();
}).then(r => {
Is there a reason why location.href = hin;
is needed? In what case it is needed?
from ajaxify.
Wow, thanks very much for the test. I didn't necessarily think that the change would get rid of the "infinite loop" problem, which surely was bugging lots of users. I will commit this small change asap and create a new version...
The other bug, with the wrong method
type and error value seems to be very similar to what you posted in the
However the general problem is that ajaxify tries to repeat request in non-preload mode...
I suppose you mean Ajaxify keeps on attempting the fallback we just debugged, even when:
prefetchoff: true
is specified by the user?
That's weird as well, but could we have look at that, when the other fix is committed properly to the repository?
I would like to make a new version(8.2.4) in the course of the day...
from ajaxify.
Is there a reason why
location.href = hin;
is needed? In what case it is needed?
Yes, if the MIME type is not detected successfully in:
_isHtml(r)
..other MIME types are "jumped" to. I see no other possibility just like the PJAX guys didn't either?
from ajaxify.
The other bug, with the wrong method type and error value seems to be very similar to what you posted in the
The bug is caused by ajaxify:
- Ajaxify fetch URL with POST
- Server returns error 404 (r.ok is false) and HTML output with error
- ajaxify loads the URL again with GET (location.href = hin) <-- here starts the bug
- Server returns 403 Wrong method.
So the problem is that ajaxify repeats the request with GET method. Is there any reason that ajaxify needs to repeat request after fetch was initiated?
I suppose you mean Ajaxify keeps on attempting the fallback we just debugged, even when:
prefetchoff: true
This happens with even with prefetchoff: true
and this is not related to prefetch, because forms are not prefetched.
What is the pre
variable?
from ajaxify.
What is the
pre
variable?
It is supposed to store, whether a prefetch is being handled
- ajaxify loads the URL again with GET (location.href = hin) <-- here starts the bug
Maybe it really does make sense to add a small check, whether the method is a POST
from ajaxify.
Yes, if the MIME type is not detected successfully in:
_isHtml(r)
..other MIME types are "jumped" to. I see no other possibility just like the PJAX guys didn't either?
But now the condition is not _isHtml(r)
, but the condition is (!r.ok || !_isHtml(r))
so "jump" happens even if MIME is HTML and request is not OK (ex. error 404).
Anyway why ajaxify tries to redirect browser to non-html MIME? JSON is application/json
and ajaxify could display it. Maybe it is the wrong question what MIME the response has. The question is if there is something to be displayed. If MP3 file is returned then this should be handled by the browser, however JSON should he handled by document output.
I think this could be configured if to repeat request in certain MIME types, but I think it should try to just display everything if it is possible.
from ajaxify.
I agree:
(!r.ok || !_isHtml(r))
doesn't really cover all cases properly
...instead of the current isHTML()
, we could introduce a function isDisplayable()
instead?
But how can that be detected?
from ajaxify.
Yes, I was just typing this pseudocode!
if (shouldBeDisplayed(r)) {
display(r);
}
It could be implemented by checking responseText propertiy or r.text() of the response object. However fetch is weird and in case of some status errors this property is available in catch, see the link:
https://stackoverflow.com/questions/36225862/handle-a-500-response-with-the-fetch-api
from ajaxify.
I don't think we need a new function:
display(r)
...because simply returning the text (which is fortunately default at the moment) ought to do the job?
On second thoughts, that will not work, because we are in the process of expecting the whole HTML of the page at that moment in time.
The trick with the responseText
property of the response
object sounds promising!
Yes, this here:
is going in the right direction, too...
from ajaxify.
And yes, check of method would be good. The main decision is if to handle response by ajaxify or repeat the action with standard browser load with location.href. Here is one idea:
// pseudocode
if (shouldBeHandled(r, method, ... other needed params to decide ...)) {
// handle status codes, method, possible config mime options, ...
} else {
// give it up to browser, possibly check configuration options
if (repeatsEnabled) {
location.href = url;
} else {
// call handler provided by developer
config.requestFallBackHandler(response, requestOptions);
}
}
from ajaxify.
Yes exactly, for all these new ideas to work, we would have to tweak Ajaxify to display non-HTML-like content in the case of error.
(As plain text or a really simple HTML error handling page)
from ajaxify.
Wow - alright! I think we both agree that that would entail quite a bit of additional Ajaxify code?
Is it alright for you, if I make the new version(8.2.4) as a fallback and genuine fix for the infinite loop problem first?
I'm not sure whether we can include an improvement of the dodgy condition:
(!r.ok || !_isHtml(r)) //doesn't really cover all cases properly
...as well (these two cases need to be handled separately for sure)
from ajaxify.
Yes, that would be very nice. Thank you.
from ajaxify.
What do you think? Should we attempt to include a better handling for this condition:
(!r.ok || !_isHtml(r)) //doesn't really cover all cases properly
?
from ajaxify.
I think in next version just infinite loop could be fixed and next next version two functions could exist:
_isHtmlHeader(r)
as refactored_isHtml(r)
_hasTextContent(r)
as actual check for content to be displayed
However _hasTextContent(r) needs to be called in catch block (there is the workaround needed to get response text) so it is more complicated than to modify that condition.
from ajaxify.
Thanks! Yes, I agree. I'll release the new version and we'll go from there...
from ajaxify.
I have created the new release.
Thanks very much!
from ajaxify.
That condition (!r.ok || !_isHtml(r))
just say that there is a hint that something is wrong. So it needs another handling. After reading this SO I think that two then
blocks could be merged into one block which will resolve the text from response.text() promise: https://stackoverflow.com/questions/40408219/how-to-get-readable-error-response-from-javascript-fetch-api
}).then(r => {
if (!r.ok || !_isHtml(r)) { // hint something is wrong
// store response in any case (location.href = hin would clear)
rsp = r;
r.text().then(text => { // resolve promise right there
if (!text) {
location.href = hin;
return;
}
_cl(1); // clear only plus variable
rsp.responseText = r; // store response text
return _cache(hin, r); // return does nothing probably
});
} else {
rsp = r;
_cl(1); // clear only plus variable
rsp.responseText = r; // store response text
return _cache(hin, r);
}
}).catch(err => {
But this is not tested and errors may not be handled.
from ajaxify.
Thanks, much appreciated!
!r.ok
indicates that something is wrong - maybe a quickreturn
with stopping execution but maybe returning the text nevertheless?!isHtml()
indicates that we're dealing with a "binary" - so to say - not necessarily a hard error. Or otherwise non-HTML plain text, which is a borderline case (this is probably the one you're interested in?)
I think these two cases should be handled separately in future (the first one first)
Thanks for the draft!
from ajaxify.
Related Issues (20)
- Hasnat's suggestions HOT 2
- Prevent multiple occurrences of event handlers HOT 2
- No timing problem(s) // how to use alwayshints
- JS Pure HOT 2
- URL Levels HOT 2
- Error with multiple forms (plain vanilla version) HOT 1
- Hash / anchor link handling bug (plain vanilla version) HOT 1
- Nuisance with "undefined elements" when clearing the cache(s) (plain vanilla version) HOT 1
- Link from root to folder makes Ajaxify struggle HOT 12
- why am i getting error [Cannot read properties of undefined (reading 'ael')] HOT 14
- Issue with script execution HOT 2
- Issue with CSS HOT 2
- Style tag attributes being removed HOT 23
- Script not executing loaded from page b HOT 8
- JS script executing 2 times HOT 4
- New Pronto events interface
- Infinite scrolling HOT 4
- Can you explain the use case of https://4nf.org/pronto/ ? HOT 5
- From root to folder and folder in folder doesn't work in Ajaxify HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ajaxify.