Skip to content

Instantly share code, notes, and snippets.

@lbrenman
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lbrenman/d645d99d3bafec8654c8 to your computer and use it in GitHub Desktop.
Save lbrenman/d645d99d3bafec8654c8 to your computer and use it in GitHub Desktop.
Appcelerator Titanium ACS Search History
Example of implementing search history synced across a user's multiple devices (like google maps)
See blog post here:
http://www.appcelerator.com/blog/2014/08/implementing-recent-history-in-your-appcelerator-apps/
Excerpt from Blog:
Implementing Recent Search History in your Appcelerator apps
By Leor Brenman
August 28, 2014 @ 11:47am
One of my favorite app features is Google Maps Recent History. This subtle feature shows up in the desktop browser as well on all my mobile devices. When I click in the search field to enter a search term, a list of recently found locations is displayed below the search field (see screenshot below).
This is convenient as I often look up the same locations.
But, what makes this feature really great is that when I am in my car and bring up the Google Maps app on my phone, the same list is displayed (see screenshot below). In other words, a search on any device (desktop browser, iPhone or android), shows up on all my devices!
Having a list of recently searched items is crucial on my phone since typing on the small screen is tedious.
So, now, if I know I will be away from my desktop computer and will need to lookup Google Maps locations on my phone, I will first look them up on my desktop browser, knowing that when I get to my phone all those searches will be displayed. Then I can get directions and use my phone as a navigation device.
The Appcelerator MBaaS/ACS enables me to add this powerful feature to any application. Imagine if you can enable search history synced across your employee’s devices for your CRM or Field Service Application without the need to add this feature in your back end applications, which might not even be possible. This post will describe how easy this is to achieve with the Appcelerator MBaaS without any back end changes required at all.
Appcelerator MBaaS/ACS
The Appcelerator MBaaS contains a set of predefined services, known as ACS – Appcelerator Cloud Services, that includes over 20 services such as file storage, photo storage, Push Notification, Checkins, Likes, Reviews and Custom Objects. The full list of services are described here.
In order to implement the Recent History feature described above, we can leverage the CustomObject service. The CustomObject service lets you define your own object types with arbitrary fields and is described in detail here.
So, the basic idea is as follows:
When the app starts, retrieve the list of prior searches from the CustomObject and store in a local array
When the user clicks in the search field, display the list of prior searches from the local array
If the user types some text and performs a search, store the selected search item in the CustomObject and update the array
Note that as in the case of Google Maps, in order for the search to be synchronized across devices the user must be logged in with the same account. In the case of the Appcelerator MBaaS, the Users service is used to log in a user to the Appcelerator MBaaS. This service is described here. The Enterprise login username can be used to log in the user to the Appcelerator MBaaS so the user does not require a separate login UI and credentials.
Initialize Local Array
As described above, the first step is that when the app starts and after the user is logged into the MBaaS, to retrieve the Search History and initialize the local array. The following code snipet accomplishes this and is shown below. The CustomObject in this example is called “history” and we are requesting the last 5 items from the history.
var historyRows =[];
//
function readHistory() {
//
Cloud.Objects.query({classname:"history", page: 1, per_page: 5, where: {user_id: userID}}, function(e){
if(e.history.length>0){
_.each(e.history, function(item) {
historyRows.push({Name: item.Name, Symbol: item.Symbol});
}
}
});
}
Display Search History
You now have the search history that can be loaded into a ListView or a TableView. For example, the following screenshot shows the search history (displayed in red) along with the search results (displayed in black) when the user enters ‘G’ in the search field.
Append to the Search History
The last step is to append new searches to the search history. In this example, when the user selects an entry in the search results that is not from the search history, we will add it to the search history. This is illustrated in the following code snipet:
$.companyTV.addEventListener('click', function(e) {
//update search history
if(!e.row.history) {
Cloud.Objects.create({
classname: 'history',
fields: {
Name: e.row.name,
Symbol: e.row.symbol
}
}, function(e) {
if (e.success) {
readHistory(); // get updated search history
}
});
}
// Do something with data from clicked row
});
  In the screen shot below, you can see the recent search history displayed when the user clicks on the search field. You can also see the data synched between the iPhone and Android devices.
By leveraging the Appcelerator MBaaS pre-built services such as Users and CustomObjects, you can add a very powerful Recent History feature to any application such as a CRM app or a Field Service application. By adding a recent history feature to your app, your users will find it much easier to use the app and increase their productivity with the mobile app.
".container": {
backgroundColor:"white"
},
"Label": {
width: Ti.UI.SIZE,
height: Ti.UI.SIZE,
color: "#000"
},
"TextField": {
left: "10",
width: Ti.UI.FILL,
color: "black",
clearButtonMode:Titanium.UI.INPUT_BUTTONMODE_ONFOCUS,
//returnKeyType: Titanium.UI.RETURNKEY_DONE,
autocapitalization:Ti.UI.TEXT_AUTOCAPITALIZATION_NONE,
autocorrect: false
},
"TableViewRow": {
height: "40",
hasChild: true
},
"TableView": {
height: Ti.UI.FILL
}
var args = arguments[0] || {};
//Ti.API.info("row created, args.name = "+args.name);
$.companyRow.name = args.name;
$.companyRow.symbol = args.symbol;
$.companyRow.history = args.history;
$.companyLbl.text = args.name;
$.symbolLbl.text = args.symbol;
if(args.history) {
$.companyLbl.color="red";
$.symbolLbl.color="red";
};
<Alloy>
<TableViewRow class="companyTVR">
<Label id="companyLbl" width="70%" left="10"/>
<Label id="symbolLbl" right="10"/>
</TableViewRow>
</Alloy>
var Cloud = require('ti.cloud');
var userID;
var historyRows = [{Name: "Texas", Symbol: "TXN"}, {Name: "Apple", Symbol: "AAPL"}];
$.infoButton.addEventListener('click', function(e) {
alert("ACS Demo to show search history across a user's devices.\n\nLong Press the i button to clear your search history");
});
$.infoButton.addEventListener('longpress', function(e) {
//alert("Longpress");
var xhr = Titanium.Network.createHTTPClient({
onload: function() {
alert("History Deleted") ;
resetTable();
readHistory();
},
onerror: function(e) {
alert("Server Error, e.error = "+e.error);
},
timeout: 10000,
});
xhr.open("DELETE", "https://api.cloud.appcelerator.com/v1/objects/history/admin_batch_delete.json?key=WjJZOuSyVzwynvSIALQyBwUYe26jzV50");
xhr.send();
});
function createUserACS(u,p,o){
Ti.API.info("index: createUserACS(), u = "+u+", p="+p);
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
alert("No Network. Please try again later.");
return;
}
Cloud.Users.create({
username : u,
password: p,
password_confirmation: p,
}, function(e){
if (e.success) {
Ti.API.info("index: New ACS userer creation successful");
var user = e.users[0];
userID = e.users[0].id;
if (o.success) { o.success(); };
} else {
alert("Server error, please try again later");
if (o.error) { o.error("ACS: createUserACS(): Server error"); };
}
});
};
function ACSLogin(u,p,o){
Ti.API.info("index: ACSLogin()");
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
alert("No Network. Please try again later.");
if (o.error) { o.error("ACSLogin(): No Network"); };
return;
}
Ti.API.info("ACS: Logging in to ACS with username = "+u+", password = "+p);
Cloud.Users.login({
login : u,
password: p,
}, function(e) {
if (e.success) {
Ti.API.info("index: Log in to ACS successful");
Ti.API.info("index: Session ID = "+Cloud.sessionId);
var user = e.users[0];
userID = e.users[0].id;
Ti.API.info("index: userID = "+userID);
if (o.success) { o.success(); };
} else {
Ti.API.info('index: Login Error:' +((e.error && e.message) || JSON.stringify(e)));
createUserACS(u,p,{
success: function(e) {
Ti.API.info('index: Create user success');
if (o.success) { o.success(); };
},
error: function(e) {
Ti.API.info(e);
Ti.API.info('index: Logout failure');
if (o.error) { o.error(); };
}
});
}
});
};
function ACSReadHistory(o){
Ti.API.info("index: ACSReadHistory()");
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
if (o.error) { o.error("ACS: ACSRead(): No Network"); };
return;
}
Cloud.Objects.query({classname:"history", page: 1, per_page: 5, where: {user_id: userID}}, function(e){
if (e.success) {
Ti.API.info("index: ACSRead(): Custom Object read successful");
Ti.API.info(e.history);
if (o.success) { o.success(e); };
} else {
Ti.API.info("index: ACSRead(): Custom Object delete failed");
if (o.error) { o.error("index: ACSRead(): Server error"); };
}
});
return;
};
function storeHistoryLocal(e) {
Ti.API.info("index: storeHistoryLocal(e), e = "+e);
historyRows.length = 0; //clear array
if(e.history.length>0){
_.each(e.history, function(item) {
Ti.API.info("Name = "+item.Name);
Ti.API.info("Symbol = "+item.Symbol);
historyRows.push({Name: item.Name, Symbol: item.Symbol});
});
}
else {
Ti.API.info("index: storeHistoryLocal(e) - NO HISTORY");
}
}
function readHistory() {
Ti.API.info("index: readHistory()");
ACSReadHistory({
success: function(e) {
Ti.API.info('ACSReadHistory success');
storeHistoryLocal(e);
},
error: function(e) {
Ti.API.info(e);
Ti.API.info('ACSReadHistory failure');
}
});
}
function addHistory() {
Ti.API.info("index: addHistory()");
_.each(historyRows, function(item) {
$.companyTV.appendRow(Alloy.createController('companyRow', {
name: item.Name,
symbol: item.Symbol,
history: true
}).getView());
});
}
function resetTable() {
Ti.API.info("index: resetTable()");
var rd = [];
$.companyTV.data = rd;
}
function writeToHistory(data, o) {
Ti.API.info("index: writeToHistory()");
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
if (o.error) { o.error("index: writeToHistory(): No Network"); };
return;
}
Cloud.Objects.create({
classname: 'history',
fields: {
Name:data.Name,
Symbol:data.Symbol
}
}, function (e) {
if (e.success) {
Ti.API.info("index: writeToHistory(): Object create successful");
Ti.API.info("index: writeToHistory: Object reply id = "+e.history[0].id);
if (o.success) { o.success(e.history[0].id); };
} else {
Ti.API.info("index: writeToHistory(): Object create failed");
if (o.error) { o.error("index: writeToHistory: Server error"); };
}
});
};
function getQuote(symbol) {
Ti.API.info("index: getQuote");
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
Ti.API.info("markit: ondemand() - No Network");
alert("No Network");
return;
}
var xhr = Titanium.Network.createHTTPClient({
onload: function() {
alert(this.responseText) ;
},
onerror: function(e) {
alert("Server Error");
},
timeout: 10000,
});
xhr.open("GET", "http://dev.markitondemand.com/Api/Quote/json?symbol="+symbol);
xhr.send();
}
$.companyTV.addEventListener('click', function(e) {
Ti.API.info("index: $.companyTV.addEventListener(click)");
//alert("row clicked, symbol = "+e.row.history);
if(!e.row.history) {
//historyRows.push({Name: e.row.name, Symbol: e.row.symbol});
writeToHistory({Name: e.row.name, Symbol: e.row.symbol}, {
success: function(e) {
Ti.API.info('Data written');
readHistory();
},
error: function(e) {
Ti.API.info('Error writing data = '+e);
alert("No network or server not available. Please try again.");
}
});
}
$.companyTF.value="";
$.companyTF.blur();
getQuote(e.row.symbol);
});
$.companyTF.addEventListener('click', function(e){
Ti.API.info("index: $.companyTF.addEventListener(click)");
if($.companyTF.value=="" || $.companyTF.value==null) {
readHistory();
resetTable();
addHistory();
}
});
$.companyTF.addEventListener('change', function(e){
Ti.API.info("index: $.companyTF.addEventListener(change)");
if($.companyTF.value=="" || $.companyTF.value==null) {
readHistory();
resetTable();
addHistory();
} else {
getCompanies($.companyTF.value, {
success: function(e) {
Ti.API.info('Recieved data = '+e);
loadTable(e);
},
error: function(e) {
Ti.API.info('Error = '+e);
alert("No network or server not available. Please try again.");
}
});
}
});
function loadTable(e) {
Ti.API.info("index: loadTable()");
var reply = JSON.parse(e);
var rows = [];
var i = 0;
Ti.API.info("index: reply = "+reply);
if(reply.length>0){
_.each(reply, function(item) {
rows.push(Alloy.createController('companyRow', {
name: item.Name,
symbol: item.Symbol,
}).getView());
});
}
else {
alert("No companies found.");
}
$.companyTV.setData(rows);
addHistory();
}
function getCompanies(text, o){
Ti.API.info("getCompanies() - text = "+text);
if(Titanium.Network.networkType == Titanium.Network.NETWORK_NONE){
Ti.API.info("markit: ondemand() - No Network");
if (o.error) { o.error("No Network"); };
return;
}
var xhr = Titanium.Network.createHTTPClient({
onload: function() {
if (o.success) { o.success(this.responseText); };
},
onerror: function(e) {
if (o.error) { o.error("Error with markitOnDemand API"); };
},
timeout: 10000,
});
xhr.open("GET", "http://dev.markitondemand.com/Api/Lookup/json?input="+text);
xhr.send();
};
$.index.open();
$.companyTF.blur();
ACSLogin("a","1234",{
success: function(e) {
Ti.API.info('ACSLogin success');
readHistory();
},
error: function(e) {
Ti.API.info(e);
Ti.API.info('ACSLogin failure');
}
});
<Alloy>
<Window class="container" layout="vertical">
<View height="40" backgroundColor="black">
<Label color="white">Search History Demo</Label>
<Button id="infoButton" right="10" color="white">i</Button>
</View>
<View layout="vertical">
<TextField id="companyTF" hintText="Enter company name" top="20" />
<TableView id="companyTV" top="20"/>
</View>
</Window>
</Alloy>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment