Infopath: Create a pseudo dialog box

In some of my projects I need to display a confirmation dialog box when a user performs an action. For example, I have a form that allows a user uploads an attachment and the file will be stored in a document library instead of embedded in the form, something quite similar to the one shown on this blog. But I also want to allow the user to remove the attachment by clicking a remove button that will remove the file from the respective document library. So I want to display a confirmation box each time the remove button clicked, otherwise user might accidently remove the attachment.

Unfortunately, Infopath web based form doesn’t support confirmation action on a button, so this post will show a simple trick how to display a confirmation message.

The top section is in my case the URL section (it can be anything). In my project the URL will store the location of the uploaded document in the document library. The bottom section is the pseudo confirmation dialog box (I called it dialog section) which by default is hidden. The dialog section has a boolean attribute called ShowDlg. By default the value is 0 (false), when ShowDlg = 1, the dialog section will be shown and the top section is hidden. The remove button on the right has an Action rule that set the value of the ShowDlg. The Ok and Cancel buttons in the dialog sections will hide the dialog section, additionaly the Ok button can perform some code behind operation such as in my case removing the file in the document library specified by the URL section. Below is the complete rule of the implementation:
Remove button:
   set field @ShowDlg = 1

Ok Button
  set field @ShowDlg = 0
  -- do code behind thing if required

Cancel Button
  set field @ShowDlg = 0

Dialog Section
  Formatting:
     Hide this control when @ShowDlg = 0
Url Section
  Formatting
     Hide this control when @ShowDlg = 1

Infopath List Form – hide/disable fields based on SharePoint group membership

As we know in SharePoint 2010 we can customize list form using Infopath 2010. With Infopath 2010 we will get some advantages of more flexible look and feel, data validation/rules as well as web services invocation in the form of data connection without writing any single of code as in fact Infopath list form does not support code behind.

In this post I will show how to use Infopath data connection to build rules for hiding/disabling fields based on the SharePoint group membership of current user. In my case I would like to disable the Approval field as shown below for any users that are not member of  a SharePoint group called Form Administrator.

infopath_approval_field

Here is my steps to achieve the goal:

  1. The first very step obviously is clicking the Customize Form on the ribbon to edit the form in Infopath Designer. I wouldn’t go to any detail of how to build a list form using Infopath 2010 as there are already a lot of posts about the topic for example this msdn article.

  2. Adding 2 received from SOAP Web Services data connections to the forms, again there are a lot of articles about this topic, so I wouldn’t go into any detail such as this one.
    • GetUserProfileByName in http://<server-name>/_vti_bin/UserProfileService.asmx. Tick Automatically retrieve data when the form opened.
    • GetGroupCollectionFromUser in http://server-name/_vti_bin/UserGroup.asmx (put a sample value, of an existing account such as contoso\account_name). Untick automatically retrieve data when the form opened.

  3. Save the form as source files as we are going to modify the xml schema of the GetGroupCollectionFromUser data connection. The reason why we need to do this is that the generated data connection doesn’t display the returned fields. As shown in the picture  that the dataFields  children elements only contain userLoginName which is not correct.
    To save the form as sources go to Files -> Publish as shown below then  click Export Source Files on the next window. The exported files would look like in the picture. It consists of infopath definition file (manifest.xsf), xml schemas of all data connections (.xsd files), some xml files that contain sample data of the data connections and an xsl file (view.xsl) used for form rendering. In our case we only focus on GetGroupCollectionFormUser.xsd (the xml schema for GetGroupCollectionFromUser data connection).

  4. Modify the GetGroupCollectionFromUser1.xsd, make sure close the Infopath Designer before editing. I found this approach from Sumit’s SharePoint blog, he explained how to correct the xsd in the ‘Correcting the xsd for the Return Fields’ section in his post. To summarize his approach basically, we need to add this below type definition
    <s:complexType name="GetGroupCollectionFromUserType">
        <s:sequence>
          <s:element minOccurs="0" maxOccurs="1" name="userLoginName" type="s:string"/>
          <s:element minOccurs="0" maxOccurs="1" name="Groups">
            <s:complexType>
              <s:sequence>
                <s:element maxOccurs="unbounded" name="Group" >
                  <s:complexType>
                    <s:attribute name="ID" type="s:unsignedShort"></s:attribute>
                    <s:attribute name="Name" type="s:string"></s:attribute>
                    <s:attribute name="Description" type="s:string"></s:attribute>
                    <s:attribute name="OwnerID" type="s:unsignedByte"></s:attribute>
                    <s:attribute name="OwnerIsUser" type="s:string"></s:attribute>
                  </s:complexType>
                </s:element>
              </s:sequence>
            </s:complexType>
          </s:element>
        </s:sequence>
      </s:complexType>
    

    below this line (at the 2nd line of the file)

    <s:import namespace="http://www.w3.org/2001/XMLSchema"></s:import>
    

    Then find this below

      <s:element name="GetGroupCollectionFromUser">
         <s:complexType>
           <s:sequence>
    	   <s:element minOccurs="0" maxOccurs="1" name="userLoginName" type="s:string">
               </s:element>
    	 </s:sequence>
          </s:complexType>
        </s:element>
    

    Replace it with this:

      <s:element name="GetGroupCollectionFromUser" type="tns:GetGroupCollectionFromUserType">
     </s:element>
    
  5. Create a rule to invoke GetGroupCollectionFromUser web services method. As the method is not invoked when the form is opened, we need to invoke the method after the GetUserProfileByName invocation finished. To do this, right click on the manifest.xsf and choose Design. Choose GetUserProfileByName data connection and apply rules on the PropertyData field as display in the picture.

    The rule condition is Name=”AccountName”, rule type is Action. The rule actions are:

    1. Set field’s value: the userLoginName of the GetGroupCollectionFromUser’s queryFields to the Property value AccountName from the GetUserProfileByName.  This blog shows how to set the property value (please follow steps 7 – 10)
    2. Query for data: GetGroupCollectionFromUser. (no need screen shot for this :) )

  6. Create a formatting rule for the field (in my case approval field) that we want to disable/hide if current user is not member of a SharePoint Group. Put a condition as below, where The Form Administrator is the SharePoint Group Name.

    And the any occurrence & number of occurrence of Name is the SharePoint groups’ name retrieved from the dataFields of GetGroupCollectionFromUser data connection below, in other words the rule check if any of the groups’ name is Form Administrator or no SharePoint groups found associated with the current user, if condition met, disable the field.

  7. And finally publish the form

Please feel free to give me feedback if there is better approach.

Add custom commands on Infopath browser form ribbon

In Infopath 2010 browser form, the commands are shown on Ribbon, so from usability point of view, it would be great if we have our custom commands reside in the Ribbon.  The OOB Ribbon are provisioned by a SharePoint  feature called IPFSSiteFeatures, one of the xml files in it contains the Ribbon definition, IPFSRibbon.xml. And the command handlers are defined in javascript file, Core.js, in <hive 14>\TEMPLATE|LAYOUTS\INC

Infopath Custom Command

In the picture, we can see I have added a custom command called Complete. The  command will display a javascript prompt and then perform a postback where the code behind run a server operation. In my case it is set the field on the form, extract some data in the form into custom lists for reporting and send emails to interested party.

Below is the ribbon definition for the Complete command. The most important part is the Location, I put my custom command into the first Ribbon group (Ribbon.ContextualTabs.InfoPathHomeTab.FormAction). The command handler is a javascript function defined in CommandUIHandlers. And also make sure to provision this custom action as site feature (Scope=Web) as this custom command will be added to any Form libraries in the site.

