March 25, 2011

Is Javascript Remoting == ViewStateLess Visualforce ?

Javascript remoting is one of my favourites from Spring’11 release and I’m constantly thinking of making best use of it in Visualforce. I believe remoting is the way to make Visualforce more "viewstate less", this post shows a POC of a complete functional example of a page that searches and updates accounts using Remoting only i.e no viewstate.
In my previous post about “javascript remoting using jquery tempaltes”, we saw how we use remoting to easily render a salesforce style grid of accounts.
In this post, we will see how we can easily extend the same account grid example, to finally have following features
  • A account search box, that allows user to search for accounts via name. (this search is powered by Javascript Remoting)
  • Account records returned by Javascript remoting search are rendered into a salesforce style pageblocktable, this is done using jquery templates.
  • Have editable PHONE field in the account grid. When user changes any phone number in this grid its updated back on the account record in salesforce (this update is powered by Javascript Remoting)
Side note : This post requires jquery knowledge, its jquery who’s making many big things look trivial in sample code. So if you are not in LOVE with jquery, starting dating her/him Open-mouthed smile

Where is the code ?

Code for both Apex Controller and Visualforce page is available on snipplr.com. If you have any comments/concerns/queries please comment back here on this post.

Remoting in Action !

Searching for accounts


Account updated on blur


So, Javascript Remoting == ViewStateLess VisualForce ?

It would be great if we can go 100% view stateless !!! but as of now, its not 100% possible to go view stateless on all fronts using Javascript Remoting.
The major reason I see is “locale specific formatting and validations”. These validations are done super smartly and silently by visualforce tags like <apex:inputField …/>.  So, you have to be careful, if you are going to deal with fields like Number, Currency and Date fields in visualforce.

But, that doesn't means we loose heart and can't do cool things, with HTML 5 "input" tag has gone much smarter then ever before i.e. supporting phone, email etc as type.

March 11, 2011

Javascript Remoting & jquery templates - easy way to rich and high performance interfaces...!

Since release of Javascript remoting feature in Spring’11 release, I was super excited about it and was looking for spare time to play with it.
Few days back I read about jquery templates, that’s an awesome jquery plugin(soon going to be part of jquery min).
jQuery templates contain markup with binding expressions ('Template tags'). Templates are applied to data objects or arrays, and rendered into the HTML DOM
This templating engine supports powerful tag lib in-built that supports conditions and iterations. For more details check this post. If you are coming from J2EE background you can treat this jquery template feature very similar to Apache Velocity and free marker templates.

Using jquery templates & Javascript Remoting together !

This mashup is important from force.com development point of view because
  • Remoting allows you to perform almost any apex logic via Javascript.
  • Using remoting one can by pass all the hassle of view state, and performance issues because of that on complex visualforce screens.
  • Mashing up these two can result in very light weight, super fast and complex visualforce screens with lesser and more human readable code

We will see a simple example below, where
  • We search for accounts for a user inputted account name using Remoting.
  • Display the search results in Salesforce pageBlockStyle table without re-rendering and form submission. All using jquery templates, the lines of code required to do so is really trivial and the markup is very straight forward to understand.

Code Snippet

This code snippet is pretty similar to what you must have already seen in visualforce remoting docs. I only enhanced it for jqueryTemplate support to render a table easily. I am assuming you are already comfortable with basic Apex, HTML, jquery and visualforce. Only suggested reading is jqueryTemplates, please have a quick look at this page before diving into the code below, for easy understanding.
Apex Controller – testremotingcontroller.cls
public with sharing class  testremotingcontroller {
    @RemoteAction
    public static Account[] searchAccounts(String accountName) {
        // support * search like salesforce lookups
        accountName = accountName.replaceAll('[*]', '%');
        return [select id, name, phone, type, numberofemployees from 
             Account where name like :accountName ];
        
    }   
}
Visualforce Page – testremoting.page
<apex:page controller="testremotingcontroller">
<apex:includeScript value="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"/>
<apex:includeScript value="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"/>

<apex:sectionHeader title="Javascript Remoting & jQuery Templates !"/>

<apex:pageBlock title="Accounts">
 <!-- Section to draw search field for account -->
    <apex:pageBlockSection title="Search Accounts" columns="2">
        <apex:pageBlockSectionItem >
            Account Name :
            <input type = "text" id = "accountNameToSearch" />
            <button onclick="searchAccounts()">Get Account</button>
        </apex:pageBlockSectionItem>        
    </apex:pageBlockSection>

 <!-- result section for showing matching accounts -->
    <apex:pageBlockSection title="Matching Accounts !" columns="1">
    <!-- 
    Created Empty table using the CSS styles of visualforce pageBlockTable 
    This gives same look and feel 
    -->
    <table cellspacing="0" cellpadding="0" border="0" id="searchResults" class="list ">
        <colgroup span="2"></colgroup>
        <thead class="rich-table-thead">
            <tr class="headerRow ">
                <th colspan="1" scope="col" class="headerRow">Id</th>
                <th colspan="1" scope="col" class="headerRow"> Name</th>
                <th colspan="1" scope="col" class="headerRow"> Phone</th>
                <th colspan="1" scope="col" class="headerRow">Type</th>
                <th colspan="1" scope="col" class="headerRow"> Number of Employees</th>                                 
            </tr>
        </thead>
    <!-- table body left empty for populating via row template using jquery -->
        <tbody />
    </table>
    </apex:pageBlockSection>
</apex:pageBlock>

<!-- 
Create a named jquery template 
This template represents just a result row, with binding variables for each queried field from account.
-->
<script id="resultTableRowTemplate" type="text/x-jquery-tmpl">
<tr onfocus="if (window.hiOn){hiOn(this);}" onblur="if (window.hiOff){hiOff(this);}" onmouseout="if (window.hiOff){hiOff(this);} " onmouseover="if (window.hiOn){hiOn(this);} " class="dataRow even  first">
    <td class="dataCell">${Id}</td>
    <td class="dataCell">${Name}</td>
    <td class="dataCell">${Phone}</td>
    <td class="dataCell">${Type}</td>        
    <td class="dataCell">${NumberOfEmployees}</td>
</tr>           
</script>

<script type="text/javascript">
// if you are inside some component
// use jquery nonConflict
// var t$ = jQuery.noConflict();

function searchAccounts() {
    var accountName = $('#accountNameToSearch').val();
    // clear previous results, if any
    $("#searchResults tbody").html('');
    
    // The Spring-11 gift from force.com. Javascript remoting fires here
    // Please note "abhinav" if my org wide namespace prefix
    // testremotingcontroller is the Apex controller
    // searchAccounts is Apex Controller method demarcated with @RemoteAction annotation.
    // DEPRECATED -     abhinav.testremotingcontroller.searchAccounts( accountName, ...) 
    // NEW - summer'12 approach for calling
    Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.testremotingcontroller.searchAccounts}',
                   accountName, function(result, event){            
        if (event.status && event.result) {  
          $.each(event.result, function () {                
             // for each result, apply it to template and append generated markup
             // to the results table body.
             $("#resultTableRowTemplate" ).tmpl(this).appendTo( "#searchResults tbody" );
          }
         );            
        } else {
           alert(event.message);
        }
    }, {escape:true});
}
</script>

</apex:page>
Thats it guys, on executing this page and searching for records it comes up beautifully like this

Where I got stuck with javascript remoting !

I was planning to present a more rich demo with ability to inline edit Account's phone on blur i.e you change any Phone displayed on grid, on leaving the cell it will update the Account for that via Remoting. But some how Javascript seems to rollback the Account updates silently.
I posted that as an question on discussion board here. Almost same code as above is in this discussion board question, if anyone of you has any clue, please share !

March 10, 2011

A Common Myth : Triggers “always” play in GOD Mode (System Context) !

I recently came across an interesting problem, reported by one of my peer colleague working on salesforce app. That salesforce app has a trigger that suddenly started failing for INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY error, more details below :

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, abhinav.TestSharingInTrigger: execution of AfterInsert caused by: System.DmlException: Update failed. First exception on row 1 with id a00900000018AKfAAM; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id

The question came out why this happened ? Triggers always play in God Mode i.e. SYSTEM CONTEXT, so how come a insufficient access rights problem come ?

Note: Similar to above error, one can also see INSUFFICIENT_ACCESS_OR_READONLY in trigger/apex code execution. This post reproduces both these errors and tell the resolution.

Why this problem occurred ?

This problem can very much happen, when

  • Sobject Sharing Rules in your org expose all records for PUBLIC READ and open limited set of records for WRITE. A good example of that is an Sobject with Sharing setting “Public Read Only”, this setting means any one can read all the records, but only owners can do the updates.
  • The user for which this code is executing is not having “Modify All” permissions on the Sobject. This is very powerful permission it by passes all sharing rules and opens all records of that Sobject for read/write access to that user.
  • A Apex Code/Trigger in Org calls a class demarcated as “WITH SHARING”, this class in turn queries the same “public read” Sobject and updates a record for whom current user is not owner.

Reproducing this issue

Now we will go thru some steps to reproduce this problem in minimal steps

Step 1 - Setting up fixture !

To discuss and reproduce this issue, following fixture was created

  • A custom sobject called “TestObj__c”, which has
    • single Date field named “A_Date_Field__c”
    • sharing settings of “Public read only”. (This is important)
    • a trigger that invokes a “with sharing” code that tries to update all TestObjs.A_Date_Field__c to something else, like 2 days from now.
  • Two users
    • One user with “System Admin” profile. This will give him all God Mode permissions, like “View/Modify” all data on sobject TestObj.
    • One user with custom “Standard Custom User” profile. This guy just has CRUD permissions on sobject TestObj.
TestSharing.trigger on TestObj__c

This trigger simply delegates the call to an apex class called “TestSharing.cls”

trigger TestSharing on TestObj__c (after insert) {
  TestSharing.handle(Trigger.new);
}
Apex Class TestSharing.cls

This class is called by the Trigger TestSharing on TestSobj and does all the arithmetic around sharing rules.

public class TestSharing {
  // Method called by Trigger 
  public static void handle(TestObj__c[] a)   {
  // try to change references in "with" sharing context 
   new WithSharingHandler().manipulateAll();
  }
  
  /*
    WITH SHARING wrapper for the above call
  */
  with sharing class WithSharingHandler {
    public void manipulateAll() {
    // query all TestObj records
    TestObj__c[] objs = [Select Id,A_Date_Field__c from TestObj__c];
    
  // iterate over each and change the date field to anything.
    for (TestObj__c ob : objs)      
      ob.A_Date_Field__c = System.today().addDays(2);         
    // Update the objs
    if (!objs.isEmpty()) 
       update objs;    
    }
  }
}

Step 2 – Reproducing issue using the fixture

One can reproduce this problem easily, via Salesforce interface or Apex Test class. We will see both. In both the cases we will follow these steps to stumble upon the problem

  • As system admin profile user, create a Testobj record.
  • After that as “Standard Custom User” profile user, try creating another TestObj record. This will flow fail.

You must be wondering why one can’t create records as “Standard Custom User” ??? because

  • We are querying all TestObj records in the Org, using “WITH SHARING” context. That is fine because TestObj’s sharing rules are “Public read only”. Note, here we can get handle to some of those records, which are not OWNED by current user. 

    TestObj__c[] objs = [Select Id,A_Date_Field__c from TestObj__c]; 
  • With in same WITH SHARING context. For all queried records, we are changing the date field and updating all TestObj records in org.

    // iterate over each and change the date field to anything.
        for (TestObj__c ob : objs)      
          ob.A_Date_Field__c = System.today().addDays(2);         
        // Update the objs
        if (!objs.isEmpty()) 
           update objs;    
        }
  • This operation failed because
    • We are in WITH SHARING Context
    • Sharing setting says the records are “Public Read Only”, that means one can query all, but can update only those he is owner or has permission to.
    • The “Standard Custom User” profile doesn’t have “Modify All” permission on TestObj, like “System Admins”. So any user belonging to that profile can only update his records, not all those are visible to him.
    • Based on above three points, in the above use case.
      • We first created a record using System Admin profile
      • when “Standard Custom User” profile user attempted the same, it got the record created by System admin in this SOQL result : [Select Id,A_Date_Field__c from TestObj__c]
      • So, when “Standard Custom User” profile user tried to update that Admin owned record in WITH SHARING context the stuff failed.
