Generate a Visualforce Page from an Existing Page Layout
February 9th, 2009
We have a rather large Org with 600+ Page Layouts. When Visualforce came out we had a number of requests from our business units for customized layouts. After about 3 weeks of hand-coding Visualforce pages I soon realized I needed a better way to communicate these change as well as manage the code modifications. Being that I am relatively lazy by nature, I decided that I needed to develop a way to make my computer do the work that I didn’t want to do.

The C# code that I wrote utilizes the Metadata API and inspects the Page Layout for a specified Object and Recordtype and generates similar Visualforce code that you can paste into the Force.com IDE. Is it perfect? No. Could it have been done better? Yes. Does it save me time, money and aggravation? Most certainly!
It also saves me time during the change management process. Now when a BA or Project Manager modifies an existing Page Layout that I have used for a Visualforce page, I don’t need documentation on what fields have changed, I just point my handy-dandy generator at the Page Layout and regenerate the code.
Default.aspx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Visualforce_Scaffolding._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Visualforce Scaffolding Generator</title> <style type="text/css"> .style1 { height: 25px; } </style> </head> <body> <form id="form1" runat="server"> <div> <h1>Visualforce Scaffolding Generator</h1> <asp:Label ID="Label3" runat="server" Text="This page generates the Visualforce code for a specific object and recordtye. The code inspects the page layout for the recordtype and creates a very similar replica of the sections and field layouts as Visualforce code. You can then take this code and paste it into a Visualforce page with minimal changes."/> <table border="0" cellpadding="2" cellspacing="2"> <tr> <td><asp:Label ID="Label1" runat="server" Text="Object"></asp:Label></td> <td><asp:TextBox ID="txtObject" runat="server" Width="200px">Account</asp:TextBox></td> </tr> <tr> <td><asp:Label ID="Label2" runat="server" Text="RecordType ID"></asp:Label></td> <td><asp:TextBox ID="txtRecordTypeId" runat="server" Width="200px">01260000000Dxxx</asp:TextBox></td> </tr> <tr> <td class="style1">Page Type</td> <td class="style1"> <asp:DropDownList ID="ddlMode" runat="server"> <asp:ListItem Value="Edit">New/Edit</asp:ListItem> <asp:ListItem>Display</asp:ListItem> </asp:DropDownList></td> </tr> <tr> <td></td> <td><asp:Button ID="Button1" runat="server" Text="Generate Code" onclick="Button1_Click" /></td> </tr> </table> </div> <asp:Literal ID="Code" runat="server"></asp:Literal> </form> </body> </html> |
Here is the code behind for Default.aspx.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; using Visualforce_Scaffolding.sforce; namespace Visualforce_Scaffolding { public partial class _Default : System.Web.UI.Page { private string username; private string password; private string sessionId; private string serverUrl; private SforceService proxy; protected void Page_Load(object sender, EventArgs e) { proxy = new sforce.SforceService(); if (Request.QueryString["sessionId"] != null) { sessionId = Request.QueryString["sessionId"].ToString(); serverUrl = Request.QueryString["url"].ToString(); } else { // hardcode the u/p for development username = "YOUR_USERNAME"; password = "YOUR_PASSWORD"; } } private void output(string s, int tabs) { // fix some characters for html output s = s.Replace("<", "<"); s = s.Replace(">", ">"); for (int j = 0; j < tabs; j++) { Code.Text = Code.Text + (" "); //Response.Write(" "); } Code.Text = Code.Text + (s); Code.Text = Code.Text + ("[br]"); } public void generateDisplayPageLayout(string objectToDescribe, string recordType) { output("<apex:page standardController=\"" + txtObject.Text + "\">", 0); output("<apex:sectionHeader title=\"" + txtObject.Text + "\" subtitle=\"{!" + txtObject.Text.ToLower() + ".name}\"/>", 1); output("<apex:pageBlock title=\"" + txtObject.Text + "\">", 1); try { sforce.DescribeLayoutResult dlr = proxy.describeLayout(objectToDescribe, new string[] { recordType }); // get all of the fields that are in this layout for (int i = 0; i < dlr.layouts.Length; i++) { sforce.DescribeLayout layout = dlr.layouts[i]; if (layout.editLayoutSections != null) { for (int j = 0; j < layout.editLayoutSections.Length; j++) { sforce.DescribeLayoutSection els = layout.editLayoutSections[j]; output("", 2); output("<apex:pageBlockSection title=\"" + els.heading + "\" columns=\"" + els.columns + "\">", 2); for (int k = 0; k < els.layoutRows.Length; k++) { sforce.DescribeLayoutRow lr = els.layoutRows[k]; for (int h = 0; h < lr.layoutItems.Length; h++) { sforce.DescribeLayoutItem li = lr.layoutItems[h]; if (li.layoutComponents != null) { output("<apex:outputField title=\"" + li.label + "\" value=\"{!" + txtObject.Text.ToLower() + "." + li.layoutComponents[0].value + "}\"/>", 3); //Response.Write(" " + h + " " + li.layoutComponents[0].value + "(editable: " + li.editable + ") label: " + li.label + " required: " + li.required + "[br]"); } } } output("</apex:pageBlockSection>", 2); } } } } catch (Exception e) { Response.Write("An exceptions was caught: " + e.Message + "[br]"); } output("</apex:pageBlock>", 1); output("</apex:page>", 0); } public void generateEditPageLayout(string objectToDescribe, string recordType) { output("<apex:page standardController=\"" + txtObject.Text + "\">", 0); output("<apex:sectionHeader title=\"" + txtObject.Text + " Edit\" subtitle=\"{!" + txtObject.Text.ToLower() + ".name}\"/>", 1); output("<apex:form>", 1); output("<apex:pageBlock title=\"" + txtObject.Text + " Edit\" mode=\"edit\">", 1); output("", 2); output("<apex:pageBlockButtons location=\"top\">", 2); output("<apex:commandButton value=\"Save\" action=\"{!save}\" />", 3); output("<apex:commandButton value=\"Save & New\" action=\"{!save}\" />", 3); output("<apex:commandButton value=\"Cancel\" action=\"{!cancel}\" />", 3); output("</apex:pageBlockButtons>", 2); output("<apex:pageBlockButtons location=\"bottom\">", 2); output("<apex:commandButton value=\"Save\" action=\"{!save}\" />", 3); output("<apex:commandButton value=\"Save & New\" action=\"{!save}\" />", 3); output("<apex:commandButton value=\"Cancel\" action=\"{!cancel}\" />", 3); output("</apex:pageBlockButtons>", 2); try { sforce.DescribeLayoutResult dlr = proxy.describeLayout(objectToDescribe, new string[] { recordType }); // get all of the fields that are in this layout for (int i = 0; i < dlr.layouts.Length; i++) { sforce.DescribeLayout layout = dlr.layouts[i]; if (layout.editLayoutSections != null) { for (int j = 0; j < layout.editLayoutSections.Length; j++) { sforce.DescribeLayoutSection els = layout.editLayoutSections[j]; output("", 2); output("<apex:pageBlockSection title=\"" + els.heading + "\" columns=\"" + els.columns + "\">", 2); for (int k = 0; k < els.layoutRows.Length; k++) { sforce.DescribeLayoutRow lr = els.layoutRows[k]; for (int h = 0; h < lr.layoutItems.Length; h++) { sforce.DescribeLayoutItem li = lr.layoutItems[h]; if (li.layoutComponents != null) { output("<apex:inputField value=\"{!" + txtObject.Text.ToLower() + "." + li.layoutComponents[0].value + "}\" required=\"" + li.required.ToString().ToLower() + "\"/>", 3); //Response.Write(" " + h + " " + li.layoutComponents[0].value + "(editable: " + li.editable + ") label: " + li.label + " required: " + li.required + "[br]"); } } } output("</apex:pageBlockSection>", 2); } } } } catch (Exception e) { Response.Write("An exceptions was caught: " + e.Message + "[br]"); } output("</apex:pageBlock>", 1); output("</apex:form>", 1); output("</apex:page>", 0); } public bool login() { // use the existing session from the org if (sessionId != null) { try { Response.Write("Using existing session[br]"); proxy.Url = serverUrl; proxy.SessionHeaderValue = new sforce.SessionHeader(); proxy.SessionHeaderValue.sessionId = sessionId; GetUserInfoResult userInfo = proxy.getUserInfo(); Response.Write("Logged in as " + userInfo.userFullName + " - " + userInfo.userName + "<hr />"); } catch (Exception ex) { Response.Write("Error logging in with session: " + ex.Message + "[br]"); return false; } return true; } else { // this is for development Response.Write("Logging in with u/p[br]"); sforce.LoginResult lr = proxy.login(username, password); if (!lr.passwordExpired) { // Reset the SOAP endpoint to the returned server URL proxy.Url = lr.serverUrl; // Create a new session header object and add the session ID returned from the login proxy.SessionHeaderValue = new sforce.SessionHeader(); proxy.SessionHeaderValue.sessionId = lr.sessionId; GetUserInfoResult userInfo = lr.userInfo; Response.Write("Logged in as " + userInfo.userFullName + " - " + userInfo.userName + "<hr />"); } else { Response.Write("Your password is expired.[br]"); return false; } return true; } } protected void Button1_Click(object sender, EventArgs e) { Code.Text = ""; if (login()) { Code.Text = Code.Text + "<hr />"; if (ddlMode.Text == "Edit") { generateEditPageLayout(txtObject.Text, txtRecordTypeId.Text); } else { generateDisplayPageLayout(txtObject.Text, txtRecordTypeId.Text); } } } } } |
Categories: .NET, Code Sample, Salesforce











That’s pretty cool Jeff. I may have to copy/paste this bad boy down the line. Hopefully as VF matures they’ll include this sort of functionality out of the box.
Nice tool Jeff. Thanks for sharing.
Looks like this might come in REALLY handy for me. I tried it, but got the following Parser Error- Could not load type ‘Visualforce_Scaffolding._Default’.
Am I missing something?
Looks great but I am getting the same issue as Mario C.
I am not a .net developer but I kind of think “using Visualforce_Scaffolding.sforce” means we need a file we don’t have.
Any one have any luck getting this to run?
Mario, “sforce” is the name of the web reference that is created when you import the Saleforce.com WSDL into Visual Studio. I’m sure there are a number of tutorials for Visual Studio but here is one that may get you started… http://wiki.developerforce.com/index.php/Force.com_for_ASP.NET_Developers.
Good luck!
I am finding the same issue even after adding the API in visual studio. Which API should be shared
Simply Amazing!
We purchased EasyPage to do this. It’s an AppExchange program that costs $250 a year for unlimited users and usage. Soooo glad we did.
Hi Jeff, I had seen this post of yours long ago and I thought of doing this using AJAX toolkit. Thought you’d like it
http://forcecrazy.blogspot.com/2010/08/creating-visualforce-page-code-against.html
Thanks.
I just found this Free AppExchange application that does the same thing. Not sure if does any better though.
Etherios EasyPage – Visualforce Page Generator
http://sites.force.com/appexchange/listingDetail?listingId=a0N300000016kP8EAI