Teach Me Salesforce

A community approach to learning salesforce.com

Archive for the ‘Advanced’ Category

Salesforce DX Monoliths: Chipping Away at the Metadata

leave a comment »

Yesterday I started preparations to build a package from actual production metadata after experimenting with 2GP in a Trial DX Org first. My goal is quite modest: a package with the 4-5 fields and matching Permission Set necessary for one of our integrations.

The preparations followed a predictable pattern:

  1. Authorize a new DevHub (I’ve got 35 orgs, I hope my DevHub aliasing convention works for the other 32 yet to be DXed!)
  2. Clone the git repo where our metadata is backed up daily by Copado (fast easy way to do this).
    1. Create a new branch for DX-related manipulations.
  3. Create a new DX project (finally understood the difference between –outputdir and –defaultpackagedir. I mistook outputdir as the location that new metadata files would go into eventually. But it’s simply the location that the sfdx-project.json, config/scratch-project-def.json, .forceignore and the readme.MD)

Then began the process of getting just the metadata I wanted in the package. I saw a couple approaches:

  1. Create and populate the files manually (maybe just cutting and pasting from actual metadata).
  2. Retrieve the actual metadata and remove what I don’t want in the package.

The first would probably have been faster and what I would do in the future but I thought it would be a good exercise to remind myself of all the metadata that is there and be sure I kept all the dependencies intact.

So for a couple hours I used git rm -r to remove all the unnecessary metadata from the branch. This was tedious, especially when I had to leave one file like a Permission Set but delete all the other files in a folder.

After getting down to just the two Objects with fields I want plus the Profile and Permission Set that will be used for the integration user, I attempted my first $sfdx force:mdapi:convert -r ../src/ -d force-app/
and I got an errorI had deleted my package.xml and had to restore it and strip out all the unnecessary references.

After I restored the package.xml, the convert worked although I got an apparently non-critical fatalError message because I left a ” </recordTypeVisibilities>” tag in the profile file. I think I also had a missing end tag in the package.xml.

[xmldom fatalError] end tag name: recordTypeVisibilities is not match the current start tagName:Profile
@#[line:8,col:30]
[xmldom error] element parse error: end tag name: recordTypeVisibilities is not match the current start tagName:Profile
@#[line:8,col:30]
[xmldom fatalError] end tag name: Profile is not match the current start tagName:undefined
@#[line:24,col:23]
[xmldom error] element parse error: end tag name: Profile is not match the current start tagName:undefined
@#[line:24,col:23]

But the convert worked anyway. I had decided to leave the two Object files intact rather than remove all the additional metadata in them. So I ended up with extra folders I’ll have to delete:

  • fieldSets
  • listViews
  • recordTypes
  • validationRules
  • webLinks
  • Plus the Account.object-meta.xml with ActionOverrides and other metadata I won’t need.

When I ran sfdx force:source:push -f I left <trackHistory>true</trackHistory> in the field definition files but had deleted the object-metadata.xml file that contained the <enableHistory>true</enableHistory> so I got errors like:

PROJECT PATH ERROR
───────────────────────────────────────────────────────────
N/A                      The entity: Account does not have history tracking enabled (3:13)
N/A                      The entity: Lead does not have history tracking enabled (3:13)

And I had a couple of custom picklist values in the Lead businessProcess and the Account recordType files that I had to remove from the XML.

PROJECT PATH ERROR
───────────────────────────────────────────────────────────N/A                      Picklist value: New not found (48:24)
N/A                      Picklist value: inboundcounselor in picklist: AccountSource not found (37:18)

But, one of those picklist values was the default, so I got a very tricky error:

PROJECT PATH ERROR
───────────────────────────────────────────────────────────N/A                     No default value specified (48:24)

And I could only figure that one out by noticing that the 48:24 reference matched a previous error so at least I could look in the correct file for the problem.

At this point I recognized that I should have left the StandardValueSet in place so that the businessProcess file can refer to the picklist values for Lead.Status. I was expecting that other dependencies were waiting for me so I decided I should just start over. git reset –hard ffc1b. Glad I was committing frequently as I made small changes!

 

Advertisements

Written by Always Thinkin

January 14, 2018 at 5:16 pm

Salesforce DX Monoliths: Exploring 2GP (2nd Generation Packaging)

with one comment

In preparation for breaking up my monolithic metadata, I’m working out how to use Second Generation Packaging or 2GP. Following the documentation’s suggestion, I’m not doing this in my own production orgs but rather in a DX Trial Org.

I studied first to get an idea of how to use 2GP, you can see the Resources list of those sites below.

I created a Scratch Org and added only 2 simple components: a custom field and a permission set for that field.

When I attempted to pull the components with force:pull to my local environment I got the error:

ERROR: Metadata API received improper input. Please ensure file name and capitalization is correct. Load of metadata from db failed for metadata of type:Profile and file name:admin.

which I could only resolve by adding Admin.profile to my .forceignore file. At first this bothered me because I was concerned that the Admin profile would not get access to the new field but as with any installed package, you can ensure that Admins get access to all components during the install process.

After getting the metadata pulled down to my /force-app folder I started the process of creating an unlocked package.

I started by running sfdx force:package2:version:create -d force-app -v gs-trial-devhub and got the expected return message:

Package2 version creation request is InProgress. Run “sfdx force:package2:version:create:get -i 08c1I000000003jQAA” to query for status.

but when I ran sfdx force:package2:version:create:get -i 08c1I000000003jQAA I got an unexpected message:

ERROR:
Tag, Branch, CreatedDate FROM Package2VersionCreateRequest WHERE
^
ERROR at Row:1:Column:125
sObject type ‘Package2VersionCreateRequest’ is not supported. If you are attempting to use a custom object, be sure to append the ‘__c’ after the entity name. Please reference your WSDL or the describe call for the appropriate names.

Try this:
Second-generation packaging is not enabled on this org. Verify that you are authenticated to the desired org and try again. Otherwise, contact Salesforce Customer Support for more information.

And, yes, I had neglected to enable Second Generation Packaging in my org. That was easy enough to resolve. After enabling it I got the expected return messages when running sfdx force:package2:version:create:get -i 08c1I000000003jQAA

=== Package2 Version Create Request
NAME VALUE
────────────────────────────── ──────────────────
ID 08c1I000000003jQAA
Status InProgress
Package2 Id 0Ho1I0000008OJDSA2
Package2 Version Id
Subscriber Package2 Version Id
Tag
Branch
Created Date 2018-01-09 18:09

And a little while later, success!

=== Package2 Version Create Request
NAME VALUE
────────────────────────────── ──────────────────
ID 08c1I000000003jQAA
Status Success
Package2 Id 0Ho1I0000008OJDSA2
Package2 Version Id 05i1I000000001nQAA
Subscriber Package2 Version Id 04t1I000003cm03QAA
Tag
Branch
Created Date 2018-01-09 18:09

After that though, things got troublesome. I decided to see what happens if you try to install a package that has not been released with the command so I ran sfdx force:package:install -u gs-trial-devhub -i 04t1I000003cm03QAA and I got:

PackageInstallRequest is still InProgress or Unknown. You can query the status using
sfdx force:package:install:get -i 0Hf1I000000Qe9cSAC -u gs-trial-devhub

When I ran the suggested command, sfdx force:package:install:get -i 0Hf1I000000Qe9cSAC -u gs-trial-devhub, I got:

ERROR: Encountered errors installing the package!,Installation errors:
1) Package(0331I000000m3Qe) Unable to install beta package, Details: The package you attempted to install is a beta package, which you can only install in Force.com Sandbox or Developer Edition organizations.
ERROR: Installation errors:
1) Package(0331I000000m3Qe) Unable to install beta package, Details: The package you attempted to install is a beta package, which you can only install in Force.com Sandbox or Developer Edition organizations.

(Note that the above messages changed recently to read “Can’t install package in a production org, Details: You can install this second-generation package only in a sandbox or scratch org.”)

Now I assumed this was because I hadn’t released the package, so I ran sfdx force:package2:version:update -i 05i1I000000001nQAA -v gs-trial-devhub –setasreleased and got the expected message:

Successfully updated the package version. ID: 05i1I000000001nQAA.

But even with a released package, I was still unable to install the package into my DX Trial Org’s Dev Hub.

