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
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