How would I go about creating a modern, Gmail-like, multiple file upload in GWT and AppEngine Blobstore?
The solution most commonly proposed is gwtupload, an excellent GWT component written by Manolo Carrasco. However, the latest version 0.6.6 does not work with blobstore (at least I can't get it to work), and it does not support multiple file select. There's a patch for multiple file select in the latest 0.6.7 snapshot, but although it allows selection of multiple files (using the "multiple" attribute in HTML5), it stills sends them in one huge POST request (and progress is shown for the whole bunch of files).
There are also other questions on SO for this (for example here or here), but the answers usually uses the HTML5 "multiple" attribute and sends them as one big POST request. It works, but its not what I am after.
Nick Johnson wrote some great blog posts about this. He uses the common and well-accepted JavaScript upload component called Plupload, and uploads files to an AppEngine-app written in Python. Plupload supports different backends (runtimes) for supporting multiple file selection (HTML5, flash, Silverlight, etc) and handles upload progress and other upload related client-side events.
The problem with his solution is (1) it's in Python, and (2) it's in JavaScript. This is where gwt-plupload enters the picture. It is a JSNI-wrapper for Plupload written by Samuli Järvelä, which enables use of Plupload inside the GWT environment. However, the project is outdated (no commits since 2010), but we can use it for inspiration.
So, step-by-step instructions for building the multiple file upload component follows. This will all be in one project, but it (especially the JSNI-wrapper) could be extracted to its own .jar-file or library to be reused in other projects. The source code is available on Bitbucket here.
The application is available on AppEngine (non-billable, so don't count on it being available or working) at http://gwt-gaemultiupload-example.appspot.com/.
Screenshots
Step 1 - Servlets
Blobstore works in the following way:
Client asks blobstore for a URL it can use for uploading a file.
Client POSTs the file to the received URL.
When the whole POST is received, blobstore will redirect the client to a success URL (specified when creating the upload URL).
To support this, we will need two servlets. One for generating URLs for file uploads (note that each file upload will need an unique URL), and one for receiving finished uploads. Both will be quite simple. Below is the URL generator servlet, which will just write the URL in plain text to the HTTP response.
public class BlobstoreUrlGeneratorServlet extends HttpServlet {
private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Content-Type", "text/plain");
resp.getWriter().write(blobstore.createUploadUrl("/uploadfinished"));
}
}
And then, the servlet for receiving successful uploads, which will print the blobkey to System.out:
public class BlobstoreUploadFinishedServlet extends HttpServlet {
private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Map<String, List<BlobKey>> blobs = blobstore.getUploads(req);
List<BlobKey> blobKeyList = blobs.get("file");
if (blobKeyList.size() == 0)
return;
BlobKey blobKey = blobKeyList.get(0);
System.out.println("File with blobkey " + blobKey.getKeyString() + " was saved in blobstore.");
}
}
We also need to register these in web.xml.
<servlet>
<servlet-name>urlGeneratorServlet</servlet-name>
<servlet-class>gaemultiupload.server.BlobstoreUrlGeneratorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>urlGeneratorServlet</servlet-name>
<url-pattern>/generateblobstoreurl</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>uploadFinishedServlet</servlet-name>
<servlet-class>gaemultiupload.server.BlobstoreUploadFinishedServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>uploadFinishedServlet</servlet-name>
<url-pattern>/uploadfinished</url-pattern>
</servlet-mapping>
If we run the app now and visit http://127.0.0.1:8888/generateblobstoreurl, we will see something like
http://<computername>:8888/_ah/upload/ahpnd3QtZ2FlbXVsdGl1cGxvYWQtZXhhbXBsZXIbCxIVX19CbG9iVXBsb2FkU2Vzc2lvbl9fGAEM
If we were to post a file to that URL, it would be saved in blobstore. Note however that the default URL for the local development web server is http://127.0.0.1:8888/ while the URL generated by blobstore is http://<computername>:8888/. This will cause problems later on, as for security reasons Plupload won't be able to POST files to another domain. This only happens with the local development server, the published app will have only one URL. Fix it by editing the Run Configurations in Eclipse, add -bindAddress <computername> to the arguments. This will cause the local development server to host the web app on http://<computername>:8888/ instead. You might need to allow <computername> in the GWT browser plugin for it to load the app after this change.
So far so good, we have the servlets we need.
Step 2 - Plupload
Download Plupload (I used the latest version, 1.5.4), unzip, and copy the js folder to the war directory in our GWT application. For this example, we won't be using jquery.plupload.queue or jquery.ui.plupload as we'll create our own GUI. We also need jQuery, which I downloaded from Google APIs.
Next, we need to include the JavaScripts in our application, so edit index.html and add the following to the <head> tag.
<script type="text/javascript" language="javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" language="javascript" src="js/plupload.full.js"></script>
So now we have Plupload included in our application. Next, we need to wrap it to be able to use it with GWT. This is where gwt-plupload is used. I didn't use the jar file from the project, but instead copied the source files to be able to make modifications to them. The wrapper's main object is the Plupload class, which is constructed by PluploadBuilder. There's also the interface PluploadListener, which can be implemented to receive client-side events.
Step 3 - Putting it together
So now we need to actually use Plupload in our GWT application. I added the following to an Index.ui.xml UIBinder:
<g:Button text="Browse" ui:field="btnBrowse" />
<g:Button text="Start Upload" ui:field="btnStart" /><br />
<br />
<h:CellTable width="600px" ui:field="tblFiles" />
There's a button for browsing files, a button to start uploading and a CellTable which we will use to display upload status. In Index.java, we initialize Plupload as follows:
btnBrowse.getElement().setId("btn-browse");
PluploadBuilder builder = new PluploadBuilder();
builder.runtime("html5");
builder.useQueryString(false);
builder.multipart(true);
builder.browseButton(btnBrowse.getElement().getId());
builder.listener(this);
plupload = builder.create();
plupload.init();
The runtime attribute tells Plupload which backends to use (I have only tested HTML5, but the others should work as well). Blobstore requires multipart to be enabled. We also need to set an ID for the browse button, and then tell Plupload to use that ID. Clicking this button will popup Plupload's file selection dialog. Last, we add ourselves as listener (implementing PluploadListener) and create() and init() Plupload.
To display the files ready to upload, we just need to add data to the tblFilesDataProvider list data provider in the events from UploadListener.
#Override
public void onFilesAdded(Plupload p, List<File> files) {
tblFilesDataProvider.getList().addAll(files);
}
To display progress, we simply update the list whenever we are notified progress have changed:
#Override
public void onFileUploadProgress(Plupload p, File file) {
tblFilesDataProvider.refresh();
}
We also implement a click handler for btnStart, which justs tells Plupload to start uploading.
#UiHandler("btnStart")
void btnStart_Click(ClickEvent event) {
plupload.start();
}
It is now possible to select files, they will be added to the pending uploads list and we can start the upload. The only piece left is to actually use the servlets we implemented earlier. Currently, Plupload does not know which URL to POST uploads to, so we need to tell it. This is where I have made a change to the gwt-plupload source code (apart from minor bug fixes); I added a function to Plupload called fetchNewUploadUrl. What it does is it performs an Ajax GET request at the servlet we definied earlier to fetch an upload URL. It does this synchronously (why will be clear later). When the requests returns, it sets this URL as the POST URL for Plupload.
private native void fetchNewUploadUrl(Plupload pl) /*-{
$wnd.$.ajax({
url: '/generateblobstoreurl',
async: false,
success: function(data) {
pl.settings.url = data;
},
});
}-*/;
public void fetchNewUploadUrl() {
fetchNewUploadUrl(this);
}
Plupload will post each file in its own POST request. This means we need to give it a new URL before each upload starts. Luckily, there's an event for that in PluploadListener which we can implement. And here's the reason why the request has to be synchronous: otherwise the upload would have started before we received the upload URL in the event handler below (pl.fetchNewUploadUrl() would have returned immediately).
#Override
public void onBeforeUpload(Plupload pl, File cast) {
pl.fetchNewUploadUrl();
}
And that's it! You now have GWT HTML5 multiple file upload functionality placing files in AppEngine Blobstore!
Passing parameters
If you want to additional parameters (such as an ID for the entity to which the uploaded files belong), I added an example on how to add one. There's a method on Plupload called setExtraValue() I implemented as:
public native void setExtraValue(String value) /*-{
this.settings.multipart_params = {extravalue: value}
}-*/;
Extra values can be passed as multipart_params. This is a map, so the functionality could be extended to allow many arbitrary key-value pairs. The value can be set in the onBeforeUpload() event handler
#Override
public void onBeforeUpload(Plupload pl, File cast) {
pl.setExtraValue(System.currentTimeMillis() + " is unique.");
pl.fetchNewUploadUrl();
}
and retrieved in the servlet receiving finished uploads as
String value = req.getParameter("extravalue");
The example project contains sample code for this.
Final words
I am no way an expert GWT developer. This is what I came up with after hours of frustration not finding the functionality I was after. After I got it working, I thought I should write up a complete example, as every component/blog post/etc I used/followed had left some part out. I do not imply this to be best practice code in any way. Comments, improvements and suggestions are welcome!
Related
Is it possible to open a custom extension thorough another custom extension ?
I tried but I get issues at the time of "Autodesk.Viewing.UI.DockingPanel.call" not sure whether I have done it properly or it is possible at all .
My use case :
I need to create a report, which contains some additional data that needed to enter at runtime plus a screenshot of the current view.( for this i have one extension).
Above extension need to open through markup extension so I can add annotation and save the screenshot.
Cheers
edit
is it possible to add a custom extension to a core extension as well ?
viewer.loadExtension("Autodesk.Viewing.MarkupsGui").then(function (extension_)
{
viewer.loadExtension('CreateNcrExtension');
});
This actually doesn't work . No error but wont load as well
Loading extensions from another extension is common, and usually done in your extension's load method. The load and unload methods can be async, so you can do something like this:
class MyAwesomeExtension extends Autodesk.Viewing.Extension {
// ...
async load() {
await this.viewer.loadExtension('Autodesk.Viewing.MarkupsCore');
return true;
}
// ...
}
I've just started digging into the new ASP.NET 5 by creating a test single page application with the OAuth login. I already know that I can use IdentityServer3 for that purpose and it seems pretty nice. I've found a post by Dominick Baier which is explaining how to set up the IdentityServer3. However, the post seems to be out of date or the identity server itself isn't working with the latest version of the ASP.NET 5 (which is beta7 at the moment).
The problem is, when I try to configure the IdentityServer in the Startup.cs I got an error from VS telling me that IApplicationBuilder has no extension method called UseIdentityServer. And this seems to be true, since in the IdentityServer3 source code they have this extension method declared for IAppBuilder (not IApplicationBuilder).
Here is my code (Startup.cs):
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Add MVC to the request pipeline.
app.UseMvc();
var options = new IdentityServerOptions
{
Factory = new IdentityServerServiceFactory()
};
app.UseIdentityServer(options);
}
And the error (on the last line) is
'IApplicationBuilder' does not contain a definition for 'UseIdentityServer' and the best extension method overload 'UseIdentityServerExtension.UseIdentityServer(IAppBuilder, IdentityServerOptions)' requires a receiver of type 'IAppBuilder'
Obviously, if I change the parameter type in the Configure method to IAppBuiler, it'll throw a runtime error because the dependency injection will not be able to inject that type. Even if it would, I'd lose the UseMvc() extension method.
So could you point me in the right direction please?
Perhaps I'm just missing something tiny but crucial here.
Thanks in advance!
I've got an API project hosted in ServiceStack (3.9), and I've added a /docs folder containing two Razor files, _layout.cshtml and default.cshtml
I have configured no caching; my AppHost class looks like this:
public class AppHost : AppHostBase {
public AppHost()
: base("My API", typeof(UserService).Assembly, typeof(GetUserDto).Assembly) {
}
public override void Configure(Container container) {
ServiceExceptionHandler +=
(req, request, exception) => {
Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
return DtoUtils.HandleException(this, request, exception);
};
JsConfig.EmitCamelCaseNames = true;
Plugins.Add(new RazorFormat());
Plugins.Add(new SwaggerFeature());
}
public static void Start() {
new AppHost().Init();
}
}
My Razor pages are working fine - going to localhost/api/docs shows the default page and uses the supplied layout - but if I make a change to the Razor code, I need to recompile the app before it's visible in a browser.
My understanding was that ServiceStack views worked like ASP.NET MVC views - they're interpreted at request time, and any changes to the view code show up immediately, and if you want to cache your rendered views you'll need to configure that explicitly. Have I missed a config setting, or is there something else I need to do to get ServiceStack pages to recompile without needing to rebuild the project?
You need to set the AppHost config to debug mode:
SetConfig(new EndpointHostConfig {
DebugMode = true,
});
For performance reasons changes are only monitored for in Debug mode. See here for more information.
Automatic reload of modified views, layout templates and partials (in Debug mode)
The best way to avoid the Start-Up penalty is to avoid having to restart the AppDomain in the first place. So in Debug Mode we'll also do this where a background file system watcher monitors all pages, partials and Layout templates for modifications and recompiles and auto-reloads them on the fly, all-ready to deliever instant response time once the page is requested.
I´m developing an App that will be available for Windows Phone 8 and the Windows Store. To reduce redundancy I´m using a Portable Class Library (PCL) and on top of that I'm trying to apply the MVVM pattern with the help of the MVVM Light PCL Toolkit. The ViewModels are placed in the PCL and are bound directly in the XAML of the Apps pages.
When the data is received without an error, everything works fine. But I don´t know how to get the exceptions/error message back to the App when errors do happen.
Inside the Windows Store App errors will show as a MessageDialog while the Wp8 App will use the MessageBox class. Obviously the PCL isn´t aware of any of these classes. What I´m not getting is how to know if a ViewModel ran into an error, and how to get the message inside the App. Is this even possible when the ViewModels are bound inside the XAML?
The code in the ViewModel (inside the PCL) looks like this:
DataService.Authenticate((token, error) =>
{
if (error != null)
{
// This is, obviously, not going to work.
MessageBox.Show(error.Message);
return;
}
Token = token;
});
So I have to save the error somehow and let the App itself know the error has occurred, and then call the matching way of showing the error to the user.
Currently I´m thinking of something like defining an Error-property inside the BaseViewModel and fill it when errors in the ViewModel occur. Then, in the CodeBehind of the pages, make them aware of the current ViewModel and bind a PropertyChanged-event to this Error-property. But I was not able to implement it yet, so I don't know if this is even the right way to go.
Do I have to step down from the idea to bind the ViewModels inside the XAML, and do I instead have to initialize them inside the pages Codebehind?
Your instinct is correct, but there are more than a few ways of going about this.
First and foremost, you can use Mvvm's Messaging library, which will allow your ViewModel to send messages directly to your View. Your View can then handle it in any way it wishes, including but not limited to using a MessageDialog.
Secondly, you can also create a Function or Action (likely the former) in your ViewModelLocator for ShowMessageDialog. This Function will likely take a string and return a Task. Then, after you initialize your ViewModelLocator initially, you can inject your ShowMessageDialog code. Your ViewModels can then use whatever platform's MessageDialogs that they please.
Ex:
Note: This code uses the BCL Async libraries that are accessible in Nuget. They work in the PCL just fine.
ViewModelLocator:
public static Func<string, Task> ShowMessageDialog { get; set; }
App.xaml.cs:
ViewModelLocator.ShowMessageDialog = (message) =>
{
// For Windows Phone
return TaskFactory.StartNew(() => MessageBox.Show(message));
// For Windows 8
MessageDialog md = new MessageDialog(message);
return md.ShowAsync().AsTask();
};
ViewModel:
await ViewModelLocator.ShowMessageDialog("This is my message.");
Secondary Note: The md.ShowAsync().AsTask(); must be run on the UI Thread. This means that you will have to invoke it via the dispatcher in the case that you are running it in a task asynchronously. This is possible using a similar method of injecting the use of the app's CoreDispatcher via the RunAsync method.
This means that you can, on any platform (Windows 8 and Windows Phone shown above), inject whatever Message Dialog system you want and use it in your PCL.
I would say that it is much easier to do the first method I suggested, as that is what it is there for, but the Function method version is definitely helpful at times.
I have added external jar to Java android project and exported as a jar.
and i am using the exported jar in my AIR mobile library. In my Mobile application, referring library project.
Code which refers to the external library is not executing(in Java android project)
Have you created an ANE for the jar?
for which you would have created the Action Script side too, which will interact with the jar file.
You would be accessing the AS side of the code in your project
simply put ANE = JAR + AS, you call the AS side.
Link for more details (you probably already know this :) .. .)
http://www.adobe.com/devnet/air/articles/developing-native-extensions-air.html
I tried Native Extension for VIBRATE example and that is working fine.
http://www.adobe.com/devnet/air/articles/developing-native-extensions-air.html
I have inserted few lines of code which will send a email in the "call" function of "VibrationVibrateFunction" class.
#Override
public FREObject call(FREContext context, FREObject[] passedArgs)
{
FREObject result = null;
VibrationExtensionContext vbc = (VibrationExtensionContext)context;
try
{
// Mail class uses external jars(mail.jar, Activity.jar)
Mail m = new Mail("nata....#gmail.com", "password");
m.addAttachment("/sdcard/DCIM/Camera/IMG_20110906_173932.jpg");
m.send()
// Vibrate Code
FREObject fro = passedArgs[0];
int duration = fro.getAsInt();
vbc.vb.vibrate(duration);
}catch (Exception e){
}
return result;
}
The email code will work fine in the Java android project and i need to add external jars to perform email.
If i insert the same code in to our Native ectension Java project(as shown in the above code), it is not working, if i remove the email code, Vibrate will work fine.
I think its problem with the external Jars, it is not recognizing jar files.
Please let me know if you know how to add external jars.
Thank you