Hi,
As promised I’m back with an example application written in Ruby on Rails and Javascript built on the Sencha Touch framework. This is a Contacts application with a “checkout” functionality. I’ve had to split it into two blog posts due to size. This is what we are trying accomplish:
- Add a new contact via the Web interface or via the device (pad, phone) interface
- Contacts that have been added on the device are stored in local storage
- Pressing “Sync” on the device will push unsync’ed data from the device to the web storage
- List all contacts that are stored locally on the phone
- List all contacts that are stored on the server in the “Catalog”
- Upon clicking an entry in the Catalog it is checked out to your local storage
Here are some screen shots of the final product – Yellow contacts are un-synced. Catalog not shown.
Let’s get started
Side Note: I recommend using Safari for development of Sencha Touch applications. Safari’s Develop menu gives it an advantage over other browsers in my opinion. Although I’ve heard Chrome is quite good as well:
- If you do not see a Develop menu at the top of the screen between Bookmarks and Window go to Preferences (Edit/Preferences on Win, Safari/Preferences on Mac). From the Preferences menu select Advanced. From the Advanced page select “Show Develop menu in menu bar”
- From Develop/User Agent you can view the site as if you are viewing it from a mobile device. I’m not sure if other browsers have this but it is a killer feature
- From Develop/Show Web Inspector you can view a very nice debugger panel. The Web Inspector panel provides a tab that displays the local storage. It also provides a console tab where you can type in JavaScript command and test code against you application. This has proved to be invaluable when trying to debug the Sencha Touch application.
Rails Application
Create the Rails Application
First thing, create your Rails app. The Ruby On Rails server app in this example is minimal. It was created as a scaffold and the controller has been customized to handle the xml going back and forth. From the command line the following will create your Rails application and database.
$ rails new contacts $ cd contacts $ rails g scaffold contact first_name:string last_name:string email:string phone:string $ rake db:migrate $ rails s
Navigate to http://localhost:3000. You should see the rails welcome page. Next we’re going to update routes.rb so that the root points to your index page for your contacts. First delete the index.html page in the public directory. Then update config/routes.rb to set the root to contacts/index.
ContactsDemo::Application.routes.draw do root :to => 'contacts#index' resources :contacts end
Update the Controller
Open up app/controllers/contacts_controllers.rb. The controller needs to be updated so that it plays nicely with your Sencha Touch app. There are two things you’re trying to do with the Rails code:
- Update the index so it delivers an xml list of your contacts
- Update the create so that new contacts are created or updated accordingly. NOTE: This is a very high-level approach to synchronizing records between two data sources. If you are familiar with Version Control (Subversion and Git) then you have seen synchronization done at a very deep level. This application will not check at the field level for collisions or anything on that order. Simply put: The application will treat any incoming record as the latest and will update the online DB with the entire record.
Index (Update: add dasherize => false. This will make your xml send first_name rather than first-name)
def index @contacts = Contact.all respond_to do |format| format.html # index.html.erb format.xml { render : xml => @contacts, :dasherize => false } # Note: WordPress workaround: do not put a space between the colon and xml # WordPress was doing funny things with colonxml so I had to put a space end end
Add 3 new methods to the end of the controller: parse_sencha_xml, update_record, and parse_contact_params
def parse_sencha_xml p_hash = params[:xmlData][:contact] return nil if p_hash.nil? || p_hash.empty? # call update if the remote_id is populated, means it's already in the db if p_hash['remote_id'].to_i > 0 return update_record(p_hash) end contact = Contact.create!(parse_contact_params(p_hash)) return contact end def update_record(p_hash) id = params[:xmlData][:contact]['remote_id'] c = Contact.find(id) update_params = parse_contact_params(p_hash) c.update_attributes(update_params) return c end def parse_contact_params(p_hash) contact_hash = Hash.new attr = Contact.new.attributes p_hash.each do |key, value| if (key == 'id' || key == 'remote_id' ) # skip, this is the device PK or the db PK. The db PK has already been captured elsif attr.has_key?(key) contact_hash[key] = value end end return contact_hash end
Finally, update the Create method to use the new methods. It will now look for matching records before adding a new record. If it finds one, it will update it. Update create so that it calls the new methods
def create respond_to do |format| format.html { @contact = Contact.new(params[:contact]) if @contact.save redirect_to root_path else render :action => 'new' end } format.xml { @contact = parse_sencha_xml render : xml => @contact, :status => :created, :dasherize => false # Note: WordPress workaround: do not put a space between the colon and xml # WordPress was doing funny things with colonxml so I had to put a space } end end
Routing and viewing both Mobile and Standard sites
Now that our controller is updated there are a few things we need to do to take care of routing and add the ability to view mobile or standard sites.
Update app/controller/application_controller to allow for mobile or standard display. The following is pulled directly from Railscasts episode 199. Rails will evaluate the user_agent from the incoming request and true if it contains “Mobile” (Android or iOS) or “webOS” (Palm)
class ApplicationController < ActionController::Base helper :all protect_from_forgery private def mobile_device? user_agent = request.user_agent user_agent =~ /Mobile|webOS/ end helper_method :mobile_device? end
Update the view so that the page displays in a browser. First update the index page. The new mobile_device? method is used to determine which part of the index page to display.
<% if(mobile_device?) %> <!-- Code Viewable to mobile devices --> <%= content_for :head do %> <%= stylesheet_link_tag 'sencha-touch-debug' %> <%= javascript_include_tag 'touch_1_0_1/sencha-touch-debug' %> <%= javascript_include_tag 'app/app' %> <%= javascript_include_tag 'app/util' %> <%= javascript_include_tag 'app/models/Contact' %> <%= javascript_include_tag 'app/views/ContactCatalog' %> <%= javascript_include_tag 'app/views/ContactEdit' %> <%= javascript_include_tag 'app/views/ContactNew' %> <%= javascript_include_tag 'app/views/ContactShow' %> <%= javascript_include_tag 'app/views/ContactsList' %> <%= javascript_include_tag 'app/views/Viewport' %> <%= javascript_include_tag 'app/controllers/contacts' %> <style> .synced { background-color: #EDE613; } </style> <% end %> <% else %> <%= content_for :head do %> <%= stylesheet_link_tag 'scaffold' %> <%= javascript_include_tag :defaults %> <% end %> <!--Add code from index.html.erb --> ... <% end %>
Next update app/views/layouts/application.html.erb to remove a couple of lines that you don’t need for mobile browsers
<!DOCTYPE html> <html> <head> <title>ContactsDemo</title> <%= csrf_meta_tag %> <%= yield :head %> </head> <body> <%= yield %> </body> </html>
That’s it for the rails application. When you view the page in a standard browser you should see the index page that lists all of your contacts. If you change the User Agent (Safari) to Mobile Safari 4.1 you will see a blank gray screen. If something appears to explode when you switch to Mobile Safari remove the javascript imports that start with ‘app/… I can’t remember if Safari gets made if you ask it to import something that is not there. I will be posting the Sencha Touch application next time.
Thanks for reading!
References:
Excellent MVC Tutorial with Sencha Touch and PhoneGap
Railscasts episodes 199, 247, and 248 – by Ryan Bates. Totally amazing gift of time, energy, and knowledge from Ryan, an absolute must watch for Rails developers. If you want to do it in Rails, Ryan has probably already done it and has a nice succinct video that explains it to you.
Great stuff! Thanks
Can’t wait to see the next part
Very good, congratulations!!
Good tutorial, looking forward to part 2.
Thanks for this… really useful tutorial.
And really looking forward to part 2!
you realy should share the source code so that everyone else could have a look at how it’s done.
congrats on the tutorial!
great example. Thx aditya for the forward
Thanks for this ….really helpfully …great tutorial.