Drag and Drop Everything (Almost Everything) continued
(Page 2 of 4 )
Finally, notice two Ajax related helpers: drop_receiving_element and observe_field. We_ll come back to these in a little bit after we have discussed some prerequisite details. Now, make these changes to photos/app/controllers/slideshows_controller.rb, replacing the edit method and creating the unused_photos method: def edit @slideshow = Slideshow.find(params[:id]) session[:slideshow] = @slideshow @photos = unused_photos(@slideshow) end def unused_photos(slideshow) all_photos = Photo.find(:all) candidates = [] for photo in all_photos in_slideshow = false for slide in slideshow.slides if slide.photo.thumbnail === photo.thumbnail in_slideshow = true break end end candidates << photo if not in_slideshow end return candidates end The purpose of this code is to retrieve all the data needed by the edit.rhtml view template: @slideshow = Slideshow.find(params[:id]) The id of the slideshow that you want to edit is passed in the request parameters from the browser. Here you retrieve that id and read that slideshow from the database, which you store in the instance variable @slideshow to make it available to the view template.
session[:slideshow] = @slideshow Ajax actions requests will be coming in as the user makes changes, and you need to know what slideshow to change. This line saves a reference to the slideshow in the session hash. I_m using a key value of :slideshow to save and retrieve this from the session, but that value is arbitrary and could have been any unique identifier.
@photos = unused_photos(@slideshow) This line calls the new method unused_photos to retrieve a list of all photos that are not in the slideshow; it then saves that list in @photos.
def unused_photos(slideshow) This method returns a list of photos that are not in the slideshow. The logic should be self-explanatory. First, create an empty array (candidates = []), and then iterate through the list of all photos, adding them to the array (candidates << photo) if they are not already in the slideshow. The technique used here is grossly inefficient, but it will suffice for our purposes.
We still need to create the photo_picker template that generates the HTML to display all the photos that can still be added to a slideshow, so go ahead and create the file photos/app/views/slideshows/_photo_picker.rhtml with this in it: <% for photo in @photos %> <%= image_tag("photos/#{photo.thumbnail}", :style => "vertical-align: middle", :id => "photo_#{photo.id}", :class => "photos") %> <%= draggable_element "photo_#{photo.id}", :revert => true %> <% end %> This template iterates through the list of photos in @photos. For each photo, it uses the image_tag helper to create an HTML image tag and the draggable_element helper to generate the JavaScript code that makes it draggable. You can see that the first parameter of draggable_element matches the value of the id attribute (:id => "photo_#{photo.id}") on the image tag. The draggable_element helper expects the id of the HTML element that it should make draggable, followed by zero or more options. The single option used here (:revert => true) says to move the element back to its original position after it is dropped. But where can these draggable images be dropped? Recall that at the end of the slideshow_s edit.rthtml template we had: <%= drop_receiving_element("slideshow-contents", :update => "slideshow-thumbs", :url => {:action => "add_photo" }, :accept => "photos", :droponempty => "true", :loading => visual_effect(:fade), :complete => visual_effect(:highlight, _sortable_thumbs_) ) %> Just like the draggable_element helper, the drop_receiving_element helper expects the ID of the HTML element onto which you can drop something that was declared as draggable. The remaining parameters are options that given as name/value pairs (the order is not important). These options are doing a lot, so let_s go through them one at a time: :update => "slideshow-thumbs" This gives the ID of the HTML element that should be updated when a photo is dropped on our slideshow-contents div. The :position and :url options say how, and with what, that HTML element should be updated. When the :position option is omitted (as it is here), the HTML returned from the server replaces the target elements HTML. The :position option says that the returned HTML should be inserted into target element, instead of replacing it. The value :position can be specified as :before, :top, :bottom, and :after.
:url => {:action => "add_photo" } This option constructs the URL that is sent to the server (via a background Ajax request) when a photo is dropped (you_ve seen this before). This executes the add_photo method in the current controller (the SlideshowsController). The add_ photo action adds the dropped photo to the slideshow and returns an HTML fragment that will replace the existing HTML in the target element, which, as you will see, is a rerendering of the slideshow_s contents, which now include the added photo.
:accept => "photos" Without this option, you could drop any draggable element here. However, this line says that only HTML elements that have the class attribute "photos" can be dropped here. Remember that in our photo picker template we gave each photo class attribute of "photos".
:droponempty => "true" This option says that the user can drop photos here even if the target is completely empty.
:loading => visual_effect(:fade) :complete => visual_effect(:highlight, _sortable_thumbs_) :loading and :complete (plus a few more events) specify client-side JavaScript event handlers that are executed at specific points in the progress of the Ajax request. In both cases, we are displaying a visual effect that gives the user positive feedback. The :loading event occurs when the browser begins loading the response, and the :complete event occurs when its all finished. The code specifies that the dropped photo will fade until it becomes invisible. It also highlights the target area on which the photo was dropped.
|