Skip to content

Instantly share code, notes, and snippets.

@sody
Created July 6, 2012 11:31
Show Gist options
  • Save sody/3059664 to your computer and use it in GitHub Desktop.
Save sody/3059664 to your computer and use it in GitHub Desktop.
Tapestry Ajax Upload Fixin
package com.example.ui.mixins;
import org.apache.tapestry5.annotations.AfterRender;
import org.apache.tapestry5.annotations.BindParameter;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
/**
* This class represents fixin for form component that adds file uploading possibility when upload component
* are placed inside tapestry zone. It will submit form in multipart request within separate iframe
* and mark request with {@code 'XHR_EMULATION'} flag. Then client-side zone will be updated with json response
* from server. If no file was selected the form will be submitted in a standard way.
* <p/>
* If browser supports html5 this component will submit form using FormData.
* <p/>
* NOTE: {@link com.example.ui.internal.FileUploadFilter} request filter should be configured.
*
* @author Ivan Khalopik
* @since 1.0
*/
@Import(
library = {
"context:js/jquery.ajax.upload.js"
})
public class AjaxUpload {
@BindParameter
private String zone;
@InjectContainer
private Form form;
@Inject
private JavaScriptSupport javascriptSupport;
/**
* Renders fixin's JavaScript if form's zone parameter is bound.
*/
@AfterRender
void renderScript() {
// render script only if form's zone parameter is bound
if (zone != null) {
javascriptSupport.addInitializerCall("ajaxUpload", new JSONObject(
"formId", form.getClientId()
));
}
}
}
package com.example.ui.internal;
import org.apache.tapestry5.ContentType;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.*;
import org.apache.tapestry5.util.ResponseWrapper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
/**
* This class represents filter that emulates xhr request when 'XHR_EMULATION' parameter is set.
*
* @author Ivan Khalopik
* @since 1.0
*/
public class FileUploadFilter implements RequestFilter {
private static final String XHR_EMULATION_PARAMETER = "XHR_EMULATION";
private final ContentType emulatedContentType;
public FileUploadFilter(@Symbol(SymbolConstants.CHARSET) String outputEncoding) {
emulatedContentType = new ContentType("text/plain", outputEncoding);
}
public boolean service(final Request request, final Response response, final RequestHandler handler) throws IOException {
//separate handler for FileUpload requests only
if (!request.isXHR() && request.getParameter(XHR_EMULATION_PARAMETER) != null) {
return handler.service(new FileUploadRequest(request), new FileUploadResponse(response));
}
return handler.service(request, response);
}
public class FileUploadRequest extends DelegatingRequest {
public FileUploadRequest(final Request request) {
super(request);
}
@Override
public boolean isXHR() {
// now request is xhr
return true;
}
}
public class FileUploadResponse extends ResponseWrapper {
public FileUploadResponse(final Response response) {
super(response);
}
@Override
public OutputStream getOutputStream(final String contentType) throws IOException {
// now content type is always text/plain
return super.getOutputStream(emulatedContentType.toString());
}
@Override
public PrintWriter getPrintWriter(final String contentType) throws IOException {
// now content type is always text/plain
return super.getPrintWriter(emulatedContentType.toString());
}
}
}
var supportsFormData = typeof(FormData) == "function" && typeof(FormData.prototype) == "object";
$.ajax({
url:self.attr("action"),
type:"post",
data:new FormData(self.get(0)),
// options to tell jquery not to process data or worry about content-type
cache:false,
contentType:false,
processData:false
});
var $originalForm = $("form"),
$file = $originalForm.find("input:file"),
$target = $("<iframe>", {
name:"upload_frame"
}).hide().appendTo("body"),
$form = $("<form>", {
target:$target.attr("name"),
method:"post",
enctype:"multipart/form-data",
encoding:"multipart/form-data",
action:$originalForm.attr("action")
}).hide().appendTo("body"),
data = $originalForm.serializeArray();
// clone file inputs and attach original to newly created form
$file.after(function() {
return this.clone();
}).appendTo($form);
// create hidden inputs for all data entries and attach them to form
$.each(data, function () {
$("<input>", {
type:"hidden",
name:this.name,
value:this.value
}).appendTo($form);
});
// submit form
$form.on("submit",function () {
$target.on("load", function () {
// when iframe is loaded cache its contents and remove it
var content = $target.contents().text(),
response = $.parseJSON(content);
$form.remove();
$target.remove();
// do something with response data
});
}).submit();
/**
* @author Ivan Khalopik
* @version 0.1
*/
(function ($) {
var uuid = 0;
var supportsFormData = typeof(FormData) == "function" && typeof(FormData.prototype) == "object";
if (supportsFormData) {
// for html5 we can use FormData approach
$.fn.upload = function (options) {
var self = this,
data = new FormData(self.get(0)),
deferred = $.Deferred();
options = options || {};
self.promise = deferred.promise;
// add additional parameters to form data
$.each(options.data || {}, function (name) {
data.append(name, this);
});
// ajax post
$.ajax({
url:self.attr("action"),
type:"post",
data:new FormData(self.get(0)),
// options to tell jquery not to process data or worry about content-type
cache:false,
contentType:false,
processData:false
}).then(deferred.resolve, deferred.reject, deferred.notify);
return this;
};
} else {
// for no html5 we can emulate ajax request via iframe
$.fn.upload = function (options) {
var self = this,
data = self.serializeArray(),
deferred = $.Deferred(),
$file = self.find("input:file"),
$target = $("<iframe>", {
name:"upload_" + ++uuid
}).hide().appendTo("body"),
$form = $("<form>", {
target:$target.attr("name"),
method:"post",
enctype:"multipart/form-data",
encoding:"multipart/form-data",
action:self.attr("action")
}).hide().appendTo("body");
options = options || {};
self.promise = deferred.promise;
// add additional parameters to form data
$.each(options.data || {}, function (name) {
data.push({ name:name, value:this });
});
// clone file inputs and attach original to newly created form
$file.after(function() {
return this.clone();
}).appendTo($form);
// add flag to simulate ajax request
data.push({ name:"XHR_EMULATION", value:true });
// create hidden inputs for all data entries and attach them to form
$.each(data, function () {
$("<input>", {
type:"hidden",
name:this.name,
value:this.value
}).appendTo($form);
});
// submit form
$form.on("submit",function () {
$target.on("load", function () {
// when iframe is loaded cache its contents and remove it
var content = $target.contents().text(),
response = $.parseJSON(content);
$form.remove();
$target.remove();
deferred.resolve(response);
});
}).submit();
return this;
};
}
})(jQuery);
<t:zone t:id="formZone" id="formZone" update="show">
<t:form t:id="form" zone="formZone" t:mixins="ajaxupload">
<t:upload t:id="file" value="file"/>
<t:submit value="Upload"/>
</t:form>
</t:zone>
T5.extendInitializers({
ajaxUpload:function (spec) {
// find tapestry form
var tapestryForm = $(spec.formId);
// bind on submit form event
tapestryForm.observe(Tapestry.FORM_PREPARE_FOR_SUBMIT_EVENT, function () {
// stop observing submit event to prevent usual tapestry form submission
tapestryForm.stopObserving(Tapestry.FORM_PROCESS_SUBMIT_EVENT);
// connect to tapestry form submission event
tapestryForm.observe(Tapestry.FORM_PROCESS_SUBMIT_EVENT, function () {
// clear submit event listener to prevent infinite loop
tapestryForm.onsubmit = null;
// upload files in iframe using jquery plugin
jQuery("#" + spec.formId).upload().promise().done(function (response) {
// update zone with POST result
var zone = Tapestry.findZoneManager(spec.formId);
zone.processReply(response);
});
});
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment