Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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.

0. 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 databaseNote that the smartclient gem has not yet been updated for Rails 4.
  • SmartClient SDK, at least version 8.3p. This can be downloaded from here.  Any edition is OK, including the free LGPL version.

1.

...

 Set up the application

(a) Create the Rails app

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

Code Block
rails new smartclient_app

...

(b) Install required gems

Next open the Gemfile in the project directory and add the 'smartclient' gem. You can download the gem from github. The smartclient gem supports the various helper classes and methods for the SmartClient framework. 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 easilyarticle – you can also see the gem source on github.

Code Block
titleGemfile
gem "smartclient", "~> 0.0.7"
gem 'mysql2'

Then run the bundle command on the console:

Code Block
bundle install

...

(c) Configure the 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

...

  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.

...

you will need to configure database.yml to your liking.

(d) Create the database

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:

Code Block
rake db:create:all

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

...

titleschema.rb

...

Note that if you end up with an error that looks like ExecJS::RuntimeUnavailable, then you need to add a Javascript runtime to your Gemfile, for instance: 

Code Block
titleGemfile
 gem 'therubyracer'

 

2. Create Models, Migrations and Sample Data

(a) Create the models

First, generate some models for the sample data.

Code Block
rails g model supplyitem
rails g model employee

The smartclient gem uses mass-assignment, so you will need to enable that in the models.

Code Block
languageruby
titleapp/models/supplyitem.rb
class Supplyitem < ActiveRecord::Base
  attr_accessible :itemName, :sku, :description, :category, :units, :unitCost, :inStock, :nextShipment, :created_at, :updated_at
end
Code Block
languageruby
titleapp/models/employee.rb
class Employee < ActiveRecord::Base
  attr_accessible :name, :reportsTo, :job, :email, :employeeType, :employeeStatus, :salary, :orgUnit, :gender, :maritalStatus, :created_at, :updated_at
end

(b) Define and Run Migrations

Then, define some migrations to set up the tables for the sample data. You can edit the blank migrations created for the models.

Code Block
languageruby
titledb/migrate/2014XXXX_create_suppyitems.rb
class CreateSupplyitems < ActiveRecord::Migration
  def change                   
    create_table :supplyitems do |t| 
      t.string   "GenderitemName"
      t.string   "MaritalStatussku"
      t.datetimestring   "created_at",description"
      t.string   :null => false"category"
      t.datetimestring   "updated_atunits",
    :null => falset.float   end "unitCost"
 create_table "supplyitems", :primary_key => "itemID", :force => true do |t| t.boolean  "inStock"
      t.stringdatetime "nextShipment"
 "itemName"     t.stringtimestamps
   "SKU" end
   t.string   "description"
    t.string   "category"end
end
Code Block
languageruby
titledb/migrate/2014XXXX_create_employees.rb
class CreateEmployees < ActiveRecord::Migration
  def change          t.string   "units"     t.float 
  "unitCost"  create_table :employees do |t.boolean|
 "inStock"     t.datetimestring   "nextShipmentname"
      t.datetimestring   "created_atreportsTo",
  :null => false   t.string   "job"
      t.string   "email"
      t.string   "employeeType"
      t.string   "employeeStatus"
      t.float    "salary"
      t.string   "orgUnit"
      t.string   "gender"
      t.datetimestring   "updated_at",maritalStatus"
      t.timestamps
  :null => falseend
  end
end

Once you have defined the table structures, you can run the rake command and then the new table will be created on the MySQL to run the migrations and create the tables in the database.

Code Block
rake db:schema:loadmigrate

(c) Generate sample data

In order to display the data on the list gridin a ListGrid, the table tables that were 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 create sample data by editing db/seeds.rb in the db directory of the applicationrails application, and then running a rake command.

Code Block
languageruby
titleseeds.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
  Supplyitem.create do itemID|item|
