Inline validation with Ruby on Rails 3.1 and jQuery April 26, 2012
Posted by hoitomt in Programming.add a comment
Hi,
I’m going to go over inline form validation (with helpers) similar to how MailChimp does it on their sign up page at: https://mailchimp.com/signup/. When you click on the form text box a helper slides out from the bottom of the input box. Then after you exit the text box it validates the data that you just put into the field. It is a super nice way to do helpers on your input forms and validation for your forms. Our goal is to avoid the standard big red Ruby on Rails error that we’ve come to know and love.
Note that some of the formatting in this blog post was removed. I think everything is still readable but if not check out the source code. You can get the source code of this tutorial from my GitHub repository: https://github.com/hoitomt/tut_inline_validation
Create the Project
I’m going to start out with a simple rails project and use scaffolding to create a simple form. We will be modifying the html and css so that our validation works the way we want it to.
$ rails new tut_inline_validation $ rails g scaffold person first_name:string last_name:string $ rake db:migrate
Next update your Gemfile to include the client_side_validations gem
source 'http://rubygems.org' gem 'rails', '3.1.3' gem 'sqlite3' gem 'client_side_validations' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.5' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' group :test do # Pretty printed test output gem 'turn', '0.8.2', :require => false end
Then install your bundle
$ bundle install
Configure client_side_validations
The client_side_validations gem requires a bit of setup. The instructions are listed on the GitHub site for the gem and are as follows:
- Run the generator for client_side_validations. NOTE: on the github page for client_side_validations it states that an additional generator needs to be run for Rails 3.1+. I did not need that generator (copy_assets) so it isn’t listed.
- Update application.js to include the rails.validations path
- Update the generated initializer (config/initializers/client_side_validations.rb)
- Update your form to use validation
Number 1: Run the generator
$ rails g client_side_validations:install
create config/initializers/client_side_validations.rb
*********************
ClientSideValidations
*********************
In your app/assets/javascripts/application.js file add the following:
//= require rails.validations
$
Number 2: Update application.js
After you run the generator there is an instruction at the bottom of the file. We need to update application.js so that it includes the validation code
// This is a manifest file that'll be compiled into including all the files listed below. // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. // //= require jquery //= require jquery_ujs //= require_tree . //= require rails.validations
Number 3: Update the initializer
# ClientSideValidations Initializer
require 'client_side_validations/simple_form' if defined?(::SimpleForm)
require 'client_side_validations/formtastic' if defined?(::Formtastic)
# Uncomment the following block if you want each input field to have the
# validation messages attached.
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
unless html_tag =~ /^<label/
%{</pre>
<div class="field_with_errors">#{html_tag}<label class="message" for="#{instance.send(:tag_id)}">#{instance.error_message.first}</label></div>
<pre>}.html_safe
else
%{</pre>
<div class="field_with_errors">#{html_tag}</div>
<pre>}.html_safe
end
end
Number 4 Update the form to use validation.
Open app/views/people/_form.html.erb and update your form_for to add validate => true. I also added an ID to make it easier for jQuery to find the form
</pre>
<%= form_for(@person, :validate => true, :html => {:id => 'awesome-form'}) do |f| %>
<pre>
Now let’s start up the server
$ rails s
If you navigate to http://localhost:3000/people you should see your person/index.html.erb file. So Far so good.
Add Model Validation
Go to person.rb (app/model/person.rb) and add the following validations. Both the first and last name are required. The length of the first name must be between 3 and 30 characters and the last name must be unique.
class Person < ActiveRecord::Base
validates :first_name, :presence => true,
:length => { :within => 3..30 }
validates :last_name, :presence => true,
:uniqueness => { :case_sensitive => false }
end
Let’s make sure they work. From http://localhost:3000/people click on the ‘New Person’ link. Don’t enter anything in the fields and click on “Create Person”. You should see the following: 
Now we know that validations are working. You can see the client_side_validations is doing some nice work already. It added in the inline validations to the right of the fields and didn’t display the big red box at the top of the form.
jQuery Goodness
Let’s improve the look a bit. Open up app/views/people/_form.html.erb file. Remove the error display and update the html as follows:
true, :html => {:id => 'awesome-form'}) do |f| %></pre>
<div id="data-entry" class="clearfix">
<div id="label"></div>
<div id="field">
<div id="person_first_name_helper" class="helper">e.g. Barack</div>
<div id="person_first_name_error"></div>
</div>
</div>
<div id="data-entry" class="clearfix">
<div id="label"></div>
<div id="field">
<div id="person_last_name_helper" class="helper">e.g. Obama</div>
<div id="person_last_name_error"></div>
</div>
</div>
<div class="actions"></div>
<pre>
The code above adds containers for the helpers and the error messages so that jQuery has somewhere to put our errors. Now that we have the form html done, let’s add the jQuery. We’ll use Unobtrusive javascript so create a file called sign_up_form.js and put it into app/assets/javascripts
$(function() {
hideAllHelpers();
$('#person_first_name').focus(function() {
$('#person_first_name_helper').show(200);
});
$('#person_first_name').blur(function() {
$('#person_first_name_helper').hide(200);
});
$('#person_last_name').focus(function() {
$('#person_last_name_helper').show(200);
});
$('#person_last_name').blur(function() {
$('#person_last_name_helper').hide(200);
});
clientSideValidations.callbacks.element.after = function(element, eventData) {
// element is the input element (text field). The text field is wrapped by
// the error so the parent is the error_wrapper. The label is a child of
// the wrapper and the html of the label is the actual error message
var elementContainer = element.parents('#field');
var errorLabel = element.parent().find('label');
var errorMsg = errorLabel.html();
errorLabel.hide();
var existingError = elementContainer.find('#validation-error');
console.log("Error Msg: " + errorMsg);
if(!errorMsg || errorMsg == null) {
existingError.remove();
} else if(existingError && existingError.length > 0) {
existingError.html(errorMsg);
} else {
elementContainer.append('</pre>
<div id="validation-error">' + errorMsg + '</div>
<pre>
');
}
}
});
function hideAllHelpers() {
$('#person_first_name_helper').hide();
$('#person_last_name_helper').hide();
}
Here is what the code above is doing:
- The hideAllHelpers() function is defined at the end of the script. This hides the helpers upon load
- The next 8 lines of code show and hide the helpers depending on which text box you are in
- The clientSideValidation callback is provided by the client_side_validations gem. When Rails validates your field it wraps each text box/label combo in a div. The client_side_validations callback is doing the heavy lifting by making the validation error available to us in javascript. We work through the DOM to put the error in the correct place. In the case of this exercise we want the error to end up under the text box.
If you go to http://localhost:3000/person/new you should now see that when you click into a box a helper slides out from underneath. When you leave the text box it will show a validation error if the text is invalid.
All of the code is on GitHub under https://github.com/hoitomt/tut_inline_validation. I’ve included a stylesheet out there to help make your form/helpers/validations look awesome.
Thanks for Reading!
References
http://railscasts.com/episodes/263-client-side-validations – It almost goes without saying that one of Ryan Bate’s screen casts would be a reference for a Rails tutorial. He is the king of Rails teaching. Specifically episode 263 is important because it covers the client_side_validations gem
Use Custom Fonts in Ruby on Rails 3.1 with the asset pipeline February 24, 2012
Posted by hoitomt in Programming.add a comment
Just a quick post on something I just learned. If you want to use a custom font in a ROR 3.1 project start with this post: http://spin.atomicobject.com/2011/09/26/serving-fonts-in-rails-3-1/
Sidebar: FontSquirrel is awesome, the kit is really helpful. It’s another one of those really fantastic web projects that somebody put out there for free.
Anyway, after you get the font kit downloaded and have it unzipped into app/assets/fonts look in the stylesheet.css file in the kit. There is some CSS that you can use to get started. Update the css to support the asset pipeline. You have to structure your URLs as follows:
@font-face {
font-family: ‘DroidSerifRegular’;
src: url(/assets/DroidSerif-Regular-webfont.eot);
src: url(/assets/DroidSerif-Regular-webfont.eot?#iefix) format(‘embedded-opentype’),
url(/assets/DroidSerif-Regular-webfont.woff) format(‘woff’),
url(/assets/DroidSerif-Regular-webfont.ttf) format(‘truetype’),
url(/assets/DroidSerif-Regular-webfont.svg#DroidSerifRegular) format(‘svg’);
font-weight: normal;
font-style: normal;
}
Finally, if you are using Chrome for development it will throw an error like “Resource interpreted as Font but transferred with MIME type application/octet-stream”. To fix this update config/initializers/mime_types with this line:
Mime::Type.register “font/opentype”, :font
That will fix the Chrome error. Cheers and thanks to the Intertubes for the help:
http://spin.atomicobject.com/
http://www.fontsquirrel.com/
http://stackoverflow.com/questions/2871655/proper-mime-type-for-fonts
Java and Ruby, Rails and Spring February 15, 2012
Posted by hoitomt in Programming.add a comment
I’m in the middle of studying for the Core Spring Essentials Certification exam and wanted to put a couple of thoughts out there with regards to Spring and Java for the Web. Spring has a nice web framework know as Spring MVC. It doesn’t need a JEE container to run so it runs on Tomcat and other non-enterprise servers. I think a person could get it set up on a PAAS like Heroku, now that they have Java support. So it makes a very viable option for developing web applications for personal and freelance projects.
However I don’t know why anybody would use it for that. If you are a Java developer and don’t know Ruby on Rails, or one of the other newer web language/framework combos, I suppose Spring MVC would be a good option. But compared to ROR, Spring MVC seems like a much more complex way to solve the exact same problem: serve dynamic web pages with data pulled from a database mixed with static content.
I know that Spring will integrate into a Java environment that supports messaging, transactions, and any other enterprise bits that you need. But ROR can do some of those things as well and, really, how many sites really need a full enterprise backend? I’m a J2EE dev in my day job so I’m tempted to use a Java framework for my side projects as well, as a way of improving my skills. But using Spring MVC seems like overkill.
I really don’t mean to bash Spring or to criticize Spring MVC. Spring is a great framework and it makes Java Enterprise Programming much easier. I understand that if you are a Java programmer and want to start web programming on the side, then Spring MVC (and probably Spring ROO) are a perfect starting point for you. But my advice would be to learn one of the new “scripting” languages (ROR) or Javascript/Node.js/Backbone.js.
Build-a-server June 29, 2011
Posted by hoitomt in Programming.1 comment so far
Whether you are interested in Node.js or not there is a really excellent beginner “book” here. What makes the tutorial/book so interesting, and valuable, is that you end up building a web server. Granted, Node.js is doing all of the serving of the pages but the tutorial walks you through the request/response lifecycle. It feels like you are coding “close to the metal” of the server machine, and it’s all done using familiar javascript.
You get started with the ubiquitous Hello World example and work your way up to a router, request handlers, and everything else that goes into a website. The steps are small and each one is explained quite well. You don’t need to know javascript to do the tutorial, but it helps if you have used it a bit.
I haven’t done much coding with PHP so if you come from a PHP background, the tutorial may be old news. However If you come from a Ruby on Rails background the tutorial really helps to tie together a lot of what we are doing in the config/routes file and how the MVC pattern works. At the very least, regardless of your background, it’s a great introduction to Node.js.
It’s like a lot of things: You don’t need to understand how it works in order to get by. But if you understand how it works, you can use it better. This tutorial does an excellent job showing you how a web server works. And you get to learn Node.js in the process, what could be better!
Here is the link to the book again: http://nodebeginner.org/
Sencha Touch application with Ruby on Rails – Part 2 April 21, 2011
Posted by hoitomt in Programming.19 comments
The source code for this application is posted on GitHub at https://github.com/hoitomt/Sencha-Tutorial
Sencha has formalized the MVC pattern recently as part of the ExtJS 4 release. Check out this link for more information.
Create the Sencha Application
I’m back with the Sencha portion of the application. We are going to create the application using the MVC (Model View Controller) pattern
Update your Rails App
In your rails public/javascripts directory create the following folders.
app
app/controllers
app/models
app/views
Add the sencha touch folder to the public/javascripts folder. Take a look at the Hello World application from Sencha for some assistance on downloading the zip file containing all of the applicable files. I downloaded it and extracted the entire package into public/javascripts/. Then I renamed the folder touch_1_0_1. So public/javascripts/touch_1_0_1 has the following directories: docs, examples, jsbuilder, pkgs, resources, src, and test. It also contains some of the other files that come with the Sencha touch download.
Now copy the stylesheet over to a directory that Rails likes. From public/javascripts/touch_1_0_1/resources/css copy “sencha-touch–debug.css” over to public/stylesheets/. If you did the first part of this tutorial, then you will now have scaffold.css and sencha-touch-debug-css in the public/stylesheets directory.
Update your index file with the references to the Sencha Touch file and stylesheet. Here is my index file.
<% 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' %>
<style>
.synced {
background-color: #EDE613;
}
</style>
<% end %>
<% else %>
<%= content_for :head do %>
<%= stylesheet_link_tag 'scaffold' %>
<%= javascript_include_tag :defaults %>
<% end %>
<h1>Listing contacts</h1>
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Phone</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @contacts.each do |contact| %>
<tr>
<td><%= contact.first_name %></td>
<td><%= contact.last_name %></td>
<td><%= contact.email %></td>
<td><%= contact.phone %></td>
<td><%= link_to 'Show', contact %></td>
<td><%= link_to 'Edit', edit_contact_path(contact) %></td>
<td><%= link_to 'Destroy', contact, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Contact', new_contact_path %>
<% end %>
Create the Sencha App
Starting point: app.js
Now you should be ready to start building the application. Let’s start with the entry point into the application, the app.js file. Put the app.js file into public/javascripts/app. The code below creates an instance of the viewport that will be used to display our panels. The Sencha API it give some helpful information about Ext.regApplication. The Sencha API will be your BFF as you build more applications. Creating your application with the regApplication function automatically creates your app.views, app.models, app.stores, and app.controllers namespaces which makes it easy to navigate through your application
Ext.regApplication(
{
name: 'app',
launch: function() {
this.views.viewport = new this.views.Viewport();
}
});
Views: Viewport.js
Now let’s create the Viewport.js file. Put the Viewport.js file in public/javascripts/app/views. The code below sets up the main panel that will provide the navigation for all of your sub-panels. When navigating between slides the code will actually be switching the active panel in the viewport. The viewport below shows all of the panels in the application. We haven’t created these yet so your application will crash if you try to run it right now. You can go ahead and create empty files for the 5 files below (ContactsList, ContactShow, ContactEdit, ContactNew, and ContactCatalog). Put them all into public/javascripts/app/views.
//app.views.Viewport
app.views.Viewport = Ext.extend(Ext.Panel, {
fullscreen: true,
layout: 'card',
cardSwitchAnimation: 'slide',
initComponent: function() {
Ext.apply(app.views, {
contactsList: new app.views.ContactsList(),
contactShow: new app.views.ContactShow(),
contactEdit: new app.views.ContactEdit(),
contactNew: new app.views.ContactNew(),
contactCatalog: new app.views.ContactCatalog()
});
Ext.apply(this, {
items: [
app.views.contactsList,
app.views.contactShow,
app.views.contactEdit,
app.views.contactNew,
app.views.contactCatalog
]
});
app.views.Viewport.superclass.initComponent.apply(this, arguments);
}
});
Update your index file (app/views/contacts/index.html.erb) to add the necessary imports. Then you can run your application and you should see the empty ContactsList panel displayed.
<%= javascript_include_tag 'app/app' %> <%= 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' %>
Model: Contact.js
Now let’s take a look at the model. Create Contact.js in public/javascripts/app/models. Our model serves three purposes:
1. Get data from the web server for the application
2. Store data locally in localstorage
3. Post data to the web server that has been captured on the device.
We’re going to do this with multiple proxies. The Sencha proxies make it easy to persist data. The code below defines the fields, validations, and a proxy for saving a single model instance to the web server. I like using xml to transmit back and forth but I notice that the Sencha guys like using json. XML is more comfortable for me at the moment and it seems to work well with ROR.
//Contact.js
app.models.Contact = new Ext.regModel('app.models.Contact', {
fields: [
{name: 'id', type: 'int'},
{name: 'remote_id', type: 'int'},
{name: 'synced', type: 'boolean'},
{name: 'first_name', type: 'string'},
{name: 'last_name', type: 'string'},
{name: 'email', type: 'string'},
{name: 'phone', type: 'string'}
],
validations: [
{type: 'presence', field: 'first_name'},
{type: 'presence', field: 'last_name'}
],
proxy: {
type: 'ajax',
url: 'contacts.xml',
reader: {
type: 'xml',
record: 'contact'
},
writer: {
type: 'xml',
record: 'contact'
}
}
});
...
Now add two more stores to your model. The first one is a local store which will help with CRUD to your local storage. The second is a remote store that we use only to get a complete list of data from the web server to populate our catalog.
//Contact.js
...
app.stores.localContacts = new Ext.data.Store({
id: 'localContacts',
model: 'app.models.Contact',
proxy: {
type: 'localstorage',
id: 'contacts'
}
});
app.stores.remoteContacts = new Ext.data.Store({
id: 'remoteContacts',
model: 'app.models.Contact',
proxy: {
type: 'ajax',
url: 'contacts.xml',
reader: {
type: 'xml',
root: 'contacts',
record: 'contact'
},
writer: {
type: 'xml',
record: 'contact'
}
}
});
...
Finally we need to add the CRUD and synchronizing code. The first function app.models.save is called from the controller when the save button is clicked on the ContactNew form. This takes all of the data from the form and persists it into local storage using the local store. The stores work really well with models so one trick that I use is to create a new model using the data from the form. Ext.ModelMgr.create(data, modelname) makes it easy to pass in a list of name-value pairs to create a new model instance. The store has a really nice method (add) for persisting existing models. Together you can pull all of your form data and persist it with only a couple of lines of code.
// Contact.js
...
app.models.save = function() {
var form = app.views.contactNew
var params = form.getValues();
var newcontact = Ext.ModelMgr.create(params, app.models.Contact);
var errors = newcontact.validate();
if (errors.isValid()) {
app.stores.localContacts.add(newcontact);
app.stores.localContacts.sync();
form.reset();
return true;
} else {
var errorMsg = '';
errors.each(function(e) {
errorMsg = errorMsg + fieldHumanize(e.field) + ' ' + e.message + "<br />";
errorMsg = errorMsg ;
});
Ext.Msg.show({
title: 'Error',
msg: errorMsg,
buttons: Ext.MessageBox.OK,
fn: function() {
return false;
}
});
}
Next we write the update function. This is called from the controller when the update button is clicked from the ContactEdit screen. All of the data is pulled from the form into a params variable. The code then loops through the params and updates everything on the model.
...
app.models.update = function(id) {
var contact = app.stores.localContacts.getById(id);
if(contact) {
var form = app.views.contactEdit
var params = form.getValues();
for(var field in params) {
console.log("field: " + field + ' | value: ' + params[field]);
contact.set(field, params[field]);
}
var errors = contact.validate();
if(errors.isValid()) {
contact.set('synced', false);
app.stores.localContacts.sync()
Ext.Msg.alert('Updated', 'The contact has been updated');
return true;
} else {
var errorMsg = '';
errors.each(function(e) {
errorMsg = errorMsg + fieldHumanize(e.field) + ' ' + e.message + "<br />";
errorMsg = errorMsg ;
});
Ext.Msg.alert('Error', errorMsg);
return false;
}
} else {
return false;
}
}
Next a short delete function to remove the record from local storage only.
app.models.destroy = function(id) {
var contact = app.stores.localContacts.getById(id);
if(contact) {
app.stores.localContacts.remove(contact);
app.stores.localContacts.sync();
return true;
} else {
return false;
}
}
Finally the sync function. After validating that the device is online the localstore is loaded with the latest data from localstorage. Each record in the local store is evaluated to find only those records that need to be synced (synced == false). Once we have the sync array and there are more than 0 records in the syncArray we loop through it. NOTE: I’m using a for loop but it is actually better to use an each loop when going through the array. To do that you would replace for(var i=0….) with syncArray.each(function(form) {… existing code …}. Then remove the line form = syncArray[i].
The pattern for syncing is as follows: use the data from the syncArray record to create a new model. Then use the proxy on the app.models.Contact model to save the record to the web server. This is why we have a proxy on the model, so that we can call syncModel.save and have the data synchronize with the server. There are three callbacks: success, failure, and callback. Success is called if the data successfully saves. Failure is called if something goes wrong and Callback is called regardless of what happens.
One of the tricky things with Javascript and ExtJS is managing the asynchronous actions. That is why the callbacks are used. If you just call syncModel.save() and then try to pop a success message, it won’t work the way you think it would. The save action will execute asynchronously and the code will move onto the next line while it the save is still executing. So your alert message would display even though the save hadn’t finished.
app.models.synchronizeLocalToRemote = function () {
if(!navigator.onLine) {
Ext.Msg.alert('Offline', 'You need to be online to sync to the web server');
return;
}
console.log('Start Sync');
var localStore = app.stores.localContacts.load();
var syncArray = getDataToSync(localStore);
var count = syncArray.length;
if(count == 0) {
Ext.Msg.show({
title: 'Synced',
msg: 'All contacts are synced'
});
return;
}
var syncInfo = "";
console.log("Number of items to sync: " + count);
// Show the syncing spinner
var mask = new Ext.LoadMask(Ext.getBody(), {msg: "Synchronizing"});
mask.show();
// Sync items to remote
for(var i = 0; i < count; i++) {
console.log("Index: " + i);
form = syncArray[i];
var syncModel = Ext.ModelMgr.create(form.data, app.models.Contact);
// Calling save on the model calls the remote proxy
syncModel.save({
success: function(result, request) {
var id = result.data['id']
console.log("Result ID: " + id);
console.log("Success");
form.set('remote_id', id);
form.set('synced', true);
localStore.sync();
syncInfo = 'Success: ' + form.get('first_name') + ' ' +
form.get('last_name') + ' has been synced<br />';
},
failure: function(result, request) {
console.log("Exception");
console.log("Result: " + request.responseText);
syncInfo = 'Failed: ' + form.get('first_name') + ' ' +
form.get('last_name') + ' has been synced<br />';
},
callback: function(result, request) {
console.log(syncInfo);
if(i >= count - 1) {
mask.hide();
Ext.Msg.show({
title: 'Complete',
msg: syncInfo
});
}
}
});
}
}
var getDataToSync = function(store) {
var syncArray = new Array();
store.each( function(form, index) {
var isSynced = form.get('synced');
if (!isSynced) {
syncArray.push(form);
}
});
return syncArray
}
Phew! Are you still with me? The code for the views is relatively straight-forward. I’ve posted the source code on GitHub so you can get walk through the views (public/javascripts/app/views) there. However I want to walk through a couple of examples of following a click from the view through the controller to another view.
Views and Controller: contacts.js
Here is a snippet from ContactsList.js. This is the button and handler for the “new” button on the top toolbar. When you click on the ‘new’ button on the toolbar the framework will take you to the controller.
//ContactsList.js
...
{
text: 'new',
ui: 'confirm',
handler: function() {
Ext.dispatch({
controller: app.controllers.contacts,
action: 'newContact',
animation: {type: 'slide', direction: 'left'}
});
}
}
...
The controller is in public/javascripts/app/controllers and is called contacts.js. From the newContact action in the controller we can see that the controller is setting the active panel on the viewport to the contactNew panel. One thing to note: it is setting the active panel to an instance of the new panel. Notice the lower-case “c” on contactNew. We are using the instances of the panels that were created in the Viewport code.
...
newContact: function(options) {
app.views.viewport.setActiveItem(app.views.contactNew, options.animation);
},
...
A more complex example of moving from panel to panel involves selecting an item from the list. From the ContactList panel you can click on an item to edit it. There are two click handlers: onItemDisclosure handles clicks directly on the arrow and onItemTap handles clicks on the row. They both do the exact same thing except that onItemDisclosure has access to the actual records whereas onItemTap has to translate the record from the item. The code below should look familiar as compared to the code above with one significant addition: We are sending an id to the controller. The id and the animation are both transmitted to the controller in an ‘options’ object.
//ContactsList.js
...
onItemTap: function(item) {
record = this.getRecord(item);
Ext.dispatch({
controller: app.controllers.contacts,
action: 'show',
id: record.getId(),
animation: {type: 'slide', direction: 'left'}
});
}
...
The controller uses the id from the options hash to retrieve the correct record for editing. If a contact is found in localstorage then the contactEdit view is updated with the data. The panel is updated by calling a method in the contactEdit view called updateWithRecord
// contacts.js
...
edit: function(options) {
var id = parseInt(options.id);
var contact = app.stores.localContacts.getById(id);
if(contact) {
app.views.contactEdit.updateWithRecord(contact);
app.views.viewport.setActiveItem(app.views.contactEdit, options.animation);
}
},
...
It is a little bit magical actually. As long as you are using a FormPanel it is really easy to update the fields with data from an existing record. By calling the ‘load’ method (this.load(record)) on the FormPanel and passing it the record each of the fields is mapped to the record object using the ‘name’ of the field. For example if the first_name of the record is populated it will automatically fill in the textfield where name: ‘first_name’. It will do this for all fields where the name equals the field from the record. It is good to keep this in mind when designing your forms: Make sure to name your fields that same as your model fields.
// ContactEdit.js
...
updateWithRecord: function(record) {
this.load(record);
var topToolbar = this.getDockedItems()[0];
topToolbar.getComponent('cancel').record = record;
var bottomToolbar = this.getDockedItems()[1];
bottomToolbar.getComponent('save').record = record;
bottomToolbar.getComponent('save').form = this;
}
Make sure to add references to the controller and model in your index.html.erb. Take care to add them in the following order. The Sencha framework will throw an error if the data for the ContactList is not available when you try to display the list. So it is important to load the model first, then the views.
<%= 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' %>
That is pretty much it for our application. There are some fun things in the source code, like a catalog where you can view a list of items from the server on your mobile device. Then touch the item to download it to the device for manipulation. Please post any questions or comments and I’ll try to help out.
Thanks for reading!
Sencha Touch application with Ruby on Rails – Part 1 March 10, 2011
Posted by hoitomt in Programming.6 comments
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.
Excellent Reference Materials December 12, 2010
Posted by hoitomt in Education, Observations, Programming, Self Improvement.add a comment
I have recently encountered two excellent resources:
1). Michael Hartl’s tutorial on Rails3. This is an excellent overview of Rails3, Test Driven Development, and the most popular tools for developing applications in Ruby on Rails. This tutorial is really for anyone that is involved with producing applications for the Internet. If you are a graphic designer it will help you learn to start adding database functionality to your sites. If you are a developer the tutorial will introduce you to the best language/framework available for creating web-based applications (Ruby on Rails). And if you already know Ruby On Rails the tutorial is truly like “looking over the shoulder of an expert RoR programmer” as the quotes claim. Fantastic and well worth the $95 for book and videos.
2). The Intelligent Entrepreneur by Bill Murphy Jr. I came across this book accidentally when my wife brought it home from the library for herself. I picked it up and started reading and couldn’t put it down. The title of the book sounds stuffy and the quotes don’t make it seem like it is going to be an inspiring read but this is an excellent book for anybody that has entrepreneurial dreams. The stories are inspiring and show you 3 ways in which businesses can be started. They also form an anecdotal account of the business climate of the late 90′s early 00′s, most notably the Internet boom and collapse. It is fascinating to read about actual entrepreneurs dealing with the challenges of that time.
One particular line prompted me to write this blog post. On the second to last page of the book he refers to a book written by a Harvard Business School professor that tries to “show readers how to juggle … the four key elements in work and life: happiness, achievement, significance, and legacy.” That line nailed it for me for that is exactly what what I’m searching for. I have asked myself the question often: Why do I want to run my own business? I have a great life with a well-paying job and low stress. After reading that line I realized that I only have 1 of the 4 (happiness). I want them all. In my opinion those four factors define complete self-fulfillment.
Calendar Date Select in Ruby on Rails 2.3.5 May 15, 2010
Posted by hoitomt in Programming.add a comment
Calendar Date Select is an excellent date picker for Ruby on Rails. I didn’t create or figure out any of the following. This is merely a combination/re-blogging of various other posts, including instructions from the calendar date select home page
- Get the gem of calendar date select: ‘gem install calendar_date_select’
- Update your project’s environment.rb to include the calendar_date_select gem: ‘config.gem “calendar_date_select” ‘
- Unpack the gems to your project. This is not required but it makes life easier: ‘rake gems:unpack’
- Start your server and navigate to your home page to make sure everything is working.
- The version in the gem has a line that needs to be updated for Rails 2.3.5. If you get the error calendar_date_select undefined method `calendar_date_select_includes’ when you start the server and navigate the site you need to update the calendar_date_select file.
- Go to project_root\vendor\gems\calendar_date_select-1.16.1\lib and update line 5 of calendar_date_select.rb.
- Before Change: if Object.const_defined?(:Rails) && File.directory?(Rails.root.to_s + “/public”)
- After Change: if Object.const_defined?(:Rails) && File.directory?(Rails.public_path)
- Thanks for this suggestion goes to this guy
- In your layout or page add references to the default javascript files and the calendar date select files
- Put this one first: <%= javascript_include_tag :defaults %>
- <%= calendar_date_select_includes “silver” %>
- Check out the calendar_date_select demo page for examples and code snippets.
- Another excellent blog post reference from the calendar date select home page
Wild hair up my…. November 11, 2009
Posted by hoitomt in Programming.add a comment
Two posts in one day, oh you lucky readers. This one is really more of a rant on Java Enterprise. I think I’ve said this before but here it is again: JAVA ENTERPISE/J2EE IS NOT FOR BEGINNERS. It isn’t even for beginning programmers. The complexity of the Java Enterprise software suite is absolutely mind-boggling.
I’ve just started as a brand-spanking new developer at my employer and I was blown away by how complex our application was. I thought I understood MVC frameworks and how Java Enterprise interacted with them but I didn’t. As a quick sample here are three design patterns that we use:
- Struts where form actions from the .jsp pages are tied to the java Command classes using an XML page. The data from the .jsp page populates a Value Object (VO).
- Then the Command class calls a Delegate which calls a Session Bean which calls a Business Object (BO). The business logic is mostly located in the BO but some of it is sprinkled into the Session Bean. Once the BO gets through with validating the data from the VO.
- The BO then calls the DAO to persist the data to our database
I’m seriously not trying to make this sound complex, that is as easy as I can write it. This is my favorite: Command -> Delegate -> Session Bean -> BO -> DAO. 5 classes!
Please understand; I’m not bashing our setup. I’ve been in the work force long enough to know that when you encounter, what appears to be, overly complicated processes/designs there were very good reasons and a bunch of really smart people behind the design and implementation. And just like the old mainframe world, due to the cost of changing, many of us new programmers are going to have to learn the existing code so we can remain gainfully employed
The amazing thing is how early in the evolution of software we really are. It’s encouraging to see guys like Adam Bien writing really great posts about how to use EJB 3.0 and Java5/6 to greatly simplify your code. Java Enterprise is not that far removed from Cobol, which is not that far removed from punch card stacks. It feels like quantum leaps because each one is so much better than it’s predecessor.
We are still mostly at the mercy of the computer’s native tongue when it comes to writing software. The next twenty years will be very exciting as new languages and hardware are created so that we can get the computer to start understanding our language for a change.
public class StoreFormData {
Take first name, last name, and address;
Store them in a place where I can retrieve them later;
Please make sure the zip code, city, and state are valid with each other;
And one more thing, please make sure it is a valid phone number;
And if anybody else needs this data while I’m using it, don’t let them have it until I’m done;
Thanks
}
That’s what user friendly software looks like
Macro De-Duplication of data August 26, 2009
Posted by hoitomt in Observations, Programming.add a comment
I thought I would use some buzz words in the title to get everybody excited. I’m not referring to removing duplicate records in databases and tables. I’m referring to an overall strategy where application developers should be looking to reduce the amount of data that is stored in their databases in context of the entire Web. Through the use of web services and a some other cool new technologies we should be looking to connect to external sources to retrieve data as it is needed. Web-wide data warehouses are sparsely available right now but they are coming online. For example do you really need to store a person’s address if you can go to the address source to get their latest address every time?
Yes, there are hurdles (speed and reliability) but with every passing year these these hurdles get lower and lower. But there are also huge benefits such as access to the most up to date information. Corporations are already leveraging Web Services to communicate between their vast number of internal applications. Many coporations are going to SOA (Service Orientated Architecture) platforms across their business. Independent developers/web designers need to start thinking the same way. The maturation of the Internet relives us from having to store non-proprietary/non-mission critical data in a “local” database.



