This is something that I developed mostly together with a two of my collegues* at Valtech in Stockholm before the summer holidays. It will be added to the production environment of the Swedish national health care website 1177.se some time early this fall; most likely in a different form. It was rather fun coding, so I asked and got permission from Inera (the client) to share it on my blog – so here it is.
* Joachim Widén from Ocean Observations and Valtech’s own Magnus Malmstedt.
The images used for the screenshots and the demo in this post are from a Sacred Birman cat breeder up in Luleå city called SE*Purrfect Dreams’ and were used with their permission.
Note: If this custom property is to be used together with EPiServer 5, there are a few changes needed to be made to both the JavaScripts as well as the CSS. Please see the article Moving the ImageMap editor custom property to EPiServer 5 for more information about nerfing the code to the previous version.
Image map creator with arbitrary rectangular hot spots
Before we go into how this EPiServer custom property was created, let us have a look at what the web editors will see in the edit mode interface when this functionality is added to a page type.
The image above shows the custom property viewed in the EPiServer edit mode. As soon as an image map image is selected from EPiServer’s VPPs, the possibility to add hot spots appear below it. Each hot spot is then rendered onto the property preview as it will be shown to the visitors in view mode (with the exception of the gray box showing the clickable area – this is only shown here). As you can see in the image, hovering the mouse cursor over a hot area in the image will also highlight the corresponding hot spot row in the table below (and vice versa). The Over/Under sorting order is useful when creating smaller hot spots within larger ones; this will be made more clear in a moment.
Clicking the edit button for an existing hot spot, or creating a new one for that matter, will bring up the hot spot editor shown below.
In this pop-up window it is possible for the web editor to change the clickable area, redirect the target URL, as well as changing the link’s title attribute. The hot spot area currently being edited is shown in red, while the other areas appear in the grayer tone. Here you can see why the sorting order mentioned before is important; if the paw area link would be placed below the larger cat area one, it would not be available to the website’s visitors. For selecting a hot spot target, the build-in EPiServer page selector is used. This is due to it suiting the client’s need better than the other selectors. Of course it is possible to have this open up the EPiServer Link URL selector instead (see how further down the post).
Areas are selected by a simple click-n-drag action, very similar to selecting areas in a graphics program such as Photoshop or Paint.NET. Further down in this post you will find an interactive example of this functionality. It is obviously not possible to select areas outside of the image, and the functionality defaults to a set minimum size if the selected area is unreasonable small; in this case 30×30 pixels, which is the size of the larger area marker dot in the preview and view mode images (the red one seen below).
The visitors will see what is shown in the image above when surfing in view mode. The gray boxes that existed in EPiServer’s edit mode are removed as they are rather superfluous to the public. A click on a hot area will be just like clicking on a normal link; which it in fact is. This is one area where the client’s solution will differ, as it will be integrated into other functionality.
How to create an EPiServer interactive image map custom property
The area selection mechanism is an improved version of the Boxer functionality found at jsbin.com. It has also been altered to work with jQuery-UI version 1.8, as changes has been made to how it handle widgets. I was going to add an interactive demo of this directly into the post, but WordPress did not seem too happy to help me (WP-Plugin conflicts between my syntax highlighter and the JavaScript-injector). Anyway, below is an IFramed version of the demo in a static HTML file (also available here: AreaSelectorDemo.html). It requires jQuery as well as parts of jQuery-UI to function properly (it is not the altered version used in the image map functionality, but one that will work with earlier versions of jQuery-UI). Feel free to select a different area on the image below.
The hot spot area positions and sizes are calculated relative to the currently selected image; one of the modifications made to the Boxer functionality in the HotSpotEditor.aspx file. This means that if the web editor should change the image by selecting another one from the EPiServer file archive, the hot spot areas will be recalculated and placed according to the new one instead; moving and resizing them depending on the new width and height. This may be useful when making minor changes to an image and not wanting to recreate the hot spot areas.
HotSpotEditor.aspx
<%-- Make sure that the selected area is not outside (above or left of) the image. --%> if (position.left < 0) { boxWidth = boxWidth - Math.abs(position.left); this.helper.css({ 'left': '0', 'width': boxWidth }); } if (position.top < 0) { boxHeight = boxHeight - Math.abs(position.top); this.helper.css({ 'top': '0', 'height': boxHeight }); }
<%-- Make sure that the selected area is not outside (below or right of) the image. --%> if (position.left + boxWidth > totalImageWidth) { boxWidth = totalImageWidth - position.left; this.helper.css('width', boxWidth); } if (position.top + boxHeight > totalImageHeight) { boxHeight = totalImageHeight - position.top; this.helper.css('height', boxHeight); }
There are several behaviour rules added to the Boxer script, like the ones above making sure that the selection never goes beyond the edges of the currently selected image. Also, a few if-statements ensuring that we will get at least our 30×30 pixel size within the image as seen below.
<%-- Make sure that the selected area offset allows for at least 30x30px area size (minWidth x MinHeight). --%> var maxOffsetX = totalImageWidth - minWidth; if(position.left > maxOffsetX) { this.helper.css('left', maxOffsetX); } var maxOffsetY = totalImageHeight - minHeight; if(position.top > maxOffsetY) { this.helper.css('top', maxOffsetY); }
<%-- Make sure the selected area is at least 30x30px (minWidth x MinHeight). --%> if(boxWidth < minWidth) { this.helper.css('width', minWidth); } if(boxHeight < minHeight) { this.helper.css('height', minHeight); }
Passing values between popup and parent window
The method for passing values between the hot spot editor and the main EPiServer edit mode edit tab, as well as saving them to the EPiServer database, is the same as the one used in the EPiServer Image Slide Show custom property, which may be further described in a later post. Basically, it involves serializing and deserializing information as the image and hot spots changes, making use of a callback function to pass values back from the popup window. The code below was shortened making it more readable in blog post form, for the rest of it, please have a look in the files at GitHub.
image-map.js
ImageMap.EPiServerPopup = ImageMap.EPiServerPopup || function (baseUrl, initialWidth, initialHeight) { var width = initialWidth; var height = initialHeight; function successCallback(returnValue, onCompleteArgs) { if (returnValue != null) { EPi.PageLeaveCheck.SetPageChanged(true); var item = new Object(); item.HotSpotId = returnValue.HotSpotId; // .. item.Height = returnValue.Height; onCompleteArgs.callBack(item); } }; return { getWidth: function () { return width; }, getHeight: function () { return height; }, setSize: function (newWidth, newHeight) { width = newWidth; height = newHeight; }, show: function (state, imageUrl, reservedPositions, updateItem) { var onCompleteArgs = { callBack: updateItem }; var args = new Object(); args.state = state; // .. args.parentWindow = document.window; args.hideBookmarks = true; var options = { 'width': width, 'height': height, scrollbars: "no" }; EPi.CreateDialog(baseUrl, successCallback, onCompleteArgs, args, options);
If the hot spot was saved with new information, the callback function (35) assembles the new data and passes it down the ladder. If the Cancel button was hit, there will be no return value. Notice that EPiServer’s page leave check (37) will be triggered if the web editor tries to leave the page without saving the changed hot spot. We use the build-in EPiServer function CreateDialog to create the popup window, feeding it with the options (60) parameter setting the correct height and width. Worth noting is that this height will change depending on the size of the image that is currently selected; this is simply in order to make the window look good.
HotSpotEditor.aspx
function SaveButtonClick(e) { e.preventDefault(); if (!validateClickTargetRequired()) { return; } if (!validateHotSpotPositionRequired()) { return; } EPi.GetDialog().Close({ HotSpotId: value($("#hot-spot-id")), Tooltip: escape(value($("#tooltip"))), // .. }); } function CancelButtonClick() { EPi.GetDialog().Close(); }
On the hot spot editor side (the popup window), we have two functions that we will bind to the click events of their corresponding buttons; Save and Cancel. The SaveButtonClick function first makes sure that the web editor filled in all of the necessary information, and then compiles a return object with all the data. Some values, such as for instance the Tooltip (105) above will need to be escaped (URL-encoded) in order for them not to break the JSON on the other side. This may happen if there are things like apostrophes (‘) in the data. This is also the reason for the ToolTip = HttpUtility.UrlDecode(hotSpot.Tooltip, System.Text.Encoding.GetEncoding(“ISO-8859-1”)), line which you will find in the view mode display control. The specified encoding makes sure that we get to keep our lovely å, ä and ö’s.
As for the mechanics of how to select target EPiServer pages and image files through the standard pop-up windows, it is all explained here. This is also a good source if you would need to change the EPiServer page selector popup into another one, like the Link URL selector mentioned earlier. You will probably also need to edit the way that the popups communicate a bit, which variables are passed along and such (like the code snippets above for instance).
SCRIPT5009: ‘JSON’ is undefined – or, IE9 error on JSON.stringify
One important thing that may cause quite a bit of headache if omitted, is that the JSON property is not built-in to all browsers. This will result in an error trying to call for instance JSON.stringify from IE9 using quirks-mode (it is built into the standards mode however, but the edit mode will not look it’s best if that is used). Douglas Crockford has written a small script (json2.js) that will help ease the pain; it checks if this property already exists in the global object, and adds one if it does not, along with a parse and a stringify method of course.
How to use the Inteactive Image Map EPiServer custom property in your solution
Implementing the property in your own project will require you to make a few adaptions. You will need to add a reference to Newtonsoft Json.NET (preferably using NuGet), change a few paths to point to your own EPiServer site’s UI path, as well as make sure all the namespaces, script file paths, image file paths and the template file paths are correct. You may also want to extract style-tag content to separate files and move the included files to directories better fitting your own project file structure; I have just crammed everything together in order to make it easy to comprehend.
When you are done inserting the files into your project, using the property is simple. Add the custom property to a page type, and let EPiServer do the rendering; add this to the page template file at the desired place.
<EPiServer:Property PropertyName="InteractiveImage" runat="server" />
Cool!
Thank you Petter :)
Awesome :)! Borrowing it :D!!
Enjoy!
I’ll post those changes needed for EPiServer 5 in a bit.