= count   item.itemName = "Pens stabiliner " + count.to_s
  SKU  item.sku = Random.rand(1000 ... 9999)
    item.description = "Description" + count.to_s
    item.category = "Office paper Product"
    item.units = "Ea"
    item.unitCost = Random.rand(0.1 ... 2)
    item.inStock = 1
    item.nextShipment = Time.now
  end

 Supplyitem Employee.create( :itemName => itemName,do |employee|
    employee.name = "Employee Name" + count.to_s
    employee.reportsTo = count.to_s
     :SKUemployee.job => SKU,"Employee Job" + count.to_s 
    employee.email = "employee" + count.to_s + "@gmail.com"
      :description =>employee.employeeType = "DescriptionEmployeeType" + itemIDcount.to_s,
    employee.employeeStatus = "EmployeeStatus" + count.to_s
    employee.salary = "Salary" + count.to_s
    :categoryemployee.orgUnit => category,"OrgUnit" + count.to_s
    employee.gender = "1"
    employee.maritalStatus = "1"
  end
  :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, 
    end

And you can run the command as follows;

Code Block
rake db:seed

Finally you can start implementing with SmartClient.

3. Adding SmartClient libraries to Rails

Eventually, you may want to integrate the SmartClient javascript libraries with the Rails "assets" pipeline. However, initially, it is easier to serve them from the public directory of your Rails app.

(a) Copy the SmartClient runtime

Unzip your SmartClient SDK and copy the complete "isomorphic" folder from smartclientRuntime/isomorphic to the public directory in your application's folder.

(b) Add the SmartClient javascript to your layout page

On pages for which you want SmartClient available, you'll need to add the usual libraries to the layout page. Here, we do this for the default layout.

Code Block
languagexml
title/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Smartclient</title>

  <script src="/isomorphic/system/modules/ISC_Core.js"></script>
  <script src="/isomorphic/system/modules/ISC_Foundation.js"></script>
  <script src="/isomorphic/system/modules/ISC_Containers.js"></script>
  <script src="/isomorphic/system/modules/ISC_Grids.js"></script>
  <script src="/isomorphic/system/modules/ISC_Forms.js"></script>
  <script src="/isomorphic/system/modules/ISC_DataBinding.js"></script>
  <script src="/isomorphic/skins/EnterpriseBlue/load_skin.js"></script>

  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

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. Create the Rails controller for data requests

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

Code Block
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 have created the new controller, you should edit the routes.rb

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

You will also need to delete the default starting page at public/index.html, so that your root route will work.

5. Handling the SmartClient data with the gem

(a)  Handling a "fetch" request using RestDataSource

In order to handle SmartClient data requests, you will need to setup a RestDataSource on the client, and a mechanism to respond on the sever.

To setup a RestDataSource, you can put some Javascript code in the Rails asset pipeline. For instance, you could create app/assets/javascripts/datasource/supplyItem.js, with the following contents:

Code Block
languagejs
titleapp/assets/javascripts/datasource/supplyItem.js
isc.RestDataSource.create({
    ID: "supplyItem",
    fields: [
        {name: "id", type: "sequence",  hidden:EmployeeType => EmployeeTypetrue, primaryKey: true},
        {name: "itemName", type: "text", title: "Item", length: 128,  required:EmployeeStatus => EmployeeStatus true},
        {name: "sku", type: "text", title: "SKU", length: 10, required: true},
  :Salary => Salary,    {name: "description", type: "text", title: "Description", length: 2000},
        {name:OrgUnit => OrgUnit "category", type: "text", title: "Category", length: 128, required: true},
        {name: "units",  type:Gender => Gender "enum", title: "Units", length: 5, valueMap: [
            :MaritalStatus => MaritalStatus   )
end

And you can run the command as follows;

Code Block
rake db:seed

Finally you can start implementing with smartclient.

 

2. Create two Models

Code Block
rrails g model supplyitem
rails g model employee

...

titleapp/model/supplyitem.rb

...

"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"
});

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, since the DSResponse class defines the response structure in the smartclient gem.

Code Block
title/app/modelcontrollers/employeesmartclient_controller.rb
require 'DSResponse'
class EmployeeSmartclientController < ActiveRecord::BaseApplicationController   attr_accessible :EmployeeId,     
    def index
       :Name,
    end
     
    def data
  :ReportsTo,    # get all supplyitems from the database
      @supplyItems =  :Job,Supplyitem.all.to_a
      # get the count of the supplyitems
       :Email, supplyitems_count = Supplyitem.count
      response = DSResponse.new
      response.data = @supplyItems
:EmployeeType,      response.startRow = 0
      response.endRow = supplyitems_count - 1
:EmployeeStatus,      response.status = 0
      response.totalRow = supplyitems_count   :Salary,   
             
  :OrgUnit,    @result = { :response => response }
       
 :Gender,     render json: @result
           :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.

...

end
end

Then, to actually use this to draw a ListGrid, you can put some Javascript code in a Rails view. (Ultimately, you'll want to put this kind of code in the asset pipeline as well, but it's easy to illustrate using a view).

Code Block
title/app/views/layoutssmartclient/applicationindex.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Smartclient</title><SCRIPT>
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700, 
  <%= stylesheet_link_tag height: 224,
 "application", :media => "all" %> alternateRecordStyles: true,
  <script>var isomorphicDir =dataSource: "./isomorphic/";</script>supplyItem",
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html> autoFetchData: true
}); 
</SCRIPT>

Now, you should be able to start the server:

Code Block
rails s

And navigate to http://localhost:3000/ to see the results!

(b) Adding Simple Criteria, Sort, and Data Paging

First, change the ListGrid to enable data paging (with dataPageSize) and filtering and sorting (with filterEditor).

Code Block
title/app/assetsviews/javascriptssmartclient/applicationindex.html.jserb
//= 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.

Code Block
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

Code Block
title/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)

Code Block
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:

Code Block
titleindex.html.erb
<SCRIPT>
isc.RestDataSource.<SCRIPT>
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700,
    height: 224, 
    alternateRecordStyles: true,
    dataSource: "supplyItem",
    showFilterEditor: true,
    autoFetchData: true,
    dataPageSize: 10
});
</SCRIPT>

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:

Code Block
languagejs
titleapp/assets/javascripts/datasource/supplyItem.js
isc.RestDataSource.create({
    "ID": "suppyItemsupplyItem",
    "fields": [
        {"name": "itemIDid", "type": "sequence", "hidden":" true", "primaryKey":" true"},
        {"name": "itemName", "type": "text", "title": "Item", "length":" 128", "required":" true"},
        {"name": "SKUsku", "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: [ 
            "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: "dataURL":fetch", dataProtocol: "postMessage", dataURL: "/smartclient/data"}
    ]
});
isc.ListGrid.create(

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

Code Block
{
    IDdataSource: "suppyItemsupplyItem",
    widthoperationType: 700"fetch",
 height:  224, alternateRecordStylesstartRow: true0,
    dataSourceendRow: suppyItem10,
    autoFetchDatatextMatchStyle: 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/
Code Block
title
"substring",
    componentId: "supplyItemGrid",
    data: {
        units: "Pkt",
        unitCost: "a"
        ...
    },
    oldValues: null
}

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

Code Block
titlesmartclient_controller.rb
require 'DSResponseRPCManager'
class SmartclientController < ApplicationController             
    def index

      
    end

    
    def data 
     # getrequest all supplyitems from the database
      @supplyItems = Supplyitem.find(:all)
      # get the count of the supplyitems
      supplyitems_count =  Supplyitem.count= params[:smartclient]      
	  rpc = RPCManager.new(request) 	
	  rpc.model = Supplyitem
      response@result = DSResponserpc.newprocessRequest 
     response.data =render @supplyItemsjson: @result
    end response.startRow = 0       response.endRow =
supplyitems_count - 1
      response.status = 0
      response.totalRow = supplyitems_count      
             
      @result = { :response => response }
       
      render json: @result
    end
end

 

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:

Code Block
isc.ListGridend 

The RPCManager helper class which the smartclient gem provides can process the request. When the RPCManager object parses the request, we need to set its model attribute to the model class (here Supplyitem).

Since we've changed the RestDataSource to use post rather than get for data, we'll need to make the same change in config/routes.rb.

Code Block
title/config/routes.rb
Smartclient::Application.routes.draw do 
  root to: 'smartclient#index'
  post 'smartclient/data' 
end

Navigate to http://localhost:3000/ again to see the results.

(c) Adding other CRUD operations

In this part, the code will be refactored from the previous sample, 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. 

First, add some additional operationBindings to the DataSource.

Code Block
title/app/assets/javascripts/datasource/supplyItem.js
isc.RestDataSource.create({
    ID: "supplyItem",
    widthfields: 700,[
        height{name: 224"id", type: "sequence", hidden: true,  alternateRecordStylesprimaryKey: true},
    dataSource: supplyItem,   {name: "itemName", showFilterEditortype: true"text", title: "Item", length: 128, autoFetchDatarequired: true},
     dataPageSize   {name: 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:

Code Block
isc.RestDataSource.create({
    "ID": "supplyItem""sku", type: "text", title: "SKU", length: 10, required: true},
    "fields":[
        {"name": "itemIDdescription", "type": "sequencetext", title: "hiddenDescription":"true", "primaryKey":"true"length: 2000},
        {"name": "itemNamecategory", "type": "text", "title": "ItemCategory", "length":" 128", "required":" true"},
        {"name": "SKUunits", "type": "textenum", "title": "SKUUnits", "length":"10" 5, "required":"true"},valueMap: [ 
            "Roll",  {"name":"description"Ea", "Pkt", "Set", "Tube", "typePad":, "textReam", "titleTin":, "DescriptionBag", "lengthCtn":, "2000"Box"
        ]},
        {"name": "categoryunitCost", "type": "textfloat", "title": "CategoryUnit Cost", "length":"128", "required":"true" },required: true, validators: [
            {"name"type: "unitsfloatRange", "type":"enum", "title":"Units", "length":"5"min: 0, errorMessage: "Please enter a valid (positive) cost"},
            "valueMap":["Roll",{type: "EafloatPrecision", "Pkt", "Set", "Tube", "Pad", "Ream", "Tin", "Bag", "Ctn", "Box"]precision: 2, errorMessage: "The maximum allowed precision is 2"}
        ]},
        {"name": "unitCostinStock", "type": "floatboolean", "title": "UnitIn CostStock"},
   "required":"true     {name: "nextShipment", type: "date", title: "Next Shipment"} 
    ],

  "validators":[  dataFormat: "json",        
     {"type":"floatRange", "min":"0", "errorMessage":"Please enter a valid (positive) cost"},
criteriaPolicy: "dropOnChange", 
    operationBindings: [       
        {"type"operationType: "floatPrecisionfetch", "precision"dataProtocol: "2postMessage", "errorMessage"dataURL:"The maximum allowed precision is 2"} "/smartclient/data"},             ]   
     },   
        {"name":"inStock{operationType: "add", dataProtocol: "postMessage", "type":"boolean", "title":"In StockdataURL: "/smartclient/data"},
        {operationType: "update", dataProtocol: "postMessage", dataURL: "/smartclient/data"},
        {"name"operationType: "nextShipmentremove", "type"dataProtocol: "datepostMessage", "title"dataURL:"Next Shipment "/smartclient/data"}
    ],   
});

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

Code Block
title/app/views/smartclient/index.html.erb
<SCRIPT>
isc.ListGrid.create({
    "dataFormat"ID: "jsonsupplyItemGrid",
    "operationBindings"width: [700,
    height: 224, 
    alternateRecordStyles: true,
   { "operationType"dataSource: "fetchsupplyItem",
"dataProtocol": "postMessage", "dataURL": "/smartclient/data" }    showFilterEditor: true,
    autoFetchData: true,
    dataPageSize: 20,
    canEdit: true,
          ] canRemoveRecords: true
});

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

Code Block

isc.IButton.create({
    "dataSource":"suppyItem"top: 250,
    "operationType":"fetch",
    "startRow":0,
    "endRow":20title: "Edit New",
    "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. 

Code Block
titlesmartclient_controller.rb
require 'RPCManager'
class SmartclientController < ApplicationController             
    def index
      
    end
    
    def data 
      request = params[:smartclient]      
	  rpc = RPCManager.new(request) 	
	  rpc.model = 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 request and model, we need to set the model to the RPCManager object. After the new object of the RPCManager was created, if you call the processRequest, the gem will process the request.

The buildStandardCriteria method of the DataSource helper class is used when the user defines the filter for the front-end.

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.

Code Block
title/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.

Code Block
title/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:

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

We don't need to define the controller again.

The add, fetch, remove, update methods of the DataSource helper class of the gem is used for the CRUD through the RPCManager helper class.

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

4) Add queuing and transaction support 

SmartClient has advanced features for queuing multiple requests in one single request. This provides a mechanism for sending multiple requests to the server in a single HTTP turnaround, thus minimizing network traffic as well as allowing the server to treat multiple requests as a single transaction (if the server is able to do so). 

You need to add the save button for the transaction progress.

Code Block
titleapp/assets/javascript/smartclient_ui.js
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700, height: 224, alternateRecordStyles: true,
    dataSource: supplyItem,
    showFilterEditor: true,
    autoFetchData:true,
    dataPageSize:20,
    canEdit:true,
    canRemoveRecords:true,
    autoSaveEdits: false
});
isc.IButton.create({
    top: 250,
    title: "Edit New",
    click: "supplyItemGrid.startEditingNew()"
});
isc.IButton.create({
    top: 250,
    left: 100,
    title: "Save all",
    click: "supplyItemGrid.saveAllEdits()"
});

We don't need to define the controller again.

The processTransaction method of the RPCManager is used for the transaction.

5)  Adding support for AdvancedCriteria