So next I created a new Scratch Org and for about 4 hours I got the same messages when trying to install the package and check its status. The first two attempts to run sfdx force:package:install -ugs-scratchorg01 -i 04t1I000003cm03QAA resulted in the same status messages as above.

Finally after the 3rd attempt at installing the package in the new Scratch Org  I logged in to the org and I was able to see the Installed Package, and when I returned to the CLI I got the happy success message when I ran sfdx force:package:install:get -i 0Hf3D0000008SUeSAM -u gs-scratchorg01

Successfully installed package [04t1I000003cm03QAA]

One note is that installing from the CLI apparently defaults the installation settings to “Install for All Users” because all the Profiles in the Scratch Org have read and edit access to the new field – even the Read Only Profile.

I can’t actually conclude that I did something different that solved this. It is a beta product, so maybe that’s why there was some delays.

Resources:

Trailblazer Community Group “Packaging 2 Beta”: https://success.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F93A000000Lg5U

Sign up for a DX Trial Org: https://developer.salesforce.com/promotions/orgs/dx-signup

DF17 Session “How Everyone Can Leverage Salesforce DX Packaging”: https://success.salesforce.com/Sessions?eventId=a1Q3A00000stRRuUAM#/session/a2q3A000000GV6nQAG

Developer Controlled Packages Description, Tips and Practice Demo: https://success.salesforce.com/0693A0000068ZKt

And, of course, TFM: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp.htm?search_text=forceignore

Written by Always Thinkin

January 10, 2018 at 3:47 pm

Integrating Google Maps in Salesforce

with one comment

Requirement is to show the location of my Account on Google Maps. It looks difficult but actually it is very simple to implement.

Just create a new visualforce page with this code

<apex:page standardController="Account">

<script src="http://maps.google.com/maps?file=api">
</script>

<script type="text/javascript">

var map = null;
var geocoder = null;

var address = "{!Account.BillingStreet}, {!Account.BillingPostalCode} {!Account.BillingCity}, {!Account.BillingState}, {!Account.BillingCountry}";

function initialize() {
if(GBrowserIsCompatible())
{
map = new GMap2(document.getElementById("MyMap"));
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl3D());

geocoder = new GClientGeocoder();
geocoder.getLatLng(
address,
function(point) {
if (!point) {
document.getElementById("MyMap").innerHTML = address + " not found";
} else {
map.setCenter(point, 13);
var marker = new GMarker(point);
map.addOverlay(marker);
marker.bindInfoWindowHtml("Account Name : <b><i> {!Account.Name} </i></b>
Address : "+address);
}
}
);
}
}
</script>
<div id="MyMap" style="width:100%;height:300px"></div>
<script>
initialize() ;
</script>

</apex:page>

As you can see that I have used standard controller so you need to pass the account Id in URL. Let’s say if the page name is “GoogleMaps” then the URL will look something like this : “…/apex/GoogleMaps?id=YOUR_ACCOUNT_ID”.

Use this page as in line visualforce page on native layout and enjoy the Google maps with Salesforce.

References
http://code.google.com/apis/maps/documentation/webservices/
http://code.google.com/apis/maps/index.html

 

Original Post by Forceguru : http://forceguru.blogspot.com/2012/01/integrating-google-maps-in-salesforce.html

Written by Ankit Arora (forceguru)

January 16, 2012 at 3:18 am

Displaying Role Hierarchy on Visualforce Page – Tree View

leave a comment »

This is a cross post from my Blog.

Ever thought of building a tree like data structure for the users based on role hierarchy and displaying it in the form of a JavaScript tree with node selection capability on the Visualforce page?

So recently I came across a functionality where a third party javascript calendar was used on the VisualForce page and all events were fetched programmatically through Apex and plotted on the calendar. The UI looked good along with other custom developed functionality. All was fine until client asked if it was possible to select some of the logged in user’s subordinates through custom VF page and plot events on the calendar for the selected users only. In other words, fetch and display only those events which were owned by users who worked below the logged in user in the role hierarchy.

It got me thinking and I did some research to check if there was an easier way to get this done, but soon realized that this required custom and tricky Apex/VF code. There’s a nice little script written by Jeff Douglas that was closest to what I actually wanted.

