Cropping With jQuery in Rails

Wed, 08/08/2012 - 23:29 -- Arisen
In this example I'm basically creating a way for users to create a properly scaled avatar or profile picture. This will allow all the images to be the same dimensions.

First, we’re telling the form helpers to create form fields for attributes like “x1? and “width”, which aren’t attributes of our User model. To make this work, we need to add some virtual attributes to the model (virtual, because they’re not associated with any fields in our database table):

#user.rb
attr_accessor :x1, :y1, :width, :height, :cropper_id
has_attached_file :user_image, :styles => { :display => '140x200'}, 
                          :whiny_thumbnails => true, :path => 
                          ":rails_root/public/system/:attachment/:id/:style/:style.:extension", 
                          :url => "/system/:attachment/:id/:style/:style.:extension"

To make this actually crop the image, we need to override the update_attributes method in the upload model:

def update_photo_attributes(att)

 require 'RMagick'

 scaled_img = Magick::ImageList.new(self.user_image.path)
 orig_img = Magick::ImageList.new(self.user_image.path(:original))
 scale = orig_img.columns.to_f / scaled_img.columns

 args = [ att[:x1], att[:y1], att[:width], att[:height] ]
 args = args.collect { |a| a.to_i * scale }

 orig_img.crop!(*args)
 orig_img.write(self.user_image.path(:original))

 self.user_image.reprocess!
 self.save

end

#user.js

function load_cropped_image() {
 var userId = $('user_cropper_id').value;
 var url = get_base_url() + 'users/'+userId+'/ajax_get_cropped_image';
 var img = "loading";
 $('profileImageContainer').update(img);
 new Ajax.Request(url, {
 method: 'GET',
 onSuccess: function(transport) {
 $('profileImageContainer').update(transport.responseText);
 },
 onFailure: function(transport) {
 //alert(transport.responseText);
 }
 });
}

These fields will be filled in automatically by the jsCropperUI. We also need to display the image we’re cropping, and identify it with a CSS ID so the jsCropperUI can attach to it. To attach the jsCropperUI, we need to add some JavaScript to the page:

 #user.js
function onEndCrop( coords, dimensions ) {
 $( 'user_x1' ).value = coords.x1;
 $( 'user_y1' ).value = coords.y1;
 $( 'user_width' ).value = dimensions.width;
 $( 'user_height' ).value = dimensions.height;
}

I created a function to call the cropper so we could use ajax to update the images on the fly. I also add the preview pane so users can see exactly what the finished image will look like:

 
 function loadImageCropper() {
 new Cropper.ImgWithPreview(
 'full_user_image',
 {
 previewWrap: 'previewWrap',
 minWidth: 140,
 minHeight: 200,
 ratioDim: { x: 7, y: 10 },
 onEndCrop: onEndCrop
 }
 );
}

In the User Controller we've setup all the functions for saving and updating the image. We can use these functions on any partials since it's all being done with ajax. Also in the controller is where you are going to call you're update_photo_attributes in order to save your newly cropped image:


#user_controller

def ajax_image_cropper
 @user = User.find_by_id(params[:id])
 render :partial => "form_cropper_wrapper"
end

def ajax_save_cropped_image
 @user = User.find_by_id(params[:id])

 #@article.document = params[:article][:document]

 if @user.update_photo_attributes(params[:user])
 if @user.update_attribute(:image_cropped, 1)
 doc_status = 'Image file was successfully created'
 end
 else
 doc_status = 'Image file upload was not successful'
 end

 @user.errors.each_full{|err|doc_status << err}

 responds_to_parent do
 render :update do |page|
 page << "alert('#{doc_status}');window.location.href = window.location.href;window.location.reload(true);"
 end
 end
end

def ajax_get_cropped_image
 @user = User.find_by_id(params[:id])

 responds_to_parent do
 render :update do |page|
 flash[:error] = @user.errors.full_messages.join("
") page.replace_html 'profileImageContainer', :partial => 'users/user_image_display', :locals => {:user => @user} end end end

Finally, we have the view files. The wrapper is just to allow the updating of the form once the user has save the image. This will allow the ajax called to 'refresh' the view:


#_form_cropper_wrapper

<%= render :partial => 'users/form_cropper' %>

Finally the form view:


#_form_cropper

<%= render :partial => "users/user_image_pagesize" %>
<%= link_to_function "Start", "loadImageCropper()" %> <% remote_form_for :user, @user, :url => {:controller=> :users, :action => "ajax_save_cropped_image", :id => @user.id}, :update => {:success => "formCropperWrapper", :failure => "formCropperWrapper"} do |form| -%> <%= form.hidden_field :x1, :size => 6, :class => "shortField" %> <%= form.hidden_field :y1, :size => 6, :class => "shortField" %> <%= form.hidden_field :width, :size => 6, :class => "shortField" %> <%= form.hidden_field :height, :size => 6, :class => "shortField" %> <%= submit_tag "Submit" %> <% end %>

Good Luck!