This question about Developing extensions (plugins skins etc.): Answered

TopicInteractionPlugin and BlueImp/FileUpload jQuery plugin

I wanted to use the Blueimp jQuery file upload plugin to support dropping files on multiple drop targets on a page, on a site where TopicInteractionPlugin is installed. However I found this use model is made more difficult than it needs to be by the way TopicInteractionPlugin works/is packaged.

Scenario

I wanted to have a macro %DROP{"name"}% which creates a drop zone where any dropped file will be uploaded after renaming to "name". The use model is a pre-populated page where standard documents and images are expected. These are uploaded by dropping onto these placeholders. The target site was already using TopicInteractionPlugin, and I wanted to remain compatible if at all possible.

To do this I created a new plugin, called DropPlugin.

Problem 1 - dropzone installed on body

In TIP uploader.js, the following call:
     $("body").foswikiUploader();

instatiates the plugin on the body, allowing users to drop anywhere on the topic (this usage is undocumented, but is reasonably intuitive).

I wanted to be able to keep this functionality of the TopicInteractionPlugin on pages where I am not installing drop targets. However it means I have to disable this feature of TIP on pages where I am. My drop targets all have the class dropPluginForm so it was easy enough to:
    $(window).on("load", function () {
        // Hack to remove the TopicInteractionPlugin dropzone from $("body") so our other dropzones fire, if DropPlugin is active on the page.
        $(".dropPluginForm").first().each(function() {
            $("body").fileupload("destroy");
        });
    });

However (aside from it taking ages to work out what to do) this is far from ideal. I had to use a load event because the JS load order runs the TIP JS after the DropPlugin JS. I didn't want to put an unconditional dependency on TIP into DropPlugin, because it shouldn't be dependent. It would have been better if TIP had limited the drop target to a zone of the attachments pane (as the images in the TIP documentation clearly suggests is the case).

Problem 2 - packaging of the blueimp code

Ideally I wanted DropPlugin to be able to work on sites that do/don't use TopicInteractionPlugin. However the blueimp plugin is bundled tightly inside TIP (the code is concatenated with the TIP specific code) and can't easily be instantiated independently. So now DropPlugin is left with a dependency on TIP just to get this small fragment of JS. Not ideal, I would have preferred it if blueimp/fileupload had been bundled in JQueryPlugin, like all the other jQuery plugins.

This could be solved by conditionally loading the blueimp JS if TopicInteractionPlugin is not installed, but I don't have time to work out how to do that, so now I'm left with a dependency.

Problem 3

I wanted to use the TIP REST handlers, but also wanted to avoid a page refresh after the upload. To do that I needed to generate a new validation code on the server to pass to the JS. So I had to use a custom REST handler (actually I just hacked the TIP one) to add the following to printJSONRPC:

    # Compile a new nonce
    if ( $Foswiki::cfg{Validation}{Method} eq 'strikeone' ) {
        require Foswiki::Validation;
        my $context =
          Foswiki::Func::getCgiQuery()
          ->url( -full => 1, -path => 1, -query => 1 ) . time();
        my $cgis = $session->getCGISession();
        my $nonce;
        if ( Foswiki::Validation->can('generateValidationKey') ) {
            $message->{nonce} =
              Foswiki::Validation::generateValidationKey( $cgis, $context, 1 );
        }
    }

then in the JS:

$(".dropPluginForm").each(function() {
    var form = this;
    $(form).fileupload({
        ...
        formData: function() {
            if (form.validation_key)
                form.validation_key.value = StrikeOne.calculateNewKey(form.validation_key.value);
            return $(form).serializeArray();
        },
        ...
        done: function(e, xhr) {
            var data = xhr.result;
            // Import the new nonce
            if (this.validation_key && data.nonce)
                this.validation_key.value = "?" + data.nonce;
        }

There might be a simpler way to achieve the same thing; if so, I'd be interested to hear it.

Conclusion

DropPlugin might be a generally useful thing for the Fosiwki community, but right now I don't feel I can release it because of this dependency on TIP.

-- CrawfordCurrie - 26 Oct 2018

First, I would not like to bundle even more modules as part of the JQueryPlugin. Instead I'd rather remove some. In general, it is not required to have them shiped as part of JQueryPlugin directly. You can have your own jquery code made accessible via %JQREQUIRE even though when it comes via a non core plugin.

With regards to you DropPlugin: which parts do you share with TopicInteractionPlugin? Is it only the jquery.fileupload.js file, or are you using its Foswiki backends as well? Do you want to use both plugins on all of the site or do you have your custom drop zones on a few pages only?

Did you consider adding your new feature to TopicInteractionPlugin directly instead of writing a new plugin?

Another idea would be to define a preference setting for a jQuery selector that points to the DOM of the drop zone, defaulting to body. If empty would it skip establishing any drop zone by default.

-- MichaelDaum - 26 Oct 2018

Yes, understood. It would be possible to bundle jquery.fileupload.js as a separate Foswiki plugin, though of course that would be more work and I could understand why you might not want to do that.

Currently I'm only using jquery.fileupload.js from TIP, though I originally wanted to use the upload REST handler as well - I would still like to do so, but the lack of validation is a (small) problem. Yes, I did think of extending TIP, but felt that keeping it separate just now was the best plan so we could see if the result was appropriate for adding to TIP, and merge it later if it was. (I'm not 100% sure it is, because I can't think of a clean way to solve the problem of content type. With DROP, you can basically drop anything on a drop site, and it will end up with the name - and therefore the MIME type - of the drop site, but the content could be some completely different type. So it feels a bit ... dangerous. Not quite sure why.)

Both plugins are required on the site, the DROP functionality will only be used on a subset of pages.

The preference setting idea would work, though it would kill the drop site in the Attachments pane when DROP is active, wouldn't it? I'd be quite happy for the whole page to act as a drop target so long as it didn't occlude the smaller drop targets embedded in the page, but I couldn't get the plugin to do that frown, sad smile

Later: After a bit more firtling I have modified it to use more of the TIP - it now uses the REST handler (though I had to switch off validation, as previously mentioned), and uses a dependency which allowed my to remove that nasty load handler. In order for the dependency to work, I had to add the following to the Perl plugin init, just before registering my own plugin:

    # Force dependency on TopicInteractionPlugin
    Foswiki::Plugins::JQueryPlugin::registerPlugin( 'uploader', 'Foswiki::Plugins::TopicInteractionPlugin::Uploader' );

Not sure this is the right way to do this, but it's the only way I could find to make it work.

Current version of the code is attached to this topic.

-- Main.CrawfordCurrie - 26 Oct 2018 - 17:01

Looking at the code. Some first impressions:

  • there is no need to register another plugin's jQuery component; it does so on its own, either as part of its own initPlugin or via LocalSite.cfg
  • your plugin's initPlugin seems to load the drop module into every page, even though there is no %DROP macro on it: only call createPlugin when you really need it.
  • actually calling createPlugin during the init phase of foswiki causes all sorts of errors as not all extensions have been init'ed yet
  • dropplugin.tmpl is missing so I wasn't able to test further
  • how about you set up some git repo

Basically, the idea of the FoswikiUploader class in TIP tries to provide a kind of singleton service point - attached to body - for all things uploading. For instance the UploadButton class calls its add() to add files to the queue and the send() method to actually perform the upload.

-- MichaelDaum - 29 Oct 2018

Thanks for your feedback,
  • I had to register the other plugin explicitly as it wasn't resolving the dependency, even after a configure run (or two)
  • yes, good point about registering the plugin only when needed. That seems to have been the cause of the above dependecy fail, it's much happier now.

P.S. I'd have said there's a strong case for these REST handlers (I'm using TopicInteractionPlugin/upload and RenderPlugin/template) to move into the core. That would allow the incremental removal of those nasty UI scripts e.g. UI/Upload.pm.

-- Main.CrawfordCurrie - 30 Oct 2018 - 08:56

I am currently trying to go away from direct calls of the RenderPlugin/template REST handler and replace it with the foswiki.loadTemplate() client (see pub/System/RenderPlugin/foswikiTemplate.js) which in turn calls RenderPlugin/jsonTemplate. This not only delivers the markup but also any zone content that is required for it. This really lets you load parts of a web page incrementally, not only the HTML but also any JS and CSS. The foswiki.loadTemplate() client side will then take those bits and inject them to the page as needed. This tech is a spin-off of the AngularPlugin that never made it into the wild.

An example implementation is loading a modal edit dialog for a DataForm. Some of the formfield types require extra js and css (date picker, color picker, textboxlist, ...). When using RenderPlugin/template directly you only get the raw HTML and you'd have the responsibility to load the additional js and css bits manually beforehand. This is currently the case in MetaDataPlugin, and it sucks. Using foswiki.loadTemplate() and RenderPlugin/jsonTemplate would be much better.

-- MichaelDaum - 30 Oct 2018

It's not a big deal for me as I only want the raw HTML. I see foswiki.loadTemplate is just a shallow wrapper around the ajax call, that's fine. The naming is a bit wierd; I thought the foswiki namespace was reserved for things defined in the core.

-- Main.CrawfordCurrie - 30 Oct 2018 - 14:38

Yea, there are too many things called "template" in foswiki. I used the foswiki object as I had to make the class accessible gobally. I could have used jQuery as well but that seemed wrong. If we planned to add RenderPlugin to the core - or rather its features - we can talk about better names of course.

-- MichaelDaum - 30 Oct 2018

DropPlugin is in git now, pretty much as specified so I'm not rushing to do any more to it. I didn't use foswiki/loadTemplate in the end because the dependencies weren't working.

Yes, far too many "template" things and I agree, extending the jQuery namespace would also be wrong.

-- Main.CrawfordCurrie - 30 Oct 2018 - 16:15

Erm, I think you've added the DropPlugin to the wrong repo. https://github.com/foswiki/distro/tree/master/DropPlugin should be https://github.com/foswiki/DropPlugin

-- MichaelDaum - 30 Oct 2018

Reading your new code: you can register a jquery module in init but not create it.

-- MichaelDaum - 30 Oct 2018

Added my 2cent to the DropPlugin. Did this answer your questions?

-- MichaelDaum - 06 Nov 2018

Yes, thanks, I think everything is in place now. There's still an outstanding bug but there's a workaround and it appears to be fixed in latest Foswiki, so....

-- Main.CrawfordCurrie - 06 Nov 2018 - 18:26
 

QuestionForm edit

Subject Developing extensions (plugins skins etc.)
Extension TopicInteractionPlugin
Version Foswiki 2.1.4
Status Answered
Related Topics
Topic revision: r14 - 06 Nov 2018, CrawfordCurrie
The copyright of the content on this website is held by the contributing authors, except where stated elsewhere. See Copyright Statement. Creative Commons License    Legal Imprint    Privacy Policy