Custom Field with ECB

This post describes how you would go about creating a new SharePoint field and control the URL it maps to and have it display the ECB (Edit Control Block) menu for the selected item.

Edit Control Block

It may look like an ordinary field with an ECB but using a custom field we can control where clicking the URL takes the user.

One very good reason for wanting to do this would be in a situation where you have a forms library using a custom InfoPath form. Now, say you didn’t want to use code behind for your form and wanted the form to be saved to the library used to create it. If you were to use a data connection with your form you’ll come across an issue with saving the form to different libraries. This is because the data connection will be hard wired to a specific location once it has been created in a data connections library.

One solution is to use code behind, but this makes the deployment of the form more complicated and for the sake of being able to save it to the expected folder is a bit over the top.

Another solution is to create a custom version of the FormServer page, which can be found in the Layouts folder of the hive. The reason for doing this is so you can handle the form submission and perform a binary save into the correct folder. This of course requires the form to not use a data connection and instead simply submit to host. Performing a submit to host against the out of the box FormServer page does not save the form due to the handler (SubmitToHost event of the XmlFormView control) for the submit not being implemented.

I’m slightly digressing from the purpose of this post but I think it will help with understanding why I’m even trying this.

Now, say you’ve created a custom FormServer page with an XmlFormView control that hooks up the SubmitToHost event and correctly saves the content of the form to the expected library. All is good in the world. Well, at least until a user clicks the link generated by the default Name field. Hovering over the link the URL looks like it points to the form xml file, which it correctly does. The problem is that when a user clicks the link, SharePoint redirects the user to the original FormServer page and opens the form. This of course brings us back to the problem of the user not being able to update the form as there is no SubmitToHost handler for this page.

The logic for redirecting InfoPath forms to the FormServer page is contained in the TEMPLATE\XML folder of the hive within a file called serverfiles_FormServer.xml. An extract from this file is shown below:

<Mapping FileExtension="xml" ProgId="InfoPath.Document" RedirectUrlTemplate="/_layouts/FormServer.aspx?XmlLocation=|0" VersionRedirectUrlTemplate="/_layouts/FormServer.aspx?XmlLocation=|0" />

A simple solution would be to update this path to your custom form server page but this will affect all sites in the farm and not just the custom form you have added. So, now back to the reason for this post and how to create a custom field that will generate the correct URL for the form.

The first thing you need to do is create a custom field, something similar to the following will do the job:

<Field ReadOnly="TRUE" Type="Computed" Name="CustomMenu" DisplayName="CustomMenu" ID="{462D1B5E-A2E0-4204-857E-E144319A75F2}" StaticName="CustomMenu" ClassInfo="Menu" AuthoringInfo="(custom link to document with edit menu)" ShowInDisplayForm="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" ShowInListSettings="FALSE" Required="FALSE">
    <FieldRefs>
      <FieldRef Name="_EditMenuTableStart" />
      <FieldRef Name="_EditMenuTableEnd" />
      <FieldRef Name="FileLeafRef" />
      <FieldRef Name="FileRef" />
      <FieldRef Name="LinkFilenameNoMenu"/>
      <FieldRef Name="ContentTypeId"/>
    </FieldRefs>
</Field>

The important things to note from this field is that it’s a Computed type and contains various field references. These references will be used when we implemented the DisplayPattern for the computed field. It’s worth noting at this point that the SP 2010 way of controlling how a field renders is via an XSLT template, which the above is most definitley not. The reason we need to implement both is because sometimes SharePoint ignores the XSLT template and tries to render using the DisplayPattern CAML. I can prove this by adding a list WebPart that references a view with the new field to a page on feature activation. You’ll notice that the field completely ignores the XSLT template and processes the DisplayPattern, if it exists. Strangely, adding the WebPart and linking it to the same view renders with the XSLT template but as yet I haven’t figured out exactly why it ignores this template for the first example.

Back to the field and the DisplayPattern. As mentioned previously, we don’t want to override the link generated for all InfoPath forms, just our custom form. Also, we need to ensure that non InfoPath items behave as expected and do not try to render with our custom link, such as folders. With this in mind, have a look at the following CAML:

<Field ReadOnly="TRUE" Type="Computed" Name="CustomMenu" DisplayName="CustomMenu" ID="{462D1B5E-A2E0-4204-857E-E144319A75F2}" StaticName="CustomMenu" ClassInfo="Menu" AuthoringInfo="(custom link to document with edit menu)" ShowInDisplayForm="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" ShowInListSettings="FALSE" Required="FALSE">
  <FieldRefs>
    <FieldRef Name="_EditMenuTableStart" />
    <FieldRef Name="_EditMenuTableEnd" />
    <FieldRef Name="FileLeafRef" />
    <FieldRef Name="FileRef" />
    <FieldRef Name="LinkFilenameNoMenu"/>
    <FieldRef Name="ContentTypeId"/>
  </FieldRefs>
  <DisplayPattern>
    <Field Name="_EditMenuTableStart" />
    <IfSubString>
      <Expr1>0x0101010026E4FEDB345F44AA846FE1FE9134E390</Expr1>
      <Expr2>
        <Column Name="ContentTypeId"/>
      </Expr2>
      <Then>
        <HTML><![CDATA[
        <script Type="text/javascript">
          function GenerateFormLink(source)
          {
            var clientContext = new SP.ClientContext.get_current();
            var options = {
              url: clientContext.get_url() + '/_layouts/Custom/CustomFormServer.aspx?XmlLocation=' + source + '&amp;Source=' + window.location + '&amp;close=1',
              title: 'Custom Form',
              allowMaximize: true,
              showClose: true
              };
            SP.UI.ModalDialog.showModalDialog(options);
          }
        </script>
        ]]></HTML>
        <HTML><![CDATA[<A onfocus="OnLink(this)" HREF="javascript:GenerateFormLink(']]></HTML>
        <Field Name="FileRef" HTMLEncode="TRUE" />
        <HTML><![CDATA[');">]]></HTML>
        <UrlBaseName HTMLEncode="TRUE"><LookupColumn Name="FileLeafRef"/></UrlBaseName>
        <HTML><![CDATA[</A>]]></HTML>
      </Then>
      <Else>
        <Field Name="LinkFilenameNoMenu"/>
      </Else>
    </IfSubString>
    <Field Name="_EditMenuTableEnd" />
  </DisplayPattern>
</Field>

The start and end sections of the above CAML call two SharePoint fields, _EditMenuTableStart and _EditMenuTableEnd. This ensures that the rest of the content is contained within an ECB menu. You’ll notice that these fields have been added to our FieldRefs section. Failing to do so will result in the rendering of the field to fail as the fields will not be found.

The next section involves a check to ensure the current content type is a child of 0x0101010026E4FEDB345F44AA846FE1FE9134E390 (a custom content type that derives from the SharePoint Form type), otherwise it uses the SharePoint field LinkFilenameNoMenu to render the link, this allows the field to behave as it should for every other content type. It’s important to ensure the content type entered here is in uppercase otherwise it will never match as the content type is always stored this way. Obviously, if you were tying to implement this you’d replace the content type id with the one your form is used by.

The rest of the CAML includes a simple JavaScript function for opening the form in a popup dialog and creates our custom link, using the FileLeafRef field as the display text. This field is contained within a UrlBaseName element to remove the anchor link that the FileLeafRef field will generate. You could of course replace the LookupColumn element with Field and then do away with the need to add the UrlBaseName, but doing this will also output the extension. Using LookupColumn will display the filename without an extension.

OK, so that’s the DisplayPattern written and ready to go. Now we need to add a couple of custom XSLT templates. Custom XSLT templates should be added to the _LAYOUTS/XSL folder. Looking in this folder, you will see lots of examples of existing templates.

The first template we need to add is shown below:

<xsl:template match="FieldRef[@Name='CustomMenu']" mode="Computed_body" ddwrt:dvt_mode="body">
  <xsl:param name="thisNode" select="."/>
  <xsl:choose>
    <xsl:when test="$MasterVersion=4 and not($NoAJAX)">
      <div class="ms-vb itx" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$thisNode/@ID}" Field="{@Name}">
        <xsl:call-template name="FieldRef_CustomMenu_body">
          <xsl:with-param name="thisNode" select="$thisNode" />
        </xsl:call-template>
      </div>
      <!-- render the markup for list item chevron from server side -->
      <div class="s4-ctx" onmouseover="OnChildItem(this.parentNode); return false;">
        <span>&#160;</span>
        <a onfocus="OnChildItem(this.parentNode.parentNode); return false;" onclick="PopMenuFromChevron(event); return false;" href="javascript:;" title="{$open_menu}">
        </a>
        <span>&#160;</span>
      </div>
    </xsl:when>
    <xsl:otherwise>
      <table height="100%" cellspacing="0" class="ms-unselectedtitle itx" onmouseover="OnItem(this)" CTXName="ctx{$ViewCounter}" id="{$thisNode/@ID}">
        <tr>
          <td width="100%" class="ms-vb">
            <xsl:call-template name="FieldRef_CustomMenu_body">
              <xsl:with-param name="thisNode" select="$thisNode" />
            </xsl:call-template>
          </td>
          <td>
            <img src="/_layouts/images/blank.gif" width="13" style="visibility:hidden" alt="" />
          </td>
        </tr>
      </table>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This is more or less a copy of the template defined in fldtypes.xsl for the match pattern FieldRef[@Name=’TitlewMenu’].

The differences with this version is the match condition which should match the name of the custom field that was created at the start of this post. Also, we reference a second custom template called FieldRef_CustomMenu_body. Basically, this template will generate the ECB menu and then call our second template for rendering the link.

The second template is as follows:

<xsl:template name="FieldRef_CustomMenu_body" ddwrt:dvt_mode="body">
  <xsl:param name="thisNode" select="."/>
 
  <script Type="text/javascript">
    function GenerateFormLink(source)
    {
      var clientContext = new SP.ClientContext.get_current();
      var options = {
        url: clientContext.get_url() + '/_layouts/Custom/CustomFormServer.aspx?XmlLocation=' + source + '&amp;Source=' + window.location + '&amp;close=1',
        title: 'Custom Form',
        allowMaximize: true,
        showClose: true
      };
      SP.UI.ModalDialog.showModalDialog(options);
    }
  </script>
 
  <xsl:choose>
    <xsl:when test="starts-with($thisNode/@ContentTypeId, '0x0101010026E4FEDB345F44AA846FE1FE9134E390')">
      <xsl:element name="a">
        <xsl:attribute name="href">
          javascript:GenerateFormLink('<xsl:value-of select="$thisNode/@FileRef"/>');
        </xsl:attribute>
        <xsl:value-of select="$thisNode/@FileLeafRef.Name"/>
      </xsl:element>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="LinkFilenameNoMenu">
        <xsl:with-param name="thisNode" select="$thisNode"/>
        <xsl:with-param name="folderUrlAdditionalQueryString" select="''"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

As you’ll notice this is very similar to the CAML used for the DisplayPattern.

Deploying all of this will result in any item matching the defined content type id displaying an ECB menu with a link to the custom FormServer page. Any other item will render and link to the correct target.

A sample project utilising the above field can be downloaded here.

6 comments

  1. Arun Aggarwal says:

    This is a great article. Thanks Stuart for this solution. Almost every application with Infopath forms can use this.
    Stuart – I have deployed the computed field in my solution after replacing the contenttype id of my form. What do I have to do in my form library to see it in action. I am missing something.
    Thanks for your help in advance.
    Regards
    -Arun

    • Stu says:

      Arun, I’ve uploaded a sample project that implements the field and ECB. Have a look at it and see if there’s anything you’ve missed.

  2. Dave says:

    I’ve created something similar to this, moving the ECB to the Title in document libraries; and it all works… but, the thing still shows up in the Edit/New/Display forms even though I’ve set the ShowInXForm=”FALSE” on all three. Does yours do that, and can you perhaps think of why this might be?

    Here’s mine, so you can see what I mean: http://www.codeproject.com/Articles/424763/Zen-and-the-art-of-XSLT-rendering-fields-SharePoin

    • Stu says:

      Hey Dave

      The reason the ShowIn??Form properties aren’t being followed is because the case of the boolean value is important. If you take a look here you’ll see that only uppercase values are allowed. Using lowercase will still deploy and if you inspect the XML for the field in SP Manager or code it will appear to have a valid value but when it deserialises into a field object the values will be null.

      Not all properties follow this rule, some will accept any case – thanks Microsoft!

      Hope that helps,

      Stu

      • Dave says:

        I’ve tested your theory, and sir, you are correct. Thanks for the heads up. It seriously bothers me that when something so simple as Convert.ToBoolean would solve this issue, they don’t bother… Anyway, good tip, thanks!

  3. John Liu says:

    Awesome post.
    Have you tried overriding the default action to point to a custom FormServer page instead of the original?

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve the maths problem shown below before posting: *

Follow

Get every new post delivered to your Inbox

Join other followers: