Creating a Django Widget to Draw and Calculate Dimensions of Rectangles on Images
02 Aug 2015In my last post
I talked about getting an image path and passing it into the next step of a
Django form wizard for use in the canvas
field. That was only the first piece
of the challenge. The purpose of the canvas
field is to hold a widget
containing a canvas to allow users to draw a rectangle within the image. The
placement and dimensions of the rectangle - specifically distance from top and
left, and height and width, as a percentage of the image, need to be entered
into the database to create a positioned ‘hotspot’.
First, I found a canvas drawing plugin that would provide crosshairs for rectangle drawing functionality. In a world of disappointing options, wPaint seemed like the best/only choice. Then I created a widget with the wPaint dependencies added as media.
This allows widget media dependencies to be loaded in dynamically through the wizard template or manually through the widget render method. I loaded them in manually when creating the plugin div and loading the custom JavaScript for calling and modifying the plugin.
Although I was specifically looking for a plugin that could have an image as the canvas background, wPaint’s image functionality did not work. Instead, I loaded in the image within the wPaint div and gave it 100% height and width. This caused problems in Safari, but it worked in Chrome. The requirement of this application is only that it works in Chrome, so this was fine.
Here’s the
widget’s render method,
with the image path being found in the value
argument, which is the value of
the widget’s field in the form’s initial dictionary:
Now, we just need to call the plugin in the hotspot drawing widget script, and the canvas functionality will appear:
Unfortunately, the plugin gives the user about a million options for how to draw, and we don’t want them to have choices. The wPaint docs claim you can enter the options you want as arguments, but that doesn’t actually work, so we’ll just remove the buttons and resize the menu on load.
Now there’s a canvas over the user-chosen image, and we can draw rectangles, undo, redo, and clear the canvas. That’s exciting, but how do we find out what the user drew and pass that data back into the form so it can be entered in the database as percentages of the image?
While inspecting the canvas, I was thrilled to discover that the plugin provides a div called ‘wPaint-canvas-temp’ that has height and width, and inline CSS for top and left, in pixels, representing the last drawn rectangle. Finally, the plugin does something I want! Except, the div changes as the user draws, and the user can also undo, redo, and clear. How do I accurately grab that information on save?
The JavaScript devs I asked for suggestions were bewildered by the idea of passing information to a back end, so I decided to focus on acting for the benefit of what I care about, which is the back end. I could have possibly made a JavaScript function to run when a user clicked the submit button, but that struck me as risking the integrity and reusability of the back end.
So, I wanted to create inputs for the top, left, height, and width fields already associated with the hotspots model, and have their values change as the canvas changes. That way, there’s no worries about syncing the back and front end, everything is done within the widget, and the database receives the positioning values as if they were manually entered by a user in a normal form.
I added both initial and default values of ‘0%’ for each of the fields, and
updated the form for the canvas page, with Django-provided HiddenInput
widgets:
I used the jquery-watch plugin to watch changes to the outer HTML of ‘wPaint-canvas-temp’. Users should not be spending much time on this page, so performance shouldn’t be, and so far hasn’t been, an issue. When changes are detected, a function is run to pull out the top, left, height, and width, calculate percentages, and insert into the associated inputs.
After adding ‘creator/jquery-watch.min.js’ to the media to be loaded, I added the watch function and calculation function. It’s ugly, but functional:
And finally, everything works. Users can choose a page and surface to add a hotspot to, view the corresponding image and draw on it where they would like to place the hotspot, upload an image or video if it is a popup-style hotspot, save their hotspot, and use it in a finished presentation.