Appcelerator Blog

The Leading Resource for All Things Mobile

Appcelerator Arrow Builder – Post Block Geocode Example

16 Flares 16 Flares ×

In addition to being able to easily create integrations to back end data sources, Arrow provides several facilitates for extending, aggregating and in general, mobile optimizing data for use in a mobile application. For example, say you have CRM account or contact data in Salesforce that you want to mobilize and present on a map on your iPhone or Android device in order to enable a sales rep to plan their travel or get directions. But your Salesforce accounts do not contain geocode data (e.g. lat/lon). Instead of fetching the account data on the phone and then make web service calls to Google’s geocode API from the mobile device, you can use Arrow’s Post Block feature to geocode the data after its been fetched from Salesforce but before it is returned to the mobile phone as depicted in the diagram below:

In this tutorial, we’ll take an Arrow Salesforce Account API, add two custom fields for lat and lon, and fill them in a Post Block so that the modified API will return the base model data plus lat and lon. We’ll use the Google geocode web service to determine the lat/lon from the Account Address.

Getting Started

Head over to the Arrow online docs at http://docs.appcelerator.com/platform/latest/#!/guide/Appcelerator_Arrow and the Appcelerator University training videos on Arrow at http://university.appcelerator.com. This should help you in setting up an Arrow project, installing the Salesforce connector and creating a basic model for the salesforce account object. That will be the starting point for this tutorial.

My Account model, Account.js is shown below:

var Arrow = require("arrow");

var Model = Arrow.Model.reduce("appc.salesforce/Account","Account",{
    "fields": {
        "Name": {
            "type": "string",
            "description": "Account Name",
            "readonly": false,
            "maxlength": 255,
            "required": true,
            "optional": false,
            "writeonly": false
        },
        "Type": {
            "type": "string",
            "description": "Account Type",
            "readonly": false,
            "maxlength": 40,
            "required": false,
            "optional": true,
            "writeonly": false
        },
        "BillingStreet": {
            "type": "string",
            "description": "Billing Street",
            "readonly": false,
            "maxlength": 255,
            "required": false,
            "optional": true,
            "writeonly": false
        },
        "Phone": {
            "type": "string",
            "description": "Account Phone",
            "readonly": false,
            "maxlength": 40,
            "required": false,
            "optional": true,
            "writeonly": false
        },
        "Website": {
            "type": "string",
            "description": "Website",
            "readonly": false,
            "maxlength": 255,
            "required": false,
            "optional": true,
            "writeonly": false
        }
    },
    "actions": [
        "create",
        "read",
        "update",
        "delete",
        "deleteAll"
    ],
    "singular": "Account",
    "plural": "Accounts"
});


module.exports = Model;

When i perform a GET on /api/account I get the following data returned:

{
  "success": true,
  "request-id": "0e15ba05-d0a3-45b5-93a0-8365eb5cdd05",
  "key": "accounts",
  "accounts": [
    {
      "id": "001i000000PscewAAB",
      "Name": "GenePoint",
      "Type": "Customer - Channel",
      "BillingStreet": "345 Shoreline Park\nMountain View, CA 94043\nUSA",
      "Phone": "(650) 867-3450",
      "Website": "www.genepoint.com"
    },


...

    {
      "id": "001i000000Pscf6AAB",
      "Name": "United Oil & Gas Corp.",
      "Type": "Customer - Direct",
      "BillingStreet": "1301 Avenue of the Americas \nNew York, NY 10019\nUSA",
      "Phone": "(212) 842-5500",
      "Website": "http://www.uos.com"
    },
    {
      "id": "001i000000Pscf7AAB",
      "Name": "sForce",
      "BillingStreet": "The Landmark @ One Market\r\nSan Francisco\r\nCA\r\n94087\r\nUSA",
      "Phone": "(415) 901-7000",
      "Website": "www.sforce.com"
    }
  ]
}

Modify the Account Model

In your favorite editor, open the model file, Account.js, and add two custom fields to the fields list:

"lat": {
    "type": "String",
    "custom": true
},
"lon": {
    "type": "String",
    "custom": true
}

Adding the two custom fields above can be accomplished other ways as well. You could have added those custom fields inside the Arrow Admin Build screen while you were creating the original Account model (reduced from the base Salesforce Account Model exposed by the Arrow Salesforce connector). Or, you could create a new model that extends an existing Account model that you would have created inside the Arrow Admin Build screen.

Create a Post Block

In the blocks folder of the Arrow project, create a new js file. Mine is called addgps.js and is shown below. Note that for more extensive, general purpose blocks you can use the appc generate command in a terminal (or command line) to create the block.

var Arrow = require('arrow');
var request = require('request');

var googleMapAPIKey = "";
var baseURL = "https://maps.googleapis.com/maps/api/geocode/json?address=";

