Skip to content

Commit

Permalink
[FIXED JENKINS-42443] Make f:select display spinner during AJAX requests
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc committed Mar 2, 2017
1 parent 03c58b9 commit ed329d1
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 9 deletions.
202 changes: 202 additions & 0 deletions core/src/main/java/hudson/util/FormFillFailure.java
@@ -0,0 +1,202 @@
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;

import hudson.Functions;
import hudson.Util;
import java.io.IOException;
import java.util.Locale;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import static hudson.Util.join;

/**
* Represents the result of the form field fill failure.
*
* <p>
* Use one of the factory methods to create an instance, then throw it from your <tt>doFillXyz</tt>
* method.
*
* @since FIXME
*/
public abstract class FormFillFailure extends IOException implements HttpResponse {

/**
* Sends out a string error message that indicates an error.
*
* @param message Human readable message to be sent.
*/
public static FormFillFailure error(@Nonnull String message) {
return errorWithMarkup(Util.escape(message));
}

public static FormFillFailure warning(@Nonnull String message) {
return warningWithMarkup(message==null?null:Util.escape(message));
}

/**
* Sends out a string error message that indicates an error,
* by formatting it with {@link String#format(String, Object[])}
*/
public static FormFillFailure error(String format, Object... args) {
return error(String.format(format,args));
}

public static FormFillFailure warning(String format, Object... args) {
return warning(String.format(format,args));
}

/**
* Sends out a string error message, with optional "show details" link that expands to the full stack trace.
*
* <p>
* Use this with caution, so that anonymous users do not gain too much insights into the state of the system,
* as error stack trace often reveals a lot of information. Consider if a check operation needs to be exposed
* to everyone or just those who have higher access to job/hudson/etc.
*/
public static FormFillFailure error(Throwable e, String message) {
return _error(FormValidation.Kind.ERROR, e, message);
}

public static FormFillFailure warning(Throwable e, String message) {
return _error(FormValidation.Kind.WARNING, e, message);
}

private static FormFillFailure _error(FormValidation.Kind kind, Throwable e, String message) {
if (e==null) return _errorWithMarkup(Util.escape(message),kind);

return _errorWithMarkup(Util.escape(message)+
" <a href='#' class='showDetails'>"
+ Messages.FormValidation_Error_Details()
+ "</a><pre style='display:none'>"
+ Util.escape(Functions.printThrowable(e)) +
"</pre>",kind
);
}

public static FormFillFailure error(Throwable e, String format, Object... args) {
return error(e,String.format(format,args));
}

public static FormFillFailure warning(Throwable e, String format, Object... args) {
return warning(e,String.format(format,args));
}

/**
* Sends out an HTML fragment that indicates an error.
*
* <p>
* This method must be used with care to avoid cross-site scripting
* attack.
*
* @param message
* Human readable message to be sent. <tt>error(null)</tt>
* can be used as <tt>ok()</tt>.
*/
public static FormFillFailure errorWithMarkup(String message) {
return _errorWithMarkup(message, FormValidation.Kind.ERROR);
}

public static FormFillFailure warningWithMarkup(String message) {
return _errorWithMarkup(message, FormValidation.Kind.WARNING);
}

private static FormFillFailure _errorWithMarkup(@Nonnull final String message, final FormValidation.Kind kind) {
return new FormFillFailure(kind, message) {
public String renderHtml() {
StaplerRequest req = Stapler.getCurrentRequest();
if (req == null) { // being called from some other context
return message;
}
// 1x16 spacer needed for IE since it doesn't support min-height
return "<div class="+ getKind().name().toLowerCase(Locale.ENGLISH) +"><img src='"+
req.getContextPath()+ Jenkins.RESOURCE_PATH+"/images/none.gif' height=16 width=1>"+
message+"</div>";
}
@Override public String toString() {
return kind + ": " + message;
}
};
}

/**
* Sends out an arbitrary HTML fragment as the output.
*/
public static FormFillFailure respond(FormValidation.Kind kind, final String html) {
return new FormFillFailure(kind) {
public String renderHtml() {
return html;
}
@Override public String toString() {
return getKind() + ": " + html;
}
};
}

private final FormValidation.Kind kind;
private boolean selectionCleared;

/**
* Instances should be created via one of the factory methods above.
* @param kind the kind
*/
private FormFillFailure(FormValidation.Kind kind) {
this.kind = kind;
}

private FormFillFailure(FormValidation.Kind kind, String message) {
super(message);
this.kind = kind;
}

public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
rsp.setContentType("text/html;charset=UTF-8");
rsp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
rsp.setHeader("X-Jenkins-Select-Error", selectionCleared ? "clear" : "retain");
rsp.getWriter().print(renderHtml());
}

public FormValidation.Kind getKind() {
return kind;
}

public boolean isSelectionCleared() {
return selectionCleared;
}

public FormFillFailure withSelectionCleared() {
this.selectionCleared = true;
return this;
}

public abstract String renderHtml();

}
34 changes: 25 additions & 9 deletions core/src/main/resources/lib/form/select/select.js
Expand Up @@ -4,8 +4,13 @@ function updateListBox(listBox,url,config) {
config = config || {};
config = object(config);
var originalOnSuccess = config.onSuccess;
config.onSuccess = function(rsp) {
var l = $(listBox);
var l = $(listBox);
var status = findFollowingTR(listBox, "validation-error-area").firstChild.nextSibling;
if (status.firstChild && status.firstChild.getAttribute('data-select-ajax-error')) {
status.innerHTML = "";
}
config.onSuccess = function (rsp) {
l.removeClassName("select-ajax-pending");
var currentSelection = l.value;

// clear the contents
Expand All @@ -30,13 +35,24 @@ function updateListBox(listBox,url,config) {

if (originalOnSuccess!=undefined)
originalOnSuccess(rsp);
},
config.onFailure = function(rsp) {
// deleting values can result in the data loss, so let's not do that
// var l = $(listBox);
// l.options[0] = null;
}
};
config.onFailure = function (rsp) {
l.removeClassName("select-ajax-pending");
status.innerHTML = rsp.responseText;
if (status.firstChild) {
status.firstChild.setAttribute('data-select-ajax-error', 'true')
}
Behaviour.applySubtree(status);
// deleting values can result in the data loss, so let's not do that unless instructed
var header = rsp.getResponseHeader('X-Jenkins-Select-Error');
if (header && "clear".toUpperCase() === header.toUpperCase()) {
// clear the contents
while (l.length > 0) l.options[0] = null;
}

};

l.addClassName("select-ajax-pending");
new Ajax.Request(url, config);
}

Expand Down Expand Up @@ -82,4 +98,4 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
}
});
});
});
});

0 comments on commit ed329d1

Please sign in to comment.