In this part, we will describe how to modify the FilterBuilder and the underlying AdvancedCriteria system, to build functionality resembling this showcase sample (but using our supplyItem DataSource, instead of the one in the webpage)

Modify app/assets/javascripts/smartclient_ui.js to include the relevant code for creating the FilterBuilder:

Code Block
titlesmartclient_ui.js
isc.FilterBuilder.create({
    "ID": "advancedFilter",
    "dataSource": "supplyItem",
    "topOperator": "and"
});

The ListGrid also requires additional code to add the FilterBuilder. This will require adding a vertical layout (VStack), together with the grid and the button needed to add for applying the filter on the ListGrid. Also going to add a horizontal layout (HStack) which will contain the two already existing buttons used for saving all data and creating a new record:

Code Block
titlesmartclient_ui.js
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700, height: 224, alternateRecordStyles: true,
    dataSource: supplyItem,
    autoFetchData:true,
    dataPageSize:20,
    canEdit:true,
    canRemoveRecords:true,
    autoSaveEdits: false
});
isc.IButton.create({
    ID: "filterButton",
    title: "Filter",
    click: function () {
        supplyItemGrid.filterData(advancedFilter.getCriteria());
    }
});
isc.HStack.create({
    membersMargin: 10,
    ID: "gridButtons",
    members: [
        isc.DynamicForm.create({
            values: { dataSource: "Change DataSource" },
            items: [
                { name: "dataSource", showTitle: false, editorType: "select",
                    valueMap: ["supplyItem", "employee"],
                    change: function (form, item, value, oldValue) {
                        if (!this.valueMap.contains(value)) return false;
                        else {
                            supplyItemGrid.setDataSource(value);
                            advancedFilter.setDataSource(value);
                            supplyItemGrid.filterData(advancedFilter.getCriteria());
                        }
                    }
                }
            ]
        }),click: "supplyItemGrid.startEditingNew()"
});
</SCRIPT>

The add, fetch, remove, update methods of the DataSource helper class of the gem are used for the CRUD through the RPCManager helper class.

Navigate to http://localhost:3000/ again to see the results.

(d) Add queuing and transaction support 

SmartClient has advanced features for queuing multiple requests in one single request. This provides a mechanism for sending multiple requests to the server in a single HTTP turnaround, thus minimizing network traffic as well as allowing the server to treat multiple requests as a single transaction (if the server is able to do so). 

You need to add the save button for the transaction progress.

Code Block
titleapp/views/smartclient/index.html.erb
<SCRIPT>
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700,
    height: 224,
    alternateRecordStyles: true,
    dataSource: "supplyItem",
    showFilterEditor: true,
    autoFetchData: true,
    dataPageSize: 20,
    canEdit: true,
    canRemoveRecords: true,
    autoSaveEdits: false
});
isc.IButton.create({
    top: 250,
    title: "Edit New",
    click: "supplyItemGrid.startEditingNew()"
});
isc.IButton.create({
    top: 250,
    left: 100,
    title: "Save all",
    click: "supplyItemGrid.saveAllEdits()"
});
</SCRIPT>