var PostBlock = Arrow.Block.extend({
    name: 'addgps',
    description: 'add lat, long to the retrieved data',

    action: function(req, resp, next) {

        var body = JSON.parse(resp.body);
        var data = body[body.key];
        var dataLen = data.length;
        var replies = 0;

        if(dataLen){ //findAll
            data.forEach(function (_row, _index) {
                var url=baseURL+encodeURIComponent(prepareAddress(_row.BillingStreet))+"&key="+googleMapAPIKey;

                request(url, function (error, response, body) {
                    if (!error && response.statusCode == 200) {
                        var res = JSON.parse(body);
                        _row.lat = res.results[0].geometry.location.lat;
                        _row.lon = res.results[0].geometry.location.lng;

                        replies++;
                        if(replies == dataLen) {
                            setReply();
                        }
                    } else {
                        console.log("google maps geocode request error");
                        replies++;
                        if(replies == dataLen) {
                            setReply();
                        }
                    }
                });
            });
        } else { //findOne
            var url=baseURL+encodeURIComponent(prepareAddress(data.BillingStreet))+"&key="+googleMapAPIKey;
            request(url, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                    var res = JSON.parse(body);
                    data.lat = res.results[0].geometry.location.lat;
                    data.lon = res.results[0].geometry.location.lng;
                    setReply();
                } else {
                    console.log("google maps geocode request error");
                    setReply();
                }
            });
        }

        function prepareAddress(str) {
            return str.replace(/(\r\n|\n|\r)/gm,",");
        }

        function setReply() {
            console.log("post block addgps executed");
            resp.success(body[body.key], next);
        }

    }
});

module.exports = PostBlock;

In the code above:

name: addgps – is defining the name of this block. This will be required later in the model to indicate that this block should be run on the model.

action: function(req, resp, next) { - defines the code to be executed.

The code above loops over the Account data set from Salesforce starting with:

data.forEach(function (_row, _index) {

_row contains the account data.

For each row, a REST web service call is made to the Google Geocode API with the code starting at:

request(url, function (error, response, body) {

Note that I am using the request npm to make the web service call.

In the reply of the call to the geocode API, the latitude and longitude are assigned to the model fields with the following code:

    if (!error && response.statusCode == 200) {
        var res = JSON.parse(body);
        _row.lat = res.results[0].geometry.location.lat;
        _row.lon = res.results[0].geometry.location.lng;

Since the calls to the geocode API are asynchronous, you need to keep track of the number of calls being made and when the final response is received. This is achieved with the following code:

    replies++;
    if(replies == dataLen) {
        setReply();
    }

This block can also be published to the Appcelerator Registry so that it can be used by your teammates or the greater Appcelerator community.

Set Post Block in Model

In the Account model, add the following property:

"after": "addgps",

This will instruct Arrow to execute the post block after retrieving the data from Salesforce.

Now the data returned to the mobile device looks like this:

{
  "success": true,
  "request-id": "cfe6321b-a23d-40d1-b6a9-28feba4fa310",
  "key": "accounts",
  "accounts": [
    {
      "id": "001i000000PscewAAB",
      "Name": "GenePoint",
      "Type": "Customer - Channel",
      "BillingStreet": "345 Shoreline Park\nMountain View, CA 94043\nUSA",
      "Phone": "(650) 867-3450",
      "Website": "www.genepoint.com",
      "lat": 37.423958,
      "lon": -122.0721391
    },

...

    {
      "id": "001i000000Pscf6AAB",
      "Name": "United Oil & Gas Corp.",
      "Type": "Customer - Direct",
      "BillingStreet": "1301 Avenue of the Americas \nNew York, NY 10019\nUSA",
      "Phone": "(212) 842-5500",
      "Website": "http://www.uos.com",
      "lat": 40.7616971,
      "lon": -73.9801263
    },
    {
      "id": "001i000000Pscf7AAB",
      "Name": "sForce",
      "BillingStreet": "The Landmark @ One Market\r\nSan Francisco\r\nCA\r\n94087\r\nUSA",
      "Phone": "(415) 901-7000",
      "Website": "www.sforce.com",
      "lat": 37.79396,
      "lon": -122.39496
    }
  ]
}

Summary

In this tutorial we saw how easy Arrow makes it to enhance an API using Blocks. We took a Salesforce Account model and added fields for geocode data and used the Post Block feature of Arrow to retrieve the geocode data from Google geocode API. Now the Account model returns the original account data and the latitude and longitude of each account so that the accounts can be displayed on a map on the mobile device.

Account.js and addgps.js for this sample project can be found here: https://gist.github.com/lbrenman/0cd009bde0624448bb1a

Read more about Arrow here:

http://docs.appcelerator.com/platform/latest/#!/guide/Appcelerator_Arrow

16 Flares Twitter 0 Facebook 0 Google+ 0 LinkedIn 16 Email -- 16 Flares ×

1 Comment

  1. Thank you very much for the contribution. Why not show screenshots example?

Comments are closed.

Sign up for updates!

Become a mobile leader. Take the first step to scale mobile innovation throughout your enterprise.
Get in touch
computer and tablet showing Appcelerator software
Start free, grow from there.
16 Flares Twitter 0 Facebook 0 Google+ 0 LinkedIn 16 Email -- 16 Flares ×