-
-
Save forcementor/a1399a77150b7a2e1304 to your computer and use it in GitHub Desktop.
Developing Mobile Applications with Force.com and Sencha Touch - Part 3 - Complete Project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
======================================================== | |
Name: PocketCRM | |
Type: Visualforce Page | |
Purpose: For Sencha Touch PocketCRM App | |
Created by: Don Robins - www.ForceMentor.com | |
Created on: August 1, 2012 | |
Copyright 2012 Outformations, Inc. | |
Rev # Revised on Revised by Revision Description | |
----- ---------- ------------------------------------- | |
1.0 08/01/2012 Don Robins Initial Release | |
========================================================= | |
--> | |
<apex:page docType="html-5.0" showHeader="false" standardStylesheets="false" cache="false"> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>Pocket CRM</title> | |
<!-- Use the Visualforce tags for scripts and stylesheets rather than their HTML counterparts --> | |
<apex:includeScript value="http://cdn.sencha.io/try/touch/2.0.1/sencha-touch-all.js" /> | |
<apex:stylesheet value="http://cdn.sencha.io/try/touch/2.0.1/resources/css/sencha-touch.css" /> | |
<!-- Custom Application Style Sheet --> | |
<c:PocketCRM_CSS /> | |
<!-- Our custom Visualforce component containing our Sencha Touch application --> | |
<c:PocketCRM_APP /> | |
</head> | |
<body> | |
<!-- An animated image that displays while loading --> | |
<div id="appLoadingIndicator"> | |
<div></div> | |
<div></div> | |
<div></div> | |
</div> | |
</body> | |
</apex:page> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
======================================================== | |
Name: PocketCRM_APP | |
Type: Visualforce Custom Component | |
Purpose: For Sencha Touch PocketCRM App | |
Created by: Don Robins - www.ForceMentor.com | |
Created on: August 1, 2012 | |
Copyright 2012 Outformations, Inc. | |
Rev # Revised on Revised by Revision Description | |
----- ---------- ------------------------------------- | |
1.0 08/01/2012 Don Robins Initial Release | |
1.1 10/01/2012 Don Robins v3 | |
========================================================= | |
--> | |
<apex:component controller="PocketCRMLeadController" > | |
<script type="text/javascript"> | |
//================================================================================================================ | |
//APPLICATION | |
//The Application class is the entry point into your Sencha Touch application. | |
//================================================================================================================ | |
Ext.application({ | |
name: "PocketCRM", | |
//Load the various MVC components into memory. | |
models: ["Lead"], | |
stores: ["Leads"], | |
controllers: ["Leads"], | |
views: ["LeadsList","LeadEditor"], | |
//The application's startup routine once all components are loaded. | |
launch: function () { | |
//Instantiate your main list view for Leads. | |
var leadsListView = { | |
xtype: "leadslistview" | |
}; | |
var leadEditorView = { | |
xtype: "leadeditorview" | |
}; | |
//Launch the primary fullscreen view and pass in the list view. | |
Ext.Viewport.add([leadsListView, leadEditorView]); | |
} | |
}); | |
//================================================================================================================ | |
//VIEWS | |
//Views display data to your users and gather input from them; they also emit events about your user interaction. | |
//================================================================================================================ | |
//The Lead list view. | |
Ext.define("PocketCRM.view.LeadsList", { | |
extend: "Ext.Container", | |
//It uses the base list class. | |
requires: "Ext.dataview.List", | |
alias: "widget.leadslistview", | |
config: { | |
//Take up the full space available in the parent container. | |
layout: { | |
type: 'fit' | |
}, | |
//Add the components to include within the list view. | |
items: [ | |
{ | |
xtype: "toolbar", | |
title: "PocketCRM", | |
docked: "top", | |
items: [ | |
{ | |
xtype: 'spacer' | |
}, | |
{ | |
xtype: "button", | |
text: 'New', | |
ui: 'action', | |
itemId: "newButton" | |
} | |
] | |
}, | |
{ | |
xtype: "toolbar", | |
docked: "bottom", | |
itemId: "bottomToolBar", | |
items: [ | |
{xtype: 'spacer'} | |
, | |
{ | |
xtype: 'searchfield', | |
itemId:'leadSearchField', | |
placeHolder: 'Name Contains...' | |
}, | |
{ | |
xtype: "button", | |
iconCls: "search", | |
iconMask: true, | |
itemId: "syncButton" | |
} | |
, | |
{xtype: 'spacer'} | |
] | |
}, | |
{ | |
//The main list and its properties. | |
xtype: "list", | |
store: "Leads", | |
itemId:"leadsList", | |
onItemDisclosure: true, | |
indexBar: true, | |
grouped: true, | |
disableSelection: false, | |
plugins: [ | |
{ | |
xclass: 'Ext.plugin.ListPaging', | |
autoPaging: true | |
} | |
], | |
//The template for display if the Store is empty of records. | |
//Note the style to control visual presentation. | |
loadingText: "Loading Leads...", | |
emptyText: '<div class="leads-list-empty-text">No leads found.</div>', | |
//The template for the display of each list item representing one record. | |
//One row will display for each record in the data Store. | |
//The fields referenced are from the entity's Model. | |
itemTpl: '<div class="list-item-line-main">{LastName}, {FirstName}</div>' + | |
'<div class="list-item-line-detail">{Company}</div>' + | |
'<div class="list-item-line-detail">{Title} - Phone: {Phone} </div>' + | |
'<div class="list-item-line-detail">{Email}</div>', | |
}], | |
listeners: [{ | |
delegate: "#newButton", | |
event: "tap", | |
fn: "onNewButtonTap" | |
}, { | |
delegate: "#syncButton", | |
event: "tap", | |
fn: "onSyncButtonTap" | |
}, { | |
delegate: "#leadsList", | |
event: "disclose", | |
fn: "onLeadsListDisclose" | |
}, { | |
delegate: "#leadSearchField", | |
event: "clearicontap", | |
fn: "onLeadSearchFieldClearIconTap" | |
}, | |
{ | |
delegate: "#leadsList", | |
event: "refresh", | |
fn: "onLeadsListRefresh", | |
}, | |
{ | |
//Listener on the view's Activate event fires when redisplayed by transition. | |
event: "activate", | |
fn: "onLeadsListViewActivate", | |
}] | |
}, | |
onSyncButtonTap: function () { | |
console.log("syncLeadCommand"); | |
this.fireEvent("syncLeadCommand", this); | |
}, | |
onNewButtonTap: function () { | |
console.log("newLeadCommand"); | |
this.fireEvent("newLeadCommand", this); | |
}, | |
onLeadsListDisclose: function (list, record, target, index, evt, options) { | |
console.log("editLeadCommand"); | |
this.fireEvent('editLeadCommand', this, record); | |
}, | |
onLeadSearchFieldClearIconTap: function () { | |
console.log("clearSearchLeadCommand"); | |
this.fireEvent('clearSearchLeadCommand', this); | |
}, | |
onLeadsListRefresh: function () { | |
console.log("onLeadsListRefresh"); | |
this.updateListCounter(); | |
}, | |
onLeadsListViewActivate: function () { | |
console.log("onLeadsListViewActivate"); | |
this.updateListCounter(); | |
}, | |
//Function to get count of records in the list and show on the search button's badge. | |
updateListCounter: function () { | |
var listCount = Ext.getStore("Leads").getCount(); | |
this.getComponent("bottomToolBar").getComponent("syncButton").setBadgeText(listCount); | |
} | |
}); | |
//The Lead form view. | |
Ext.define("PocketCRM.view.LeadEditor", { | |
extend: "Ext.form.Panel", | |
requires: "Ext.form.FieldSet", | |
alias: "widget.leadeditorview", | |
config: { | |
scrollable: 'vertical', | |
items: [ | |
{ | |
xtype: "toolbar", | |
docked: "top", | |
title: "Edit Lead", | |
items: [ | |
{ | |
xtype: "button", | |
ui: "back", | |
text: "Home", | |
itemId: "backButton" | |
}, | |
{ xtype: "spacer" }, | |
{ | |
xtype: "button", | |
ui: "action", | |
text: "Save", | |
itemId: "saveButton" | |
} | |
] | |
}, | |
{ | |
xtype: "toolbar", | |
docked: "bottom", | |
items: [ | |
{ | |
xtype: "button", | |
iconCls: "trash", | |
iconMask: true, | |
itemId: "deleteButton" | |
} | |
] | |
}, | |
{ xtype: "fieldset", | |
title: 'Lead Info', | |
items: [ | |
{ | |
xtype: 'textfield', | |
name: 'FirstName', | |
label: 'First Name' | |
}, | |
{ | |
xtype: 'textfield', | |
name: 'LastName', | |
label: 'Last Name', | |
required: true | |
}, | |
{ | |
xtype: 'textfield', | |
name: 'Company', | |
label: 'Company', | |
required: true | |
}, | |
{ | |
xtype: 'textfield', | |
name: 'Title', | |
label: 'Title' | |
}, | |
{ | |
xtype: 'selectfield', | |
name: 'Status', | |
label: 'Status', | |
required: true, | |
value: 'Open - Not Contacted', | |
options: [ | |
{text: 'Open - Not Contacted', value: 'Open - Not Contacted'}, | |
{text: 'Working - Contacted', value: 'Working - Contacted'}, | |
{text: 'Closed - Converted', value: 'Closed - Converted'}, | |
{text: 'Closed - Not Converted', value: 'Closed - Not Converted'} | |
], | |
}, | |
] | |
}, | |
{ xtype: "fieldset", | |
title: 'Contact Info', | |
items: [ | |
{ | |
xtype : 'textfield', | |
name : 'Phone', | |
label : 'Phone', | |
component: {type: 'tel'} | |
}, | |
{ | |
xtype : 'textfield', | |
name : 'MobilePhone', | |
label : 'Mobile', | |
component: {type: 'tel'} | |
}, | |
{ | |
xtype: 'emailfield', | |
name: 'Email', | |
label: 'Email Address' | |
}, | |
] | |
}, | |
], | |
listeners: [ | |
{ | |
delegate: "#backButton", | |
event: "tap", | |
fn: "onBackButtonTap" | |
}, | |
{ | |
delegate: "#saveButton", | |
event: "tap", | |
fn: "onSaveButtonTap" | |
}, | |
{ | |
delegate: "#deleteButton", | |
event: "tap", | |
fn: "onDeleteButtonTap" | |
} | |
] | |
}, | |
onSaveButtonTap: function () { | |
console.log("saveLeadCommand"); | |
this.fireEvent("saveLeadCommand", this); | |
}, | |
onDeleteButtonTap: function () { | |
console.log("deleteLeadCommand"); | |
Ext.Msg.confirm("Delete Lead", "Are you sure?", function(button){ | |
if (button == 'yes') { | |
this.fireEvent("deleteLeadCommand", this); | |
} else { | |
return false; | |
} | |
}, this); | |
}, | |
onBackButtonTap: function () { | |
console.log("backToHomeCommand"); | |
this.fireEvent("backToHomeCommand", this); | |
} | |
}); | |
//================================================================================================================ | |
//CONTROLLERS | |
//Controllers manage the communication of your application and the coordination between the views and the model; | |
//they listen for the events emitted by the views and react accordingly. | |
//================================================================================================================ | |
//The controller for the Leads list view | |
Ext.define("PocketCRM.controller.Leads", { | |
extend: "Ext.app.Controller", | |
config: { | |
refs: { | |
// We're going to lookup our views by alias. | |
leadsListView: "leadslistview", | |
leadEditorView: "leadeditorview", | |
leadsList: "#leadsList", | |
//Add a new search field. | |
leadsListSearchField: "#leadSearchField" | |
}, | |
control: { | |
leadsListView: { | |
// The commands fired by the list container. | |
syncLeadCommand: "onSyncLeadCommand", | |
newLeadCommand: "onNewLeadCommand", | |
editLeadCommand: "onEditLeadCommand", | |
//Add an event when clearing the search text. | |
clearSearchLeadCommand: "onClearSearchLeadCommand" | |
}, | |
leadEditorView: { | |
// The commands fired by the note editor. | |
saveLeadCommand: "onSaveLeadCommand", | |
deleteLeadCommand: "onDeleteLeadCommand", | |
backToHomeCommand: "onBackToHomeCommand" | |
} | |
} | |
}, | |
//View Transitions | |
slideLeftTransition: { type: 'slide', direction: 'left' }, | |
slideRightTransition: { type: 'slide', direction: 'right' }, | |
//View Transition Helper functions | |
activateLeadEditor: function (record) { | |
var leadEditorView = this.getLeadEditorView(); | |
leadEditorView.setRecord(record); | |
Ext.Viewport.animateActiveItem(leadEditorView, this.slideLeftTransition); | |
}, | |
activateLeadsList: function () { | |
Ext.Viewport.animateActiveItem(this.getLeadsListView(), this.slideRightTransition); | |
}, | |
onSyncLeadCommand: function () { | |
console.log("onSyncLeadCommand"); | |
this.loadList(); | |
}, | |
onNewLeadCommand: function () { | |
console.log("onNewLeadCommand"); | |
//Set a default value for the Status selectfield. | |
var newLead = Ext.create("PocketCRM.model.Lead", { | |
Status: "Open - Not Contacted" | |
}); | |
this.activateLeadEditor(newLead); | |
}, | |
onEditLeadCommand: function (list, record) { | |
console.log("onEditLeadCommand"); | |
this.activateLeadEditor(record); | |
}, | |
onSaveLeadCommand: function () { | |
console.log("onSaveLeadCommand"); | |
//Update the field values in the record. | |
var leadEditorView = this.getLeadEditorView(); | |
var currentLead = leadEditorView.getRecord(); | |
var newValues = leadEditorView.getValues(); | |
this.getLeadEditorView().updateRecord(currentLead); | |
//Check for validation errors. | |
var errors = currentLead.validate(); | |
if (!errors.isValid()) { | |
var msg = ''; | |
errors.each(function(error) { | |
msg += error.getMessage() + '<br/>'; | |
}); | |
console.log('Errors: ' + msg); | |
Ext.Msg.alert('Please correct errors!', msg, Ext.emptyFn); | |
currentLead.reject(); | |
return; | |
} | |
//Get a ref to the store. | |
var leadsStore = Ext.getStore("Leads"); | |
//Add new record to the store. | |
if (null == leadsStore.findRecord('id', currentLead.data.id)) { | |
leadsStore.add(currentLead); | |
} | |
//Resync the proxy and activate the list. | |
leadsStore.sync(); | |
this.activateLeadsList(); | |
}, | |
onDeleteLeadCommand: function () { | |
console.log("onDeleteLeadCommand"); | |
//Get a ref to the form and its record. | |
var leadEditorView = this.getLeadEditorView(); | |
var currentLead = leadEditorView.getRecord(); | |
//Get a ref to the store and remove it. | |
var leadsStore = Ext.getStore("Leads"); | |
leadsStore.remove(currentLead); | |
//Resync the proxy and activate the list. | |
leadsStore.sync(); | |
this.activateLeadsList(); | |
}, | |
onBackToHomeCommand: function () { | |
console.log("onBackToHomeCommand"); | |
this.activateLeadsList(); | |
}, | |
//Reload the list when clearing the search value. | |
onClearSearchLeadCommand: function () { | |
console.log("onClearSearchLeadCommand"); | |
//Get a ref to the search field. | |
var leadSearchField = this.getLeadsListSearchField(); | |
leadSearchField.setValue(''); | |
console.log("Cleared Field Value: " + leadSearchField.getValue()); | |
this.loadList(); | |
}, | |
//Add separate load function to be called by multiple event handlers. | |
loadList: function () { | |
//Get a ref to the store and remove it. | |
var leadsStore = Ext.getStore("Leads"); | |
//Get any search text. | |
var leadSearchField = this.getLeadsListSearchField(); | |
var searchText = leadSearchField.getValue(); | |
console.log("Search Field Value in reSync: " + searchText); | |
if (searchText.length > 0 && searchText.length < 2) { | |
var msg = 'Search requires more text.'; | |
Ext.Msg.alert('Please correct errors!', msg, Ext.emptyFn); | |
return; | |
} | |
//Add wild cards. | |
if (searchText != '') { | |
//Surround the search value with a wildcard for SOQL search. | |
searchText = '%' + searchText + '%'; | |
} else { | |
//Set wildcard for wide open filter. | |
searchText = '%'; | |
} | |
console.log("Search Field Value: " + searchText); | |
//Set the value of the searchFilter param to pass with the query. | |
var model = Ext.ModelMgr.getModel('PocketCRM.model.Lead'); | |
model.getProxy().setExtraParam('searchFilter', searchText); | |
//Clear all data in the store before loading it. | |
//This is necessary to make sure that the proxy doesn't get confused by | |
//the loss of records not reloaded with a new filter. | |
//Without the clear(), the proxy assumes a deletion was processed and | |
//calls a destroy to be executed on missing records with the next sync() operation. | |
leadsStore.getData().clear(); | |
leadsStore.loadPage(1); | |
//Reshow the list. | |
this.activateLeadsList(); | |
}, | |
// Base Class functions. | |
launch: function () { | |
console.log("launch"); | |
this.callParent(arguments); | |
//Load up the Store associated with the controller and its views. | |
console.log("load Leads"); | |
this.loadList(); | |
}, | |
init: function() { | |
this.callParent(arguments); | |
console.log("init"); | |
//Listen for exceptions observed by the proxy so we can report them and clean up. | |
Ext.getStore('Leads').getProxy().addListener('exception', function (proxy, response, operation, options) { | |
// only certain kinds of errors seem to have useful information returned from the server | |
if (response) { | |
if (response.errorMessage) { | |
Ext.Msg.alert('Error', response.errorMessage); | |
} else { | |
Ext.Msg.alert('Error', operation.config.action + ' failed: ' + response.errorMessage); | |
} | |
} else { | |
Ext.Msg.alert('Error', operation.config.action + ' failed for an unknown reason: proxy = ' + proxy); | |
} | |
}); | |
}, | |
}); | |
//================================================================================================================ | |
//MODELS | |
//Models are the objects on your application. | |
//================================================================================================================ | |
//AN IMPORTANT NOTE: if your org has a registered namespace, you MUST reference the Apex controller name in the | |
//proxy your JavaScript with your org's namespace. If you fail to do this, you will get a a JavaScript error that | |
//the Apex controller can NOT be found! | |
//================================================================================================================ | |
//The Lead model will include whatever fields are necssary to manage. | |
Ext.define("PocketCRM.model.Lead", { | |
extend: "Ext.data.Model", | |
config: { | |
idProperty: 'Id', | |
fields: [ | |
{ name: 'Id', type: 'string', persist: false}, | |
{ name: 'Name', type: 'string', persist: false }, | |
{ name: 'FirstName', type: 'string' }, | |
{ name: 'LastName', type: 'string' }, | |
{ name: 'Company', type: 'string' }, | |
{ name: 'Title', type: 'string' }, | |
{ name: 'Phone', type: 'string' }, | |
{ name: 'MobilePhone', type: 'string' }, | |
{ name: 'Email', type: 'string' }, | |
{ name: 'Status', type: 'string' } | |
], | |
validations: [ | |
{ type: 'presence', field: 'LastName', message: 'Enter a last name.' }, | |
{ type: 'presence', field: 'Company', message: 'Enter a company.' }, | |
{ type: 'presence', field: 'Status', message: 'Select a status.' } | |
], | |
//Bind each CRUD functions to a @RemoteAction method in the Apex controller | |
proxy: { | |
type: 'direct', | |
api: { | |
read: PocketCRMLeadController.Query, | |
create: PocketCRMLeadController.Add, | |
update: PocketCRMLeadController.Edit, | |
destroy: PocketCRMLeadController.Destroy | |
}, | |
limitParam: 'recordCount', // because "limit" is an Apex keyword | |
sortParam: 'sortParams', // because "sort" is a keyword too | |
pageParam: false, // we don't use this in the controller, so don't send it | |
extraParams: ( { 'searchFilter':'%' } ), | |
reader: { | |
type: 'json', | |
rootProperty: 'records', | |
messageProperty: 'errorMessage' | |
}, | |
writer: { | |
type: 'json', | |
root: 'records', | |
writeAllFields: false, // otherwise empty fields will transmit as empty strings, instead of "null"/not present | |
allowSingle: false, // need to always be an array for code simplification | |
encode: false // docs say "set this to false when using DirectProxy" | |
} | |
} | |
}, | |
}); | |
//================================================================================================================ | |
//PROXY RELATED EXTENSIONS | |
//================================================================================================================ | |
//AN IMPORTANT NOTE: if your org has ANY installed packages, you MUST reference the Apex controller name in your | |
//JavaScript with your org's namespace. If you fail to do this, you will get a a JavaScript error that the Apex | |
//controller can NOT be found! | |
//================================================================================================================ | |
//Adjust our read method to add a function that Touch expects to see to get Arguments. | |
PocketCRMLeadController.Query.directCfg.method.getArgs = | |
function (params, paramOrder, paramsAsHash) { | |
console.log('getArgs: ' + params.data); | |
return [params]; | |
} | |
Ext.data.proxy.Direct.prototype.createRequestCallback = | |
function(request, operation, callback, scope){ | |
var me = this; | |
return function(data, event){ | |
console.log('createRequestCallback: ' + operation); | |
me.processResponse(event.status, operation, request, data, callback, scope); | |
}; | |
}; | |
//================================================================================================================ | |
//STORES | |
//Stored serve as the client-side cache of your data; they loading data into your app's views. | |
//================================================================================================================ | |
Ext.define("PocketCRM.store.Leads", { | |
extend: "Ext.data.Store", | |
requires: "Ext.data.proxy.LocalStorage", | |
config: { | |
model: "PocketCRM.model.Lead", | |
autoLoad: true, | |
pageSize: 25, | |
//Create a grouping; be certain to use a field with content or you'll get errors! | |
groupField: "Status", | |
groupDir: "ASC", | |
//Create additional sorts for within the Group. | |
sorters: [{ property: 'LastName', direction: 'ASC'}, { property: 'FirstName', direction: 'ASC'}], | |
}, | |
listeners: { | |
load: function(){ | |
console.log('store.load(): loaded!'); | |
}, | |
} | |
}); | |
</script> | |
</apex:component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
======================================================== | |
Name: PocketCRM_CSS | |
Type: Visualforce Custom Component | |
Purpose: For Sencha Touch PocketCRM App | |
Created by: Don Robins - www.ForceMentor.com | |
Created on: August 1, 2012 | |
Copyright 2012 Outformations, Inc. | |
Rev # Revised on Revised by Revision Description | |
----- ---------- ------------------------------------- | |
1.0 08/01/2012 Don Robins Initial Release | |
1.1 10/01/2012 Don Robins v3 | |
========================================================= | |
--> | |
<apex:component> | |
<style type="text/css"> | |
/** | |
* Example of an initial loading indicator. | |
* It is recommended to keep this as minimal as possible to provide instant feedback | |
* while other resources are still being loaded for the first time | |
*/ | |
html, body { | |
height: 100%; | |
background-color: #1985D0 | |
} | |
#appLoadingIndicator { | |
position: absolute; | |
top: 50%; | |
margin-top: -15px; | |
text-align: center; | |
width: 100%; | |
height: 30px; | |
-webkit-animation-name: appLoadingIndicator; | |
-webkit-animation-duration: 0.5s; | |
-webkit-animation-iteration-count: infinite; | |
-webkit-animation-direction: linear; | |
} | |
#appLoadingIndicator > * { | |
background-color: #FFFFFF; | |
display: inline-block; | |
height: 30px; | |
-webkit-border-radius: 15px; | |
margin: 0 5px; | |
width: 30px; | |
opacity: 0.8; | |
} | |
@-webkit-keyframes appLoadingIndicator { | |
0% { | |
opacity: 0.8 | |
} | |
50% { | |
opacity: 0 | |
} | |
100% { | |
opacity: 0.8 | |
} | |
} | |
/** | |
* PocketCRM Custom styles | |
*/ | |
/* Increase height of list item so title and narrative lines fit */ | |
.x-list .x-list-item .x-list-item-label { | |
min-height: 4.5em!important; | |
} | |
/* Move up the disclosure button to account for the list item height increase */ | |
.x-list .x-list-disclosure { | |
position: absolute; | |
bottom: 0.85em; | |
right: 0.44em; | |
} | |
.list-item-line-main { | |
float:left; | |
width:100%; | |
font-size:100%; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
padding-right:25px; | |
line-height:115%; | |
} | |
.list-item-line-detail { | |
float:left; | |
width:95%; | |
font-size:60%; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
padding-right:25px; | |
padding-left:10px; | |
line-height:125%; | |
} | |
.x-item-selected .list-item-line-detail { | |
color:#ffffff; | |
font-weight: bold; | |
} | |
.leads-list-empty-text { | |
padding:10px; | |
} | |
</style> | |
</apex:component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//======================================================== | |
// Name: PocketCRMLeadController | |
// Type: Controller | |
// Purpose: For Sencha Touch PocketCRM App | |
// Created by: Don Robins - www.ForceMentor.com | |
// Created on: August 1, 2012 | |
// Copyright 2012 Outformations, Inc. | |
// | |
// Rev # Revised on Revised by Revision Description | |
// ----- ---------- ------------------------------------- | |
// 1.0 08/01/2012 Don Robins Initial Release | |
// 1.1 10/01/2012 Don Robins v3 | |
//========================================================= | |
public with sharing class PocketCRMLeadController{ | |
public PocketCRMLeadController(){} | |
//======================================================================== | |
//INNER CLASSES | |
//These support data request/response transport for remoting. | |
//======================================================================== | |
//The server response expected by the ExtJS DirectProxy API methods. | |
public class Response { | |
public Boolean success; | |
public String errorMessage; | |
public List<SObject> records; | |
public Integer total; | |
Response() { | |
records = new List<SObject>(); | |
success = true; | |
} | |
} | |
//One of the parameters supplied by the DirectProxy read method. | |
public class QueryRequest { | |
Integer start; | |
Integer recordCount; | |
List<Map<String, String>> sortParams; | |
String searchFilter = ''; | |
Public QueryRequest() { | |
start = 1; | |
recordCount = 1; | |
} | |
Public QueryRequest(Integer pStart, Integer pRecordCount) { | |
start = pStart; | |
recordCount = pRecordCount; | |
} | |
} | |
//======================================================================== | |
//REMOTE ACTION METHODS | |
//RemoteAction CRUD methods called by the Sencha touch Proxy in the data package. | |
//======================================================================== | |
@RemoteAction | |
public static Response Query(QueryRequest qr){ | |
Response resp = new Response(); | |
//Enforce a limit on the number of rows requested. | |
final integer QUERY_LIMIT = 500; | |
if( qr.start >= QUERY_LIMIT ){ | |
resp.success = false; | |
resp.errorMessage = 'Maximum number of records (' + String.valueOf(QUERY_LIMIT) + ') exceeded!'; | |
return resp; | |
} | |
try { | |
getAllLeads(qr, resp); | |
} catch (Exception e) { | |
resp.success = false; | |
resp.errorMessage = 'Query failed: ' + e.getMessage(); | |
} | |
return resp; | |
} | |
//======================================================================= | |
//PUBLIC CRUD REMOTE ACTION METHODS | |
//======================================================================= | |
@RemoteAction | |
public static Response Edit(List<Lead> LeadData){ | |
return updateLeadList(LeadData); | |
} | |
@RemoteAction | |
public static Response Add(List<Lead> LeadData){ | |
return insertLeadList(LeadData); | |
} | |
@RemoteAction | |
public static Response Destroy(List<Lead> LeadData){ | |
return deleteLeadList(LeadData); | |
} | |
//======================================================================= | |
//PRIVATE HELPER METHODS | |
//======================================================================= | |
private static void getAllLeads(QueryRequest qr, Response resp){ | |
//Page size is set in the Sencha store as recordCount. | |
Integer pageSize = qr.recordCount; | |
//Page number will be calculated. | |
Integer pageNumber = 0; | |
//Start is the record number indicating the start of the page. | |
if( qr.start > 0 ){ | |
pageNumber = qr.start / pageSize; | |
} | |
//Calculate the offset for SOQL. | |
Integer offset = pageNumber * pageSize; | |
//Build the query in pieces. | |
String fieldList = 'Id,FirstName,LastName,Company,Title,Phone,MobilePhone,Email,Status'; | |
String whereClause = (qr.searchFilter != '' ? 'WHERE Name LIKE \'' + qr.searchFilter + '\'' : '') ; | |
String orderByClause = 'LastName, FirstName'; | |
//Construct a base query to which the page offsets will be added. | |
String baseQuery = 'SELECT ' + fieldList + ' FROM Lead ' + whereClause + ' ORDER BY ' + orderByClause; | |
//Construct a count query to pass back the total records matching a search criteria. | |
String baseCountQuery = 'SELECT COUNT() FROM Lead ' + whereClause ; | |
//Construct the fetch query with the offset. | |
String fetchQuery = baseQuery + ' LIMIT ' + pageSize + ' OFFSET ' + offset; | |
try { | |
//Set the count. | |
resp.total = Database.countQuery(baseCountQuery); | |
//Set the fetched recordset. | |
resp.records = Database.query(fetchQuery); | |
//Set the status flag. | |
resp.success = true; | |
} catch (Exception e) { | |
//Set the total count of records matching the query. | |
resp.total = 0; | |
//Set the recordset to return. | |
resp.records = new List<Lead>(); | |
//Set the status flag. | |
resp.success = false; | |
} | |
} | |
private static Response insertLeadList(List<Lead> LeadData){ | |
Response resp = new Response(); | |
resp.success = true; | |
try { | |
INSERT LeadData; | |
} catch (Exception e) { | |
resp.success = false; | |
resp.errorMessage = 'Insert failed: ' + e.getMessage(); | |
} | |
return resp; | |
} | |
private static Response updateLeadList(List<Lead> LeadData){ | |
Response resp = new Response(); | |
resp.success = true; | |
try { | |
UPDATE LeadData; | |
} catch (Exception e) { | |
resp.success = false; | |
resp.errorMessage = 'Update failed: ' + e.getMessage(); | |
} | |
return resp; | |
} | |
private static Response deleteLeadList(List<Lead> LeadData){ | |
Response resp = new Response(); | |
resp.success = true; | |
try { | |
DELETE LeadData; | |
} catch (Exception e) { | |
resp.success = false; | |
resp.errorMessage = 'Deletion failed: ' + e.getMessage(); | |
} | |
return resp; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
An important note: if your org has a registered namespace, you MUST reference the Apex controller name in your JavaScript with your org's namespace. If you fail to do this, you will get a JavaScript error that the Apex controller can NOT be found!