Tuesday, September 25, 2012

HOWTO: Use the unused space on the top of any entity form in Dynamics CRM 2011


You certainly know the empty space on the top of all entity forms in Dynamics CRM 2011 between the entity name on the left and the entity selector on the right-hand side. Here's what I mean:


Probably I'm not the only one askinkg himself why so much visible and valuable space on the form is left unused. So I wrote a JScript that adds a table in here and renders the attributes passed to the OnLoad event handler. Here's are the steps I took to add attributes to that area:

1. Create a JScript Webresource called yourprefix_AccountForm.js and paste the following into the source code:

function setFormTitle(titleFields) {

    // get the title container
    var titleDIV = document.getElementById("form_title_div");

    // prepare the table
    var titleTable = document.createElement("table");
    var titleTableBody = document.createElement("tbody");
    titleTable.appendChild(titleTableBody);
    var titleTableRow = document.createElement("tr");
    titleTableRow.setAttribute("min-height", "25");

    // prepare the left cell with entity name
    var leftTableCell = document.createElement("td");
    leftTableCell.setAttribute("colSpan", "2");
    leftTableCell.className = "ms-crm-Field-Data-Print";
    leftTableCell.style.paddingRight = "30px";

    // prepare the holding nodes for the entity name
    var cloneNodes = new Array();

    // move the name nodes into the left table cell 
    for (var i = titleDIV.childNodes.length - 1; i >= 0; i--) {

        // clone the child of the title container
        cloneNodes[i] = titleDIV.childNodes[i].cloneNode(true);

        // remove the child form the title container 
        titleDIV.removeChild(titleDIV.childNodes[i]);

        // and append the clone to the left container
        leftTableCell.insertBefore(cloneNodes[i], leftTableCell.firstChild);
    }

    // apend the left cell to the table row
    titleTableRow.appendChild(leftTableCell);

    // append new cells for the fields to the right     
    for (var f = 0; f < titleFields.length; ++f) {

        // prepatre the cell
        var titleTableCell = document.createElement("td");
        titleTableCell.setAttribute("colSpan", "2");
        titleTableCell.className = "ms-crm-Field-Data-Print";
        titleTableCell.style.paddingRight = "10px";

        // get the label and the data of the field
        var titleField = Xrm.Page.ui.controls.get(titleFields[f]);
        var attributeField = Xrm.Page.data.entity.attributes.get(titleField.getName());
        var dataField = "";

        // this is what a standard header field looks like
        var labelDiv = document.createElement("div");
        labelDiv.className = "ms-crm-Field-Label-Print ms-crm-FieldLabel-LeftAlign";

        // create and append the container for the label
        var labelSpan = document.createElement("span");
        labelSpan.className = "ms-crm-Field-Label-Print ms-crm-FieldLabel-LeftAlign";
        var myText = document.createTextNode(titleField.getLabel().replace("Kundenstatus ", ""));
        labelSpan.appendChild(myText);
        labelDiv.appendChild(labelSpan);
        titleTableCell.appendChild(labelDiv);

        // create and append the container for the data
        var dataDiv = document.createElement("div");
        dataDiv.className = "ms-crm-Field-Data-Print";
        dataDiv.setAttribute("colspan", "2");
        var dataSubDiv = document.createElement("div");
        dataSubDiv.className = "ms-crm-Field-Data-Print";
        dataSubDiv.style.paddingBottom = "0px";
        
        // handle different types
        var dataNode = null;
        switch (attributeField.getAttributeType()) {
            case "optionset":
                var optionValue = attributeField.getSelectedOption();
                if (optionValue != null) {
                    dataNode = document.createTextNode(optionValue.text);
                }
                break;
            case "lookup":
                // this is an array of objects with properties: entityType, id, name
                var lookupValue = attributeField.getValue();
                if (lookupValue != null) {
                    dataNode = document.createElement("span");
                    dataNode.setAttribute("title", attributeField.getValue()[0].name);
                    dataNode.className = "ms-crm-Lookup-Item";
                    dataNode.setAttribute("contentEditable", "false");
                    dataNode.setAttribute("onclick", "openlui()");
                    dataNode.setAttribute("oncontextmenu", "handleGridRightClick()");
                    dataNode.setAttribute("otypename", lookupValue[0].entityType);
                    dataNode.setAttribute("otype", "8");
                    dataNode.setAttribute("oid", attributeField.getValue()[0].id);
                    
                    // append the the icon
                    var entityCode = getEntityCode(attributeField.getValue()[0].entityType);
                    if (entityCode != "0" && entityCode != null) {
                        iconNode = document.createElement("img");
                        iconNode.className = "ms-crm-Lookup-Item";
                        iconNode.alt = "";
                        iconNode.src = "/_imgs/ico_16_" + entityCode + ".gif";
                        //iconNode.complete= "complete";
                        dataNode.appendChild(iconNode);
                    }

                    // append the text
                    dataNode.appendChild(document.createTextNode(attributeField.getValue()[0].name));
                }
                break;

            default: // all other types
                dataNode = document.createTextNode(attributeField.getValue());
        }
        if (dataNode != null) {
            dataSubDiv.appendChild(dataNode);
        }
        dataDiv.appendChild(dataSubDiv);
        titleTableCell.appendChild(dataDiv);

        // append the cell
        titleTableRow.appendChild(titleTableCell);
    }

    // append the row
    titleTableBody.appendChild(titleTableRow);

    // append the table
    titleDIV.appendChild(titleTable);

}
2. Create a second JScript webresource called yourprefix_EntityCodes.js an paste the following into the source code. This is needed to retrieve the proper icon for lookup attributes but you can use this function any time you need to retrieve the typecode for a given entity name:
function getEntityCode(name) {
    switch (name) {
        case "account": return "1"; break;
        case "contact": return "2"; break;
        case "opportunity": return "3"; break;
        case "lead": return "4"; break;
        case "annotation": return "5"; break;
        case "businessunitmap": return "6"; break;
        case "owner": return "7"; break;
        case "systemuser": return "8"; break;
        case "team": return "9"; break;
        case "businessunit": return "10"; break;
        case "principalobjectaccess": return "11"; break;
        case "roleprivileges": return "12"; break;
        case "systemuserlicenses": return "13"; break;
        case "systemuserprincipals": return "14"; break;
        case "systemuserroles": return "15"; break;
        case "accountleads": return "16"; break;
        case "contactinvoices": return "17"; break;
        case "contactquotes": return "18"; break;
        case "contactorders": return "19"; break;
        case "servicecontractcontacts": return "20"; break;
        case "productsalesliterature": return "21"; break;
        case "contactleads": return "22"; break;
        case "teammembership": return "23"; break;
        case "leadcompetitors": return "24"; break;
        case "opportunitycompetitors": return "25"; break;
        case "competitorsalesliterature": return "26"; break;
        case "leadproduct": return "27"; break;
        case "roletemplateprivileges": return "28"; break;
        case "subscription": return "29"; break;
        case "filtertemplate": return "30"; break;
        case "privilegeobjecttypecodes": return "31"; break;
        case "salesprocessinstance": return "32"; break;
        case "subscriptionsyncinfo": return "33"; break;
        case "subscriptiontrackingdeletedobject": return "35"; break;
        case "clientupdate": return "36"; break;
        case "subscriptionmanuallytrackedobject": return "37"; break;
        case "teamroles": return "40"; break;
        case "principalentitymap": return "41"; break;
        case "systemuserbusinessunitentitymap": return "42"; break;
        case "principalattributeaccessmap": return "43"; break;
        case "principalobjectattributeaccess": return "44"; break;
        case "incident": return "112"; break;
        case "competitor": return "123"; break;
        case "documentindex": return "126"; break;
        case "kbarticle": return "127"; break;
        case "subject": return "129"; break;
        case "businessunitnewsarticle": return "132"; break;
        case "activityparty": return "135"; break;
        case "usersettings": return "150"; break;
        case "activitymimeattachment": return "1001"; break;
        case "attachment": return "1002"; break;
        case "internaladdress": return "1003"; break;
        case "competitoraddress": return "1004"; break;
        case "competitorproduct": return "1006"; break;
        case "contract": return "1010"; break;
        case "contractdetail": return "1011"; break;
        case "discount": return "1013"; break;
        case "kbarticletemplate": return "1016"; break;
        case "leadaddress": return "1017"; break;
        case "organization": return "1019"; break;
        case "organizationui": return "1021"; break;
        case "pricelevel": return "1022"; break;
        case "privilege": return "1023"; break;
        case "product": return "1024"; break;
        case "productassociation": return "1025"; break;
        case "productpricelevel": return "1026"; break;
        case "productsubstitute": return "1028"; break;
        case "systemform": return "1030"; break;
        case "userform": return "1031"; break;
        case "role": return "1036"; break;
        case "roletemplate": return "1037"; break;
        case "salesliterature": return "1038"; break;
        case "savedquery": return "1039"; break;
        case "stringmap": return "1043"; break;
        case "uom": return "1055"; break;
        case "uomschedule": return "1056"; break;
        case "salesliteratureitem": return "1070"; break;
        case "customeraddress": return "1071"; break;
        case "subscriptionclients": return "1072"; break;
        case "statusmap": return "1075"; break;
        case "discounttype": return "1080"; break;
        case "kbarticlecomment": return "1082"; break;
        case "opportunityproduct": return "1083"; break;
        case "quote": return "1084"; break;
        case "quotedetail": return "1085"; break;
        case "userfiscalcalendar": return "1086"; break;
        case "salesorder": return "1088"; break;
        case "salesorderdetail": return "1089"; break;
        case "invoice": return "1090"; break;
        case "invoicedetail": return "1091"; break;
        case "savedqueryvisualization": return "1111"; break;
        case "userqueryvisualization": return "1112"; break;
        case "ribbontabtocommandmap": return "1113"; break;
        case "ribboncontextgroup": return "1115"; break;
        case "ribboncommand": return "1116"; break;
        case "ribbonrule": return "1117"; break;
        case "ribboncustomization": return "1120"; break;
        case "ribbondiff": return "1130"; break;
        case "replicationbacklog": return "1140"; break;
        case "fieldsecurityprofile": return "1200"; break;
        case "fieldpermission": return "1201"; break;
        case "systemuserprofiles": return "1202"; break;
        case "teamprofiles": return "1203"; break;
        case "annualfiscalcalendar": return "2000"; break;
        case "semiannualfiscalcalendar": return "2001"; break;
        case "quarterlyfiscalcalendar": return "2002"; break;
        case "monthlyfiscalcalendar": return "2003"; break;
        case "fixedmonthlyfiscalcalendar": return "2004"; break;
        case "template": return "2010"; break;
        case "contracttemplate": return "2011"; break;
        case "unresolvedaddress": return "2012"; break;
        case "territory": return "2013"; break;
        case "queue": return "2020"; break;
        case "license": return "2027"; break;
        case "queueitem": return "2029"; break;
        case "userentityuisettings": return "2500"; break;
        case "userentityinstancedata": return "2501"; break;
        case "integrationstatus": return "3000"; break;
        case "connectionrole": return "3231"; break;
        case "connectionroleassociation": return "3232"; break;
        case "connectionroleobjecttypecode": return "3233"; break;
        case "connection": return "3234"; break;
        case "equipment": return "4000"; break;
        case "service": return "4001"; break;
        case "resource": return "4002"; break;
        case "calendar": return "4003"; break;
        case "calendarrule": return "4004"; break;
        case "resourcegroup": return "4005"; break;
        case "resourcespec": return "4006"; break;
        case "constraintbasedgroup": return "4007"; break;
        case "site": return "4009"; break;
        case "resourcegroupexpansion": return "4010"; break;
        case "interprocesslock": return "4011"; break;
        case "emailhash": return "4023"; break;
        case "displaystringmap": return "4101"; break;
        case "displaystring": return "4102"; break;
        case "notification": return "4110"; break;
        case "activitypointer": return "4200"; break;
        case "appointment": return "4201"; break;
        case "email": return "4202"; break;
        case "fax": return "4204"; break;
        case "incidentresolution": return "4206"; break;
        case "letter": return "4207"; break;
        case "opportunityclose": return "4208"; break;
        case "orderclose": return "4209"; break;
        case "phonecall": return "4210"; break;
        case "quoteclose": return "4211"; break;
        case "task": return "4212"; break;
        case "serviceappointment": return "4214"; break;
        case "commitment": return "4215"; break;
        case "userquery": return "4230"; break;
        case "recurrencerule": return "4250"; break;
        case "recurringappointmentmaster": return "4251"; break;
        case "emailsearch": return "4299"; break;
        case "list": return "4300"; break;
        case "listmember": return "4301"; break;
        case "campaign": return "4400"; break;
        case "campaignresponse": return "4401"; break;
        case "campaignactivity": return "4402"; break;
        case "campaignitem": return "4403"; break;
        case "campaignactivityitem": return "4404"; break;
        case "bulkoperationlog": return "4405"; break;
        case "bulkoperation": return "4406"; break;
        case "import": return "4410"; break;
        case "importmap": return "4411"; break;
        case "importfile": return "4412"; break;
        case "importdata": return "4413"; break;
        case "duplicaterule": return "4414"; break;
        case "duplicaterecord": return "4415"; break;
        case "duplicaterulecondition": return "4416"; break;
        case "columnmapping": return "4417"; break;
        case "picklistmapping": return "4418"; break;
        case "lookupmapping": return "4419"; break;
        case "ownermapping": return "4420"; break;
        case "importlog": return "4423"; break;
        case "bulkdeleteoperation": return "4424"; break;
        case "bulkdeletefailure": return "4425"; break;
        case "transformationmapping": return "4426"; break;
        case "transformationparametermapping": return "4427"; break;
        case "importentitymapping": return "4428"; break;
        case "relationshiprole": return "4500"; break;
        case "relationshiprolemap": return "4501"; break;
        case "customerrelationship": return "4502"; break;
        case "customeropportunityrole": return "4503"; break;
        case "audit": return "4567"; break;
        case "entitymap": return "4600"; break;
        case "attributemap": return "4601"; break;
        case "plugintype": return "4602"; break;
        case "plugintypestatistic": return "4603"; break;
        case "pluginassembly": return "4605"; break;
        case "sdkmessage": return "4606"; break;
        case "sdkmessagefilter": return "4607"; break;
        case "sdkmessageprocessingstep": return "4608"; break;
        case "sdkmessagerequest": return "4609"; break;
        case "sdkmessageresponse": return "4610"; break;
        case "sdkmessageresponsefield": return "4611"; break;
        case "sdkmessagepair": return "4613"; break;
        case "sdkmessagerequestfield": return "4614"; break;
        case "sdkmessageprocessingstepimage": return "4615"; break;
        case "sdkmessageprocessingstepsecureconfig": return "4616"; break;
        case "serviceendpoint": return "4618"; break;
        case "asyncoperation": return "4700"; break;
        case "workflowwaitsubscription": return "4702"; break;
        case "workflow": return "4703"; break;
        case "workflowdependency": return "4704"; break;
        case "isvconfig": return "4705"; break;
        case "workflowlog": return "4706"; break;
        case "applicationfile": return "4707"; break;
        case "organizationstatistic": return "4708"; break;
        case "sitemap": return "4709"; break;
        case "processsession": return "4710"; break;
        case "webwizard": return "4800"; break;
        case "wizardpage": return "4802"; break;
        case "wizardaccessprivilege": return "4803"; break;
        case "timezonedefinition": return "4810"; break;
        case "timezonerule": return "4811"; break;
        case "timezonelocalizedname": return "4812"; break;
        case "solution": return "7100"; break;
        case "publisher": return "7101"; break;
        case "publisheraddress": return "7102"; break;
        case "solutioncomponent": return "7103"; break;
        case "dependency": return "7105"; break;
        case "dependencynode": return "7106"; break;
        case "invaliddependency": return "7107"; break;
        case "post": return "8000"; break;
        case "postrole": return "8001"; break;
        case "postregarding": return "8002"; break;
        case "postfollow": return "8003"; break;
        case "postcomment": return "8005"; break;
        case "postlike": return "8006"; break;
        case "report": return "9100"; break;
        case "reportentity": return "9101"; break;
        case "reportcategory": return "9102"; break;
        case "reportvisibility": return "9103"; break;
        case "reportlink": return "9104"; break;
        case "transactioncurrency": return "9105"; break;
        case "mailmergetemplate": return "9106"; break;
        case "importjob": return "9107"; break;
        case "webresource": return "9333"; break;
        case "sharepointsite": return "9502"; break;
        case "sharepointdocumentlocation": return "9508"; break;
        case "goal": return "9600"; break;
        case "goalrollupquery": return "9602"; break;
        case "metric": return "9603"; break;
        case "rollupfield": return "9604"; break;
        default: return 0;
    }
}
3. Add both webresources to the form of the entity you want to fill with data. Here's what it should like:
4. Finally pass an array of the attribute names you want to show as an array:

new Array("name", "primarycontactid", "address1_shippingmethodcode")


Your done. This is what it looks like in the end: