If you have worked with CRM 4.0 long enough, you might have noticed a behavior designed by CRM team, which is when a CRM record has been deemed to be read only, all its associated views (except the activity ones) will not have any "Create New" or "Add Existing ..." button. For instance, my Contract entity has a one-to-many relationship to a custom entity called Payment. The Payment entity is designed to collect payments from my client for the contract record. When a CRM contract is invoiced or activated, the associated views for the payment entity will not have any "Create New" or "Add Existing ..." button (shown below) as CRM platform has determined that this contract record is no longer a draft one, CRM users are not supposed to create any new child records.
This makes sense, but in the case that I have my custom Payment entity involved, I do want to collect and record Payment information as the Contract goes, even after it has been activated or invoiced. Is it possible to have the Create New button back in this case? The answer is YES, you can do it through HTML hack. The following is the script that you can copy to your form’s onLoad event.
/**
* Recover "New" button in a CRM associated view where the master CRM record is read-only.
* @author Daniel Cai, http://danielcai.blogspot.com/
*
* Parameters:
* @param navItemId: LHS navigator's HTML element ID of the associated view.
It usually starts with "nav".
* @param relName: The relationship name that the associated view represents.
* @param label: The label of the New button.
* @param title: The title of the New button.
*/
function recoverNewButtonForAssociatedView(navItemId, relName, label, title) {
var clickActionPattern = /loadArea\(['"]{1}([A-Za-z0-9_]+)['"]{1}\).*/;
var iframe;
var recoverNewButton = function() {
var frameDoc = iframe.contentWindow.document;
if (!frameDoc) return;
var grid = frameDoc.all['crmGrid'];
if (!grid) return;
var addNewBtnId = formatString('_MBlocAddRelatedToNonForm{0}{1}GUID', grid.GetParameter('otc'), crmForm.ObjectTypeCode);
var addNewBtn = frameDoc.getElementById(addNewBtnId);
if (addNewBtn === null)
{
var menuBar = frameDoc.getElementById('mnuBar1');
if (!menuBar) return;
var toolbar = menuBar.childNodes[0].childNodes[0].childNodes[0].childNodes[0];
var html = "<li id=_MBlocAddRelatedToNonForm{0}{1}GUID class=ms-crm-Menu title=\"{3}\" tabIndex=-1 onclick=window.execScript(action) " +
"action=\"locAddRelatedToNonForm({0},{1},'{2}', '')\">" +
"<span class=ms-crm-Menu-Label><a class=ms-crm-Menu-Label tabIndex=-1 onclick=\"return false;\" href=\"javascript:onclick();\" target=_self>" +
"<img class=ms-crm-Menu-ButtonFirst tabIndex=-1 alt=\"{3}\" src=\"/_Common/icon.aspx?objectTypeCode={0}&iconType=DBGridIcon&inProduction=1&cache=1\">" +
"<span class=ms-crm-MenuItem-TextRTL tabIndex=0>{4}</span></a></span>";
html = formatString(html, grid.GetParameter('otc'), crmForm.ObjectTypeCode, crmForm.ObjectId, title, label);
toolbar.innerHTML = html + toolbar.innerHTML;
}
};
var onReadyStateChange = function() {
if (iframe.readyState === 'complete') {
recoverNewButton();
}
};
(function init() {
if (!crmForm.ObjectId) return;
var navItem = document.getElementById(navItemId);
if (!navItem) return;
var clickAction = navItem.getAttributeNode('onclick').nodeValue;
if (!clickAction || !clickActionPattern.test(clickAction))
return;
var areaId = clickAction.replace(clickActionPattern, '$1');
navItem.onclick = function loadAreaOverride() {
loadArea(areaId);
iframe = document.getElementById(areaId + 'Frame');
if (!iframe) return;
iframe.attachEvent('onreadystatechange', onReadyStateChange);
}
})();
}To call the above function, you can do something like this.
recoverNewButtonForAssociatedView('nav_new_contract_payment', 'new_contract_payment', 'New Payment', 'Add a new Payment to this record');If you get everything right, your associated view should look like this:
A few final notes about the script before we go:
- The script opens up the possibility to create or update information for any CRM record that is read-only, by using a child entity assuming that the user has proper privileges against the child entity.
- The script should work for any 1-to-many relationship if you have provided navItemId and relName parameters correctly.
- The script doesn't actually check if the login user has the proper privilege to create new record for the associated entity. The New button will show regardless the current user has the privilege or not, but the CRM platform should prohibit Save happening if the user doesn't actually have the Create privilege for the associated child entity.
- Undocumented CRM JavaScript function formatString was used to make the code easier to read.
- The script doesn't look very pretty due to the lengthy HTML code, but it should be quite readable, in my humble opinion. :-)
Hope this helps.
P.S. This blog post is actually a response to a question on CRM Development Forum.