“Up Guards, and at ‘em!” (Attrib. to the Duke of Wellington.)
As noted above I do not think the available facilities for uploading and picking images are very user-friendly. So, have a go at making a better one.
As always the best way to start is to find something to plagiarise. In my opinion the most user-friendly current feature is the button in the TinyMCE editor for inserting images into the text. The main differences to be dealt with are:
- I want the styling to remain under the control of the template/stylesheet, rather than the user. Therefore the control should return a simple image identifier, as with the basic MediaPicker, rather than inserting a complete HTML tag with all its attributes as TinyMCE does. (This has the added advantage of making the Control a drop-in replacement for MediaPicker.)
- We therefore do not need all the options for adding attributes, resizing the image, etc. Apart from anything else there is no easy way to store these in the standard media node.
After quite a bit of digging through the source code I think that what I need to build is as follows:
- A UserControl which embodies the new Datatype (YetAnotherMediaPicker, (yamp)). This can use either of the mechanisms referenced in Chapter 3, i.e. implement the standard datatype interface, or utilise the UserControlWrapper. This control will display a thumbnail to the user, and have a “Select” button which will invoke:
- A dialog window (yampSelect.aspx) which contains the relevant bits copied from the TinyMCE version in the file insertImage.aspx. (In \umbraco\plugins\tinymce in the installed system, or \umbraco\umbraco\presentation\umbraco\plugins\tinymce in the source.)
This can be cut down a long way from the TinyMCE implementation, since the outer three tabs of General/Appearance/Advanced are not needed, neither are any of the fields for entering attributes such as Title. The Javascript and C# code can similarly be cut down, but do need some alteration to return a simple image Id, rather than the HTML tag string.
- This dialog itself invokes a further three standard dialogs (within IFrames) which (I sincerely hope) can be used exactly as they are. These are parts of the core Umbraco, and not the TinyMCE plugin.
-
- umbraco/dialogs/imageViewer.aspx
- umbraco/dialogs/uploadImage.aspx
- Also further Javascript etc. invoked by these pages.
- Unfortunately the uploadImage.aspx dialog seems to provide no way by which the image identifier can be extracted after the upload. It knows and can be persuaded to report the URL, as used by TinyMCE, but seems to hide the identifier. I can see two possible solutions:
-
- Reverse engineer the identifier by extracting it from the URL. I can’t see an easy way to do this since (N.B.) the number in the filestore folder is not the same as the Node Id, which is what we want. Therefore will have to:
- Write a variant of uploadImage which does expose the identifier. This is quite easy once one has worked out where it is hiding. (Though I have substituted this for returning the other information, it would be quite easy to combine the two if required.)
Returning the result
By far the hardest part to work out was how to return the result from the bottom of this stack of dialogs to the top. In summary:
- If just selecting an existing image, then the TreeInit.aspx IFrame already has a way of returning this. TreeInit is invoked dynamically by the insertImage (yampSelect) window, which passes it a Javascript callback function reference (dialogHandler(id)). The standard code uses this to bring up a thumbnail when the image is selected, but it can also be stored for later use.
- If uploading a new image, the standard uploadImage.aspx IFrame does not have a similar callback function. Instead the server-side Postback code (invoked when the Save button is pressed) extracts the various details and embeds them in a bit of Javascript, which it attaches to the Panel which is then opened to display the thumbnail. On opening, this Panel then executes the inserted Javascript and calls a function resident in its parent window (i.e. insertImage (yampSelect)), passing it the required details. This is changed to call a different function, passing the Id value instead of the filename etc.. (The Id happens to be readily available as an attribute of the “media” object, though is not used in the standard code.)
- The yampSelect window has now received the image Id by one of two routes. This can be dropped into a textbox (hidden since the user does not need to see it), for subsequent return to the top level when the Insert button is pressed.
- When Insert (in the yampSelect popup) is pressed it does one of two things, depending on whether it has been opened as a modal window or not (see notes). (It may be easier to combine these two by always using the non-modal mechanism – TODO.)
- If modal, return the id as the return value of the showModalWindow call.
- If not, then callback a procedure in the main window, by opener.storeImageId(id);
- The main window now has the Id at the client side. The simplest mechanism seems to be to drop it into a hidden textbox, then invoke a Postback. The server-side Page Load procedure will then pick it up and use it to retrieve the thumbnail to display to the user.
An Improvement (TODO)
It should be possible to improve this by passing the source URL up to the top level along with the Id (which the standard code already does). Having done so, it should be possible to update the main page thumbnail from the client side, thus removing the need for a postback. The server-side will only pick up the Id (and only needs to) when the Save Page button is pressed.
Concurrency
There is a further problem if one tries to put two instances of the YAMP control on a single page, in that the various Javascript functions do not know which copy of the various textboxes etc. it should be working on.
The solution is to obtain the client Id prefix (which ASP.NET prepends to the control Ids) with the function e.g. string strJSCallbackPrefix = this.ClientID; and pass this down to each lower level window and/or function. The Javascript functions will then use this to construct the correct control identifier.
Unfortunately I think the Usercontrol Wrapper itself gives further concurrency problems, so I will need to convert YAMP to a native datatype before one can put two copies onto a page.
NOTES:
- Important. Umbraco restricts directories which can contain loadable pages, so to allow the new custom dialog to execute one must add the directory /usercontrols/YetAnotherMediaPicker/ to the key “umbracoReservedPaths” in web.config.
- The TinyMCE code opens the various sub-windows using the Javascript function window.showModalWindow. Unfortunately this is an Internet Explorer only extension, and does not work with Firefox for example.
The code which launches the main insertImage window has in fact been extended to use the standard window.open function for non IE browsers, though I have used a simpler bit of code I found on the web that does the same thing.
There is another case within the uploadImage window where a new Treepicker is launched to select a new folder, which the standard code does not cope with. The same mechanism as above can be used to launch the Treepicker window, but I can’t work out a method of returning the result. Abandon this and report a bug.
- There seem to be three different ways available to define a URL when opening a window, or fetching a file such as stylesheet or javascript:
- Use a relative path, e.g. href="../css/umbracoGui.css"
- Use an absolute path, e.g. src="/usercontrols/YetAnotherMediaPicker/yampUploadImage.aspx"
- Use the function as follows: “<%=umbraco.GlobalSettings.Path%>/dialogs/treePicker.aspx”
They all seem to work equally well, though there may be something in the context of each which affects it. Otherwise I am not sure whether it matters.
Reference
Details of how to get a copy of YAMP are at http://forum.umbraco.org/17971#post-17971.