So I came up with this handy utility which fulfils my requirements. Getting user IDs of subordinates could also be useful in situations where, for example, you want to do a comparative analysis of performance for all users reporting to a manager.

There are mainly two parts to the solution I designed:

1. RoleUtil (Apex Class): Utility class which exposes the following API

a. public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) – function creates the tree data structure for the requested user or role ID and returns the root node to the caller

b. public static ListgetAllSubordinates (Id userId) – function returns the list of all subordinate users for the requested user ID

c. public static String getTreeJSON (Id userOrRoleId) – function returns the JSON string for the requested user or role ID

d. public static String getSObjectTypeById(Id objectId) – general utility function to return the string representation of the object type for the requested object ID

e. public static Boolean isRole (Id objId) – internally uses the getSObjectTypeById (#d above) to check whether the requested object ID is of UserRole type

f. public class RoleNodeWrapper (inner class) – wrapper for user role, represents a node in the tree data structure mentioned above and exposes boolean properties like hasChildren, hasUsers, isLeafNode, etc

public class RoleUtil {

    /********************* Properties used by getRootNodeOfUserTree function - starts **********************/
    // map to hold roles with Id as the key
    private static Map  roleUsersMap;

    // map to hold child roles with parentRoleId as the key
    private static Map > parentChildRoleMap;

    // List holds all subordinates
    private static List allSubordinates {get; set;}

    // Global JSON generator
    private static JSONGenerator gen {get; set;}

    /********************* Properties used by getRootNodeOfUserTree function - ends **********************/

    /********************* Properties used by getSObjectTypeById function - starts ********************* */
    // map to hold global describe data
    private static Map gd;

    // map to store objects and their prefixes
    private static Map keyPrefixMap;

    // to hold set of all sObject prefixes
    private static Set keyPrefixSet;
    /********************* Properties used by getSObjectTypeById function - ends **********************/

    /* // initialize helper data */ 
    static {
        // initialize helper data for getSObjectTypeById function
        init1();

        // initialize helper data for getRootNodeOfUserTree function
        init2();
    }

    /* // init1 starts  */
    private static void init1() {
        // get all objects from the org
        gd = Schema.getGlobalDescribe();

        // to store objects and their prefixes
        keyPrefixMap = new Map{};

        //get the object prefix in IDs
        keyPrefixSet = gd.keySet();

        // fill up the prefixes map
        for(String sObj : keyPrefixSet) {
            Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
            String tempName = r.getName();
            String tempPrefix = r.getKeyPrefix();
            keyPrefixMap.put(tempPrefix, tempName);
        }
    }
    /* // init1 ends */

    /* // init2 starts  */
    private static void init2() {

        // Create a blank list
        allSubordinates = new List();

        // Get role to users mapping in a map with key as role id
        roleUsersMap = new Map([select Id, Name, parentRoleId, (select id, name from users) from UserRole order by parentRoleId]);

        // populate parent role - child roles map
        parentChildRoleMap = new Map >();        
        for (UserRole r : roleUsersMap.values()) {
            List tempList;
            if (!parentChildRoleMap.containsKey(r.parentRoleId)){
                tempList = new List();
                tempList.Add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
            else {
                tempList = (List)parentChildRoleMap.get(r.parentRoleId);
                tempList.add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
        }
    } 
    /* // init2 ends */

    /* // public method to get the starting node of the RoleTree along with user list */
    public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) {
        return createNode(userOrRoleId);
    }

    /* // createNode starts */
    private static RoleNodeWrapper createNode(Id objId) {
        RoleNodeWrapper n = new RoleNodeWrapper();
        Id roleId;
        if (isRole(objId)) {
            roleId = objId;
            if (!roleUsersMap.get(roleId).Users.isEmpty()) {
                n.myUsers = roleUsersMap.get(roleId).Users;
                allSubordinates.addAll(n.myUsers);
                n.hasUsers = true;
            }
        }
        else {
            List tempUsrList = new List();
            User tempUser = [Select Id, Name, UserRoleId from User where Id =: objId];
            tempUsrList.add(tempUser);
            n.myUsers = tempUsrList;
            roleId = tempUser.UserRoleId;
        }
        n.myRoleId = roleId;
        n.myRoleName = roleUsersMap.get(roleId).Name;
        n.myParentRoleId = roleUsersMap.get(roleId).ParentRoleId;

        if (parentChildRoleMap.containsKey(roleId)){
            n.hasChildren = true;
            n.isLeafNode = false;
            List lst = new List();
            for (UserRole r : parentChildRoleMap.get(roleId)) {
                lst.add(createNode(r.Id));
            }           
            n.myChildNodes = lst;
        }
        else {
            n.isLeafNode = true;
            n.hasChildren = false;
        }
        return n;
    }

    public static List getAllSubordinates(Id userId){
        createNode(userId);
        return allSubordinates;
    }

    public static String getTreeJSON(Id userOrRoleId) {
        gen = JSON.createGenerator(true);
        RoleNodeWrapper node = createNode(userOrRoleId);
        gen.writeStartArray();
            convertNodeToJSON(node);
        gen.writeEndArray();
        return gen.getAsString();
    }

    private static void convertNodeToJSON(RoleNodeWrapper objRNW){
        gen.writeStartObject();
            gen.writeStringField('title', objRNW.myRoleName);
            gen.writeStringField('key', objRNW.myRoleId);
            gen.writeBooleanField('unselectable', false);
            gen.writeBooleanField('expand', true);
            gen.writeBooleanField('isFolder', true);
            if (objRNW.hasUsers || objRNW.hasChildren)
            {
                gen.writeFieldName('children');
                gen.writeStartArray();
                    if (objRNW.hasUsers)
                    {
                        for (User u : objRNW.myUsers)
                        {
                            gen.writeStartObject();
                                gen.writeStringField('title', u.Name);
                                gen.writeStringField('key', u.Id);
                            gen.WriteEndObject();
                        }
                    }
                    if (objRNW.hasChildren)
                    {
                        for (RoleNodeWrapper r : objRNW.myChildNodes)
                        {
                            convertNodeToJSON(r);
                        }
                    }
                gen.writeEndArray();
            }
        gen.writeEndObject();
    }

    /* // general utility function to get the SObjectType of the Id passed as the argument, to be used in conjunction with */ 
    public static String getSObjectTypeById(Id objectId) {
        String tPrefix = objectId;
        tPrefix = tPrefix.subString(0,3);

        //get the object type now
        String objectType = keyPrefixMap.get(tPrefix);
        return objectType;
    }
    /* // utility function getSObjectTypeById ends */

    /* // check the object type of objId using the utility function getSObjectTypeById and return 'true' if it's of Role type */
    public static Boolean isRole (Id objId) {
        if (getSObjectTypeById(objId) == String.valueOf(UserRole.sObjectType)) {
            return true;
        }
        else if (getSObjectTypeById(objId) == String.valueOf(User.sObjectType)) {
            return false;
        } 
        return false;
    }
    /* // isRole ends */

    public class RoleNodeWrapper {

        // Role info properties - begin
        public String myRoleName {get; set;}

        public Id myRoleId {get; set;}

        public String myParentRoleId {get; set;}
        // Role info properties - end

        // Node children identifier properties - begin
        public Boolean hasChildren {get; set;}

        public Boolean isLeafNode {get; set;}

        public Boolean hasUsers {get; set;}
        // Node children identifier properties - end

        // Node children properties - begin
        public List myUsers {get; set;}

        public List myChildNodes {get; set;}
        // Node children properties - end   

        public RoleNodeWrapper(){
            hasUsers = false;
            hasChildren = false;
        }
    }
 }

2. TreeView (Visualforce component): Dynatree based reusable VF component that exposes input parameters like

a. roleOrUserId – required string type input attribute

b. selectable – boolean attribute to indicate whether you want to display checkboxes against nodes in the tree for user selection

c. JsonData – optional string type input attribute, if supplied to the component ignores the “roleOrUserId” attribute and displays the tree structure for the input JSON string

d. value – a string type output attribute which returns the IDs/Keys of the selected nodes in the CSV format, which can then be utilised by the page controller

<apex:component controller="TreeViewController">
<apex:attribute name="roleOrUserId" required="true" type="String" assignTo="{!roleOrUserId}" description="Enter Role or User Id to build the hierarchy. Pass null if you are passing JSON data as a parameter" />
<apex:attribute name="selectable" type="Boolean" assignTo="{!selectable}" description="Do you want nodes to be selectable?" />
<apex:attribute name="value" type="String" description="IDs of selected Nodes in CSV format" />
<apex:attribute name="JsonData" type="String" assignTo="{!JsonData}" description="JSON input for the tree component" />
<apex:inputHidden id="selectedKeys" value="{!value}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery-ui.custom.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.cookie.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'src/jquery.dynatree.js' )}" />

<apex:stylesheet value="{!URLFOR($Resource.DynaTree, 'src/skin/ui.dynatree.css')}" />

<!-- Add code to initialize the tree when the document is loaded: -->
<script type="text/javascript">
$(function(){
// Attach the dynatree widget to an existing <div id="tree"> element
// and pass the tree options as an argument to the dynatree() function:
$("#tree").dynatree({
onActivate: function(node) {
// A DynaTreeNode object is passed to the activation handler
// Note: we also get this event, if persistence is on, and the page is reloaded.
//alert("You activated " + node.data.key);
},
persist: false,
checkbox: {!selectable},
generateIds: false,
classNames: {
checkbox: "dynatree-checkbox",
expanded: "dynatree-expanded"
},
selectMode: 3,
children: {!JsonString},
onSelect: function(select, node) {
// Get a list of all selected nodes, and convert to a key array:
var selKeys = $.map(node.tree.getSelectedNodes(), function(node){
return node.data.key;
});
jQuery(document.getElementById("{!$Component.selectedKeys}")).val(selKeys.join(", "));

// Get a list of all selected TOP nodes
var selRootNodes = node.tree.getSelectedNodes(true);
// ... and convert to a key array:
var selRootKeys = $.map(selRootNodes, function(node){
return node.data.key;
});
},
});
});
</script>

<!-- Add a <div> element where the tree should appear: -->
<div id="tree"> </div>

</apex:component>

You can see a working demo of the functionality here: http://treeview-developer-edition.ap1.force.com/

The code is available as unmanaged package (https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000LlqQ) if you want to use it in your org. The code has been written assuming positive use cases and exceptional situations have not much been handled. It is advised to review and tweak the code before you use it in your org.

Written by Ankit Arora (forceguru)

January 16, 2012 at 2:59 am

JavaScript Remoting

with 2 comments

One of the major enhancements from visualforce this release(Summer 11) and my favorite is JavaScript remoting.

JavaScript Remoting is the ability to invoke apex class method from javascript embedded in a visualforce page.JavaScript Remoting is now available after summer 11 release.

Here is a quick example : we have a Apex class which defines a get Account method.

global with sharing class MyJSRemoting
{
    public static Account account { get; set; }

    @RemoteAction
    global static Account getAccount(String accountName) {
        account = [select id, name, phone, type, numberofemployees from
             Account where name = :accountName limit 1];
        return account;
    }
}

This method is having @RemoteAction annotation making it available to be called from a visualforce page. Now in our visualforce page we have a script tag set which is how we embed javascript within visualforce pages.

<apex:page controller="MyJSRemoting">

<script type="text/javascript">
function getAccountJS() {
    var accountNameJS = document.getElementById('accountname').value;

    ankit.MyJSRemoting.getAccount( accountNameJS, function(result, event)
    {
        if (event.status)
        {
           document.getElementById("{!$Component.theBlock.pbs.pbsi2.accId}")
                                  .innerHTML = result.Id;
           document.getElementById("{!$Component.theBlock.pbs.pbsi1.name}")
                                  .innerHTML = result.Name;
        }
    }, {escape:true});
}
</script>

    <input id="accountname" type="text">

    <button onclick="getAccountJS();">Get Account</button>

    <apex:pageblock id="theBlock">
        <apex:pageblocksection columns="2" id="pbs">

            <apex:pageblocksectionitem id="pbsi1">
                 <apex:outputtext id="name">
            </apex:outputtext></apex:pageblocksectionitem>

            <apex:pageblocksectionitem id="pbsi2">
                <apex:outputtext id="accId">
            </apex:outputtext></apex:pageblocksectionitem>

        </apex:pageblocksection>
    </apex:pageblock>
</apex:page>

This JavaScript code then invokes the getAccount method in the apex class to take action.

Please note I have used namespace “ankit.MyJSRemoting” as there is a namespace registered on my organization.

JavaScript Remoting is the feature which is primarily be used by developers and will allow them to create richer and more interactive user interfaces that everyone will benefit from.

In particular, lets developers create rich javascript based user interfaces by invoking apex controller methods within JavaScript code. JavaScript is typically used to render small parts of user interface. Result from a Javascript remoting call are much faster then using a typical visualforce reRender model, developers can provide more instant feedback to the user. This is because remoting calls are stateless. Means only data is set back and forth instead a lot of bulky HTML.

Additional Visualforce enhancements :

1) Filtered Lookup via Visualforce.
2) Inline editing for rich text area field.
3) Field Set property accessors.

This is a cross post to my Blog.

Written by Ankit Arora (forceguru)

June 19, 2011 at 4:33 pm

Visualforce Code Generator

with 9 comments

This is a cross post from my Blog Post.

We know how much important role visualforce plays in our application. There are many instance where we need to create a visualforce page and apex class. Salesforce provides powerful tools like workflows, approvals, validation etc. from which we can implement our business functionalities with just button clicks.

Now I was having a requirement where I need to create many visualforce pages, some are custom and some are cloning native page with native layouts with some small changes. This just clicked me to create a tool from which we can create a visualforce page code with just clicking buttons. Yes!! Visualforce page code with button click.

This saved a lot of time, as I do not need to write any thing to create a heavy visualforce page and amazingly it is done just using button clicks.

Install the package : https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000Pqos

Just append “/apex/vfgenerator__codegenerator” in URL.

Now let me jump into the explanation what exactly I have done. A simple UI will be displayed, where user can select the desired type of page, object name, record type, email field.

Object Name : Valid object API name (include namespace if any)

Record Type : Record type Id of object selected in Object Name
Type of Page :

  1. Edit : Code will be generated for the edit page of the object (according to “record type” layout if selected any).
  2. Detail : Code will be generated for the detail page of the object (according to “record type” layout if selected any)
  3. Custom Detail : Code will be generated for detail page according to the fields selected from UI
  4. Custom Edit : Code will be generated for edit page according to the fields selected from UI

