Previously I showed some HTML and JavaScript for cropping and uploading an image. Here is what I did on the server side to support this.
First I make sure I had
rails 3 installed,
configured for HAML and
jquery. I also download the
jqueryui and
jcrop javascript and css files and add them to my project. Then, I use the rails scaffolding functionality to get started:
ruby script\rails generate scaffold image content:binary width:integer height:integer name:string
After running:
rake db:migrate
I have a database table for storing an uploaded image, including the binary content, the height and width of the image and a name. Now that I have a place to store the image, I need to modify the generated web pages, so I can upload it.
Since I
prefer HAML, the first thing that I do is rename the two erb files that I need to change:
views/layouts/applications.html.erb to
views/layouts/applications.html.haml and
app/views/images/_form.html.erb to
app/views/images/_form.html.haml. The
applications.html file is the template for all the pages, and I need to modify that to allow pages to add their own javascript into the header.
_form.html is the partial that is used for both uploading new images and editing images.
Converting the
applications.html file from erb to haml is just a straightforward transformation. Here is what it looks like when I am done:
1 !!!
2 %html
3 %head
4 %title Image Upload
5 = stylesheet_link_tag :all
6 = javascript_include_tag :defaults
7 = csrf_meta_tag
8 = yield :head
9 %body
10 = yield
The only real line of note is the named
yield on line 8. This is what will allow me to insert custom stylings and javascript into specific pages.
The only thing left is to rewrite
_form.html.haml so that it generates HTML and Javascript like discussed in the
client side post. Here is what this looks like.
1 - content_for :head do
2 = stylesheet_link_tag 'jquery.Jcrop'
3 = javascript_include_tag 'jquery.Jcrop.min'
4 %style
5 -# insert styling described in client post here
6 :javascript
7 // insert javascript described in client post here
8 = form_for(@image) do |f|
9 - if @image.errors.any?
10 #error_explanation
11 %h2
12 = pluralize(@image.errors.count, "error")
13 prohibited this image from being saved:
14 %ul
15 - @image.errors.full_messages.each do |msg|
16 %li= msg
17 .field
18 = f.label :name
19 %br
20 = f.text_field :name
21
22 Drag and drop image here:
23 #cropWidget{:width=>400, :height=>400}
24 #surface
25 %canvas#canv1{:width=>400, :height=>400}
26
27 = f.hidden_field :content64
28 Uploaded image is here:
29 %canvas#canv2{:width=>200, :height=>200}
30 .field
31 = f.label :width
32 %br
33 = f.text_field :width, :readOnly=>true
34 .field
35 = f.label :height
36 %br
37 = f.text_field :height, :readOnly=>true
38 .actions
39 = f.submit :id=>"imageSubmit", :onClick=>"uploadSelection();", :disabled=>true;
I left out the specific CSS styles and JavaScript as they are described in the
previous post. Notice that lines 1-7 are inserted into the head of the template as described by
yield :head
on line 8 of the
application.html.haml file above. Lines 9-16 are the boilerplate error handling that was generated by the scaffold command. The remainder just generates the HTML for displaying the image and for entering the name of the image.
We are now almost, but not quite, done. If you notice the field that we are storing the image content in is content64, while the database column for the binary data is just called content. We need to take the base64 encoding of the image that is sent over the wire as part of the HTML POST and decode it to binary before saving in the database. To do that, we just need to add new methods in our
app/models/image.rb Image class.
1 require "base64"
2 class Image < ActiveRecord::Base
3 def content64
4 return nil unless content
5 return "data:image/png;base64," + Base64.encode64(content);
6 end
7
8 def content64=(c64)
9 index = c64.index(',');
10 self.content = Base64.decode64(c64[index..-1]);
11 end
12 end
This way when the
content64 attribute is assigned in our controller class, it'll actually write the binary data to the content field so it can be saved to the database. The only clever part of this is that the content64 data that is passed from the HTML form has the initial string
"data:image/png;base64,". Lines 9-10 calculate where this prefix ends and just decodes the remainder of the string. Line 5 adds this prefix back on to the encoded binary content.
Other Work
So the above will allow you to upload a new image that has been cropped and save it to the database. However, to be able to view that image or edit it, more work is needed. Probably the most important thing to do is to add a new action which will return the image's binary content. The url for this action can be used in the src attribute of <img> tags. The other scaffold generated pages for viewing and listing the uploaded images are then modified to display the image rather than showing the binary content. The last step is to modify the
javascript described previously to check if an image already exists (i.e. we are editing an existing uploaded image rather than uploading a new image), and preload that. Since this post has gone on long enough, I will leave all of those details as an exercise for the reader.