Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

This sample shows how to integrate a SmartClient front-end with Ruby on Rails (also known as RoR or just "Rails").

The content discussed here will explore a way to integrate RoR with SmartClient, by leveraging SmartClient javascript API's on the front-end and MVC architecture in RoR web application on the back-end to generate the result sets. In this sample, a DataSource will be created which inherits from RestDataSource. This will get the data from the database using the smartclient gem and will serialize it back to the DataSource in JSON format. The results will be displayed in a list grid.

Prerequisites

  • Ruby on Rails framework, at least version 3.x (requires Ruby 1.8.6 or higher). It can be downloaded for free from here.
  • MySQL Server has been used as the database.
  • SmartClient SDK, at least version 8.3p. This can be downloaded from here.  Any edition is OK, including the free LGPL version.

1. Setting up the application

Once you installed Rails on your development computer, first create the new Rails project with the command as follows:

rails new smartclient_app

After the new project has created, unzip your SmartClient SDK and copy the complete "isomorphic" folder from *smartclientRuntime\isomorphic to the public directory in your application's folder.

Next open the Gemfile in the project directory and add the 'smartclient' gem. You can download the gem from github. We will discuss how the smartclient gem was written at the end of this article.

The "smartclient version 0.0.7" supports the various helper classes and methods for the SmartClient framework, you can use it easily.

Gemfile
gem "smartclient", "~> 0.0.7"
gem 'mysql2'

Then run the bundle command on the console:

bundle install

Configuring a Database

The database to use is specified in a configuration file, config/database.yml. If you open this file in a new Rails application, you’ll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default:

  • The development environment is used on your development/local computer as you interact manually with the application.
  • The test environment is used when running automated tests.
  • The production environment is used when you deploy your application for the world to use.

 

If you choose to use MySQL database, your config/database.yml will look a little different. Here’s the development section:

 

development:
  adapter: mysql2
  encoding: utf8
  database: smartclient_development
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

 

If your development computer’s MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the development section as appropriate.

Creating database and sample SQL table

Now that you have your database configured, it’s time to have Rails create an empty database for you. You can do this by running a rake command:

rake db:create:all

And you need to define the schema.rb and create the table.

schema.rb
ActiveRecord::Schema.define(:version => 20130521133624) do
  create_table "employees", :primary_key => "EmployeeId", :force => true do |t|
    t.string   "Name"
    t.string   "ReportsTo"
    t.string   "Job"
    t.string   "Email"
    t.string   "EmployeeType"
    t.string   "EmployeeStatus"
    t.float    "Salary"
    t.string   "OrgUnit"
    t.string   "Gender"
    t.string   "MaritalStatus"
    t.datetime "created_at",     :null => false
    t.datetime "updated_at",     :null => false
  end
  create_table "supplyitems", :primary_key => "itemID", :force => true do |t|
    t.string   "itemName"
    t.string   "SKU"
    t.string   "description"
    t.string   "category"
    t.string   "units"
    t.float    "unitCost"
    t.boolean  "inStock"
    t.datetime "nextShipment"
    t.datetime "created_at",   :null => false
    t.datetime "updated_at",   :null => false
  end
end

Once you defined the table structures, you can run the rake command and then the new table will be created on the MySQL database.

rake db:schema:load

Generate sample data

In order to display the data on the list grid, the table that just was created should have sample rows.

You can do it by running a rake command, before you run the command, you need to define the seeds.rb in the db directory of the application.

seeds.rb
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
for count in 1..20
  itemID = count
  itemName = "Pens stabiliner " + count.to_s
  SKU = Random.rand(1000...9999)
  category = "Office paper Product"
  units = "Ea"
  unitCost = Random.rand(0.1...2)
  inStock = 1
  nextShipment = Time.now
  
  Supplyitem.create( :itemName => itemName, 
                    :SKU => SKU, 
                    :description => "Description" + itemID.to_s, 
                    :category => category, 
                    :units => units,
                    :unitCost => unitCost,
                    :inStock => inStock,
                    :nextShipment => nextShipment )
                  
  Name = "Employee Name" + count.to_s
  ReportsTo = count.to_s
  Job = "Employee Job" + count.to_s
  Email = "employee" + count.to_s + "@gmail.com"
  EmployeeType = "EmployeeType" + count.to_s
  EmployeeStatus = "EmployeeStatus" + count.to_s
  Salary = "Salary" + count.to_s
  OrgUnit = "OrgUnit" + count.to_s
  Gender = "1"
  MaritalStatus = "1"
   
  Employee.create( :Name => Name,
                  :ReportsTo => ReportsTo, 
                  :Job => Job, 
                  :Email => Email, 
                  :EmployeeType => EmployeeType, 
                  :EmployeeStatus => EmployeeStatus, 
                  :Salary => Salary, 
                  :OrgUnit => OrgUnit, 
                  :Gender => Gender,
                  :MaritalStatus => MaritalStatus   )