In the above screen I have selected “Edit” as the type of page and “Account” in object, also provided my email. When I click “Generate Code” it displays like this :

Just copy the code and paste it in new visualforce page. Why I have given my email id because when you paste the code in visualforce page it will loose all the formatting and will display in one single line, but code sent on email will come with formatting. So we can copy the generated code from email also. Now when you hit the newly created visualforce page it will display all fields in edit mode which are displayed on native edit page. Isn’t that good?

Now lets try creating some custom visualforce page. Select “Custom Edit” in “Type of Page” and “Account” in “Object Name”. It will display a section “Select Fields”. Click on “Display Fields”, it will display all fields which are up datable by present user.

Select some fields and click on  “Generate Code”. Again copy paste the code in your visualforce page, it will display selected fields in edit mode.

Now heavy visualforce pages are created with just button clicks.

Package provided is managed because the tool is under testing and I would like to have feedback from you all. Once it is free from all bugs I will provide the code.

Declaration: You may find similar post related to “Visualforce Code Generator”. The end result may be similar but there is a considerable difference in the approaches being followed here. So I, hereby declare that my project is entirely my own creation and has not been copied from any other person/organization’s words or idea. Please feel free to drop me an email at “arora.salesforce@gmail.com” if there is any disagreement.

Cheers