<CustomAction Id="IFCustomRibbon" Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
         <CommandUIDefinition Location="Ribbon.Tabs.InfoPathHomeTab.FormActions.Controls._children">
             <Button Id="Ribbon.Tabs.Lcms.FormActions.Controls.Complete"
               Sequence="70" Command="CompleteForm"
               Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-48" Image16by16Left="-48"
              Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="0" Image32by32Left="-96"
              LabelText="Complete"
              TemplateAlias="o2"
              ToolTipTitle="Complete"
              ToolTipDescription="$Resources:CcaLcms,cui_desc_CompleteForm;"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
   <CommandUIHandlers>
       <CommandUIHandler Command="CompleteForm" CommandAction="javascript:CompleteForm();
          function CompleteForm(){
             if(confirm('This will complete the form and send notification to interested party'))
             {
               var $command = $get('FormControl_V1_I1_B29');
               $command.click();
             }
          }
        "/>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>

The Command handler is mapped to a javascript function. In it it will display a confirmation to users and then perform a postback. The trick to do a postback is on the Infopath form we have a hidden button that runs custom code during on click event. As it is hidden the button wouldn’t be displayed on the browser form but its html representation (<input>) can be still be found in form’s html.

To have the button callable in javascript we need to get the button Id. This can be achieved by using IE Developer Tool or Firebug to inspect the browser form and get the id of the html input tag that represents the button. In this example The html input tag would have attributes: buttonid=FormComplete and value=Complete. The
FormControl_V1_I1_B29 in the javascript above is the input tag id of the Complete button.

With the mechanism we basically can execute any Infopath buttons from our ribbon’s custom commands, such as run a workflow, open a modal dialog, etc. Though there is a catch that the buttons id tend to change if we change the structure of the form, but if we put the hidden buttons on the very top of the form, we can avoid the changes of the ids when the form structure changes.

Additionally we also can call the submit and close from javascript. I got this following functions from Core.js mentioned at the beginning of the article. You can dig the Core.js to find more functions.

   var a = IPFSRibbon_GetFocusedFormControlId();
   IPFSRibbon_HandleButtonClick(a, 'submit');  //this will submit the form
   IPFSRibbon_HandleButtonClick(a, 'close');   //this will close the form
   var navigateUrl = CurrentFormData_UrlToNavigateToOnClose(a);

NOTE. You must delete the browser’s cache files every time deploying the changes of the ribbon definition (CustomAction).

Microsoft SharePoint 2010, Infopath 2010, .NET

Infopath Form redirect to a Custom FormServer page

In one of my project I need to use a custom FormServer.aspx. The reason I created a custom FormServer page was to be able to inject ribbon commands into the browser form and also handle the XmlFormView’s NotifyHost event. I didn’t do much modification of the OOB FormServer.aspx apart from adding a Delegate control and handle the XmlFormView’s OnNotifyHost event. The delegate control would contains some logic for the ribbon as well as the implementation of the OnNotifyHost event.

As by default a browser form will be opened by FormServer.aspx, I need to redirect the form to my custom FormServer page. There are some suggestions here such as:

  • Create a custom link: this is not applicable for me as users would go to the Form Library and click on the form, another reason is there is workflow on the form and any link on the email would use the OOB FormServer page.
  • Modify the serverfiles.xml: I dont want to modify any OOB xml as it would be overwritten during upgrade.
  • Create http module: this solution is clean, but this is not applicable in my case.

Another solution is simply redirect the form during OnLoading event. This approach has a catch though as the server redirection (Response.Redirect or Server.Transfer) would not work as during the OnLoading event as the HTTP Response already sent to the client. So the only way to perform redirection is using javascript redirection.

Below is the part of the code to perform the redirection. The redirection is performed by OOB javascript function called STSNavigate. On line 6, I injected a style sheet to hide the ribbon otherwise there would be flickering on the ribbon as the ribbon html would be displayed before the script run.

if (!CustomFormPage)
{
  Page page = HttpContext.Current.Handler as Page;
  request = request.Replace("_layouts/FormServer.aspx", "_layouts/Custom/FormServer.aspx");
  request = request.Contains("Source=") ? request : request += "&Source=" + SPContext.Current.Web.Url;
  HttpContext.Current.Response.Write("<style>#RibbonWrapper{display:none;}</style>");
  page.ClientScript.RegisterStartupScript(page.GetType(), "redirect",
       "STSNavigate('" + request + "');", true);
}

Please feel free to suggest a better approach.

SharePoint 2010, Infopath 2010