end

And you can run the command as follows;

rake db:seed

Finally you can start implementing with smartclient.

 

2. Create two Models


rrails g model supplyitem
rails g model employee


app/model/supplyitem.rb
class Supplyitem < ActiveRecord::Base
  attr_accessible :itemID, 
                  :itemName, 
                  :SKU, 
                  :description, 
                  :category, 
                  :units, 
                  :unitCost, 
                  :inStock, 
                  :nextShipment 
end
app/model/employee.rb
class Employee < ActiveRecord::Base
  attr_accessible :EmployeeId,
                  :Name, 
                  :ReportsTo, 
                  :Job, 
                  :Email, 
                  :EmployeeType, 
                  :EmployeeStatus, 
                  :Salary, 
                  :OrgUnit, 
                  :Gender,
                  :MaritalStatus 
end

3. Adding SmartClient library path to the layout view

When Rails renders a view as a response, it does so by combining the view with the current layout.

We need to add the SmartClient library path to the layout page.

/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Smartclient</title>
     
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <script>var isomorphicDir = "./isomorphic/";</script>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
/app/assets/javascripts/application.js
//= require ./isomorphic/system/modules/ISC_Core
//= require ./isomorphic/system/modules/ISC_Foundation
//= require ./isomorphic/system/modules/ISC_Containers
//= require ./isomorphic/system/modules/ISC_Grids
//= require ./isomorphic/system/modules/ISC_Forms
//= require ./isomorphic/system/modules/ISC_DataBinding
//= require ./isomorphic/skins/TreeFrog/load_skin

Adding these script tags will now ensure the master page will manage the loading of SmartClient, providing that this page is always defined as the master page for the view.

4. Creating the DataSource and Rails controller for data requests

We can use the rails command to create the new controller.

rails g controller smartclient

The Rails router recognizes URLs and dispatches them to a controller’s action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.

So after you created the new controller,  you should define the routes.rb

/config/routes.rb
Smartclient::Application.routes.draw do 
  root to: 'smartclient#index'
  get 'smartclient/data' 
end

By running rails server command, the application will start and a grid fetching and displaying the rows found in the table will be shown. (http://localhost:3000)

rails s

 

Now we are ready everything! Once we define the user interface javascript with smartclient as the JSON format, we can implement and control any component of the smartclient with the gem through the controller class.

5. Handling the SmartClient data with the gem

1)  Handling a Fetch Request From RestDataSource

Open the created view for the application (in the Solution Explorer, in the app/views/smartclient folder, open Index.html.erb) and change it's content to:

index.html.erb
<SCRIPT>
isc.RestDataSource.create({
    "ID": "suppyItem",
    "fields":[
        {"name":"itemID", "type":"sequence", "hidden":"true", "primaryKey":"true"},
        {"name":"itemName", "type":"text", "title":"Item", "length":"128", "required":"true"},
        {"name":"SKU", "type":"text", "title":"SKU", "length":"10", "required":"true"},
        {"name":"description", "type":"text", "title":"Description", "length":"2000"},
        {"name":"category", "type":"text", "title":"Category", "length":"128", "required":"true" },
        {"name":"units", "type":"enum", "title":"Units", "length":"5",
            "valueMap":["Roll", "Ea", "Pkt", "Set", "Tube", "Pad", "Ream", "Tin", "Bag", "Ctn", "Box"]
        },
        {"name":"unitCost", "type":"float", "title":"Unit Cost", "required":"true",
            "validators":[
                {"type":"floatRange", "min":"0", "errorMessage":"Please enter a valid (positive) cost"},
                {"type":"floatPrecision", "precision":"2", "errorMessage":"The maximum allowed precision is 2"}
            ]
        },
  
        {"name":"inStock", "type":"boolean", "title":"In Stock"},
        {"name":"nextShipment", "type":"date", "title":"Next Shipment"}
    ],
  
    "dataFormat":"json",
    "dataURL":"/smartclient/data"
});
isc.ListGrid.create({
    ID: "suppyItem",
    width: 700, height: 224, alternateRecordStyles: true,
    dataSource: suppyItem,
    autoFetchData: true
}); 
</SCRIPT>

