#Appcelerator Arrow Builder - Multiple Post Blocks Salesforce/Geocode/Foursquare Example
In a prior post, I described how Arrow’s Block feature can be leveraged to enhance data retrieved from Salesforce to geocode the data. The sales reps that are using the mobile application are now able to map their accounts as part of their prospecting get turn-by-turn directions without having to copy and paste or manually switching between apps. Many of the sales reps asked if there was a way to see restaurants or coffee shops near the account they are visiting so that when they plan their client visit they can suggest a local coffee shop to meet at. In this blog post, we add on from our last example and add a second block that will take the Salesforce account data that now has geocode information and use the Foursquare API to get coffee shops that are near the Account. This is commonly refferred to as a data mashup. The diagram below helps to illustrate the data flow for our aggregated API that we are creating.

##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. Also, review the previous post at http://www.appcelerator.com/blog/2015/05/appcelerator-arrow-builder-post-block-geocode-example/. 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
},
"lat": {
"type": "String",
"custom": true
},
"lon": {
"type": "String",
"custom": true
}
},
"after": "addgps",
"actions": [
"create",
"read",
"update",
"delete",
"deleteAll"
],
"singular": "Account",
"plural": "Accounts"
});
module.exports = Model;
This model is basically a reduced Salesforce Account model that is exposed automatically in Arrow using the Arrow Salesforce connector with the addition of the lat/lon custom fields and a post block addgps. This was all described in the prior blog post.
So, now that we have geocode data with each account, we can make a call to Foursquare using the lat/lon and retrieve nearby coffee shops.
##Modify the Account Model
In your favorite editor, open the model file, Account.js, and add a custom fields to the fields list:
"coffeeshops": {
"type": "Array",
"custom": true
}
Notice that the coffeeshops field is an array because we will fetch a number of coffeeshops near each account.
Adding the custom field 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 addcoffeeshops.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 FSclient_id = "<YOUR FOURSQUARE CLIENT ID>";
var FSclient_secret = "<YOUR FOURSQUARE CLIENT SECRET>";
var baseURL = "https://api.foursquare.com/v2/venues/search?client_id="+FSclient_id+"&client_secret="+FSclient_secret+"&v=20141024&query=coffee&limit=3&ll=";
var PostBlock = Arrow.Block.extend({
name: 'addcoffeeshops',
description: 'add coffee shops to the retrieved data',
action: function(req, resp, next) {
// req.log.info("Post Example Block executed");
var body = JSON.parse(resp.body);
var data = body[body.key];
var dataLen = data.length;
var replies = 0;
if(dataLen){
data.forEach(function (_row, _index) {
var url = baseURL+_row.lat+","+_row.lon;
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
var res = JSON.parse(body);
if(res.response.venues.length>0) {
var shops=[];
for(var i=0;i<res.response.venues.length; i++) {
var shop = {};
shop.id=res.response.venues[i].id;
shop.name=res.response.venues[i].name;
shop.location=res.response.venues[i].location;
shops.push(shop);
}
_row.coffeeshops = shops;
}
replies++;
if(replies == dataLen) {
setReply();
}
} else {
console.log("foursquare request error");
if(replies == dataLen) {
setReply();
}
}
});
});
} else {
var url = baseURL+data.lat+","+data.lon;
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
var res = JSON.parse(body);
if(res.response.venues.length>0) {
// _row.coffeeshops = response.response.venues; //assign all venue data
var shops=[];
for(var i=0;i<res.response.venues.length; i++) {
var shop = {};
shop.id=res.response.venues[i].id;
shop.name=res.response.venues[i].name;
shop.location=res.response.venues[i].location;
shops.push(shop);
}
data.coffeeshops = shops;
}
setReply();
} else {
setReply();
}
});
}
function setReply() {
resp.success(body[body.key], next);
}
}
});
module.exports = PostBlock;
In the code above, for each Account, a REST web service call is made to the Foursquare API, using the request npm, with the code starting at:
request(url, function (error, response, body) {
In the reply of the call to the Foursquare API, the coffe shops are assigned to the model field array coffeeshops with the following code:
var res = JSON.parse(body);
if(res.response.venues.length>0) {
var shops=[];
for(var i=0;i<res.response.venues.length; i++) {
var shop = {};
shop.id=res.response.venues[i].id;
shop.name=res.response.venues[i].name;
shop.location=res.response.venues[i].location;
shops.push(shop);
}
_row.coffeeshops = shops;
```
Since the calls to the Foursquare 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:
```javascript
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, we need to add this block to the after property. However, since this is the second block to be executed, we will specify the blocks as an array as follows:
"after": ["addgps","addcoffeeshops"],
This will instruct Arrow to execute the addspgs post block after retrieving the data from Salesforce and then execute the addcoffeeshops post block.
Now the data returned to the mobile device looks like this:
{
"success": true,
"request-id": "ad3b22cd-aecf-4a72-bb80-248391f9c420",
"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,
"coffeeshops": [
{
"id": "531f4262498e7dfd38788daf",
"name": "Root Coffee Bar",
"location": {
"address": "1200 Charleston Rd",
"lat": 37.42125,
"lng": -122.071451,
"distance": 307,
"postalCode": "94043",
"cc": "US",
"city": "Mountain View",
"state": "CA",
"country": "United States",
"formattedAddress": [
"1200 Charleston Rd",
"Mountain View, CA 94043",
"United States"
]
}
},
{
"id": "5281225911d2fbe8d10f9463",
"name": "Barefoot Coffee Roasters van",
"location": {
"lat": 37.419684936426464,
"lng": -122.07105266770425,
"distance": 485,
"cc": "US",
"city": "San Jose",
"state": "CA",
"country": "United States",
"formattedAddress": [
"San Jose, CA",
"United States"
]
}
},
{
"id": "50660dc7e4b0b68b5dbe8858",
"name": "Googleplex - Sight Glass Coffee",
"location": {
"address": "Google",
"lat": 37.420315759073134,
"lng": -122.07286151728987,
"distance": 410,
"postalCode": "94043",
"cc": "US",
"city": "Mountain View",
"state": "CA",
"country": "United States",
"formattedAddress": [
"Google",
"Mountain View, CA 94043",
"United States"
]
}
}
]
},
…….
{
"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,
"coffeeshops": [
{
"id": "4b6ad59ef964a5209be22be3",
"name": "Coffee Cart",
"location": {
"address": "51st Street",
"crossStreet": "7th Ave",
"lat": 40.76337735085997,
"lng": -73.98092822202503,
"distance": 198,
"cc": "US",
"city": "New York",
"state": "NY",
"country": "United States",
"formattedAddress": [
"51st Street (7th Ave)",
"New York, NY",
"United States"
]
}
},
{
"id": "4ec11fdff5b93923c212cdc0",
"name": "Coffee Cart",
"location": {
"address": "50th And 6th",
"lat": 40.75999512452107,
"lng": -73.98103726694443,
"distance": 204,
"postalCode": "11220",
"cc": "US",
"city": "New York",
"state": "NY",
"country": "United States",
"formattedAddress": [
"50th And 6th",
"New York, NY 11220",
"United States"
]
}
},
{
"id": "4ddc6032b0fba481fc81546f",
"name": "Coffee Cart (46th & 6th)",
"location": {
"address": "46th St",
"crossStreet": "at 6th Ave (NE Corner)",
"lat": 40.75970808566679,
"lng": -73.9874267578125,
"distance": 654,
"postalCode": "10036",
"cc": "US",
"city": "New York",
"state": "NY",
"country": "United States",
"formattedAddress": [
"46th St (at 6th Ave (NE Corner))",
"New York, NY 10036",
"United States"
]
}
}
]
},
{
"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,
"coffeeshops": [
{
"id": "4b0d71eaf964a5207b4823e3",
"name": "The Coffee Bean & Tea Leaf",
"location": {
"address": "150 Drumm St",
"crossStreet": "at Four Embarcadero Center",
"lat": 37.79469538242046,
"lng": -122.39654863295293,
"distance": 161,
"postalCode": "94111",
"cc": "US",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"formattedAddress": [
"150 Drumm St (at Four Embarcadero Center)",
"San Francisco, CA 94111",
"United States"
]
}
},
{
"id": "4aafc57df964a520ab6420e3",
"name": "Peet's Coffee & Tea",
"location": {
"address": "1 California St",
"crossStreet": "at Drumm St",
"lat": 37.793323943617715,
"lng": -122.39672565009258,
"distance": 170,
"postalCode": "94111",
"cc": "US",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"formattedAddress": [
"1 California St (at Drumm St)",
"San Francisco, CA 94111",
"United States"
]
}
},
{
"id": "4cc6f529be40a35dc8198d4c",
"name": "Justin Herman Plaza- Keurig Coffee Pop Up",
"location": {
"lat": 37.79467,
"lng": -122.394862,
"distance": 79,
"postalCode": "94105",
"cc": "US",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"formattedAddress": [
"San Francisco, CA 94105",
"United States"
]
}
}
]
}
]
}
##Summary
In this tutorial we saw how easy Arrow makes it to enhance an API using Blocks. We took a Salesforce Account model which had geocode data added to it (in the last blog post) and added a field for an array of related objects (coffee shops) and used the Post Block feature of Arrow to retrieve the data from the Foursquare API. Now the Account model returns the original account data and nearby coffee shops for each account so that the user can suggest a nearby meeting location for a client visit.
Account.js, addgps.js and addcoffeeshops.js for this sample project can be found here: https://gist.github.com/lbrenman/25287e1f2db932d85b3f
Read more about Arrow here:
http://docs.appcelerator.com/platform/latest/#!/guide/Appcelerator_Arrow