• JeffTrull
  • NEWBIE
  • 25 Points
  • Member since 2009

  • Chatter
    Feed
  • 1
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 5
    Questions
  • 13
    Replies

I have previously made use of the AJAX Toolkit to create a generic VF component that displays selected fields of any sObject in a user-friendly grid, using the ExtJS library.  I was excited to give the new REST API a try, thinking that it would be both simpler and more performant than the SOAP-based Toolkit.

 

I've started coding up a new VF page using this approach, but have run into a fairly major stumbling block.  The XHR request is mysteriously failing; as far as I can tell it is because my Visualforce pages are of the form c.na7.visual.force.com/apex/whatever, while the REST API lives at na7.salesforce.com/services/data/v20.0, and this is considered to be a different "domain".

 

Have I correctly diagnosed the source of my problem?  If so, is there a chance for REST access in the future?

 

Thanks,

Jeff Trull

 

I was wondering how many people out there are coding using the Bulk API, and what your experiences have been... For me it's been a bit frustrating.  Batch processing is very inconsistent and (seemingly) temperamental.  The strangest of all has been the mysterious hangs of batches.  For example, right now I have a job currently consisting of four batches, each of 5000 Opportunity records.  The first three required between 3 and 4 minutes to process, while the fourth has been running now for five hours, but still says "In Progress" (and not timed out, etc. as you might expect), with 3700 complete.  The Processing Time information shows values below those of the prior three batches, so I know it's not actually doing anything, or having some type of trigger cascade, or whatever.  It's just sitting there...  Is there any way to get it running again?

 

I'd really like to rely on this feature for our data migration, but these runtime issues are pretty painful.

 

In addition, during deletes I sometimes will get an "unexpected error, contact support" message from a batch.  See case 03853809.

 

If anyone has advice or best practices for dealing with this I would be grateful to hear them.  FWIW I'm running from Perl via WWW::Salesforce::Simple and REST::Client, but this seems to be a server side issue.

 

Regards,

Jeff

 

I am having some trouble performing searches on time-based workflow.  For debugging purposes, it's very helpful to search for workflows related to a particular record - usually a Contact.  Since we cannot do this with a SOQL query, I'm falling back on the Time-Based Workflow Monitoring page, and am encountering some difficulty with the Record Name field.  Specifically I find that if I supply the Record Name as listed in the search results with the "equals" relation, the search cannot find my workflows.

 

For example, if I create a Contact record with first name Robert, last name Dobbs, and then trigger a workflow on this Contact, an unqualified search on the monitoring page will turn up a workflow queued with the record name "Robert Dobbs".  However, if I do a search for "Record Name" equals "Robert Dobbs", no results will be found.  I've tried numerous variants on the name, and I find that the "equals" check never seems to work.  "starts with" will work, but only (and interestingly!) if I specify the last name - and only the last name.

 

 

Should I be able to search for "Record Name" "equals" Firstname Lastname?

 

Thanks and Regards,

Jeff Trull

 

Just downloaded and installed according to the directions here.  "Force.com" is not one of the listed perspectives.

 

I did try the suggested fix at the end of the install directions, although they were a little vague:

 

"Add the explicit path to the JRE in the shortcut you use to launch Eclipse"

 

I took this to mean that I should run as follows:

 

eclipse -vm /usr/lib/jvm/java-6-openjdk/jre/bin

 

That didn't make any difference.

 

I also tried to first install the "WST" components referred to in other posts.  That didn't help either.

 

Is there some way to manually activate the IDE within Eclipse?  Some config setting, perhaps?

 

My setup:

 

Ubuntu Lucid 10.04 Beta 2

Eclipse 3.5.2

 

Thanks,

Jeff

 

I just did some work to take advantage of the tree nature of the Campaign hierarchy to make a cleaner display/selection mechanism for users.  I thought I'd post it here and see what people thought.  I used ExtJS and a custom controller to create:

  1. A cascading popup menu of campaigns (one menu per level)
  2. A tree view, similar to file/directory browsers, where more of the hierarchy is revealed as you expand "folders" (Campaigns with child Campaigns)

It looks like this:

campaign hierarchy browse/select in action

I think this is an improvement over the built-in "search by name" popup in that users may not know what they're looking for in advance.  The alternative of scrolling through all the names may be painful too (we have > 500 Campaigns).

 

The Apex class is here and the Visualforce page is here.  A much longer blog post on this subject is here.

 

Would be very interested to hear people's impressions and suggestions.

 

Thanks,

Jeff Trull

 

Message Edited by JeffTrull on 02-23-2010 12:23 PM

I have the forcedotcom/SalesforceMobileSDK-Android cloned and I'm trying to upgrade our existing mobile app to the latest SDK and I'm unable to find the Salesforce jar file that is referenced.

 

Does anyone know where to find it?

Hey all,

Just a quick simple question.

Trying to update an object through Apex remoting. One of the fields I am attempting to update is a date time. In what format do I need to pass a datetime for it to work? I've tried just passing a date object, and date.toUTCString(). How do I need to format the datetime in javascript for apex to be able to deal with it? Thanks!

 

 

 

 

Hy,

 

I am trying to use the ExtJS4 grid widget to display and modify SObjects on a page as shown here:

 

Others like Jeff Trull have published great solutions to this using

Javascript Remoting and a custom Javascript Rest proxy to interact with Salesforce REST API.

 

What I don't like with both solutions is that they are constructed out of hundred lines of (at least for me ;-) hard to maintain lines of Javascript. So I was really happy to find the this tutorial: RESTful Store with GridPanel and RowEditor

 

My problem is that it simply doesn't work with Salesforce.com somewhat non-standard REST API:

 

Problem 1: Parameters from ExtJS go to Salesforce proxy instead to REST API

