papegaaij commented on code in PR #1634:
URL: https://github.com/apache/wicket/pull/1634#discussion_r4167318273


##########
wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/veil/wicket-veil.js:
##########
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Veils the page, or a single component, while Ajax requests are in flight.
+ *
+ * The veil is transparent and only blocks the mouse. If a request is still 
running after the
+ * target's spinner delay, the veil gets the 'wicket-veil-busy' class, which 
shows a spinner;
+ * once shown, the spinner stays for at least the target's minimum time, so it 
does not flicker.
+ * A request carrying the extra parameter 'wicket_nb' is never veiled.
+ *
+ * A local veil can also be raised by the server, for a component it is about 
to update through a
+ * WebSocket push: a WebSocket text message {"wicketVeil":"show","id":"<markup 
id>"} raises it,
+ * Wicket.Veil.hide(id) - evaluated after the pushed update - or 
{"wicketVeil":"hide",...} lowers it.
+ */
+;(function (undefined) {
+       'use strict';
+
+       if (typeof(Wicket.Veil) === "object") {
+               return;
+       }
+
+       const NO_VEIL_PARAMETER = 'wicket_nb';
+       const VEIL_CLASS = 'wicket-veil';
+       const BUSY_CLASS = 'wicket-veil-busy';
+       const HOST_CLASS = 'wicket-veil-host';
+       const WEBSOCKET_MESSAGE_TOPIC = '/websocket/message';
+       const MESSAGE_PREFIX = '{"wicketVeil"';
+
+       let pageTarget = null;
+       let localTargets = {};
+       let subscribed = false;
+
+       function createTarget(id, options) {
+               return {
+                       id: id,
+                       delay: options.delay,
+                       minimum: options.minimum,
+                       count: 0,
+                       host: null,
+                       veil: null,
+                       shownAt: -1,
+                       spinnerTimer: null,
+                       hideTimer: null
+               };
+       }
+
+       function configure(target, options) {
+               target.delay = options.delay;
+               target.minimum = options.minimum;
+       }
+
+       function isOptedOut(attrs) {
+               const ep = attrs.ep;
+               if (Array.isArray(ep)) {
+                       return ep.some(function (parameter) {
+                               return parameter && parameter.name === 
NO_VEIL_PARAMETER;
+                       });
+               }
+               return !!ep && typeof(ep) === "object" &&
+                       Object.prototype.hasOwnProperty.call(ep, 
NO_VEIL_PARAMETER);
+       }
+
+       function findTarget(attrs) {
+               let node = attrs.c ? document.getElementById(attrs.c) : null;
+               for (; node && node !== document; node = node.parentNode) {
+                       if (node.id && localTargets[node.id]) {
+                               return localTargets[node.id];
+                       }
+               }
+               return pageTarget;
+       }
+
+       function dropStaleTargets() {
+               for (const id in localTargets) {
+                       if (Object.prototype.hasOwnProperty.call(localTargets, 
id) &&
+                               localTargets[id].count === 0 && 
!document.getElementById(id)) {
+                               delete localTargets[id];
+                       }
+               }
+       }
+
+       function hide(target) {
+               const clock = Wicket.Veil._clock;
+               clock.clearTimeout(target.spinnerTimer);
+               clock.clearTimeout(target.hideTimer);
+               target.spinnerTimer = null;
+               target.hideTimer = null;
+               target.shownAt = -1;
+               if (target.veil && target.veil.parentNode) {
+                       target.veil.parentNode.removeChild(target.veil);
+               }
+               if (target.host && target !== pageTarget) {
+                       target.host.classList.remove(HOST_CLASS);
+               }
+               target.veil = null;
+               target.host = null;
+       }
+
+       function show(target) {
+               const clock = Wicket.Veil._clock;
+               if (target.hideTimer !== null) {
+                       if (document.body.contains(target.veil)) {
+                               // the previous request's spinner is still on 
its minimum time: carry on with it
+                               clock.clearTimeout(target.hideTimer);
+                               target.hideTimer = null;
+                               return;
+                       }
+                       hide(target);
+               }
+
+               const host = target === pageTarget ? document.body : 
document.getElementById(target.id);
+               const veil = document.createElement('div');
+               veil.className = VEIL_CLASS;
+               if (target !== pageTarget) {
+                       host.classList.add(HOST_CLASS);
+               }
+               host.appendChild(veil);
+               target.host = host;
+               target.veil = veil;
+
+               target.spinnerTimer = clock.setTimeout(function () {
+                       target.spinnerTimer = null;
+                       veil.classList.add(BUSY_CLASS);
+                       target.shownAt = clock.now();
+               }, target.delay);
+       }
+
+       function reattach(target) {
+               if (target === pageTarget || !target.veil || 
document.body.contains(target.veil)) {
+                       return;
+               }
+               const host = document.getElementById(target.id);
+               if (host) {
+                       host.classList.add(HOST_CLASS);
+                       host.appendChild(target.veil);
+                       target.host = host;
+               }
+       }
+
+       function release(target) {
+               const clock = Wicket.Veil._clock;
+               if (target.shownAt < 0) {
+                       hide(target);
+                       return;
+               }
+               const remaining = target.minimum - (clock.now() - 
target.shownAt);
+               if (remaining > 0) {
+                       // the update may have replaced the element, and the 
veil with it
+                       reattach(target);
+                       target.hideTimer = clock.setTimeout(function () {
+                               hide(target);
+                       }, remaining);
+               } else {
+                       hide(target);
+               }
+       }
+
+       function acquire(target) {
+               target.count++;
+               if (target.count === 1) {
+                       show(target);
+               }
+       }
+
+       function releaseOne(target) {
+               if (target.count > 0) {
+                       target.count--;
+                       if (target.count === 0) {
+                               release(target);
+                       }
+               }
+       }
+
+       function onBeforeSend(jqEvent, attrs) {
+               if (!attrs || isOptedOut(attrs)) {
+                       return;
+               }
+               dropStaleTargets();
+               const target = findTarget(attrs);
+               if (target === null) {
+                       return;
+               }
+               attrs.wicketVeil = target;
+               acquire(target);
+       }
+
+       function onDone(jqEvent, attrs) {

Review Comment:
   Thanks for implementing this, and sorry: I overlooked a very important 
detail when I suggested it, and I'd like to ask you to revert this part of 
7ec250509d.
   
   A redirect does not always leave the page. Core sets `isRedirecting` for 
every `Ajax-Location` response, and that includes redirects the browser never 
turns into a new page:
   
   - a `RedirectToUrlException` (or any `RedirectRequestHandler`) to a URL that 
is not a page: a download resource, a `mailto:` or other protocol handler, a 
URL answering 204;
   - a `beforeunload` prompt, for example from a dirty-form guard, answered 
with "Stay on page". That can follow a plain `setResponsePage`.
   
   The browser fires no event when a navigation does not happen, so the page 
cannot tell these apart from a slow page load. With the veil held on a 
redirect, it never comes down in these cases, and with a `PageVeilBehavior` on 
a base page the whole page stays blocked until it is reloaded. A timeout 
doesn't fix that, it only guesses: too short and the double-submit window is 
back, too long and the page is frozen for that long after every redirect that 
stays. Core could tell page redirects (`WebPageRenderer`) from URL redirects 
(`RedirectRequestHandler`), but that still leaves the `beforeunload` case, and 
it needs another core change.
   
   The slow part the veil is there for, the server's work, is covered either 
way. The window between the response and the next page is usually short, and it 
is the same window every Wicket application has today. That isn't worth a page 
that can end up blocked for good, so I think the veil should come down at 
`/ajax/call/done` for a redirect too, as in the first version.
   
   That means reverting:
   
   - the extra `isRedirecting` argument of `done()` and `/ajax/call/done` in 
`wicket-ajax-jquery.js` and `wicket-ajax.js`, the two core QUnit tests for it, 
and the `/ajax/call/done` line in `ajax_6.adoc`, so the PR no longer changes 
core;
   - `isRedirecting` in `onDone`, the `pageshow` handler, and the redirect test 
in `veil-test.js` (with the extra parameter of its `done` helper);
   - the redirect paragraphs in the `AbstractVeilBehavior` Javadoc, in 
`ajax_12.adoc` and in the header of `wicket-veil.js`.
   
   `lowerAll()` can stay, `_reset()` uses it. My apologies for the extra round.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to