We don't need to define the controller again.

The processTransaction method of the RPCManager is used for the transaction.

(e)  Adding support for AdvancedCriteria

In this part, we will describe how to modify the FilterBuilder and the underlying AdvancedCriteria system, to build functionality resembling this showcase sample (but using our supplyItem DataSource, instead of the one in the webpage)

Modify app/assets/javascripts/smartclient_ui.js to include the relevant code for creating the FilterBuilder:

The ListGrid also requires additional code to add the FilterBuilder. This will require adding a vertical layout (VStack), together with the grid and the button needed to add for applying the filter on the ListGrid. Also going to add a horizontal layout (HStack) which will contain the two already existing buttons used for saving all data and creating a new record:

Code Block
titleapp/views/smartclient/index.html.erb
<SCRIPT>
isc.FilterBuilder.create({
    ID: "advancedFilter",
    dataSource: "supplyItem",
    topOperator: "and"
});
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700,
    height: 224,
    alternateRecordStyles: true,
    dataSource: "supplyItem",
    autoFetchData: true,
    dataPageSize: 20,
    canEdit: true,
    canRemoveRecords: true,
    autoSaveEdits: false
});
isc.IButton.create({
    ID: "filterButton",
    title: "Filter",
    click: function () {
        supplyItemGrid.filterData(advancedFilter.getCriteria());
    }
});
isc.HStack.create({
    membersMargin: 10,
    ID: "gridButtons",
    members: [
        isc.IButton.create({
            top: 250,
            title: "Edit New",
            click: "supplyItemGrid.startEditingNew()"
        }),
        isc.IButton.create({
            top: 250,
            left: 100,
            title: "Save all",
            click: "supplyItemGrid.saveAllEdits()"
        }),
    ]
});
isc.VStack.create({
    membersMargin: 10,
    members: [advancedFilter, filterButton, supplyItemGrid, gridButtons]
});
</SCRIPT>