Create a datasource which inherits RestDataSource and define the data URL to a controller. This controller will handle the requests from the DataSource. The fields in the newly created DataSource will match the columns in the database table created earlier.

Note, The required format  is 'json' and the data url is set to 'smartclient/data', which means the controller name will need to be set to 'SmartclientController' and the method name will be 'fetch'. As everything else is unchanged, the DataSource will send the request parameters in the URL of the request.

Now, describe the controller to send back the response. You need to include the DSResponse.rb file in the header, the DSResponse class defines the response structure.

/app/controllers/smartclient_controller.rb
require 'DSResponse'
class SmartclientController < ApplicationController         
    def index
       
    end
     
    def data
      # get all supplyitems from the database
      @supplyItems = Supplyitem.find(:all)
      # get the count of the supplyitems
      supplyitems_count =  Supplyitem.count
      response = DSResponse.new
      response.data = @supplyItems
      response.startRow = 0
      response.endRow = supplyitems_count - 1
      response.status = 0
      response.totalRow = supplyitems_count      
             
      @result = { :response => response }
       
      render json: @result
    end
end

The complete code for this sample project can be downloaded from github.

2) Adding simple Criteria, Sort, and Data Paging

Firstly, Change the DataSource so, instead of using the URL parameters for sending data to the back-end (filter, etc), send parameters as a JSON payload to the POST method. Additionally, configure the list grid to let the back-end do the pagination and filtering.

For this, remove the 'dataFetchMode:"local"' attribute on the grid. This will cause the grid to send criteria and pagination information to the back-end:

isc.ListGrid.create({
    ID: "supplyItem",
    width: 700,
    height: 224, 
    alternateRecordStyles: true,
    dataSource: supplyItem,
    showFilterEditor: true,
    autoFetchData: true,
    dataPageSize: 20
});

Please note:, Grid filters have also been enabled with showFilterEditor:true. This allows for easier testing later. Also note that the dataPageSize has been set to 20, to show pagination working.

Additionally, change the DataSource definition to use POST instead of the default GET HTTP method used by normal fetch operations. This will post the JSON as a payload to the server instead of passing it the variables in a URL. To do this, add an operationBinding definition to the DataSource. The DataSource should now look like this:

isc.RestDataSource.create({
    "ID": "supplyItem",
    "fields":[
        {"name":"itemID", "type":"sequence", "hidden":"true", "primaryKey":"true"},
        {"name":"itemName", "type":"text", "title":"Item", "length":"128", "required":"true"},
        {"name":"SKU", "type":"text", "title":"SKU", "length":"10", "required":"true"},
        {"name":"description", "type":"text", "title":"Description", "length":"2000"},
        {"name":"category", "type":"text", "title":"Category", "length":"128", "required":"true" },
        {"name":"units", "type":"enum", "title":"Units", "length":"5",
            "valueMap":["Roll", "Ea", "Pkt", "Set", "Tube", "Pad", "Ream", "Tin", "Bag", "Ctn", "Box"]
        },
        {"name":"unitCost", "type":"float", "title":"Unit Cost", "required":"true",
            "validators":[
                {"type":"floatRange", "min":"0", "errorMessage":"Please enter a valid (positive) cost"},
                {"type":"floatPrecision", "precision":"2", "errorMessage":"The maximum allowed precision is 2"}
            ]
        },
  
        {"name":"inStock", "type":"boolean", "title":"In Stock"},
        {"name":"nextShipment", "type":"date", "title":"Next Shipment"}
    ],  
    "dataFormat":"json",
    "operationBindings": [
                { "operationType": "fetch", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" },                
            ] 
});

If the sample,was run at this stage the payload sent to the server will look similar to this:

{
    "dataSource":"suppyItem",
    "operationType":"fetch",
    "startRow":0,
    "endRow":20,
    "textMatchStyle":"substring",
    "componentId":"suppyItem",
    "data":{
        "units":"Pkt",
        "unitCost":"a"
        ...
    },
    "oldValues":null
}

The "smartclient" gem supports the DSRequest class to define the object which is created by de-serializing from JSON and which mimics the previous JSON. 

smartclient_controller.rb
require 'RPCManager'
class SmartclientController < ApplicationController             
    def index
      
    end
    
    def data 
      request = params[:smartclient]      
      rpc = RPCManager.new(request, Supplyitem)
      @result = rpc.processRequest 
      render json: @result
    end           
end 

The RPCManager helper class which the smartclient gem supports can process the request. When the new object of the RPCManager class parses two parameters, the first parameter is for the request, and the second parameter is for the model, in order words the second parameter selects the table in the database. After the new object of the RPCManager was created, if you call the processRequest, the gem will process the request.

The complete code for this sample project can be downloaded from github.

3) Addding other CRUD operations