Due to the Same-Origin Policy thing I am calling the API by using a SalesforceProxy-Endpoint header.

 ...
 proxy: {
	type: 'rest',
	noCache: false,
	url: 'https://' + location.hostname + '/services/proxy',
	headers: {
		'SalesforceProxy-Endpoint': '{!URLFOR('/services')}/apexrest/sobject/{!object}',
		'Authorization': 'OAuth {!GETSESSIONID()}',
		'Accept': 'application/json'
	},
	...

 With this ExtJS is not able to construct correct REST URLs. E.g. When doing an DELETE Id it concats "/{Theid} TO the services/proxy url. Instead I need it as part of my SalesforceProxy-Endpoint url.

 

Problem 2: No standard GET on REST API

 

To get all accounts ExtJS calls  a GET .../Account/ but Salesforce.com expects a non standard /Accounts?query=SELECT Name FROM....

 

Do you have any idea to solve this?

My yet inimplemented idea is to create an adapter REST service with APEX that works like ExtJS expects it an internally calls the Standard REST API...just passing through its results.

I'm trying to use the ExtJS 4 Grid widget to display multipe SObjects on a page in an editable grid.

 

I have a visualforce page which just calls a custom component.

 

<apex:page sidebar="false" showHeader="false">

	<c:sobjectGrid objectName="Opportunity" />
	
	<c:sobjectGrid objectName="Contact" />
	
	<div id="pmgrid">
		<c:sobjectGrid objectName="Milestone__c" width="600" height="600" title="Milestones" />
	</div>
	
</apex:page>

 

This visualforce component initializes the ExtJS Grid

<apex:component >
	<apex:attribute name="objectName" 	description=" " type="String" 	required="true"/>
	<apex:attribute name="fields" 		description=" " type="String" 	required="false" default="Id, Name" />
	<apex:attribute name="limit" 		description=" " type="String" 	required="false" default="100" />
	<apex:attribute name="orderby" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="search" 		description=" " type="String" 	required="false"/>
	<apex:attribute name="width" 		description=" " type="Integer" 	required="false" default="400" />
	<apex:attribute name="height" 		description=" " type="Integer" 	required="false" default="300" />
	<apex:attribute name="title" 		description=" " type="String" 	required="false" default="" />

	<!-- Visualforce takes care that this is only loaded once when having more than one component in a page  -->
	<apex:includeScript value="http://cdn.sencha.io/ext-4.0.7-gpl/ext-all.js" />
	<apex:stylesheet value="http://cdn.sencha.io/ext-4.0.7-gpl/resources/css/ext-all.css" />

	<script type="text/javascript">

		Ext.require(['Ext.data.*', 'Ext.grid.*']);

		Ext.define('CurrentSObject', {
			extend: 'Ext.data.Model',
			fields: ['id', 'name']
		});

		Ext.onReady(function(){
			var store = Ext.create('Ext.data.Store', {
				autoLoad: true,
				autoSync: true,
				model: 'CurrentSObject',
				proxy: {
					type: 'rest',
					url: '../services/apexrest/sobject/{!objectName}',
					params: {
		  				sessionId: '{!$Api.Session_ID}'
			  		},
					reader: {
						type: 'json'
					},
					writer: {
						type: 'json'
					}
				},
				listeners: {
					write: function(store, operation){
						var record = operation.getRecords()[0],
							name = Ext.String.capitalize(operation.action),
							verb;
							
						if (name == 'Destroy') {
							record = operation.records[0];
							verb = 'Destroyed';
						} else {
							verb = name + 'd';
						}
						Ext.example.msg(name, Ext.String.format("{0} {!objectName}: {1}", verb, record.getId()));
					}
				}
			});
			
			var rowEditing = Ext.create('Ext.grid.plugin.RowEditing');
			
			var grid = Ext.create('Ext.grid.Panel', {
				renderTo: parent.Ext.getBody(),
				plugins: [rowEditing],
				width: {!width},
				height: {!height},
				title: '{!title}',
				store: store,
				columns: [{
					text: 'ID',
					width: 40,
					sortable: true,
					dataIndex: 'id'
				}, {
					text: 'Name',
					flex: 1,
					sortable: true,
					dataIndex: 'name',
					field: {
						xtype: 'textfield'
					}
				}],
				dockedItems: [{
					xtype: 'toolbar',
					items: [{
						text: 'Add',
						iconCls: 'icon-add',
						handler: function(){
							// empty record
							store.insert(0, new CurrentSObject());
							rowEditing.startEdit(0, 0);
						}
					}, '-', {
						itemId: 'delete',
						text: 'Delete',
						iconCls: 'icon-delete',
						disabled: true,
						handler: function(){
							var selection = grid.getView().getSelectionModel().getSelection()[0];
							if (selection) {
								store.remove(selection);
							}
						}
					}]
				}]
			});
			grid.getSelectionModel().on('selectionchange', function(selModel, selections){
				grid.down('#delete').setDisabled(selections.length === 0);
			});
		});
	</script>
</apex:component>

 and loads via this generic SObject CRUD Service that is implemented as Apex REST:

 

@RestResource(urlMapping='/sobject/*') 
global with sharing class SObjectRestService { 
	
	@HttpPost
	global static String doCreate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId == null) {
			return createResource(resourceType, request);
		} 
		else {	
			response.statusCode = 400;	// BAD REQUEST
			return 'Invalid operation';
		} 
	}

	@HttpGet
	global static List<SObject> doRead(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return getSpecificResource(resourceType, resourceId, request);
		} 
		else {	
			return getAllResources(resourceType, request);
		} 
	}		
	
	@HttpPut
	global static String doUpdate(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);
		Map<String, String> params = request.params;

		if (resourceId != null) {
			return updateResource(resourceType, resourceId, request);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
	
	@HttpDelete
	global static String doDelete(RestRequest request, RestResponse response) {
		String resourceType = getType(request);
		String resourceId = getId(request);

		if (resourceId != null) {
			return deleteResource(resourceType, resourceId);
		} 
		else {	
			return 'Invalid operation';
		} 
	}
  
  	private static String getType(RestRequest request) {
  		String resourceType = null;
  		
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash == lastSlash) {
  			resourceType = request.requestURI.substring(firstSlash + 1);
  		}
  		else {
  			resourceType = request.requestURI.substring(firstSlash + 1, lastSlash);
  		}
  		return resourceType;
  	}
  
	private static String getId(RestRequest request) {
  		String resourceId = null;
  		  	
  		Integer firstSlash = request.requestURI.indexOf('/sobject/') + 8;
  		Integer lastSlash = request.requestURI.lastIndexOf('/');
  		
  		if(firstSlash != lastSlash) {
  			resourceId = request.requestURI.substring(lastSlash + 1);
  		}
  		return resourceId;  	
  	}
	
  	private static List<SObject> getSpecificResource(String resourceType, String resourceId, RestRequest request) {
    	String qryFields = 'id, name';

    	if (request.params.containsKey('fields')) {
    		qryFields = request.params.get('fields');
    	}
   		return Database.query('select ' + qryFields + ' from ' + resourceType + ' where Id = \'' + resourceId +'\'');
  	}
  
  	private static List<SObject> getAllResources(String resourceType, RestRequest request) { 
	    String qryFields = 'id, name';
	    String qryLimit = 'limit 100';   
	    String qryOrderby = '';      
	    String qryWhere = '';  
	      
	    if (request.params.containsKey('fields')) qryFields = request.params.get('fields');
	    if (request.params.containsKey('limit')) qryLimit = 'limit ' + request.params.get('limit'); 
	    if (request.params.containsKey('orderby')) qryOrderby = 'order by ' + request.params.get('orderby');
	    if (request.params.containsKey('search')) qryWhere = 'where Name LIKE \'' + request.params.get('search') +'%\'';
	      
	    return Database.query('select ' + qryFields + ' from ' + resourceType + ' ' + qryWhere + ' ' + qryOrderby + ' ' + qryLimit);
	}

  	private static String updateResource(String resourceType, String resourceId, RestRequest request) {  	
  		SObject resource;
	    Map<String, Schema.SObjectField> sObjectFieldsMap = Schema.getGlobalDescribe().get(resourceType).getDescribe().fields.getMap();
	  	
	  	try {
			// fetch the member by username if it exists
			resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
		  	
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			update resource;
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String createResource(String resourceType, RestRequest request) {  	
  		SObject resource;
  		Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
	    Map<String, Schema.SObjectField> sObjectFieldsMap = gd.get(resourceType).getDescribe().fields.getMap();
	    
	  	try {
			Schema.SObjectType st = gd.get(resourceType);
		        
	        resource = st.newSObject();
	        
			// populate the object's fields
			for (String key : request.params.keySet()) {
		    	if (sObjectFieldsMap.containsKey(key) && sObjectFieldsMap.get(key).getDescribe().isUpdateable()) {
					resource.put(key, request.params.get(key)); 
				}
			}	  
			insert resource;
		}
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
	  	return resource.id;
  	}
  	
  	private static String deleteResource(String resourceType, String resourceId) {  	
	  	try {
			SObject resource = Database.query('select Id from ' + resourceType + ' where Id = \'' + resourceId +'\'');
			delete resource;
			return 'DELETED';
		}
		catch (QueryException qe) {
			return resourceType + ' with Id ' + resourceId + ' not found.';		  
		} 
		catch (DMLException de) {
		   	return de.getDmlMessage(0);   
		} 
		catch (Exception e) {
	    	return e.getMessage();
  		}   
  	}
}

Everything was tested succesfully in separation. The service returns correct JSON. I tested it with the ApiGee Console. The Component renders perfectly with static JSON.

 

But when I load my page it just renders three empty grids and Firebug is showing me 3 empty XDR response with return code 302 Found.

 

I have absolutely no clue why. Maybe you can help me?

 

Ideas what I might have done wrong include:

 

- Do I need to pass the session id in the components javascript? Have I done it wrong?

- Is ExtJS expecting another JSON format? (This would produce different errors!)

 

Your ideas are very welcome.

 

Robert

I have previously made use of the AJAX Toolkit to create a generic VF component that displays selected fields of any sObject in a user-friendly grid, using the ExtJS library.  I was excited to give the new REST API a try, thinking that it would be both simpler and more performant than the SOAP-based Toolkit.

 

I've started coding up a new VF page using this approach, but have run into a fairly major stumbling block.  The XHR request is mysteriously failing; as far as I can tell it is because my Visualforce pages are of the form c.na7.visual.force.com/apex/whatever, while the REST API lives at na7.salesforce.com/services/data/v20.0, and this is considered to be a different "domain".

 

Have I correctly diagnosed the source of my problem?  If so, is there a chance for REST access in the future?

 

Thanks,

Jeff Trull

 

I was wondering how many people out there are coding using the Bulk API, and what your experiences have been... For me it's been a bit frustrating.  Batch processing is very inconsistent and (seemingly) temperamental.  The strangest of all has been the mysterious hangs of batches.  For example, right now I have a job currently consisting of four batches, each of 5000 Opportunity records.  The first three required between 3 and 4 minutes to process, while the fourth has been running now for five hours, but still says "In Progress" (and not timed out, etc. as you might expect), with 3700 complete.  The Processing Time information shows values below those of the prior three batches, so I know it's not actually doing anything, or having some type of trigger cascade, or whatever.  It's just sitting there...  Is there any way to get it running again?

 

I'd really like to rely on this feature for our data migration, but these runtime issues are pretty painful.

 

In addition, during deletes I sometimes will get an "unexpected error, contact support" message from a batch.  See case 03853809.

 

If anyone has advice or best practices for dealing with this I would be grateful to hear them.  FWIW I'm running from Perl via WWW::Salesforce::Simple and REST::Client, but this seems to be a server side issue.

 

Regards,

Jeff

 

Just downloaded and installed according to the directions here.  "Force.com" is not one of the listed perspectives.

 

I did try the suggested fix at the end of the install directions, although they were a little vague:

 

"Add the explicit path to the JRE in the shortcut you use to launch Eclipse"

 

I took this to mean that I should run as follows:

 

eclipse -vm /usr/lib/jvm/java-6-openjdk/jre/bin

 

That didn't make any difference.

 

I also tried to first install the "WST" components referred to in other posts.  That didn't help either.

 

Is there some way to manually activate the IDE within Eclipse?  Some config setting, perhaps?

 

My setup:

 

Ubuntu Lucid 10.04 Beta 2

Eclipse 3.5.2

 

Thanks,

Jeff

 

I just did some work to take advantage of the tree nature of the Campaign hierarchy to make a cleaner display/selection mechanism for users.  I thought I'd post it here and see what people thought.  I used ExtJS and a custom controller to create:

  1. A cascading popup menu of campaigns (one menu per level)
  2. A tree view, similar to file/directory browsers, where more of the hierarchy is revealed as you expand "folders" (Campaigns with child Campaigns)

It looks like this:

campaign hierarchy browse/select in action

I think this is an improvement over the built-in "search by name" popup in that users may not know what they're looking for in advance.  The alternative of scrolling through all the names may be painful too (we have > 500 Campaigns).

 

The Apex class is here and the Visualforce page is here.  A much longer blog post on this subject is here.

 

Would be very interested to hear people's impressions and suggestions.

 

Thanks,

Jeff Trull

 

Message Edited by JeffTrull on 02-23-2010 12:23 PM

Hello,

 

EXTJS library is being used by many people these days to enhace the standard VF functionality.

Here is one example of how to create a EXTJS Datagrid in a VF page (from Appirio):  link

 

My question is related to the interaction between different pieces used in the approach: Apex (Controller) <-> Visualforce components <-> EXTJS components:

 

1. The first type of interaction is following:

 

a. Apex fetches data  (either SFDC data or makes a WS callout to get data)

b. Visualforce components, such as <apex:repeat> are involved into formatting the data to be provided to EXTJS component

c. EXTJS component shows data that was formatted properly using Visualforce components (you'll get a better understanding if you look at a link above)

 

2. Second type of interaction is the point where questions comes: when data is displayed within EXTJS components, how do we link events that happen on EXTJS components to Apex? To make it clear, here is an example:

 

There is a <apex:commandButton> Visualforce component that has an 'action' attribute that links to a method within a Controller. Whenever a user presses the button a method is invoked. - this is all clear because we are using Visualforce only.

However, when it comes to EXTJS - its components have their own buttons and links, or any other GUI controls that can generate events. The question is - how to link a button on EXTJS component to a method within a Controller?

 

Generally speaking, the question would sound like: "How to link events, such as button clicks, in a custom javascipt code to a method within a Controller?"

 

 

 

Many thanks!

  • January 24, 2009
  • Like
  • 0