Also note, the filter has been removed top of the grid, as it is being replaced with the FilterBuilder.

...

The buildCriterion method is called for this part.

...

Navigate to http://localhost:3000/ again to see the results.

(f)  Make it data-driven

This example takes the previous sample and makes it data driven and adds a way for the user to define new DataSource types. It will also be extended to define a new DataSource and change the user interface to allow the user to switch between the two DataSources.

...

Code Block
titleapp/assets/javascripts/datasource/employee.js
isc.RestDataSource.create({
    "ID": "employee",
    "fields": [
        { "name": "Nameid", "title"type: "Namesequence", "type"hidden: "text"true, "length"primaryKey: "128" true},
        { "name": "EmployeeIdname", "title": "Employee IDName", "type": "integertext", "primaryKey": "true", "hidden": "true" length: 128},
        { "name": "ReportsToreportsTo", "title": "Manager", "type": "integer", "required": "true", "rootValue": "1", "detail": "true" },
        { "name": "Jobjob", "title": "Title", "type": "text", "length": "128" },
        { "name": "Emailemail", "title": "Email", "type": "text", "length": "128" },
        { "name": "EmployeeTypeemployeeType", "title": "Employee Type"," type": "text", "length": "40" },
        { "name": "EmployeeStatusemployeeStatus", "title": "Status", "type": "text", "length": "40" },
        { "name": "Salarysalary", "title": "Salary", "type": "float" },
        { "name": "OrgUnitorgUnit", "title": "Org Unit", "type":"text", "length":" 128" },
        {name: "namegender", title: "Gender", type:"title": "Gendertext", length: 7, valueMap: [
            "male", "type":"textfemale"
        ]},
        {name: "maritalStatus", "length":"7",
title: "Marital Status", type: "text", length: 10, valueMap: [
           "valueMap": ["malemarried", "femalesingle"]
        ]},
    ],
   { "name"dataFormat: "MaritalStatusjson", "title"
    operationBindings: [
        {operationType: "Marital Statusfetch", "type"dataProtocol: "textpostMessage", "length"dataURL: "10/smartclient/data"},
            "valueMap": ["married", "single"]    
    }     ]{operationType: "add", dataProtocol:    "dataFormatpostMessage", dataURL: "json/smartclient/data"},
    "operationBindings": [   {operationType: "update", dataProtocol: "postMessage", dataURL: "/smartclient/data"},
        { "operationType": "fetchremove", "dataProtocol": "postMessage", "dataURL": "/smartclient/data" },
    ]
});

