onrails.org home

dvds.onrails.org - Integrating Flash and Ruby On Rails

dvds.onrails.org

The purpose of the DvdReleases demo application is to investigate some of the possibilities to integrate a Flash Component with a Ruby On Rails application.

We will describe:

The application is quite simple and provides a list of dvd releases allowing to rate from 1 to 5 the individual dvds. The data has been downloaded from http://hometheaterinfo.com/dvdlist.htm. As this is only a demo, you can vote several times. So, which is your favorites Dvd title?

Enabling flash and javascript integration for you Ruby on Rails application.

Macromedia provided the Flash/Javascript Integration Kit a javascript library to embeded Flash with an .rhtml and javascript page.

  1. download the integration kit.
  2. copy JavaScriptFlashGateway.js to rails public/javascript folder.
  3. copy JavaScriptFlashGateway.swf to the public folder of your rails app.
  4. copy your component, RateMovie.swf in our case, to the public folder of your rails app.

Then in your .rhtml view (list.rhtml in our example) add to the <head> section the following line to enable the Flash Javascript gateway:

        <%= javascript_include_tag "JavaScriptFlashGateway" %>

Embedding the FlashTag in the .rhtml file

Then declare the following javascript code to the list.rhtml file.

      <script type="text/javascript">

            var uid = new Date().getTime();  // uniq

            

            function insertFlashComponent(dvd_id, vote, average) {               

                  var tag = new FlashTag('../RateMovie.swf', 137, 50); // last two arguments are height and width

                  tag.setFlashvars('lcId='+uid+'&dvdid='+dvd_id+'&dvdvotes='+vote+'&dvdaverage='+average+'');

                    tag.write(document);

             }

            

              function hightlightVote(dvd_id) {

                  new Effect.Highlight('flashdiv'+dvd_id);

             }

      </script>

The insertFlashComponent javascript method write the FlashTag dynamically to the html document when invoked.

Passing initial values to a Flash component from an ActiveView.

The insertFlashComponent method takes the initial parameters of the Flash component.

  insertFlashComponent(<%= dvd_id %>, <%= dvd_vote %>, <%= dvd_average %>);

These parameters can now be access from flash from the _root object as follows:
_root.dvdid, _root.dvdvotes, _root.dvdaverage

Embedding multiple Flash components in a .rhtml file.

This is how each of the Dvds selected by the ActionController is displayed in the table body

<tbody>

<% for dvd in @dvds %>

  <tr>

  <% for column in Dvd.content_columns[0,3] %>

    <td><div id="flashdiv<%= dvd.id %>"><%=h dvd.send(column.name) %></div></td>

  <% end %>

      <td>

      <script type="text/javascript">

         <% dvd_id = dvd.id

            dvd_vote = dvd.vote!=nil ? dvd.vote.votes : "0"

                  dvd_average = dvd.vote!=nil ? dvd.vote.average : "null"

            %>

            insertFlashComponent(<%= dvd_id %>, <%= dvd_vote %>, <%= dvd_average %>);

      </script>    

      </td>

    <td><div id="mydiv<%= dvd.id %>">  <%= link_to_remote("show",

                              :update => "mydiv#{dvd.id}",

                              :url => { :action => 'show_in_place',  :id => dvd.id  }) %></div>

      </td>

  </tr>

<% end %>

 </tbody>

Invoking remotly an ActionController from a Flash component.

Now let's look at the Flash Component that allows you to rate your favorite Dvd.

Initially the components show the current number of votes and the average of all votes. When moving the mouse over, it tracks if you rating the moving from 1 to 5. On release of the mouse button it sends your vote to the ActionController.

The component is an instance of VotePanelClass Actionscript class. The code extract here after shows how the initial values passed to the javascript insertFlashComponent can be retrieve from the Flash component by using the _root.dvdid, _root.dvdvotes, and _root.dvdaverage variables.

class VotePanelClass extends MovieClip {
        
        function onLoad() {
                stars = [s0, s1, s2, s3, s4];
                for (var i=0; ilength; i++) stars[i].setRank(i+1);
                setParams(_root.dvdid, _root.dvdvotes, _root.dvdaverage);
                setState(ShowVoteStateClass);
        }
        
        public function submitVote(vote:Number) {
                currentVote = vote;
                message = "Sending vote..."
                setState(WaitStateClass);
                var voteCall = new RubyCall(this, voteResult);
                voteCall.execute("/admin/vote_for_dvd/"+dvdId+"?rank="+vote);
        }
        
        public function voteResult(result) {
                if (result.success) {
                        message = null;
                        votes = result.data.votes;
                        average = result.data.average;
                        getUrl("javascript:hightlightVote('"+dvdId+"')");
                } else {
                        message = "Send Error!";
                }
                setState(ShowVoteStateClass);
        }
        
}

To invoke the ActionController we simply use the standard Flash LoadVar class that issues a HttpRequest. We did wrap the LoadVar in the following class:

import mx.utils.Delegate;

class RubyCall {
        
        var callback:Function;
        
        function RubyCall(obj, func) {
                callback = Delegate.create(obj, func);
        }
        
        function execute(url:String) {
                var result_lv:LoadVars = new LoadVars();
                result_lv.delegate = this;
                result_lv.onLoad = function(success:Boolean) {
                        this.delegate.onResult(success, this);
                };
                var send_lv:LoadVars = new LoadVars();
                send_lv.sendAndLoad(url, result_lv, "POST");
        }
        
        function onResult(success:Boolean, lv:LoadVars) {
                callback({success:success, data:lv});
        }
        
}

The following call invokes the controller and pass the dvdId and rank.

voteCall.execute("/admin/vote_for_dvd/"+dvdId+"?rank="+vote);

From Ruby the admin_controller.vote_for_dvd method is invoked (@todo: change admin_controller to dvd_controller)

      def vote_for_dvd

            @dvd = Dvd.find(@params[:id], :include => :vote)

            rank = @params[:rank].to_i

            vote = @dvd.vote

            vote.votes += 1

            vote.average += (rank/vote.votes)

            @dvd.save

            render(:layout => false, :action => 'submit_vote')

      end

Calling a javascript function from a Flash component.

Finally we call javascript from the Flash component just to demonstrate that it can be done.

getUrl("javascript:hightlightVote('"+dvdId+"')");

Conclusion

In this demo we just scratched the surface on how Flash and Ruby on Rails can be integrated. The same example we showed can easily be written in plain Javascript. However on many projects you could use these techniques to add richer components like charts and other graphical representations of data.

There are some nice opensource frameworks that allow to generate Flash component directly from a Ruby application, there is the AMF4R library that enable passing typed objects between Ruby and Flash, there is the Flash Player 8 comming out with a new External Interface that opens great new possiblities. So this is just the start!

Enjoy!
Daniel and Lee.

 

Fork me on GitHub