Pages

Monday, November 29, 2010

Renaming Buttons and MenuItems

While working on a recent thread in the CRM Developer forum, I discovered that buttons and menu items throughout CRM have a particular and easily adjustable HTML structure.  Because the labels for these elements are generally statically defined for non-custom actions, sometimes it’s desirable to change them.

While there may be an esoteric method of performing this with language-packs and/or translation code files, I’ve cooked up a Javascript method that allows for this process to be performed wherever custom Javascript can be deployed. 

As with the code used to locate buttons to hide, we search for the “title” attribute of the element.  This is because the “id” attribute is dynamic and inconstant between page-loads.  The following function sufficiently implements the relabeling for both “Button” and “MenuItem” elements:

function RenameTitledButtons(targetElement, origTitle, newName, newTitle) {
  var liElements = targetElement.getElementsByTagName('li');

  for (var i=0; i < liElements.length; i++) {
    if (liElements[i].getAttribute('title').substr(0, origTitle.length) == origTitle) {
      var labelSpan = liElements[i].childNodes[0];

      for (var j=0; j < labelSpan.childNodes[0].childNodes.length; j++) {
        if (labelSpan.childNodes[0].childNodes[j].nodeName == "SPAN") {
          var labelTextSpan = labelSpan.childNodes[0].childNodes[j];

          labelTextSpan.innerHTML = newName;
          break;
        }
      }

      liElements[i].title = newTitle;
      break;
    }
  }
}

To use the function, here are the parameters:

targetElement:  The HTML DOM element containing the items with the label to adjust; document is an acceptable value for buttons within the current form, however a variation of this script is used to target labels in associated views.  (See below)

origTitle:  A string containing any portion (from the first character) of the “title” attribute of the labeled button/menu item.  E.g. the “New XXX” button in an associated view has the title “Add a new XXX to this record”; it’s perfectly acceptable to pass the value “Add a new” into this parameter for this button.

newName:  The new name you wish to provide to the button/menu item.  Whatever is passed to this parameter will become the label.

newTitle:  Whatever is passed to this parameter will replace the contents of the “title” attribute, which will allow you to change the text seen when the mouse hovers over the element.

Now, for a wrapper to this function that works with views contained in Iframes, I’ve developed the following function:

function RenameViewButtons(Iframe, origTitle, newName, newTitle) {
  function RenameViewButtonsByContext() {
    if (Iframe.readyState == 'complete') {
      var iFrame = frames[window.event.srcElement.id];
      RenameTitledButtons(iFrame.document, origTitle, newName, newTitle);
    }
  }

  Iframe.attachEvent("onreadystatechange", RenameViewButtonsByContext);
}

With both functions placed in your code, you can call the new wrapper-method with one parameter difference:

Iframe:  The Iframe element containing the view.  This can be an “IFRAME” control on the form, or that of an associated view.

CAVEAT:  You must call this code before the src attribute of the Iframe is updated.  It’s designed to function by attaching to the “onreadystatechange” event of the Iframe, and executing when the readyState finally changes to “complete”.  If you must perform the renaming afterward, simply use the first function and pass the document element of the Iframe into the first parameter.

For working with naturally navigable associated views, we borrow the form of another code wrapper from my “Improved Code for Hiding Buttons” post:

function RenameAssociatedViewButtons(loadAreaId, origTitle, newName, newTitle){
  function RenameViewButtonsByContext() {
    RenameViewButtons(document.getElementById(loadAreaId + 'Frame'), origTitle, newName, newTitle);
  }
  
  AttachActionToLoadArea(loadAreaId, RenameViewButtonsByContext);
}

Where loadAreaId is the string passed to the loadArea() function by the <A> element’s onclick attribute.  This is discoverable by using the Developer Toolbar in IE8 by pressing [F12] and using the “Select element by click” feature to view the HTML of the navigation link.

That wrapper function, however, requires this bit of utility code:

function AttachActionToLoadArea(loadAreaId, navigationHandler) {
  var navElement = document.getElementById('nav_' + loadAreaId); 

  // if at first you don't succeed...
  if (navElement == null) {
   navElement = document.getElementById('nav' + loadAreaId);
  }

  if (navElement != null)  {    
    navElement.attachEvent("onclick", navigationHandler);
  }
}

The utility code is a reusable segment that attaches any function to the “onclick” event of the navigation link.