Friday, June 7, 2013

Rich Text Editors

On a recent project, I was asked to implement a visualforce page with rich text editing capability.  As I've come to learn, when you put an <apex:inputTextArea> tag inside a visualforce page, you don't get a rich text editor.  You get a text area.  No toolbars for formatting, unlike the standard layouts.  The standard layout editor appears to be ckeditor but if you're using visualforce, you have to put in the rich text editor yourself.  Fortunately, there are lots of editors out there.  I had no idea.

Based on the project needs and features, I ended up implementing the following for user evaluation:

  1. TinyMCE
  2. CKEditor
  3. NicEdit
  4. Redactor

TinyMCE ended up being the selected editor, mostly because of the ICE plugin.  I use strikingly to power my website and strikingly uses TinyMCE as it's editor.  It's fairly straightforward to implement:


  1. Download TinyMCE and upload the zip it as a static resource
    1. If you're using ICE, you'll need to include it in your plugin directory, rezip, and upload
  2. Update you visualforce page as follows
<apex:includeScript value="{!URLFOR($Resource.tinymce, 'tinymce/jscripts/tiny_mce/tiny_mce.js')}"/> 
...

<apex:inputTextArea value="{!TEST__c.Some_Field__c}" id="somefield" style="width:100%;" styleclass="mceEditor"/>
<!--this is the initialization required for TINYMCE  -->
                <script type="text/javascript">
                   tinymce.init({
                            mode : "textareas",
                            editor_selector :"mceEditor",
                            theme : "advanced",
                            plugins : "ice,icesearchreplace,spellchecker,pagebreak,style,layer,table,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,visualchars,wordcount",
                            theme_advanced_buttons1: 'ice_togglechanges,ice_toggleshowchanges,iceacceptall,icerejectall,iceaccept,icereject,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect',
                            theme_advanced_buttons2: 'spellchecker,cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor',
                            theme_advanced_buttons3: 'tablecontrols,wordcount',
                            theme_advanced_buttons4: "",
                            theme_advanced_toolbar_location: "top",
                            theme_advanced_toolbar_align: "left",
                            theme_advanced_toolbar_location : "top",
                            theme_advanced_statusbar_location : "bottom",
                            theme_advanced_resizing : true,
                            ice: {
                                          user: { name: '{!$User.Alias}', id: '{!$User.Alias}'},
                                          preserveOnPaste: 'p,a[href],i,em,strong'
                                },
                            width: "100%",
                            height: "200"      
                        });
                </script>

I highlighted a couple sections that are noteworthy:
  • You need to mention that you're using TinyMCE so you'll need the <apex: includeScript> tag
  • You can have multiple TextArea fields on your page and selectively enable TinyMCE using the editor_selector property when you initialize the editor.  Just set the styleClass property on your text area fields that you want to override with TinyMCE.
  • If you're using ICE, the modification here allows you to capture the user who edits the text.
And if all goes well, you should have something like this:

Deployment Failure

I recently assisted an organization with developing a couple of triggers to help them roll up some child data onto the parent records.  On the evening we were set to deploy, we ran into a couple issues, both of which could be filed under "WTF":

1. Change Sets were disabled for the organization
2. Deploying via Eclipse generated over 150 errors in the managed packages that were installed in their org

The client's system admin cases opened for both issues - the first with Salesforce, the second with the managed package vendor.

The response from the managed package vendor was illuminating so I wanted to share.  They forwarded us this community thread in which the question of managed package errors was settled:

http://boards.developerforce.com/t5/Apex-Code-Development/Unit-Test-Code-Coverage-and-Managed-Packages/m-p/471121#M86324

In summary: if you deploy with change sets, managed package code is ignored.  If you deploy with Eclipse, you're out of luck if there are test class failures in the managed package.  

Will update the blog with Salesforce's explanation of issue #1.  

Tuesday, May 21, 2013

User Hierarchy based Sharing

I have a client who has flattened out their role hierarchy to enable some other business processes within Salesforce.  So when it came for them to implement an object in Salesforce that required some hierarchy based sharing we had to build something custom based on the user record's manager hierarchy.  For example, if Joe owns a record, his manager, Jane, should have read access to the record.  Jane's manager Mike, should also have read access.  This access should continue to the top of the hierarchy.

In the code below, keep this in mind:

Joe -> Jane -> Mike -> Mary

In our org, the sensitive records that require Private org wide defaults give record owner's read-write access by virtue of their ownership of the record.  For all others to have read access, we have to create sharing records for the object.

A share is composed of the following elements:
  • ParentId - the record that you want to share with others
  • RowCause - the custom reason you are sharing this record
  • AccessLevel - the level of access being given (in our ex: read)
  • UserOrGroupId - the user (or group) who should have access