For the user interface, a change is required to allow users to switch the current DataSource. Add a form with a drop-down with the DataSources to switch and place it in front of the grids below the ListGrid. This requires putting the form in the HStack layout used for the buttons.

Code Block
titleapp/views/smartclient/index.html.erb
<SCRIPT>
isc.FilterBuilder.create({
    ID: "advancedFilter",
                   { "operationType"dataSource: "addsupplyItem",
 "dataProtocol": "postMessage", "dataURL"   topOperator: "/smartclient/dataand"
},
);
isc.ListGrid.create({
    ID: "supplyItemGrid",
    width: 700,
   { "operationType"height: "update"224,
  "dataProtocol": "postMessage", "dataURL": "/smartclient/data" } alternateRecordStyles: true,
    dataSource: "supplyItem",
    autoFetchData: true,
    { "operationType"dataPageSize: "remove"20,
"dataProtocol": "postMessage", "dataURL": "/smartclient/data" }
 canEdit: true,
    canRemoveRecords: true,
    autoSaveEdits: ]false
});

Finally,  load this newly defined DataSource into the browser. For this edit the index.php and add the following code:

Code Block
titleapps/views/smartclient/index.html.erb
<%= javascript_include_tag "datasource/supplyitem" %>
<%= javascript_include_tag "datasource/employee" %>
<%= javascript_include_tag "smartclient_ui" %>

On the user interface, a change is required to allow users to switch the current DataSource. Add a form with a drop-down with the DataSources to switch and place it in front of the grids below the ListGrid. This requires putting the form in the HStack layout used for the buttons: 

Code Block
titlesmartclient_ui.js

isc.IButton.create({
    ID: "filterButton",
    title: "Filter",
    click: function () {
        supplyItemGrid.filterData(advancedFilter.getCriteria());
    }
});
isc.HStack.create({
    "membersMargin": 10,
    "ID": "gridButtons",
    "members": [
        isc.DynamicForm.create({
            "values": {
                dataSource: "Change DataSource"
            },
            "items": [
{
               { "name": "dataSource", show"title"
                showTitle: false,
"editortype"                editorType: "select",
                    "valueMap": ["supplyItem", "employeesemployee"],
                    "change": function (form, item, value, oldValue) {
                        if (!this.valueMap.contains(value)) return false;
      value)) {
                 else {      return false;
                     supplyItemGrid.setDataSource(value);
} else {
                           advancedFiltersupplyItemGrid.setDataSource(value);
 
                          supplyItemGrid.filterData(advancedFilter.getCriteriasetDataSource()value);
                        }supplyItemGrid.filterData(advancedFilter.getCriteria());
                    }
                }
            }]
        }),
        isc.IButton.create({
            "top": 250,
            "title": "Edit New",
            "click": "supplyItemGrid.startEditingNew()"
        }),
           isc.IButton.create({
            "top": 250,
            "left": 100,
            "title": "Save all",
            "click": "supplyItemGrid.saveAllEdits()"
        })
    ]
});
isc.VStack.create({
    membersMargin: 10,
    members: [advancedFilter, filterButton, supplyItemGrid, gridButtons]
});
</SCRIPT>

Finally we should change the controller, when the user selects the method, we can get the model by get_datasource method of the RPCManager helper class.

 

Code Block
titlesmartclient_controller.rb
require 'RPCManager'
class SmartclientController < ApplicationController             
    def index
      
    end
    
    def data 
      request = params[:smartclient]
      rpc = RPCManager.new(request) 
      data_source = rpc.get_datasource
                 
      case data_source
      when 'supplyitem'
          # select supplyitems table
          model = Supplyitem
      when 'employee'
          # select employees table
          model = Employee          
      else
          # default
          model = Supplyitem
      end
      # set the table
      rpc.model = model
      # set the request parameters            
      @result = rpc.processRequest 
      render json: @result
    end           
end 

This example now shows a data-driven DataSource that allows users to add/remove/update two DataSources with two different entity DataSource, and also apply various filter criteria built with the Filter Builder.

The complete code for this sample project can be downloaded from hereNavigate to http://localhost:3000/ again to see the results.

6. smartclient gem 

As we explained about this gem before, this gem has the RPCManager, DSRequest, DSResponse, DataSource helper classes.

...