You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@wicket.apache.org by Martin Grigorov <mg...@apache.org> on 2017/03/23 14:31:27 UTC

Re: [2/2] wicket git commit: WICKET-6286 added Location.Blob

This new AjaxDownload utility became much smarter than the original one! :-)

I see that you have changed the default location to be Blob.
According to http://caniuse.com/#search=blob it could be used with pretty
much all modern browsers!
Do we need to add support for fallbacks via ClientProperties and UserAgent
? Or we will keep it simple until someone really needs this ? The simplest
thing we can do is make #getLocation() non-final and use it in #initiate().
This way the developer can use any kind of detection for the best location
type.
WDYT ?


Martin Grigorov
Wicket Training and Consulting
https://twitter.com/mtgrigorov

On Thu, Mar 23, 2017 at 3:22 PM, <sv...@apache.org> wrote:

> WICKET-6286 added Location.Blob
>
> downloads via Blob and createObjectURL
>
> Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
> Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/10175f1e
> Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/10175f1e
> Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/10175f1e
>
> Branch: refs/heads/master
> Commit: 10175f1ef969ae0feccafc9bf997c78e42c8cc54
> Parents: e7c2320
> Author: Sven Meier <sv...@apache.org>
> Authored: Thu Mar 23 15:22:15 2017 +0100
> Committer: Sven Meier <sv...@apache.org>
> Committed: Thu Mar 23 15:22:15 2017 +0100
>
> ----------------------------------------------------------------------
>  .../examples/ajax/builtin/AjaxDownloadPage.html |  3 +-
>  .../examples/ajax/builtin/AjaxDownloadPage.java | 48 ++++++++++++++++++-
>  .../wicket/extensions/ajax/AjaxDownload.java    |  9 +++-
>  .../extensions/ajax/wicket-ajaxdownload.js      | 49 ++++++++++++++++++--
>  4 files changed, 101 insertions(+), 8 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/wicket/blob/
> 10175f1e/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.html
> ----------------------------------------------------------------------
> diff --git a/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.html b/wicket-examples/src/main/
> java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html
> index 0be7fe2..dd7e3ab 100644
> --- a/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.html
> +++ b/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.html
> @@ -27,7 +27,8 @@
>  </p>
>
>  <ul>
> -       <li>via an <a wicket:id="download">iframe</a>.</li>
> +       <li>for <a wicket:id="download">modern browsers</a>.</li>
> +       <li>in an <a wicket:id="downloadIframe">iframe</a>.</li>
>         <li>in a <a wicket:id="downloadInNewWindow">new browser
> window</a>.</li>
>         <li>in the <a wicket:id="downloadInSameWindow">same
> window</a>.</li>
>  </ul>
>
> http://git-wip-us.apache.org/repos/asf/wicket/blob/
> 10175f1e/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.java
> ----------------------------------------------------------------------
> diff --git a/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.java b/wicket-examples/src/main/
> java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java
> index b589091..6d1aa3e 100644
> --- a/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.java
> +++ b/wicket-examples/src/main/java/org/apache/wicket/
> examples/ajax/builtin/AjaxDownloadPage.java
> @@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
>  import org.apache.wicket.ajax.AjaxRequestTarget;
>  import org.apache.wicket.ajax.markup.html.AjaxLink;
>  import org.apache.wicket.extensions.ajax.AjaxDownload;
> +import org.apache.wicket.extensions.ajax.AjaxDownload.Location;
>  import org.apache.wicket.markup.html.WebMarkupContainer;
>  import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeExceptio
> n;
>  import org.apache.wicket.request.resource.ContentDisposition;
> @@ -51,6 +52,8 @@ public class AjaxDownloadPage extends BasePage
>                 add(downloadingContainer);
>
>                 initDownload();
> +
> +               initDownloadInIframe();
>
>                 initDownloadInNewWindow();
>
> @@ -67,7 +70,7 @@ public class AjaxDownloadPage extends BasePage
>                 // download cannot continue on page refresh
>                 downloadingContainer.setVisible(false);
>         }
> -
> +
>         private void initDownload()
>         {
>                 IResource resource = new ExampleResource("downloaded via
> ajax")
> @@ -109,6 +112,49 @@ public class AjaxDownloadPage extends BasePage
>                         }
>                 });
>         }
> +
> +       private void initDownloadInIframe()
> +       {
> +               IResource resource = new ExampleResource("downloaded via
> ajax in iframe")
> +                       .setContentDisposition(
> ContentDisposition.ATTACHMENT);
> +
> +               final AjaxDownload download = new AjaxDownload(resource) {
> +
> +                       @Override
> +                       protected void onBeforeDownload(AjaxRequestTarget
> target)
> +                       {
> +                               downloadingContainer.setVisible(true);
> +                               target.add(downloadingContainer);
> +                       }
> +
> +                       @Override
> +                       protected void onDownloadSuccess(AjaxRequestTarget
> target)
> +                       {
> +                               downloadingContainer.setVisible(false);
> +                               target.add(downloadingContainer);
> +                       }
> +
> +                       @Override
> +                       protected void onDownloadFailed(AjaxRequestTarget
> target)
> +                       {
> +                               downloadingContainer.setVisible(false);
> +                               target.add(downloadingContainer);
> +
> +                               target.appendJavaScript("alert('Download
> failed');");
> +                       }
> +               };
> +               download.setLocation(Location.IFrame);
> +               add(download);
> +
> +               add(new AjaxLink<Void>("downloadIframe")
> +               {
> +                       @Override
> +                       public void onClick(AjaxRequestTarget target)
> +                       {
> +                               download.initiate(target);
> +                       }
> +               });
> +       }
>
>         private void initDownloadReference()
>         {
>
> http://git-wip-us.apache.org/repos/asf/wicket/blob/
> 10175f1e/wicket-extensions/src/main/java/org/apache/
> wicket/extensions/ajax/AjaxDownload.java
> ----------------------------------------------------------------------
> diff --git a/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/AjaxDownload.java b/wicket-extensions/src/main/
> java/org/apache/wicket/extensions/ajax/AjaxDownload.java
> index 804a794..e50cf9d 100644
> --- a/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/AjaxDownload.java
> +++ b/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/AjaxDownload.java
> @@ -73,6 +73,13 @@ public class AjaxDownload extends
> AbstractDefaultAjaxBehavior
>
>         public enum Location {
>                 /**
> +                * The resource will be downloaded into a blob.
> +                * <p>
> +                * This is recommended for modern browsers.
> +                */
> +               Blob,
> +
> +               /**
>                  * The resource will be downloaded via a temporary created
> iframe, the resource has to be a
>                  * {@link ContentDisposition#ATTACHMENT}.
>                  * <p>
> @@ -115,7 +122,7 @@ public class AjaxDownload extends
> AbstractDefaultAjaxBehavior
>
>         private PageParameters resourceParameters;
>
> -       private Location location = Location.IFrame;
> +       private Location location = Location.Blob;
>
>         /**
>          * Download of a {@link Resource}.
>
> http://git-wip-us.apache.org/repos/asf/wicket/blob/
> 10175f1e/wicket-extensions/src/main/java/org/apache/
> wicket/extensions/ajax/wicket-ajaxdownload.js
> ----------------------------------------------------------------------
> diff --git a/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/wicket-ajaxdownload.js b/wicket-extensions/src/main/
> java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js
> index ec81ca4..7c320c3 100644
> --- a/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/wicket-ajaxdownload.js
> +++ b/wicket-extensions/src/main/java/org/apache/wicket/
> extensions/ajax/wicket-ajaxdownload.js
> @@ -29,10 +29,10 @@
>                 initiate : function(settings) {
>
>                         var notifyServer = function(result) {
> -                settings.attributes.ep = settings.attributes.ep || {};
> -                settings.attributes.ep.result = result;
> -                Wicket.Ajax.ajax(settings.attributes);
> -            };
> +                               settings.attributes.ep =
> settings.attributes.ep || {};
> +                               settings.attributes.ep.result = result;
> +                               Wicket.Ajax.ajax(settings.attributes);
> +                       };
>
>                         var checkComplete = function(watcher) {
>                                 var result;
> @@ -82,7 +82,7 @@
>                                                 }
>                                         }
>                                 });
> -                       } else {
> +                       } else if (settings.method == 'iframe') {
>                                 var frame = jQuery("<iframe></iframe>").hide().prop("src",
> settings.downloadUrl).appendTo("body");
>                                 checkComplete({
>                                         html: function() {
> @@ -96,6 +96,45 @@
>                                                 }, 0);
>                                         }
>                                 });
> +                       } else {
> +                               jQuery.ajax({
> +                                       type: 'get',
> +                                       url: settings.downloadUrl,
> +                                       success: function (response,
> status, xhr) {
> +                                               var filename = "";
> +                                               var disposition =
> xhr.getResponseHeader("Content-Disposition");
> +                                               if (disposition &&
> disposition.indexOf("attachment") !== -1) {
> +                                                       var matches =
> /filename[^;=\n]*=(([""]).*?\2|[^;\n]*)/.exec(disposition);
> +                                                       if (matches !=
> null && matches[1]) {
> +                                                               filename =
> matches[1].replace(/[""]/g, "");
> +                                                       }
> +                                               }
> +
> +                                               var type =
> xhr.getResponseHeader("Content-Type");
> +                                               var blob = new
> Blob([response], {type: type});
> +
> +                                               var blobUrl = (window.URL
> || window.webkitURL).createObjectURL(blob);
> +
> +                                               var anchor =
> jQuery("<a></a>")
> +                                                       .prop("href",
> blobUrl)
> +                                                       .prop("download",
> filename)
> +                                                       .appendTo("body")
> +                                                       .hide();
> +
> +                                               anchor[0].click();
> +
> +                                               setTimeout(function () {
> +
>  URL.revokeObjectURL(blobUrl);
> +                                                       anchor.remove();
> +                                               }, 100);
> +
> +                                               notifyServer("success");
> +                                       },
> +
> +                                       error: function (response, status,
> xhr) {
> +                                               notifyServer("failed");
> +                                       }
> +                               });
>                         }
>                 }
>         };
>
>

Re: [2/2] wicket git commit: WICKET-6286 added Location.Blob

Posted by Sven Meier <sv...@meiers.net>.
Hi Martin,

indeed, I didn't expect us to put so much effort into this :P

Yes, IMHO Blob would be the default: Why use anything else with a modern 
browser?

Note that currently the whole download has to fit into memory, we could 
optimize this even further with the new file stream api.

I'd rather not build in all bells and whistles until somebody runs into 
a problem. Many information I've found about this solution is from 2013, 
so many workarounds might no longer be needed.

 >The simplest thing ... #getLocation() non-final and use it in 
#initiate().
 >This way the developer can use any kind of detection for the best location
 > WDYT

Exactly my thoughts.

Sven

On 23.03.2017 15:31, Martin Grigorov wrote:
> This new AjaxDownload utility became much smarter than the original one! :-)
>
> I see that you have changed the default location to be Blob.
> According to http://caniuse.com/#search=blob it could be used with pretty
> much all modern browsers!
> Do we need to add support for fallbacks via ClientProperties and UserAgent
> ? Or we will keep it simple until someone really needs this ? The simplest
> thing we can do is make #getLocation() non-final and use it in #initiate().
> This way the developer can use any kind of detection for the best location
> type.
> WDYT ?
>
>
> Martin Grigorov
> Wicket Training and Consulting
> https://twitter.com/mtgrigorov
>
> On Thu, Mar 23, 2017 at 3:22 PM, <sv...@apache.org> wrote:
>
>> WICKET-6286 added Location.Blob
>>
>> downloads via Blob and createObjectURL
>>
>> Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
>> Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/10175f1e
>> Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/10175f1e
>> Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/10175f1e
>>
>> Branch: refs/heads/master
>> Commit: 10175f1ef969ae0feccafc9bf997c78e42c8cc54
>> Parents: e7c2320
>> Author: Sven Meier <sv...@apache.org>
>> Authored: Thu Mar 23 15:22:15 2017 +0100
>> Committer: Sven Meier <sv...@apache.org>
>> Committed: Thu Mar 23 15:22:15 2017 +0100
>>
>> ----------------------------------------------------------------------
>>   .../examples/ajax/builtin/AjaxDownloadPage.html |  3 +-
>>   .../examples/ajax/builtin/AjaxDownloadPage.java | 48 ++++++++++++++++++-
>>   .../wicket/extensions/ajax/AjaxDownload.java    |  9 +++-
>>   .../extensions/ajax/wicket-ajaxdownload.js      | 49 ++++++++++++++++++--
>>   4 files changed, 101 insertions(+), 8 deletions(-)
>> ----------------------------------------------------------------------
>>
>>
>> http://git-wip-us.apache.org/repos/asf/wicket/blob/
>> 10175f1e/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.html
>> ----------------------------------------------------------------------
>> diff --git a/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.html b/wicket-examples/src/main/
>> java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.html
>> index 0be7fe2..dd7e3ab 100644
>> --- a/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.html
>> +++ b/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.html
>> @@ -27,7 +27,8 @@
>>   </p>
>>
>>   <ul>
>> -       <li>via an <a wicket:id="download">iframe</a>.</li>
>> +       <li>for <a wicket:id="download">modern browsers</a>.</li>
>> +       <li>in an <a wicket:id="downloadIframe">iframe</a>.</li>
>>          <li>in a <a wicket:id="downloadInNewWindow">new browser
>> window</a>.</li>
>>          <li>in the <a wicket:id="downloadInSameWindow">same
>> window</a>.</li>
>>   </ul>
>>
>> http://git-wip-us.apache.org/repos/asf/wicket/blob/
>> 10175f1e/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.java
>> ----------------------------------------------------------------------
>> diff --git a/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.java b/wicket-examples/src/main/
>> java/org/apache/wicket/examples/ajax/builtin/AjaxDownloadPage.java
>> index b589091..6d1aa3e 100644
>> --- a/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.java
>> +++ b/wicket-examples/src/main/java/org/apache/wicket/
>> examples/ajax/builtin/AjaxDownloadPage.java
>> @@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
>>   import org.apache.wicket.ajax.AjaxRequestTarget;
>>   import org.apache.wicket.ajax.markup.html.AjaxLink;
>>   import org.apache.wicket.extensions.ajax.AjaxDownload;
>> +import org.apache.wicket.extensions.ajax.AjaxDownload.Location;
>>   import org.apache.wicket.markup.html.WebMarkupContainer;
>>   import org.apache.wicket.request.http.flow.AbortWithHttpErrorCodeExceptio
>> n;
>>   import org.apache.wicket.request.resource.ContentDisposition;
>> @@ -51,6 +52,8 @@ public class AjaxDownloadPage extends BasePage
>>                  add(downloadingContainer);
>>
>>                  initDownload();
>> +
>> +               initDownloadInIframe();
>>
>>                  initDownloadInNewWindow();
>>
>> @@ -67,7 +70,7 @@ public class AjaxDownloadPage extends BasePage
>>                  // download cannot continue on page refresh
>>                  downloadingContainer.setVisible(false);
>>          }
>> -
>> +
>>          private void initDownload()
>>          {
>>                  IResource resource = new ExampleResource("downloaded via
>> ajax")
>> @@ -109,6 +112,49 @@ public class AjaxDownloadPage extends BasePage
>>                          }
>>                  });
>>          }
>> +
>> +       private void initDownloadInIframe()
>> +       {
>> +               IResource resource = new ExampleResource("downloaded via
>> ajax in iframe")
>> +                       .setContentDisposition(
>> ContentDisposition.ATTACHMENT);
>> +
>> +               final AjaxDownload download = new AjaxDownload(resource) {
>> +
>> +                       @Override
>> +                       protected void onBeforeDownload(AjaxRequestTarget
>> target)
>> +                       {
>> +                               downloadingContainer.setVisible(true);
>> +                               target.add(downloadingContainer);
>> +                       }
>> +
>> +                       @Override
>> +                       protected void onDownloadSuccess(AjaxRequestTarget
>> target)
>> +                       {
>> +                               downloadingContainer.setVisible(false);
>> +                               target.add(downloadingContainer);
>> +                       }
>> +
>> +                       @Override
>> +                       protected void onDownloadFailed(AjaxRequestTarget
>> target)
>> +                       {
>> +                               downloadingContainer.setVisible(false);
>> +                               target.add(downloadingContainer);
>> +
>> +                               target.appendJavaScript("alert('Download
>> failed');");
>> +                       }
>> +               };
>> +               download.setLocation(Location.IFrame);
>> +               add(download);
>> +
>> +               add(new AjaxLink<Void>("downloadIframe")
>> +               {
>> +                       @Override
>> +                       public void onClick(AjaxRequestTarget target)
>> +                       {
>> +                               download.initiate(target);
>> +                       }
>> +               });
>> +       }
>>
>>          private void initDownloadReference()
>>          {
>>
>> http://git-wip-us.apache.org/repos/asf/wicket/blob/
>> 10175f1e/wicket-extensions/src/main/java/org/apache/
>> wicket/extensions/ajax/AjaxDownload.java
>> ----------------------------------------------------------------------
>> diff --git a/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/AjaxDownload.java b/wicket-extensions/src/main/
>> java/org/apache/wicket/extensions/ajax/AjaxDownload.java
>> index 804a794..e50cf9d 100644
>> --- a/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/AjaxDownload.java
>> +++ b/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/AjaxDownload.java
>> @@ -73,6 +73,13 @@ public class AjaxDownload extends
>> AbstractDefaultAjaxBehavior
>>
>>          public enum Location {
>>                  /**
>> +                * The resource will be downloaded into a blob.
>> +                * <p>
>> +                * This is recommended for modern browsers.
>> +                */
>> +               Blob,
>> +
>> +               /**
>>                   * The resource will be downloaded via a temporary created
>> iframe, the resource has to be a
>>                   * {@link ContentDisposition#ATTACHMENT}.
>>                   * <p>
>> @@ -115,7 +122,7 @@ public class AjaxDownload extends
>> AbstractDefaultAjaxBehavior
>>
>>          private PageParameters resourceParameters;
>>
>> -       private Location location = Location.IFrame;
>> +       private Location location = Location.Blob;
>>
>>          /**
>>           * Download of a {@link Resource}.
>>
>> http://git-wip-us.apache.org/repos/asf/wicket/blob/
>> 10175f1e/wicket-extensions/src/main/java/org/apache/
>> wicket/extensions/ajax/wicket-ajaxdownload.js
>> ----------------------------------------------------------------------
>> diff --git a/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/wicket-ajaxdownload.js b/wicket-extensions/src/main/
>> java/org/apache/wicket/extensions/ajax/wicket-ajaxdownload.js
>> index ec81ca4..7c320c3 100644
>> --- a/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/wicket-ajaxdownload.js
>> +++ b/wicket-extensions/src/main/java/org/apache/wicket/
>> extensions/ajax/wicket-ajaxdownload.js
>> @@ -29,10 +29,10 @@
>>                  initiate : function(settings) {
>>
>>                          var notifyServer = function(result) {
>> -                settings.attributes.ep = settings.attributes.ep || {};
>> -                settings.attributes.ep.result = result;
>> -                Wicket.Ajax.ajax(settings.attributes);
>> -            };
>> +                               settings.attributes.ep =
>> settings.attributes.ep || {};
>> +                               settings.attributes.ep.result = result;
>> +                               Wicket.Ajax.ajax(settings.attributes);
>> +                       };
>>
>>                          var checkComplete = function(watcher) {
>>                                  var result;
>> @@ -82,7 +82,7 @@
>>                                                  }
>>                                          }
>>                                  });
>> -                       } else {
>> +                       } else if (settings.method == 'iframe') {
>>                                  var frame = jQuery("<iframe></iframe>").hide().prop("src",
>> settings.downloadUrl).appendTo("body");
>>                                  checkComplete({
>>                                          html: function() {
>> @@ -96,6 +96,45 @@
>>                                                  }, 0);
>>                                          }
>>                                  });
>> +                       } else {
>> +                               jQuery.ajax({
>> +                                       type: 'get',
>> +                                       url: settings.downloadUrl,
>> +                                       success: function (response,
>> status, xhr) {
>> +                                               var filename = "";
>> +                                               var disposition =
>> xhr.getResponseHeader("Content-Disposition");
>> +                                               if (disposition &&
>> disposition.indexOf("attachment") !== -1) {
>> +                                                       var matches =
>> /filename[^;=\n]*=(([""]).*?\2|[^;\n]*)/.exec(disposition);
>> +                                                       if (matches !=
>> null && matches[1]) {
>> +                                                               filename =
>> matches[1].replace(/[""]/g, "");
>> +                                                       }
>> +                                               }
>> +
>> +                                               var type =
>> xhr.getResponseHeader("Content-Type");
>> +                                               var blob = new
>> Blob([response], {type: type});
>> +
>> +                                               var blobUrl = (window.URL
>> || window.webkitURL).createObjectURL(blob);
>> +
>> +                                               var anchor =
>> jQuery("<a></a>")
>> +                                                       .prop("href",
>> blobUrl)
>> +                                                       .prop("download",
>> filename)
>> +                                                       .appendTo("body")
>> +                                                       .hide();
>> +
>> +                                               anchor[0].click();
>> +
>> +                                               setTimeout(function () {
>> +
>>   URL.revokeObjectURL(blobUrl);
>> +                                                       anchor.remove();
>> +                                               }, 100);
>> +
>> +                                               notifyServer("success");
>> +                                       },
>> +
>> +                                       error: function (response, status,
>> xhr) {
>> +                                               notifyServer("failed");
>> +                                       }
>> +                               });
>>                          }
>>                  }
>>          };
>>
>>