Written by Ankit Arora (forceguru)

June 15, 2011 at 2:32 pm

Sending more than 10 E-mails from Apex

with 5 comments

Most of time we stuck with limits which salesforce.com pull over us. I found this interesting as this push me to explore more how we can overcome this.

Now we have a limit of using “sendEmail” in apex and it is 10 times. Isn’t this bad??

This is a cross-post to my Blog.

Now when I am dealing with anything in bulk first thing comes to my mind is Batch Class. Now what is the scenario which leads me to send more than 10 emails?? I have 15 system administrators in my organization and each logs 2-3 cases in a day. Now I want to send an email which contains details of case logged by each system administrator separately, means To address and Body of each email will be different.

So just create a batch class with this code :

global class Batch_CaseEmail implements Database.Batchable<sObject>,Database.Stateful
{
   Map<Id , List<Case>> userCaseMap {get; set;}
   List<Case> allCaseLoggedToday {get; set;}

   global Batch_CaseEmail()
   {
       //Map to maintain user id and cases logged by them today
       userCaseMap = new Map<Id, List<Case>>() ;

       //All sales rep (System admins)
       List<User> salesRep = new List<User>() ;
       salesRep = [select id , name , Email , ManagerId from User
                    where Profile.Name = 'System Administrator'] ;

       //All sales rep ids
       List<Id> salesIds = new List<Id>() ;
       for(User ur : salesRep)
       {
           salesIds.add(ur.Id) ;
       }

       //All cases logged today by sales rep
       allCaseLoggedToday = new List<Case>() ;
       allCaseLoggedToday = [select Id, CaseNumber,CreatedById, Owner.name 
                            , account.Name , contact.name from Case 
                            where CreatedDate = TODAY AND CreatedById in : salesIds] ;
   }

   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      //Creating map of user id with cases logged today by them
      for(Case c : allCaseLoggedToday)
      {
          if(userCaseMap.containsKey(c.CreatedById))
          {
              //Fetch the list of case and add the new case in it
              List<Case> tempList = userCaseMap.get(c.CreatedById) ;
              tempList.add(c);
              //Putting the refreshed case list in map
              userCaseMap.put(c.CreatedById , tempList) ;
          }
          else
          {
              //Creating a list of case and outting it in map
              userCaseMap.put(c.CreatedById , new List<Case>{c}) ;
          }
      }

      //Batch on all system admins (sales rep)
      String query = 'select id , name , Email from User 
                     where Profile.Name = \'System Administrator\'';
      return Database.getQueryLocator(query);
   }

   global void execute(Database.BatchableContext BC, List<sObject> scope)
   {

      for(Sobject s : scope)
      {
          //Type cast sObject in user object
          User ur = (User)s ;

          //If system admin has logged any case today then only mail will be sent
          if(userCaseMap.containsKey(ur.Id))
          {
              //Fetching all cases logged by sys admin
              List<Case> allCasesOfSalesRep = userCaseMap.get(ur.Id) ;

              String body = '' ;
              //Creating tabular format for the case details
              body = BodyFormat(allCasesOfSalesRep) ;

              //Sending Mail
              Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage() ;

              //Setting user email in to address
              String[] toAddresses = new String[] {ur.Email} ;

              // Assign the addresses for the To and CC lists to the mail object
              mail.setToAddresses(toAddresses) ;

              //Email subject to be changed
              mail.setSubject('New Case Logged');

              //Body of email
              mail.setHtmlBody('Hi ' + ur.Name + ',

 Details of Cases logged today is as follows : 

' + body + '
 Thanks');

              //Sending the email
              Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
          }
      }
   }

   public String BodyFormat(List<Case> lst)
   {
       String str = '' ;
       for(Case cs : lst)
       {
           str += '<tr><td>'+ cs.CaseNumber +'</td>'+'<td>'+ cs.Owner.Name 
                   +'</td>'+'<td>'+ cs.Account.Name +'</td>'
                   +'<td>'+ cs.Contact.Name +'</td>'+'</tr>' ;
       }
       str = str.replace('null' , '') ;
       String finalStr = '' ;
       finalStr = '<table border="1"> <td> CaseNumber </td> <td> Owner </td> 
                   <td> Account </td> <td> Contact </td> ' + str +'</table>' ;
       return finalStr ;
   }

   global void finish(Database.BatchableContext BC)
   {
   }

}

Code is self explanatory, hope there will be no issues understanding it.

Execute this class using below script in system logs :

Batch_CaseEmail controller = new Batch_CaseEmail() ;
Integer batchSize = 1;
database.executebatch(controller , batchSize);

All system administrators will get an email something like this :

Email Format

Written by Ankit Arora (forceguru)

June 5, 2011 at 2:12 pm