By creating a record with these required elements, these private records can be shared with those users (or groups) mentioned in the shares.

Programmatically, it is pretty easy to create a trigger on an object and create a related share for the object's owner.  Salesforce's documentation of apex managed sharing does an adequate job of this.  Where I scratched my head a little was figuring out how to share all the way up the hierarchy.  And do the sharing without hitting any governor limits.  I did some searching of blogs and found an interesting post by Jeff Douglas.  Jeff's solution is elegant, but I wanted to try another way myself.  In plain english here is what I wanted to do:

1. Create an object share record for the record's owner's manager
2. Then, create another object share for that manager's manager
3. and so on...
4. Insert list of object share records

It felt like a loop to me, so what I tried initially was to iterate through the object share collection, and for each record in the collection, create another share record, assign the manager, then add it to the collection and continue until a manager is no longer found for the user.  In this snippet, the collection of all users and their managers is held in a map:

         for(User u: [Select u.Id,u.ManagerId from User u])
        {
            mapUserManagers.put(u.Id,u.ManagerId);
        }

for(someObject__Share os: sharesToCreate)
        {
            if(mapUserManagers.get(os.UserOrGroupId) != null)
            {
                someObject__Share os_mgr = new someObject__Share ();
                os_mgr.AccessLevel = MGR_ACCESS;
                os_mgr.ParentId = es_mgr.ParentId;
                os_mgr.RowCause = MGR_ROW_CAUSE;
                os_mgr.UserOrGroupId = mapUserManagers.get(os_mgr.UserOrGroupId);
                sharesToCreate.add(os_mgr);
            }
        }

This approach throws an error:  "Cannot Modify a Collection While It Is Being Iterated"

Some folks over at stackexchange explain the issue with the index behind this loop.

So, I took a slightly different approach to the loop:

        allUsers = [Select u.Id,u.ManagerId from User u];
        for(User u: allUsers)
        {
            mapUserManagers.put(u.Id,u.ManagerId);
        }
        Id managerId;      
        for(SomeObject__c so: RecsToProcess)
        {
            managerId = mapUserManagers.get(so.OwnerId);
            do{
                SomeObject__Share sos = new SomeObject__Share ();
                sos.AccessLevel = SHARE_MGR_ACCESS;
                sos.ParentId = so.Id;
                sos.RowCause = SHARE_MGR_ROW_CAUSE;
                sos.UserOrGroupId = managerId;
                sharesToCreate.add(sos);
                managerId = mapUserManagers.get(managerId);
            } while (managerId !=null);
        }
             
        //allow for partial successes
        Database.SaveResult[] srList = Database.insert(sharesToCreate, false);

This compiles without issue and achieves the effect of creating a collection of share records for each of the managers with only 1 SOQL call.  It's another way to solve the hierarchy loop challenge.  Hope it helps you find a solution to your own problem.

Wednesday, May 15, 2013

Buyer Beware - Cont'd

The recent immigration reform bill that is being discussed has a lot of compromise built into it to allow highly skilled workers to more easily work in the US.  As I've previously posted, I've had some negative experiences finding "highly skilled workers".  In speaking to someone who places IT workers on H1B, he confirmed that there is a lot of bait and switch that occurs in the interviewing process.  Having already been told by a former coworker that the proxy-interviewing was occurring, I thought I had heard the worst of it.  But my headhunter source tells me that it's also common for someone who gets through the interviewing/bait and switch, who is inexperienced, to basically off-shore their work to cheaper labor back home.  I guess I shouldn't be surprised but I'm curious about whether any of you who follow this blog have an opinion on this.  Is this the tip of the iceberg?  Does it matter how the work gets done so long as it's done?

Upcoming Topics

I've been doing some interesting development in the last few months that I'll blog about when things slow down.  Some of the topics:

  • Replacing Salesforce's standard rich text editor with TinyMCE, CKEditor, Redactor, and more.
  • Spell checking and Salesforce
  • Wrappers

Tuesday, June 19, 2012

Monday, June 18, 2012

Workbench Discovered

In typical form, I came across the Salesforce Workbench today while looking for the answer to something completely unrelated.  I was reading a cookbook article on developing a mobile app on the relatively new mobile SDK and saw a reference to this Workbench.  It looks like a lot of familiar capabilities like SOQL/SOSL search, object descriptions, etc.., wrapped up in a clean UI.  One capability struck me right away, that almost every sys admin will want to be aware of, is the ability to SET a user's password.  To the best of my knowledge, there was no other way to SET another user's password.  I'm sure most sys admins out there will appreciate this capability.  Check out the rest of the workbench here:

https://workbench.developerforce.com
http://wiki.developerforce.com/page/Workbench