In this part the code will be refactored from the previous sample (Adding simple criteria, sorting and data paging), the correct filtering functions will be implemented for the text columns and additional functionality will be added to have a fully working DataSource implementation.

In the previous sample, filtering by a text value would not select rows which contained the filter term, only those that were specifically equal to it. To correctly filter by a text value, it is necessary to identify which columns are of type text and which are other types. To achieve this, the declaration of the DataSource needs to be moved to a separate file, which will be loaded on both the client and server side. 

Firstly, create a new folder, called 'datasource' in the /app/assets/javascripts directory.  In this folder, create a javascript file which defines the DataSource itself. Give this file the same name as the ID of the DataSource.

/app/assets/javascripts/datasource/supplyitem.js
isc.RestDataSource.create({
    "ID": "suppyItem",
    "fields":[
        {"name":"itemID", "type":"sequence", "hidden":"true", "primaryKey":"true"},
        {"name":"itemName", "type":"text", "title":"Item", "length":"128", "required":"true"},
        {"name":"SKU", "type":"text", "title":"SKU", "length":"10", "required":"true"},
        {"name":"description", "type":"text", "title":"Description", "length":"2000"},
        {"name":"category", "type":"text", "title":"Category", "length":"128", "required":"true" },
        {"name":"units", "type":"enum", "title":"Units", "length":"5",
            "valueMap":["Roll", "Ea", "Pkt", "Set", "Tube", "Pad", "Ream", "Tin", "Bag", "Ctn", "Box"]
        },
        {"name":"unitCost", "type":"float", "title":"Unit Cost",
            "validators":[
                {"type":"floatRange", "min":"0", "errorMessage":"Please enter a valid (positive) cost"},
                {"type":"floatPrecision", "precision":"2", "errorMessage":"The maximum allowed precision is 2"}
            ]
        },
  
        {"name":"inStock", "type":"boolean", "title":"In Stock"},
        {"name":"nextShipment", "type":"date", "title":"Next Shipment"}
    ],
    "criteriaPolicy":"dropOnChange",
    "dataFormat":"json",
    "operationBindings": [
                { "operationType": "fetch", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" },                
                { "operationType": "add", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" },
                { "operationType": "update", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" },
                { "operationType": "remove", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" }
            ] 
});

Now we should change the user interface for the CRUD operations with the smartclient.

/app/assets/javascript/smartclient_ui.js
isc.ListGrid.create({
    ID: "supplyItem",
    width: 700,
    height: 224, 
    alternateRecordStyles: true,
    dataSource: supplyItem,
    showFilterEditor: true,
    autoFetchData: true,
    dataPageSize: 20,
    canEdit:true,
    canRemoveRecords:true
});
isc.IButton.create({
    top: 250,
    title: "Edit New",
    click: "suppylItem.startEditingNew()"
});

Now, modify the view file for the application to load this additional DataSource. You should edit the 'app/views/smartclient/index.html' file and change it's content to:

<%= javascript_include_tag "datasource/supplyitem" %>
<%= javascript_include_tag "smartclient_ui" %>

We don't need to define the controller again.

The complete code for this sample project can be downloaded from github.

4) Add queuing and transaction support 

  • No labels