Step 2.1 Reproducing issue via Salesforce UI

Here are the steps

  1. Login as “System Admin” profile user and create one TestObj record. Enter any date value you want.
  2. Login as “Standard Custom User” profile and try creating the record now. You will get an error (INSUFFICIENT_ACCESS_OR_READONLY)

Error: Invalid Data.
Review all error messages below to correct your data.
Apex trigger abhinav.TestSharingInTrigger caused an unexpected exception, contact your administrator: abhinav.TestSharing: execution of AfterInsert caused by: System.DmlException: Update failed. First exception on row 1 with id a00900000018ANNAA2; first error: INSUFFICIENT_ACCESS_OR_READONLY, insufficient access rights on object id: []:

Step 2.2 Reproducing issue via Apex Test Class

We will do the same steps as above to reproduce the problem

public static testMethod void testSharings() {
  // Query Standard User 
  Profile p = [SELECT Id FROM profile WHERE name='Standard Custom User'];
  // Create a in-memory mock user for 
  // running tests in limited data access context
  User mockUser = new User(alias = 'newUser1', email='newuser@testorg.com',
  emailencodingkey='UTF-8', lastname='Testing', 
  languagelocalekey='en_US', localesidkey='en_US', profileid = p.Id,
  timezonesidkey='America/Los_Angeles', username='newuser1234@testorg.com');

  // Create a record as Admin User
  insert new TestObj__c(A_Date_Field__c = System.today());
    
  System.runAs(mockUser) {    
    // Create a record as Standard Custom User Profile
  // It will fail at this point.
    insert new TestObj__c(A_Date_Field__c = System.today());
  }          
}

On executing above test case it fails for this error

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, abhinav.TestSharing: execution of AfterInsert caused by: System.DmlException: Update failed. First exception on row 0 with id a00900000018ALhAAM; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []

 

Open Question ?

Why I’m getting different error codes in Salesforce UI(INSUFFICIENT_ACCESS_OR_READONLY) vs Apex Test case(CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY). I am doing exactly the same in both Apex Test and UI. If you have idea about what’s causing this difference, please share !

Conclusion – Take “Sharing Settings” seriously !

  • When creating new Sobjects, take sharing settings into consideration. Specially when you not using “Private” sharing. Similar to “Public read only”, other sharing settings like “Controlled by Parent” can lead to similar crash on different user profiles.
  • Developers use System Admin profile for creating and testing Apex/Trigger code. This is fine, but apex test cases should be crafted in way to simulate as all possible user profiles in the system. This will save you for sure from nightmares coming from production orgs.
    • Query and create mock users for each Custom Profile you app is going to support.
    • For simulating as mock users try using System.runAs(), note  you don’t need to insert user record. For more details on this, please check cookbook, this post.
  • Apex Triggers always run in GOD Mode (System Context) irrespective of sharing settings, is a myth !
    • If your apex trigger is calling a class declared using “WITH SHARING”, then all sharing rules for the context user will apply during trigger execution.
    • If you apex triggers is expecting to query all records and that code is running under WITH SHARING context, then user’s profile will come into play. For ex. With sharing setting of “Private”, a trigger querying records in WITH SHARING context might see lesser number of records, if the user profile is not having “View All” permission. This applied to both SOQL and SOSL queries
  • Their are exceptions to WITH SHARING context also, i.e. : all SOQL or SOSL queries that use PriceBook2 ignore the with sharing keyword. All PriceBook records are returned, regardless of the applied sharing rules. More details here
  • Current sharing rules can make DML operations fail, because the current user does not have the correct permissions. For example, if the user specifies a foreign key value that exists in the organization, but which the current user does not have access to.