Matt Faus
in

InfoPath Dev

Matt Faus

  • Increase Build Speed for Automated Builds in Team Foundation Server

    Have you ever opened Team Explorer to kick off a build and watched in awe as the server stayed in the “Getting Sources” state for several minutes when the actual source code should only amount to a few kilobytes?  If so, this blog’s for you!

    The default for a new build created by the “New Build Type Wizard” from Team Explorer is to copy all of the source code for the entire Team Project into an intermediate directory before attempting to compile it.  As seen in Figure 1, the default build copies everything outlined in red, even though you might only need the folder shown in blue.

    Figure 1. Default versus necessary code copy.

    One way to solve this is by segmenting the Team Project into smaller pieces, but sometimes this just does not make very much sense and can also be a maintenance nightmare.  Instead, you can make a simple edit to the Build script to only copy the necessary files before trying to compile.

    1.       From Source Control Explorer check out and open the $/_TeamProject_/TeamBuildTypes/_BuildType_/TFSBuild.proj file, substituting the appropriate values for _TeamProject_ and _BuildType_.

    2.       At the very bottom of the file, paste the following XML between the closing ItemGroup tag and the closing Project tag.

    <Target Name="CoreGet"

      Condition=" '$(IsDesktopBuild)'!='true' "

      DependsOnTargets="$(CoreGetDependsOn)" >

      <!-- Get all the latest sources from the given workspace-->

      <Get Condition=" '$(SkipGet)'!='true' "

        Workspace="$(WorkspaceName)"

        Recursive="$(RecursiveGet)"

        Force="$(ForceGet)"

        Version="$(VersionToGet)"

        FileSpec="$/Cadabra/Infomentum/QdabraWebDavRootHandler/Properties" />

    </Target>

    3.       Alter the value for the FileSpec attribute to match your project.

    4.       Save the file, check in, and initiate the build.

    5.       Compare build times to realize how much time you just saved.

    Below are some links to the articles that helped me figure this out and a screenshot with a bigger view on the .proj file so you know exactly where to paste the code snippet.

    ·         http://msdn2.microsoft.com/en-us/library/0k6kkbsd.aspx

    ·         http://msdn2.microsoft.com/en-us/library/aa337598(VS.80).aspx

    ·         http://blogs.msdn.com/nagarajp/archive/2005/10/21/483590.aspx

    Figure 2. The final result of the Build edit.

  • InfoPath bug with linking external files from custom task pane HTML

    While working on the Catalog Base Form for DBXL, I nearly went nuts trying to debug why my <link> tags were not working in the task pane.  I had the file included as a form resource file, and when I opened the HTML file directly from Internet Explorer, everything was rendered correctly.  However, when InfoPath wrapped everything up into an XSN on publish, it simply broke!  After talking with a few colleagues and noting that they were having the same problem, I decided to e-mail my contacts at Microsoft.

     

    It turns out that this is indeed a bug, but not exactly in InfoPath.  InfoPath takes advantage of the Internet Explorer engine to render all of its HTML.  So, if you have are running Internet Explorer 7 and have updated it within the past month or so, you will be able to reproduce this bug.

    A KB article along with an IE hotfix are in the works to resolve this issue, but I do not know when they will be complete.  As soon as I hear, I will post it up!

    Here are the specific reproduction steps if you would like to try this out for yourself.

    1. Open a text editor, and then paste this code into a new file.

    <html>

    <head>

          <link rel="stylesheet" href="style.css"/>

    </head>

     

    <body>

          This should be red.

    </body>

    </html>

     

    1. Save the file as taskpane.htm
    2. Open a text editor, and then paste this code into a new file.

    body

    {

          color: red;

    }

     

    1. Save the file as style.css, in the same folder as taskpane.htm
    2. Open Internet Explorer.
    3. From the File Menu, select Open, browse to the taskpane.htm file, and click Open

    Note: File is opened and text is displayed as red.

     

    1. Open InfoPath and choose to design a new blank form.
    2. From the Tools menu, select Form Options.
    3. On the Advanced tab, select to enable the custom task pane, and then click Resource Files.
    4. Add the taskpane.htm and style.css files as Resource Files.
    5. Select taskpane.htm as the task pane location, and type test for the task pane name.
    6. Preview the form. 

    Result: Task pane is displayed with black text.

    Expected: The text should be red.

  • Filter a Repeating Drop-Down List on Previous and Future Selections

    It is likely that you will someday need to create a form that allows only one selection of an item even though the field is held in a repeating table. One example of this is a form that handles seating arrangements at a music hall. As seats are bought you should not be able to select the seats that have already been sold out.

    In this task we will create a simple form that has a repeating drop-down list that is populated from another repeating table in the form. After the appropriate filters are applied each instance of this drop-down list will only display selections that have not already been made from other rows in the form. To start, let's create a new blank form.

    Create the form:

    1. Open InfoPath, select Design a Form, and then select New Blank Form from the Design a Form task pane.
    2. From the controls task pane, select repeating table, change the number of columns to one, and then click OK.
    3. Type List of Seats in the header of the table, and then resize the table to 250px from the Table Properties dialog box.
    4. Press Enter twice to enter some whitespace, and then type Record of Sold Seats:.
    5. From the controls task pane, select repeating table, make sure the number of columns is three, and then click OK.
    6. Type Seat in the first column's header, First Name in the second column's header, and then type Last Name in the third column's header. Refer to figure 1.


    Figure 1. The form design.

    Revise the schema for clarity:

    As you were inserting the controls into the form you might have noticed that InfoPath automatically generated an XML Schema to map these controls to. We will now modify this schema to be easier to understand.

    1. Open the data source task pane, double-click the node labeled group1, rename the node to Seats, and then click OK.
    2. Using Figure 2 as a guide, rename the rest of the nodes with the following values:
      • group2 = Seat
      • field1 = SeatName
      • group3 = SoldTickets
      • group4 = SoldTicket
      • field2 = SeatSold
      • field3 = FirstName
      • field4 = LastName


    Figure 2. Renaming the schema.

    Add the filtered drop-down list:

    1. Right-click the textbox control in the first column of the second table, hover over Change To, and then select Drop-Down List Box from the fly out menu.
    2. Now that we have a drop-down list, double-click the control to open the Drop-Down List Box Properties dialog box.
    3. In the List Box Entries section, select the option to Look Up Values In The Form's Data Source, and then click the XPath button to the right of the Entries field.
    4. From the box that displays select the myFields/Seats/Seat node, and then click the Filter Data button in the bottom left corner.
    5. Click the Add button, select The Expression from the first drop-down list, delete everything in the textbox and insert this expression:

    not(my:SeatName = current()/preceding-sibling::my:SoldTicket/my:SeatSold)

    1. Click the And >> button, select The Expression from the first drop-down list again, delete everything from the textbox and then replace with this expression:

    not(my:SeatName = current()/following-sibling::my:SoldTicket/my:SeatSold)

    1. Click OK on all open dialog boxes until you return to the view.


    Figure 3. Specifying the filter conditions.


    Figure 4. Setting up the drop-down list.

    Try it:

    1. Open the form in Preview Mode and type values into the List of Seats table.
    2. Select a seat from the first row, insert a new row, and then select another seat.
    3. Notice that only options that have not been selected are displayed for selection. Cool, huh?


    Figure 5. Using the form.

    Further Considerations:

    One further aspect to consider about this scenario is how you want to handle the situation when you insert the last possible row in the second table. You should not allow the user to insert any additional rows but should allow them to delete previous rows. There are a few ways to solve this problem, including using two instances of the same table with conditional formatting and using a custom button to control the insertion and deletion of rows. These techniques and the application of their use to your problem domain are left as an exercise for the reader.

  • Automatically Populate Fields from a Drop-Down Selection

    When creating a form that deals with data that has already been collected and stored it is very convenient to have a drop-down menu that will populate several other fields whenever a selection is made. One such example of this would be a form used for shipping items to clients. If shipment information has already been input and saved a drop-down that allows all of the information to be automatically recalled is very helpful in filling out the form every time thereafter.

    In this task we will create a secondary data source that contains information about a list of clients and then enable a drop-down menu listing these clients to populate several other fields on the view. First, let’s make the secondary data source.

    Create the secondary data source:

    Copy the following XML text into a text editor, and then save the file as Clients.xml.

    <?xml version="1.0" encoding="UTF-8"?>
    <ClientInfo>
        <Clients>
            <Client>
                <ID>1</ID>
                <Name>JT Flights</Name>
                <Address1>2200 University Drive</Address1>
                <Address2>Suite 110</Address2>
                <City>New York</City>
                <State>New York</State>
            </Client>
            <Client>
            <ID>2</ID>
                <Name>Northern Lights Advertising</Name>
                <Address1>1234 Grand Way</Address1>
                <Address2></Address2>
                <City>Plano</City>
                <State>Texas</State>
            </Client>
            <Client>
            <ID>3</ID>
                <Name>On Target Sales Consulting</Name>
                <Address1>1704 S 108th Avenue</Address1>
                <Address2>Suite 213</Address2>
                <City>Seattle</City>
                <State>Washington</State>
            </Client>
            <Client>
            <ID>4</ID>
                <Name>Plush Designs</Name>
                <Address1>6099 Main Street</Address1>
                <Address2>Building 2</Address2>
                <City>Panama City</City>
                <State>Florida</State>
            </Client>
        </Clients>
    </ClientInfo>

    Add the secondary data source to the form template as a data connection:

    1. Design a new blank form.
    2. Choose Data Connections from the Tools menu, and then click Add.
    3. In the Data Connection Wizard, select Receive Data, and then click Next.
    4. Select XML Document, and then click Next.
    5. Click Browse, locate and select the Clients.xml file, click Open, and then click Next.
    6. Click Finish, click Yes, and then click Close.

    Design the form:

    1. From the Layout task pane insert a custom table with 2 columns and 10 rows.
    2. From the Controls task pane add a drop-down list box to the first column of the first row.
    3. Add text box controls to the third, fifth, sixth, eighth, and tenth rows of the second column.
    4. Type the labels Name, Address, City, and State to match Figure 1.


    Figure 1. The form design.

    Revise the schema:

    As you were adding controls in the previous section you might have noticed that InfoPath was automatically generating nodes in the schema to map these new controls to. Instead of the generated names that InfoPath gives these nodes, we want to rename them to make them easier to understand.

    1. Open the Data Source task pane, double-click the field1 node, and then change the name to ClientID.
    2. Click OK.
    3. Repeat steps 1 and 2 to change the names of the nodes according to the following mapping:
      1. field2 = Name
      2. field3 = Address1
      3. field4 = Address2
      4. field5 = City
      5. fiedl6 = State


    Figure 2. The data source.

    Fill the drop-down list with values from the secondary data source and add rules:

    1. Double-click the drop-down list in the view.
    2. In the Data Source section select Look Up Values From Data Connection, select Clients from the Data Connection drop-down list, and then click the XPath button to the right of the Entries field.
    3. From the dialog box that appears choose /ClientInfo/Clients/Client, and then click OK.
    4. Click the XPath button to the right of Display Name, select Name as the value to display, and then click OK.
    5. Click Apply, and then click Rules.
    6. Click Add, name the new rule Set Name, and then click Add Action.
    7. Select Set A Field’s Value from the action drop-down list, and then click the XPath button to the right of Field.
    8. Select /my:myFields/my:Name from the list, and then click OK.
    9. Click the formula button to the right of Value, click Insert Field Or Group, select Clients (Secondary) from the Data Source drop-down, select /ClientInfo/Clients/Client/Name in the list, and then click OK.
    10. Select the option to Edit XPath (advanced), modify the XPath by appending an XSL conditional statement at the end to match the following example.

    xdXDocument:GetDOM("Clients")/ClientInfo/Clients/Client/Name[../ID = current()]

    1. Click OK three times.
    2. Repeat steps 6 through 11, while substituting the appropriate nodes, for the Address, City, and State fields.


    Figure 3. Filling the drop-down list with options from the secondary data source.


    Figure 4. Adding the rules to the drop-down list control.

    Try it:

    1. Click Preview Form (or press ALT+P on the keyboard)
    2. Make a selection from the drop-down list on the right.
    3. Notice how all of the fields are automatically populated from the secondary data connection.


    Figure 5. Using the form.

  • Allow Offline Use of a SharePoint List by Caching Locally

    Populating drop-down lists and other components of an InfoPath form from data stored in a SharePoint list is a great technique that allows centralized storage of data and ease of maintenance. The only downside of storing data in a SharePoint list is that the user of an InfoPath form must have network access to the SharePoint site in order for the form to work properly. However, there is a rather simple way to create an InfoPath form that uses a local version of the list by default and provides the ability to update the list when a connection to the SharePoint site is available. Using this technique allows the SharePoint list to be updated as often as necessary but new versions of the InfoPath form (with the latest local version of the list) need only be deployed as desired.

    In this task we will create an InfoPath form that has data connections to a custom SharePoint list and an XML Document that we will include in the form template that represents the local version of the list. Once these connections are made we will use code to make a button which updates the local version of the list with the data from SharePoint. Let’s start by creating the SharePoint list.

    Create the SharePoint list:

    1. Browse to the SharePoint site that you will be using for your form, and then click the Create link at the top of the page.
    2. Scroll down to the Custom Lists section, and then click the link to create a new Custom List.
    3. Name the list Products, change the name of the Title column to Name, insert a new column, Description, and insert five rows of data. Refer to Figure 1.


    Figure 1. Creating the SharePoint List.

    Create the XML local list:

    1. Open a text editor, copy the following text, and then paste it into the text editor.
    2. Save the file as Local Products.xml.

    <?xml version="1.0" encoding="UTF-8"?>
    <CachedList>
        <Products>
            <Product ID="1" Name="Race Car" Description="A remote controlled race car. Speeds up to 100mph"/>
            <Product ID="2" Name="Airplane" Description="Flies very high!"/>
            <Product ID="3" Name="Boat" Description="Guaranteed to never sink."/>
            <Product ID="4" Name="Truck" Description="Same style and colors as the US Army."/>
            <Product ID="5" Name="Hovercraft" Description="Over land or sea, this beast of a machine floats on a bubble of air!"/>
        </Products>
    </CachedList>

    Create the form and add the SharePoint List data connections:

    1. Open InfoPath and choose to Design A New Blank Form.
    2. From the Tools menu, select Data Connections.
    3. Click Add, select Receive Data, and then click Next.
    4. Select SharePoint Library Or List, and then click Next.
    5. Type the URL to your SharePoint site into the field, and then click Next.
    6. Select Products from the list of available Lists, and then click Next.
    7. Select the ID, Name, and Description fields, unselect everything else, and then click Next.
    8. Change the name of the data connection to Products Online, uncheck the box labeled Automatically Retrieve Data When Form Is Opened, and then click Finish.

    Add the offline version of the list as a data connection:

    1. In the Data Connections dialog box, click Add.
    2. Select Receive Data, and then click Next twice.
    3. Click Browse, browse to the Local Products.xml file, click Open, and then click Next.
    4. Name the data connection Products Offline, click Finish, click Yes on the box that pops up, and then click OK to close the Data Connections dialog box.

    Design the Form:

    1. Type Products Viewer into the view, and then type Enter a few times to insert some whitespace.
    2. Open the Layout task pane and insert a custom table with two columns and two rows.
    3. Resize the table to match figure 2 and type Products and Description into the columns of the first row.
    4. Open the Controls task pane and insert a List Box control inside the first column of the second row.
    5. Double-click the list box, change the Field Name to Product, and then select Look up values in a data connection.
    6. Select Products Offline from the Data Connection drop-down list, select /CachedList/Products/Product for the Entries, select @ID for the Value, select @Name for the Display Name, and then click OK.
    7. Open the Controls task pane and insert an expression box into the second column of the second row.
    8. Click the Formula button in the box that pops up, click Insert Field or Group, select Products Offline (Secondary) from the Data Source drop-down list, select Products/Product/Description, and then click Filter Data.
    9. Click Add, select ID from the first drop-down list, select Is Equal To from the second drop-down list, and then select Select a Field or Group from the third drop-down list.
    10. Select Main from the Data Source drop-down list, select myFields/Product, and then click OK until all dialog boxes are closed. Refer to Figure 2.


    Figure 2. The form design.

     


    Figure 3. Populating the Products list from the local version of the data source.

    Add the Refresh Products button and code:

    1. Click to the right of the Products Viewer text to set the insertion point. Type Space a few times to insert some whitespace.
    2. Open the Controls task pane, and then insert a button.
    3. Double-click the button that was just inserted, change the label to Refresh Products, change the ID to butRefreshProducts, click Apply, and then click Edit Form Code to open the Microsoft Script Editor.
    4. Replace the contents of the OnClick event handler with the following code:

    try
    {
        // Get the updated version of the List
        // If the user is offline, the code will skip to the "catch" block below
        XDocument.DataObjects["Products Online"].Query();

        // Get the ShareList DOM
        var oSharePointDom = XDocument.GetDOM( "Products Online" );
        // Setup the Namespaces
        oSharePointDom.setProperty("SelectionNamespaces",
            'xmlns:dfs="http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" ' );

        // Get the local DOM - to does not use namespaces
        var oLocalDom = XDocument.GetDOM( "Products Offline" );

        // Get a sample cached list node that we will need to clone later
        var nSampleProductNode = oLocalDom.selectSingleNode( "/CachedList/Products/Product" );

        // Remove all of the items from the local copy of the list
        oLocalDom.selectNodes( "/CachedList/Products/Product" ).removeAll();

        // Loop through all of the items in the SharePoint List
        var nlSharedItems = oSharePointDom.selectNodes( "/dfs:myFields/dfs:dataFields/dfs:Products" );

        while( (nSharedItem = nlSharedItems.nextNode() ) != null )
        {
            // Create a new row from the sample
            var nNewLocalCacheRow = nSampleProductNode.cloneNode(true);

            // Set the values
            nNewLocalCacheRow.selectSingleNode( "@ID" ).text = nSharedItem.selectSingleNode( "@ID" ).text;
            nNewLocalCacheRow.selectSingleNode( "@Name" ).text = nSharedItem.selectSingleNode( "@Name" ).text;
            nNewLocalCacheRow.selectSingleNode( "@Description" ).text = nSharedItem.selectSingleNode( "@Description" ).text;

            // Append to to our local cached list
            oLocalDom.selectSingleNode( "/CachedList/Products" ).appendChild( nNewLocalCacheRow );
        }
    }
    catch( e )
    {
        // Uncomment this line for more debug information
        //XDocument.UI.Alert( e.description );

        // Tell the user that they must be connected to the Internet to use this feature.
        XDocument.UI.Alert( "A connection to the SharePoint List must be available to use this feature." );
    }

    Try it:

    1. Open a Web Browser and browse to the SharePoint list that our form references.
    2. In the InfoPath designer click Preview Form, and select one of the Products from the list box. Notice that the description is what was stored in the XML document.
    3. Switch to your web browser and edit the Products list. Try inserting a new row as well as editing the name and description values for existing rows.
    4. Switch back to the previewed form, and click Refresh Products. Notice that the list now contains any extra rows that you added in SharePoint and the descriptions and names are also updated.
    5. Take your computer offline. This can be done by either physically disconnecting the network cable in the back of your computer, or by disabling the connection from the Network Connections folder in the Control Panel.
    6. After you have gone offline, notice that clicking the Refresh Products button emits an error message telling the user that they must be online to use the feature.
    7. While still offline, close the preview and open another preview.
    8. Click the Refresh Products button and notice that the error message is emitted and the local version of the list is used.


    Figure 4. Previewing the form after refreshing the local version from the SharePoint list.

    Customizing for your solution:

    One potential downside of putting the Refresh Products code behind a button is that the user will not always click the button and could therefore be filling out the form with an old version of the list. A way to remedy this situation is to put the code provided above within the OnLoad event handler. This way every time the user opens the form the newest version of the list will be obtained if a connection is available.

  • Add Custom Buttons to the Repeating Table and Section Widget

    A powerful technique to add extra functionality to a form without cluttering up a design that has been perfected is to add custom buttons to the widget menus of repeating tables and sections. Although this is not an easy task, it is definitely attainable through InfoPath’s open standard application model, and once completed will provide a very powerful and clean addition to your form.


    Figure 1. A preview of the final product of this How-To.

    In this task we will edit the manifest.xsf of a form template to add a button to the widget icon of a repeating table and add a custom event handler function that will execute upon the clicking of the custom button. Then, we will extend our form to respond to context-specific widget events. Let’s start by designing a new blank form.

    Design the form:

    1. In the Controls task pane click Repeating Table to insert a repeating table with 3 columns.
    2. Type Name, Department, and E-mail into the repeating table headings. Refer to Figure 2.
    3. Open the Data Source task pane and edit the names of the automatically generated schema nodes to match Figure 3. This is done by double-clicking the node in the Data Source task pane, editing the Name field, and then clicking OK.


    Figure 2. The form design.


    Figure 3. The data source.

    Make the Department column a drop-down list:

    In order to predefine the options for the Department field and simplify the code that will run behind our form, it is a good idea to only allow a predefined set of values for the Department field. In order to this we will change the textbox control to a drop-down list.

    1. Right-click the Department textbox control on the view, select Change To, and then click Drop-Down List Box.
    2. Double-click the new drop-down list in the view to open the Drop-Down List Box Properties dialog.
    3. In this dialog, click Add in the List box entries section, and then type Sales in the box that pops up. This will add the Sales option to the drop-down box.
    4. Repeat step 3 for the following options: Marketing, Management, Research. Refer to Figure 4.


    Figure 4. Adding the drop-down list items.

    Extract the form files:

    InfoPath .xsn files are actually a compressed package file that contains quite a few other files, such as sample data files and configuration files. In order to edit the manifest.xsf file and add custom widget buttons we must extract these form files so that we can work with them individually. To repackage the files into an .xsn file you will publish the form to a new location.

    1. In the Form Designer, choose Extract Form Files from the File menu.
    2. Create a new folder somewhere where you can easily find the folder again and name it HowTo – Custom Widget Buttons.
    3. Click OK.
    4. Close the InfoPath Designer.
    5. Browse to the HowTo – Custom Widget Buttons folder.

    Edit manifest.xsf:

    In order to edit the form files directly, we must close the InfoPath Designer so InfoPath will release the lock and these files. In the next few steps we will be editing the manifest.xsf (which stores XML) directly.

    1. Open manifest.xsf in a text editor.
    2. Move the insertion point to the end of line 69, type Enter, and then paste the following code into the manifest.xsf.

    Note: The XSF file is automatically generated by InfoPath, so the insertion point might not be exactly at line 70. Please use Figure 5, which displays a large section of the XSF, to determine to the appropriate context for your form.

    <!-- Custom widget buttons -->
    <xsf:button name="widgetEmailThisContact" caption="Email this Contact"/>       
    <xsf:button name="widgetEmailAll" caption="Email all Contacts"/>
    <xsf:menu caption="&amp;Email Department...">
        <xsf:button name="widgetEmailSales" caption="Sales"/>
        <xsf:button name="widgetEmailMarketing" caption="Marketing"/>
        <xsf:button name="widgetEmailManagement" caption="Management"/>
        <xsf:button name="widgetEmailResearch" caption="Research"/>           
    </xsf:menu>
    <!-- End custom widget buttons -->

    1. Save and close the manifest.xsf file.
    2. Browse to the HowTo – Custom Widget Buttons folder in Windows Explorer, right-click manifest.xsf, and then select Design to open the InfoPath Designer.


    Figure 5. Editing the XSF file.

    Add code to handle the button click events:

    1. From the Tools menu choose Programming | Microsoft Script Editor to open the code editor.
    2. At the very bottom of the file that is created in Microsoft Script Editor paste the following code.

    function widgetEmailAll::OnClick()
    {
        var sEmailAddresses = compileEmailString( "All" );   
        XDocument.UI.Alert( sEmailAddresses );
    }

    function widgetEmailSales::OnClick()
    {
        var sEmailAddresses = compileEmailString( "Sales" );   
        XDocument.UI.Alert( sEmailAddresses );
    }

    function widgetEmailMarketing::OnClick()
    {
        var sEmailAddresses = compileEmailString( "Marketing" );   
        XDocument.UI.Alert( sEmailAddresses );
    }

    function widgetEmailManagement::OnClick()
    {
        var sEmailAddresses = compileEmailString( "Management" );   
        XDocument.UI.Alert( sEmailAddresses );
    }

    function widgetEmailResearch::OnClick()
    {
        var sEmailAddresses = compileEmailString( "Research" );   
        XDocument.UI.Alert( sEmailAddresses );
    }

    function compileEmailString( department )
    {
        var sEmailString = "";
       
        // Grab e-mail regardless of department.
        if( department == "All" )
        {
            var nlContactsEmail = XDocument.DOM.selectNodes( "/my:AddressBook/my:Contacts/my:Contact/my:Email" );
           
            while( (nContactEmail = nlContactsEmail.nextNode()) != null )
                sEmailString += nContactEmail.text + "; ";       
        }
        // Department has been specified.
        else
        {
            var nlContactsEmail = XDocument.DOM.selectNodes( "/my:AddressBook/my:Contacts/my:Contact[my:Department = '" + department + "']/my:Email" );
           
            while( (nContactEmail = nlContactsEmail.nextNode()) != null )
                sEmailString += nContactEmail.text + "; ";       
        }

        return sEmailString;   
    }

    Try it:

    The actual code to send e-mails from within InfoPath is not within the scope of this document, and the pop-up displaying the e-mail address string is displayed in an alert box to demonstrate the ability of the custom widget button.

    1. Save the JScript file by choosing Save from the File menu, and then switch back over to the InfoPath Designer.
    2. Click Preview Form, insert a few contacts with e-mail addresses and then click the widget buttons to see a pop-up corresponding to the appropriate e-mail addresses.


    Figure 6. The result of clicking the Email all Contacts custom widget button.

    Add context-specific functionality for the Email this Contact button:

    As you were trying out the form, it is important to note that the widget buttons created so far are not related to a specific row in the table and performed functions related to the form as a whole. To add functions that relate to the specific context of the widget button clicked (such as the Email This Contact button), you must add a OnContextChange event handler and a private variable in your code to maintain where the user is currently in the form.

    Add the OnContextChange event handler and private variable:

    1. In the InfoPath Designer choose Programming | On Context Change Event from the Tools menu. This will open the Microsoft Script Editor again and generate an OnContextChange event handler function.
    2. Replace the content of the event handler with the following code:

    if (eventObj.Type == "ContextNode")
    {       
        var nContextNode = eventObj.Context;
       
        // This node might actually be one of the column nodes for the
        // row, but we always want to work with the Contact.
       
        // If this is a child node, change reference to parent node
        if( nContextNode.parentNode.nodeName == "my:Contact" )
            nContextNode = nContextNode.parentNode;
       
        // Update the global variable with the current row node.
        nCurrentRow = nContextNode;
        return;
    }

    1. At the very top of your code, add this line to declare a global variable:

    var nCurrentRow;

    1. Now we must add the function to handle the event of the Email This Contact button. To do this, paste the following function directly below the OnContextChange event handler.

    function widgetEmailThisContact::OnClick()
    {
        // Use the nCurrentRow node (which is continually kept up-to-date
        // by the OnContextChange event handler) to get the e-mail
        // of the current contact.
        var nEmail = nCurrentRow.selectSingleNode("my:Email");   
        XDocument.UI.Alert( nEmail.text );
    }

    With another preview of the form you will notice that clicking the Email This Contact button in the widget menu now displays the e-mail address of the Contact on which the widget was accessed.

    Gotchas:

    One limitation of adding custom widget menu buttons is that they cannot be conditionally shown for only one repeating table or section in the view. If there are custom widget buttons they will be shown on every widget menu in the entire view. This is because we are calling custom code instead of using one of the xCollection or other operations. Please see the XSF Schema documentation for more detail.

  • Setup Namespaces for a Secondary Data Source

    An often overlooked fact about accessing a secondary data source via code behind an InfoPath form is the need to setup the correct namespaces. Tutorials presented throughout the Internet often presume that the process for setting up namespaces is already known and do not mention it when they give code snippets with different namespaces.

    Some very common errors that are usually due to namespace (or other XPath) issues are:

    • Object Required
    • Reference to undeclared namespace prefix: 'dfs'.
    • 'null' or null is not an object


    Figure 1. Two very common errors.

    There are two common ways to obtain information about the namespaces that a data connection uses. Regardless of the data connection, the namespaces can be discovered through the InfoPath Data Source task pane. For XML documents, however, you may open the original XML file and look at the attributes of the root node to discover what namespaces need to be added.
     
    Obtaining the namespace via the Data Source task pane:

    1. Open the Data Source task pane, and then choose the secondary data source from the Data Source drop-down list.
    2. Double-click on the node that you need the namespace for, and then click to the Details tab.
    3. The namespace is given in the first field, named Namespace.


    Figure 2. The namespace for an XML document created by InfoPath.

    Obtaining the namespaces of an XML document:

    1. Open a text editor, such as Microsoft Notepad and open the XML document that will be added as a secondary data source.
    2. Find the root node of the XML structure. This is usually the first node that begins with a < instead of <?.
    3. Find all of the xmlns attributes of this element. The names of the namespaces are directly after the colon and the value of each namespace is in quotation marks after the equals sign.


    Figure 3. Finding the namespace using Notepad.

    Once you know the namespace, a reference to it must be established in the code behind the form. At the very top of the code that InfoPath automatically generates there is a section where the namespaces for the Main DOM are referenced and this is where the references for the secondary data sources should go.

    Add references to the secondary namespaces:

    1. Open the Microsoft Script Editor by choosing Programming | Microsoft Script Editor from the Tools menu.
    2. At the end of the introductory comments there is a section of code like the following:

    //<namespacesDefinition>
    XDocument.DOM.setProperty("SelectionNamespaces", 'xmlns:my="
    http://schemas.microsoft.com/office/infopath/2003/myXSD/2006-01-17T04:02:20"');
    //</namespacesDefinition>

    1. This is the code that references the namespaces of the Main DOM. To add code that references the namespaces of the secondary data sources follow this template (replace red text with pertinent information):

    XDocument.GetDOM( "Name of your secondary data source" ).setProperty( "SelectionNamespaces" , 'xmlns:ns1="Text obtained from Figure 2" xmlns:ns2="Another namespace"' );

    1. After updating the above template with your own values add the line of code directly beneath the line with references for the Main DOM.

    Now that the additional namespaces have been added to the code, you can reference nodes from secondary data sources with a statement similar to this:

    XDocument.GetDOM("Name of your secondary data source").selectSingleNode( "/ns1:myFields/ns1:field1");

  • Add SharePoint List Items with InfoPath

    One very useful feature of Microsoft SharePoint is its ability to create and maintain lists that users can leverage to store, view, and share items with other users of the SharePoint site. It is often desirable to be able to view these items and to add additional items with an InfoPath form; one such example is a list of contacts. In this example we will add items to the automatically generated Contacts list in SharePoint, but this technique can be used to add list items to any SharePoint list.

    In this task we will add three data connections to a form: a connection to a SharePoint List to view the Contacts list, a connection to the SharePoint lists.asmx Web Service to submit new Contacts to, and a secondary XML document that we will use as a Collaborative Application Markup Language (CAML) template. Let’s start by designing a new blank form.

    Add the CAML template:

    Copy the following code into a text editor, and then save the file as Add List Item Template.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <Batch OnError="Continue">
        <Method ID="1" Cmd="New">
            <Field Name='Title'></Field>
            <Field Name="FirstName"></Field>
            <Field Name="Email"></Field>
            <Field Name="WorkPhone"></Field>
        </Method>
    </Batch>

    Now that the CAML template has been saved, let’s design the form.

    Add the CAML template as a secondary data source:

    1. Design a new blank form.
    2. Choose Data Connections from the Tools menu, and then click Add.
    3. In the Data Connection Wizard, select Receive Data, and then click Next.
    4. Select XML Document, and then click Next.
    5. Click Browse, locate and select the Add List Items Template.xml file, click Open, and then click Next.
    6. Click Finish, click Yes, and then click Close.

    Add the SharePoint data connection to receive data:

    1. Choose Data Connections from the Tools menu, and then click Add.
    2. In the Data Connection Wizard, select Receive Data, and then click Next.
    3. Select SharePoint Library or List, and then click Next.
    4. Type the name of your SharePoint site into the text box, and then click Next.
    5. Select Contacts from the list of libraries, and then click Next.
    6. Select the Last_Name, First_Name, E-mail_Address, and Business_Phone fields, deselect every other field, and then click Next.
    7. Click Finish.

    SharePoint stores lists using a unique identifier, called a GUID, for each list. You must know the GUID for your specific implementation of SharePoint in order to submit new items to it. This concept is also true for the field names represented in the CAML file and if you decide to create a new CAML template with more fields (or for a different list) you will have to follow a similar process to obtain the internal SharePoint field name for the particular columns.

    Obtain the Contacts list GUID:

    1. Open Internet Explorer, log on to your SharePoint site, and browse to the Contacts list.
    2. Click Modify Settings and Columns on the left side of the browser window.
    3. Look at the URL in the Address Bar and copy the bracketed value of the List variable at the end of the URL (see Figure 1). Paste this text into a text editor (such as Notepad) for later use.


    Figure 1. Obtaining the List name.

    Create the default view:

    1. Switch back to the InfoPath Designer, and then open the Controls Task Pane.
    2. Insert a button into the view, and then double-click the new button.
    3. Select Refresh from the Action drop-down list, select One Secondary Data Source in the Refresh dialog box, and then select Contacts from the drop-down list.
    4. Click OK two times, and then type Enter a few times to insert some white space below the refresh button.
    5. Open the Data Source task pane, and then choose Contacts from the Data Source drop-down list.
    6. Right-click the myFields/dataFields/Contacts node and choose repeating table.
    7. Type Enter a few times to insert some white space.
    8. Open the Controls task pane, and then select Text Box to insert a new text box field into the view.
    9. Double-click this text box, rename to ListName, and then copy and paste the GUID of the Contact List into the Default Value field. (Refer to Figure 2).
    10. Click OK.


    Figure 2. Adding the ListName text box.

    We now have everything we need to add the data connection to the SharePoint web service, let’s do that now.

    Add the connection to the SharePoint web service:

    1. Choose Data Connections from the Tools menu, and then click Add.
    2. Select Submit Data, click Next, select To a Web Service, and then click Next.
    3. Type the location of the lists.asmx file on your SharePoint site. This is usually located at http://sharepointserver/_vti_bin/lists.asmx.
    4. Click Next.
    5. Select UpdateListItems from the list, and then click Next.
    6. For the s0:listName parameter assign the /my:myFields/my:ListName node as the value.
    7. Highlight the s0:updates parameter, click the data source button, select Add List Item Template from the data source drop-down list, highlight the /Batch node, and then click OK.
    8. Choose XML Subtree, Including Selected Element from the Include drop-down list, and then click Next.
    9. Click Finish.


    Figure 3. Assigning the Web Service variables.

    Now that all of the data connections are properly configured we can finish designing our form.

    Add the submit section:

    1. Open the Data Source task pane, and then choose Add List Item Template from the Data Source drop-down list.
    2. Right-click the /Batch/Method/Field node, and then choose Repeating Table.
    3. Double-click the text box control in the Name column, switch to the Display tab, select Read-Only, and then click OK.

    NOTE: This field must be read-only because if it changes, our CAML will be affected and the submit will fail.

    1. Resize the columns to match Figure 5.
    2. Type Enter a few times to enter some white space.
    3. Open the Controls task pane, and then insert a new button.
    4. Double-click the button, select Submit from the Action drop-down list, Select Enable Submit Commands and Buttons, select Web Service from the Submit To drop-down list, and then select Submit from the data connection drop-down list.
    5. Click OK two times.


    Figure 4. Adding the submit button.


    Figure 5. The completed view.

    Try it out:

    1. Click the Preview Form button (or type Alt+P).
    2. Click the Refresh button to obtain the list of Contacts already on the SharePoint site.
    3. Type a new Contact into the four field rows, and then click the Submit button.
    4. Click the Refresh button again to observe the new contact that has been added.
    5. Open Internet Explorer.
    6. Browse to the Contacts list on your SharePoint site and verify that a new contact has indeed been added.
  • Add Language Localization to a Form

    When a form is used in several different countries the need for easy language localization becomes a very necessary goal. By leveraging expression boxes, a secondary data source, and data filtering techniques the ability to easily switch between languages on a form becomes a small feat.


    Figure 1. An example of an InfoPath Form with localized text.

    Although changing labels in a form is not difficult, there are will be some aspects missing from the form that keep it from achieving true localization. The following are some areas where you might run into problems when internationalizing your form:

    • Tooltip text
    • Insert Item links for repeating tables and sections
    • Button labels
    • Calendars from different parts of the world


    In this task we will create a new blank form with a connection to a secondary data source that provides language localization settings. Let’s start by creating the XML file that will store the language settings.

    Create the secondary data source:

    Copy the following code into a text editor, and then save the file as Language Settings.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <LocalSettings>
        <Languages>
            <Language Name="English" Code="ENG"/>
            <Language Name="Español" Code="SPA"/>
            <Language Name="Français" Code="FRE"/>   
        </Languages>
        <Labels>
            <Label Name="First Name">
                <LocalLabel Code="ENG" Value="First Name"/>
                <LocalLabel Code="SPA" Value="Nombre"/>
                <LocalLabel Code="FRE" Value="Nom"/>   
            </Label>
            <Label Name="Last Name">
                <LocalLabel Code="ENG" Value="Last Name"/>
                <LocalLabel Code="SPA" Value="Apellido"/>
                <LocalLabel Code="FRE" Value="Nom de Famille"/>   
            </Label>
            <Label Name="Phone Number">
                <LocalLabel Code="ENG" Value="Phone Number"/>
                <LocalLabel Code="SPA" Value="Número de Teléfono"/>
                <LocalLabel Code="FRE" Value="Numéro de Téléphone"/>   
            </Label>
            <Label Name="Address">
                <LocalLabel Code="ENG" Value="Address"/>
                <LocalLabel Code="SPA" Value="Dirección"/>
                <LocalLabel Code="FRE" Value="Adresse"/>       
            </Label>
            <Label Name="Age">
                <LocalLabel Code="ENG" Value="Age"/>
                <LocalLabel Code="SPA" Value="Edad"/>
                <LocalLabel Code="FRE" Value="Age"/>
            </Label>
            <Label Name="FavoriteColor">
                <LocalLabel Code="ENG" Value="Favorite Color"/>
                <LocalLabel Code="SPA" Value="Color Favorito"/>
                <LocalLabel Code="FRE" Value="Couleur Préférée"/>
            </Label>
        </Labels>
        <Colors>       
            <LocalColor ID="Blue" Code="ENG" Value="Blue"/>
            <LocalColor ID="Blue" Code="SPA" Value="Azul"/>
            <LocalColor ID="Blue" Code="FRE" Value="Bleu"/>       
            <LocalColor ID="Green" Code="ENG" Value="Green"/>
            <LocalColor ID="Green" Code="SPA" Value="Verde"/>
            <LocalColor ID="Green" Code="FRE" Value="Vert"/>       
            <LocalColor ID="Red" Code="ENG" Value="Red"/>
            <LocalColor ID="Red" Code="SPA" Value="Rojo"/>
            <LocalColor ID="Red" Code="FRE" Value="Rouge"/>       
            <LocalColor ID="Yellow" Code="ENG" Value="Yellow"/>
            <LocalColor ID="Yellow" Code="SPA" Value="Amarillo"/>
            <LocalColor ID="Yellow" Code="FRE" Value="Jaune"/>       
        </Colors>
    </LocalSettings>

    Now that we have our secondary data source, let’s design our form.

    Add the secondary data source:

    1. Design a new blank form.
    2. Choose Data Connections from the Tools menu, and then click Add.
    3. In the Data Connection Wizard, select Receive Data, and then click Next.
    4. Select XML Document, and then click Next.
    5. Click Browse, locate and select the Language Settings.xml file, click Open, and then click Next.
    6. Click Finish, click Yes, and then click Close.

    Add the language selection drop-down list:

    1. Open the Controls task pane.
    2. Add a drop-down list box to the view.
    3. Double-click the new drop-down list, rename the field to LanguageChoice, and then select Look Up Values From A Data Connection in the List Box Entries section.
    4. Select Language Settings from the Data Connection drop-down list.
    5. Select /LocalSettings/Languages/Language for the Entries field by clicking on the icon to the right of the field.
    6. Select @Code for the Value and @Name for the Display Name. Refer to Figure 2.
    7. Click OK to close the Drop-Down List Box Properties dialog box.


    Figure 2. Configuring the language selection drop-down list.

    Add the layout table and controls:

    1. Open the Layout task pane.
    2. Insert a Custom Table with 3 columns and 4 rows.
    3. Merge the last two columns of the third and fourth row (refer to Figure 5).
    4. Open the Controls task pane, and then insert a text box control into the first column of the second row.
    5. Double-click this text box, change the name to FirstName, and then click OK.
    6. Insert a text box into the second column of the second row, and then change the name to LastName.
    7. Insert a text box into the last column of the second row, and then change the name to Age.
    8. Insert a text box into the first column of the last row, and then change the name to PhoneNumber.
    9. Insert a text box into the last column of the last row, and then change the name to Address.

    Now that all of the controls are in the layout, we need to add labels so that the user knows what information to type into each field. Using expression boxes we can dynamically set the content of each label depending on the language preference.


    LOCALIZED FIELD LABELS

    Add the expression boxes with data filtering:

    1. Click inside the first column of the first row to set the insertion point.
    2. Select Expression Box from the Controls task pane.
    3. Click the Function button on the dialog box that appears.
    4. Click Insert Field or Group in the Insert Formula dialog box.
    5. Select Language Settings (Secondary) from the Data Source drop-down box, select /LocalSettings/Lables/Label/LocalLabel/Value, and then click Filter Data.
    6. Click Add.
    7. Select Name from the Select A Field or Group first drop-down list, select Labels/Label/Name, click OK, and then type First Name into the third drop-down list.
    8. Click And, select Code from the first drop-down list, and then select Select A Field Or Group from the third-drop down list.
    9. Select Main from the Data Source drop-down list, and then select myFields/LanguageChoice.
    10. Click OK 6 times.
    11. Repeat steps 1 through 9 for each additional label, except in step 7 type the text that corresponds to whichever label you are adding.


    Figure 3. Specifying the filter conditions.


    Figure 4. The completed function for the expression box.

    Make the labels bold:

    1. Select all of the expression boxes by holding the Ctrl key while clicking each one.
    2. Click the Bold button (or type Ctrl+B).


    Figure 5. The completed view.

    Now our form has labels that change depending on what the user selects from the drop-down list. Let’s preview our accomplishments before adding a finishing touch.

    Preview the form:

    1. Click Preview Form (or type Alt+P).
    2. Select any of the languages from the drop-down list.
    3. Notice how the labels are blank originally and then change to the specified language depending on what is chosen from the drop-down list.


    Figure 6. Selecting the language.


    Figure 7. Labels in Spanish.

    To remedy the problem of all of the labels being blank by default we will add a default value to the myFields/LanguageChoice node.

    Set the default language:

    1. Open the Data Source task pane.
    2. Double-click the myFields/LanguageChoice node.
    3. Type ENG as the Default Value, and then click OK.

    With another quick preview you will see that the drop-down list is automatically set to English and none of the labels are blank.


    LOCALIZED DROP-DOWN LISTS

    Now that the expression boxes have all been updated to display appropriate labels depending on the language choice, we will add a drop-down list that demonstrates similar functionality. The secondary data source already has elements with all of the correct information, so we will simply add a drop-down list with the appropriate data filtering.

    Add the drop-down list:

    1. Click below the table to set the insertion point and type Enter a few times to insert some white space.
    2. Add an expression box that displays the Favorite Color label using the process described in the first section.
    3. Open the Controls task pane and select Drop-Down List to insert a new control.
    4. Double-click the drop-down list, and then change the name to FavoriteColor.
    5. Select Look Up Values In A Data Connection, select Language Settings from the data source drop-down list, and then click the field selection button.
    6. Select Colors/LocalColor, and then click the Filter Data button.
    7. Click Add, select Code from the first drop-down list, and then select Select A Field Or Group from the third drop-down list.
    8. Choose Main from the data source drop-down list, select /myfields/LanguageChoice, and then click OK four times to return to the Drop-Down List Box Properties dialog box.
    9. Select the @ID attribute as the Value for the entries, select the @Value attribute as the Display Name, and then click OK.


    LOCALIZED BUTTONS

    The last aspect of adding localization to the form will be making sure that the button labels are correctly displayed. Unfortunately, InfoPath does not allow the button labels to be changed programmatically, so we are forced to use several button controls with conditional formatting to solve this problem. This same process can be used to change the tooltip text and Insert Item links for repeating tables and sections, but depending on how many languages need to be supported this can generate tremendous amounts of clutter in design mode.

    Add the buttons:

    1. Open the Controls task pane and insert a button onto the view.
    2. Double-click the button, change the Label to Submit, change the ID to butSubmit.
    3. Click the Display tab, click the Conditional Formatting button, and then click Add.
    4. In the first drop-down list select LanguageChoice, select Is Not Equal To in the second drop-down list, and then select Type Text and type ENG into the third drop-down list.
    5. Select Hide This Control, and then click OK three times.
    6. Copy and paste the button until there are three identical buttons right next to each other.
    7. Change the Label on the second button to Sométase, change the ID to butSubmit, and then follow step 4 to apply conditional formatting using SPA as the text instead of ENG.
    8. Change the Label on the third button to Soumettre, change the ID to butSubmit, and then follow step 4 to apply conditional formatting using FRE as the text instead of ENG.

    Note: The ID of all three buttons should be the same because they will all perform the same action and any code that you attach to one button should be attached to the others.


    Figure 8. The completed form in design mode.


    Figure 9. The completed form being filled out.

  • Create a Tabbed Content Pane

    When filling out a form with several different sections it is very convenient to use tabs to separate these sections. Tabs quickly and easily let the user know what sections are available and have become a vital part of graphical user interfaces. Using advanced design techniques, InfoPath can be used to create a fantastic and appealing tabbed interface.

    In this task we will use a layout table, buttons with code, and three views to create a tabbed content pane. Let's start by creating a new blank form.

    Add the layout table and buttons:

    1. Open the Layout task pane and choose Custom Table to create a table with 3 columns and 2 rows.
    2. Highlight the entire second row, right-click and choose Merge Cells to create one big cell.
    3. Change the size of the one big cell by clicking and dragging its bottom border down the page.
    4. Click inside the first column of the first row to set the insertion point.
    5. Open the Controls task pane and select Button.
    6. Repeat steps 4 and 5 for the next two columns so that there is a button in each of the columns of the first row.

    Format the buttons to look like tabs:

    1. Double-click the button in the first column, change the Label to Tab 1, change the ID to btnTab1, and then click OK.
    2. Right-click the Tab 1 button and choose Borders and Shading.
    3. Click None under the Presets section of the Borders tab to remove the borders of the button.
    4. Switch to the Shading tab of the Borders and Shading dialog box, select No Color, and then click OK.
    5. Resize the button so that it will almost fill the entire cell.
    6. Right-click inside the table cell that contains the button and choose Borders and Shading. If you have a hard time doing this try clicking on the button and then pressing the right arrow key to set the insertion point inside the table cell. After this right-click on the insertion point to access the menu.
    7. Switch to the Shading tab, choose a dark grey color from the drop-down list, and then click OK.
    8. Repeat steps 1 through 6 for the other two buttons naming them Tab 2 and Tab 3, respectively. Also, instead of a dark grey color for the shading of the table cells select a light grey color. Refer to Figure 1.


    Figure 1. The completed default view.

    Add the other views:

    1. Open the Views Task Pane, double-click View 1, rename it to Tab 1, and then click OK.
    2. Click Add A New View, name the view Tab 2, and then click OK.
    3. Add one more view named Tab 3.

    Now that the form is starting to take shape, let's add some functionality via rules on each of the buttons. Please note that the following actions can also be achieved with code. For this example, we chose to use rules, but you may substitute code if you prefer. Also note that although rules will be carried along with a button after copying and pasting, adding rules later will require another copy and paste for the rules to propagate.

    Add rules to the buttons:

    1. In the Views task pane select Tab 1 to switch to the view with the tabbed content pane.
    2. Double-click the Tab 1 button to open the Button Properties dialog box, click Rules, and then click Add.
    3. Name the rule Switch Views to Tab 1, and then click Add Action.
    4. Select Switch Views from the Action drop-down list, select Tab 1 from the View drop-down list, and then click OK four times to close the Button Properties dialog box and apply the rule.
    5. Follow steps 2 through 4 for the other two buttons, adding actions to switch to their respective views.

    Copy the tabbed content pane to the other views:

    1. Copy all of the content of the Tab 1 view by choosing Select All from the Edit menu, and then choosing Copy from the Edit menu. This can also be done by pressing Ctrl+A then Ctrl+C.
    2. In the Views task pane select Tab 2 to switch to the Tab 2 view, and then choose Paste from the Edit menu. This can also be done by pressing Ctrl+V.
    3. Switch to the Tab 3 view and Paste again.
    4. Reformat the table cells on each view so that the dark grey cell on the view corresponds to which view it is.
    5. Type some identifying text into the one big cell of each view. Refer to Figures 2 and 3.


    Figure 2. The Tab 2 view.


    Figure 3. The Tab 3 view.

    Try it:

    1. Preview the form by clicking the Preview Form button or pressing Alt+P.
    2. Observe how clicking the buttons switches views and changes the formatting of the buttons to display a tabbed content pane.

    You will see that we have now achieved a very functional and easy-to-use tabbed content pane. With the help of images and careful adjustments, this simple tabbed content pane can be transformed into a very clean and professional looking interface.

  • Create a Custom Task Pane to Switch Views

    Custom task panes, and their ability to interface with objects and functions in InfoPath, can be very useful. One such example of utilizing this functionality is creating a custom task pane using some simple JavaScript code that dynamically generates links to the different views in a form. The main difference between changing views in the task pane and changing views in InfoPath is that the task pane requires code to change views, whereas you can use rules or simply the View menu items in InfoPath.

    In this task we will create a custom task pane HTML file in a text editor and add it to an InfoPath sample form.

    Create a custom task pane:

    Copy the following code into a text editor, and then save the file as SwitchViewsTaskPane.htm.

    <html>
    <head>

    <script language="JavaScript">

    var XDocument;
    var View;

    function PrintLinks()
    {
        // Get a reference to the XDocument object for the InfoPath Form.
        XDocument = window.external.Window.XDocument;

        // Set the reference to the View object.
        View = XDocument.View; 

        // Get the ViewInfos object.
        var oViewInfos = XDocument.ViewInfos;

        var sInnerHtml = "";

        // Loop through the views, generate the HTML, and store it in sInnerHtml.
        for(i = 0; i < oViewInfos.Count; i++)
        {
            sInnerHtml = sInnerHtml
                + "<a href='#' OnClick=\"View.SwitchView('" + oViewInfos(i).Name + "');\">"
                + (i+1) + ". &nbsp;" + oViewInfos(i).Name + "</a><br />" ;   
        }

        // Display the HTML that was just generated.
        ViewLinks.innerHTML = sInnerHtml; 
    }

    </script>
    </head>

    <body onload="PrintLinks()">

     <div id="ViewLinks"></div>

    </body>
    </html>

    Customize the Project Plan sample form:

    1. Open InfoPath, click Design A Form in the Fill Out A Form dialog box, and then click Customize A Sample in the Design A Form task pane.
    2. Select Project Plan from the Samples tab, and then click OK.
    3. Choose Form Options from the Tools menu.
    4. Switch to the Advanced Tab, select Enable Custom Task Pane, and then click Resource Files.
    5. Click Add, locate the SwitchViewsTaskPane.htm file that was created above, and then click OK.
    6. Name the task pane Switch Views (as shown in Figure 1), and then click OK to close the Form Options dialog box.


    Figure 1. Adding the custom task pane.

    Preview the form and notice that the task pane automatically generates links to all of the available views within the form, as shown in Figure 2.


    Figure 2. The completed task pane applied to the Project Plan sample form.

  • Programmatically Move Focus to Lists, Check Boxes, and Radio Buttons without Allowing Deletion

    The XDocument.View.SelectNodes() function can be very useful when you need to "jump" and give focus to a certain field on your form with code. However, the SelectNodes() function only allows for the selection of certain controls and there are several common controls that are not normally selectable. Drop-down and normal lists, check boxes, radio buttons, and ink picture controls cannot be selected directly in InfoPath. To get around this problem we will place the non-selectable controls within a section control that is bound to the same node as our target control. Once this is accomplished we can use the SelectNodes() function on the containing section to achieve the desired effects.

    In this task we will create a drop-down list and a button that will change focus to this drop-down list. Let's start by creating a new blank form.

    Add a textbox, drop-down list, and button control:

    1. Open the Controls task pane.
    2. Insert a Text Box into the view.
    3. Press Enter twice to insert some white space.
    4. Insert a Drop-Down List Box into the view.
    5. Press Enter twice to insert some white space.
    6. Insert a Button into the view.

    Rename the Drop-Down List Box schema node and Button:

    1. Double-click the Button in the view, change the Label to Change Focus, and then change the ID to btnChangeFocus, as shown in Figure 1, and then click OK.
    2. Open the Data Source task pane, and then choose Main from the Data Source drop down list.
    3. Double-click myFields/field2, and then rename it to DropDown.
    4. Double-click myFields/field1, and then rename it to Date.


    Figure 1. The completed default view.

    Add code to the button:

    1. Double-click on the button control in the view.
    2. Click Edit Form Code.
    3. In the Microsoft Script Editor, replace the contents of the OnClick event handler with the following code:

    BLOCKED SCRIPT
    XDocument.View.SelectNodes(XDocument.DOM.selectSingleNode("//my:DropDown"));

    C#:
    thisXDocument.View.SelectNodes(thisXDocument.DOM.selectSingleNode("//my:DropDown"), Type.Missing, Type.Missing);

    After reading the documentation on the SelectNodes() function it might seem that our form would work correctly as it has been made so far, but a short preview will prove otherwise.

    Preview the Error:

    1. Click Preview Form on the Standard Toolbar.
    2. Click Change Focus (an error message pops up, as shown in Figure 2).
    3. Click No, click OK, and then click Close Preview.


    Figure 2. The SelectNodes() error message.

    Now we must add a section that is bound to the same node as our drop-down list box. The reason we bind it to the same node and not create a new node is to prevent excessive clutter in the DOM.

    Add a section and move the drop-down list:

    1. Open the Data Source task pane.
    2. Choose Main from the Data Source drop down list.
    3. Right-drag the DropDown node onto the view, and then choose Section.
    4. Drag-and-drop the drop-down list box into the section that was just created.
    5. Rearrange and resize the new section to match Figure 3.


    Figure 3. The section and drop-down list bound to the same node.

    At this point it might still seem like the button will work correctly, but another preview will show us that this is not the case. The section that contains the drop-down list must be set to allow users to delete the section in order for the button to work.

    Make the section able to be deleted:

    1. Double-click the section.
    2. Select the Allow Users To Delete The Section check box under the Default Settings section.
    3. Click OK.

    Finally, a preview after these steps shows us that we have accomplished our goal. Previewing the form and clicking the button makes focus "jump" from the text box to the section that holds the drop-down list without issuing any errors. Once focus is set to the section, the user must simply press any of the arrow keys or the tab key to choose a new value for the drop down list.

    In the last few steps we enabled the ability to delete the section, but this should not be permissible. To avoid this problem we use a clever conditional formatting technique to ensure that the section will never be deleted.

    Add conditional formatting:

    1. Double-click the section.
    2. Click the Display tab.
    3. Click Conditional Formatting.
    4. Click Add.
    5. Choose The Expression from the first drop-down list.
    6. Replace the text in the text-box to the right of the drop-down list with 2+2=4 (or any other tautology: a statement that is always true).
    7. Select the Don't Allow Users To Insert Or Delete This Control check box.
    8. Click OK three times.

    After these steps the button will work exactly as it is supposed to and there are no chances of the section being removed or deleted from the form. All of these steps can be applied to the other non-selectable items (check boxes and option buttons) to achieve the same effect.

  • Create a Multi-View Master/Detail using a Query Filter

    When using InfoPath as a front end to a large database it is often convenient to be able to skim through the rows looking at only a few columns, such as a contact name, and then click a button to view the rest of the information.

    To obtain optimal performance when using a very large database, you can use two data connections to the same database: one querying a small subset of columns, the other a single row of all columns. If the database is small to moderately large, or performance is not an issue, see Create a Multi-View Master/Detail using an InfoPath Data Filter.

    In this task we will create a new form connected to a sample Access database, supplemented by a secondary connection to the same database, and use controls, rules, and queries to create a multi-view master/detail list.

    Obtaining the sample Access database:

    The sample Access database we will use is supplied as part of the Microsoft Office 2003 default installation. If installed, you can locate it at C:\Program Files\Microsoft Office\OFFICE11\SAMPLES\Nwind.mdb. If the database file does not exist in this directory, you can download it from the Microsoft Web site at http://www.infopathdev.com/cgi-bin/awredir.pl?url=http://www.microsoft.com/downloads/details.aspx?FamilyID=C6661372-8DBE-422B-8676-C632D66C529C&displaylang=en.

    Create a new form from a database connection:

    1. Launch InfoPath, and then choose Design A Form from the File menu.
    2. In the Design A Form task pane, click New From Data Connection.
    3. In the Data Connection Wizard, select Database, and then click Next.
    4. Click Select Database, locate and select Nwind.mdb, and then click Open.
    5. In the Select Table dialog box, select the Customers table, and then click OK.
    6. Click Next, and then click Finish.

    The main data connection will be used for our detail view. We will query for details about the customer selected by the user. Now let's add a supplementary data connection which will be used to display our master view. It is optimized to retrieve only the columns we will display.

    Add a supplementary data connection to the same database:

    1. Choose Data Connections from the Tools menu.
    2. In the Data Connections dialog box, click Add.
    3. In the Data Connection Wizard, select Receive Data, and then click Next.
    4. Select Database, and then click Next.
    5. Click Select Database, locate and select Nwind.mdb, and then click Open.
    6. In the Select Table dialog box, select the Customers table, and then click OK.
    7. In the Data Source Structure list box, clear every check box except CustomerID and ContactName, and then click Next.
    8. Name the data connection Master.
    9. Ensure that Automatically Retrieve Data When Form is Opened is selected, click Finish, and then click Close.

    Create the master view:

    1. Delete everything that was automatically generated in the view.
    2. Open the Data Source task pane, and then select Master from the Data Source drop-down list.
    3. Right-click dataFields/d:Customers, and then choose Repeating Table.
    4. Delete the Customer ID column.
    5. Right-click the Contact Name column label, and then choose Insert | Columns To The Right.
    6. Click inside the new column to set the insertion point.
    7. Open the Controls task pane, and then click Button.
    8. Double-click the inserted button.
    9. In the Button Properties dialog box, change the label to View Detail, change the ID to btnViewDetail, and then click OK (see Figure 1).


    Figure 1. The completed master view.

    Create the detail view:

    1. Open the Views task pane.
    2. Click Add A New View.
    3. Name the view Detail, and then click OK.
    4. Open the Data Source task pane, and then select Main from the Data Source drop-down list.
    5. Right-click dataFields/d:Customers, and then choose Repeating Section With Controls (see Figure 2).


    Figure 2. The completed detail view.

    Add code behind the View Detail button:

    The following code can also be expressed in rules. For this example, we chose to use code, but you may substitute rules if you prefer.

    1. In the Views task pane, select View 1.
    2. Double-click the View Detail button, and then click Edit Form Code.
    3. In the Microsoft Script Editor, replace the contents of the OnClick event handler with the following code:

    var oQueryId = XDocument.DOM.selectSingleNode( "/dfs:myFields/dfs:queryFields/q:Customers/@CustomerID" );
    oQueryId.text = eventObj.Source.selectSingleNode("@CustomerID").text;
    XDocument.Query();
    XDocument.View.SwitchView("Detail");

    Try it:

    1. Click Preview Form (or press Alt+P on the keyboard).
    2. Click View Detail for one of the rows to view the selected contact in the detail view.
  • Create a Multi-View Master/Detail using an InfoPath Data Filter

    When using InfoPath as a front end to a large database it is often convenient to be able to skim through the rows looking at only a few columns, such as a contact name, and then click a button to view the rest of the information.

    You can use an InfoPath data filter to easily and quickly implement this functionality. However, this method might not be ideal for very large databases as its performance could suffer. If you are connecting to a very large database, and performance is important, please see Create a Multi-View Master/Detail using a Query Filter.

    In this task we will create a new form connected to a sample Access database and use controls, rules, and filters to create a multi-view master/detail list.

    Obtaining the sample Access database:

    The sample Access database we will use is supplied as part of the Microsoft Office 2003 default installation. If installed, you can locate it at C:\Program Files\Microsoft Office\OFFICE11\SAMPLES\Nwind.mdb. If the database file does not exist in this directory, you can download it from the Microsoft Web site at http://www.infopathdev.com/cgi-bin/awredir.pl?url=http://www.microsoft.com/downloads/details.aspx?FamilyID=C6661372-8DBE-422B-8676-C632D66C529C&displaylang=en.

    Create a new form from a database connection:

    1. Launch InfoPath, and then choose Design A Form from the File menu.
    2. In the Design A Form task pane, click New From Data Connection.
    3. In the Data Connection Wizard, select Database, and then click Next.
    4. Click Select Database, locate and select Nwind.mdb, and then click Open.
    5. In the Select Table dialog box, select the Customers table, and then click OK.
    6. Click Next, and then click Finish.
    7. Delete everything that was automatically generated in the view except for the Run Query button (we will keep this at the top of the page).
    8. Click below the Run Query button to set the insertion point.

    Create the master view:

    1. Open the Data Source task pane.
    2. Right-click dataFields/d:Customers, and then choose Repeating Table.
    3. Delete every column in the table except for Contact Name.
    4. Right-click the Contact Name column label, and then choose Insert | Columns To The Right.
    5. Click inside the new column to set the insertion point.
    6. Open the Controls task pane, and then click Button.
    7. Double-click the inserted button.
    8. In the Button Properties dialog box, change the label to View Detail, change the ID to btnViewDetail, and then click OK (see Figure 1).



    Figure 1. The completed master view.

    Create the detail view:

    1. Open the Views task pane.
    2. Click Add A New View.
    3. Name the view Detail, and then click OK.
    4. Open the Data Source task pane.
    5. Right-click dataFields/d:Customers, and then choose Repeating Section With Controls (see Figure 2).


    Figure 2. The completed detail view.

    We need to add a helper node to the schema to store the ID of the customer selected in the master view. This ID will be used to filter the display in the detail view to show only the selected customer. After setting this value, we will switch to the detail view.

    Add the helper node:

    1. In the Data Source task pane, right-click myFields, and then choose Add.
    2. Name the field DetailId.
    3. Select Whole Number from the Data Type drop-down list.
    4. Click OK.

    Add rules to the View Detail button:

    1. Open the Views task pane, and then select View 1.
    2. Double-click the View Detail button.
    3. In the Button Properties dialog box, click Rules, and then click Add.
    4. Name the rule Update DetailId and Change Views.
    5. Click Add Action.
    6. Choose Set A Field’s Value from the Action drop-down list.
    7. Click the Data Source button to the right of the Field text box.
    8. In the Select A Field Or Group dialog box, select my:DetailId, and then click OK.
    9. Click the Insert Formula button to the right of the Value text box.
    10. In the Insert Formula dialog box, click Insert Field Or Group.
    11. Select dataFields/d:Customers/:CustomerID, and then click OK three times.
    12. In the Rule dialog box, click Add Action.
    13. Choose Switch Views from the Action drop-down list.
    14. Choose Detail from the View drop-down list, and then click OK (see Figure 3).
    15. Click OK three times to close all open dialog boxes.


    Figure 3. Adding rules to the View Detail button.

    The functionality has been added to the button, but the detail view still displays all of the rows. Let's add a filter to repeating section in the detail view to show only the selected row.

    Add a filter to the detail section:

    1. In the Views task pane, select Detail.
    2. Double-click the repeating section label.
    3. On the Display Tab, click Filter Data, and then click Add.
    4. In the Specify Filter Conditions dialog box, select CustomerID from the first drop-down list.
    5. In the second drop-down list, select Is Equal To.
    6. In the third drop-down list, select Select A Field Or Group.
    7. In the Select A Field Or Group dialog box, choose my:DetailId, and then click OK (see Figure 4).
    8. Click OK three times to close all open dialog boxes.


    Figure 4. Filter the detail view based on the selected Customer ID.

    Try it:

    1. Click Preview Form (or press Alt+P on the keyboard).
    2. Click Run Query to populate the master table.
    3. Click View Detail for one of the rows to view the selected contact in the detail view.
  • Repeat Table Headers Across Multiple Printed Pages

    InfoPath is a great tool for collecting and organizing data, but it lacks when it comes to print formatting. For example, when a dynamic table with a large set of rows must be formatted to print across several pages of a report, the column headers should appear, for better readability, at the top of each page containing this table. Otherwise, you have to flip back to a previous page each time you needed to reference the column names. Furthermore, rows should not break across two pages (i.e. part of the row on one page and part on the next).

    In this task we will use code to create table headers that repeat across multiple printed pages. We will also explain how to expand your code to prevent rows breaking across pages. We will accomplish this using a repeating section which contains a page break and a repeating table. This structure, along with our code, will dynamically create page breaks so that table headers appear at the top of the page. To demonstrate this technique, we need a data source with plenty of rows.


    THE SECONDARY DATA SOURCE

    Copy the following code into a text editor, and then save the file as Vehicles.xml. This file will be used as a secondary data source in our form.

    <?xml version="1.0" encoding="UTF-8"?>
    <Vehicles>
        <Vehicle year="1963" make="Chevrolet" model="Corvette" description="Features:
        -Fast
        -Stylish
        -Powerful   
        -Convertible"/>
        <Vehicle year="1963" make="Chevrolet" model="Nova" description="Features:
        -Fast
        -Stylish
        -Powerful
        -2 year warranty"/>
        <Vehicle year="1963" make="Porsche" model="356" description="Features:
        -Very fast
        -Stylish
        -Impressive body design   
        -3 year warranty"/>
        <Vehicle year="1963" make="Porsche" model="Carrera" description="Features:
        -Fast
        -Powerful
        -Plenty of room
        -3 year warranty
        -Free tires"/>
        <Vehicle year="1973" make="Ford" model="Bronco" description="Features:
        Towing power
        -High road clearance
        -Free tires
        -3 year warranty
        -Mud flaps"/>
        <Vehicle year="1973" make="Ford" model="Mustang" description="Features:
        -Fast
        -Stylish
        -Convertible
        -Great sound system" />
        <Vehicle year="1973" make="Ford" model="Pinto" description="Features:
        -Beautiful finish
        -3 year warranty
        -Extra mirrors"/>
        <Vehicle year="1973" make="Toyota" model="Celica" description="Features:
        -Reliable
        -Stylish
        -Good gas mileage"/>
        <Vehicle year="1973" make="Toyota" model="Land Cruiser" description="Features:
        -Tire upgrade options
        -Sunroof
        -Cruise Control
        - Off-road capabilities"/>
        <Vehicle year="1979" make="Jaguar" model="XJ6" description="Features:
        -Stylish
        -Powerful
        -Convertible
        -European style headlights
        -3 year warranty
        -Fun to drive"/>
        <Vehicle year="1979" make="Pontiac" model="Trans Am" description="Features:
        -Fast
        -Stylish
        -Reliable
        -2 year parts and labor included   
        -Financing available"/>
        <Vehicle year="1979" make="Volvo" model="240" description="Features:
        -Reliable
        -Convertible
        -European style headlights
        -3 year warranty"/>
        <Vehicle year="1979" make="Volvo" model="244DL" description="Features:
        -Stylish
        -Powerful   
        -Great sound system
        -Great gas mileage
        -3 year warranty
        -Fun to drive"/>
        <Vehicle year="1987" make="Dodge" model="Ram Charger" description="Features:
        -Fast
        -Stylish
        -Powerful   
        -High safety rating
        -3 year warranty
        -Fun to drive around very tight corners and up slippery slopes filled with ice"/>   
    </Vehicles>


    THE FORM

    Now that we have our secondary data source, let's create a new blank form with two views: a default view for data entry, and a print view that repeats the table header across pages. We create two views so that data collection is easier, and so that InfoPath only has to format our print view upon switching to that view as formatting might take some time. We, therefore, want to run this code as infrequently as possible.

    Add the secondary data source to a form:

    1. Design a new blank form.
    2. Choose Data Connections from the Tools menu, and then click Add.
    3. In the Data Connection Wizard, select Receive Data, and then click Next.
    4. Select XML Document, and then click Next.
    5. Click Browse, locate and select the Vehicles.xml file, click Open, and then click Next.
    6. Click Finish, click Yes, and then click Close.

    Design the data entry view:

    1. Open the Data Source task pane.
    2. Choose the Vehicles from the Data Source drop down list.
    3. Drag-and-drop the year attribute into the view. This will create a repeating section with the year text box inside.
    4. Drag-and-drop the make, model and description attributes into the repeating section, and then resize the fields as shown in Figure 1.
    5. On the Display tab of the Text Box Properties dialog box, select Paragraph Breaks, Read-Only, and Wrap Text, and then click OK.


    Figure 1. The completed data entry view.

    Add a new view for printing:

    1. Open the Views task pane.
    2. Click Add A New View.
    3. Name the view Print View, and then click OK.

    Design the print view:

    1. Open the Controls task pane.
    2. Insert a Repeating Section into the view.
    3. Click inside the Section to set the insertion point.
    4. Choose Page Break from the Insert menu.
    5. Press Enter to move to the next line.
    6. Click Repeating Table in the Controls task pane, type 4 for the number of columns, and then click OK.
    7. Type Year, Make, Model and Description for the column header labels
    8. Right-Click the Year label, and then choose Table Properties.
    9. On the Column tab of the Repeating Table Properties dialog box, set the column widths of columns A-D to 56px, 75px, 91px and 400px respectively, as shown in Figure 2.


    Figure 2. The completed print view.

    As you inserted controls into the view, InfoPath automatically generated schema nodes for these controls. Let's rename these generated nodes so that they are easier to identify.

    Rename the generated schema nodes:

    1. Open the Data Source task pane.
    2. Choose Main from the Data Source drop down list.
    3. Double-click each node in the data source tree and rename as follows (refer to Figure 3):
      • group1 to Document
      • group2 to Page
      • group3 to Table
      • group4 to Row
      • field1 to Year
      • field2 to Make
      • field3 to Model
      • field4 to Description


    Figure 3. The revised schema.

    Now that we have finished designing our form layout, let's add functionality by adding code. The code will generate our print list when the user switches to the print view.

    Add the OnSwitchView event handler code:

    1. Choose Programming | On Switch Views Event from the Tools menu.
    2. In the Microsoft Script Editor, replace the contents of the OnAfterChange event handler with the following code:

    if(XDocument.View.Name == "Print View")
        GeneratePrintList();

    This specifies to call a custom function each time the user switches to the print view. Now let's write the code to generate the repeating table header pages.

    1. Paste the following code directly below the OnSwitchView() function:

    function GeneratePrintList()
    {
        // Number of lines we want on a page.
        var MAX_ROWS_ON_PAGE = 10;

        // Number of lines we have placed on current page.
        var iCurrentPageRowCount = 0;

        // Index of the current page we are adding to.
        var iCurrentPageIndex = 1;

        // Set up access to secondary data source.
        var domVehicles = XDocument.GetDOM("Vehicles");   

        // Source nodes.
        var oVehicles = domVehicles.selectNodes("/Vehicles/Vehicle");

        // Now we will get a node list for each of our groups and clear them
        // of all children. We will be generating the list from scratch each
        // time we switch to the Print View so that we can deal with changes
        // that have occurred since the last generation of the list.

        // ALL ROWS
        // Get the node list.
        var oRows = XDocument.DOM.selectNodes("/my:myFields/my:Document/my:Page/my:Table/my:Row");

        // Save a sample node from this list for later - we will have to clone
        // it to add our rows back in.
        var oSampleRow = oRows.nextNode();

        // Remove all the rows from all the repeating tables.
        oRows.removeAll();

        // ALL TABLES
        var oTables = XDocument.DOM.selectNodes("/my:myFields/my:Document/my:Page/my:Table");   
        var oSampleTable = oTables.nextNode();   
        oTables.removeAll();

        // ALL PAGES
        var oPages = XDocument.DOM.selectNodes("/my:myFields/my:Document/my:Page");   
        var oSamplePage = oPages.nextNode();   
        oPages.removeAll();   

        // THE DOCUMENT
        var oDocument = XDocument.DOM.selectSingleNode("/my:myFields/my:Document");   

        // We have only 1 document node, but we just emptied it of all
        // contents, so we must add back in at least one page and one
        // table node because we know we will be adding at least one row
        // into this table.

        // Add the first page.
        oDocument.appendChild(oSamplePage.cloneNode(false));

        // Add the first table.
        XDocument.DOM.selectSingleNode("/my:myFields/my:Document/my:Page").appendChild(oSampleTable.cloneNode(false));

        // Loop through all source nodes and add them
        // to the table we are generating.
        while(oSrc = oVehicles.nextNode())
        {   
            // We are about to put a new row on the page.
            iCurrentPageRowCount++;

            // If the row we about to put on the page will not fit, create a new page.
            if(iCurrentPageRowCount > MAX_ROWS_ON_PAGE)
            {
                // Reset our row count (we are about to add a row to the next page,
                // so that page's row count will be 1).
                iCurrentPageRowCount = 1;

                // We are creating a new page and working with it, so increment the index.
                iCurrentPageIndex += 1;

                // Add the new page.
                var oNewPage = oDocument.appendChild(oSamplePage.cloneNode(false));

                // Add a new table to the page that we just created.
                oNewPage.appendChild(oSampleTable.cloneNode(false));           
            }

            // Create a new row by cloning our sample row.
            // This node must be cloned using "deep" mode to maintain structure of the
            // DOM below it. However, we are about to overwrite all of the nodes anyway.
            var oNewRow = oSampleRow.cloneNode(true);

            // Set the values in the new row from the source row

            // that we are reading from on this iteration.

            oNewRow.selectSingleNode("my:Year").text = oSrc.selectSingleNode("@year").nodeValue;

            oNewRow.selectSingleNode("my:Make").text = oSrc.selectSingleNode("@make").nodeValue;

            oNewRow.selectSingleNode("my:Model").text = oSrc.selectSingleNode("@model").nodeValue;

            oNewRow.selectSingleNode("my:Description").text = oSrc.selectSingleNode("@description").nodeValue;

            // Add the row to the table that is on the current page.
            XDocument.DOM.selectSingleNode("/my:myFields/my:Document/my:Page[" + iCurrentPageIndex + "]/my:Table").appendChild(oNewRow);

        } // End while looping through all source nodes.
    } // End function GeneratePrintList().

    Try it:

    1. Switch back to the InfoPath Designer.
    2. Open the Views task pane, and then select View 1.
    3. Preview the form.
    4. Choose Print View from the Views menu.
    5. Choose Print Preview from the File menu.

    While editing, the tables will appear to be right next to each other vertically, but when looking at the print preview, each page has a table with the column headers and ten data rows (as specified in our code). You will also notice that at the bottom of the first table, the row for "Jaguar XJ6" breaks in the middle, carries over to the next page, and is immediately followed by page break. This creates a huge gap in the printed pages.

    The first page is blank because the page break appears before the very first page node. This can easily be remedied by placing the page break inside a section, and then adding conditional formatting to hide the section for the first page node.


    PREVENTING ROWS FROM BREAKING ACROSS PAGES

    To prevent rows from breaking across pages you need to take into consideration more than the just the number of rows per page; you need to know the number of lines of text per page. This process requires quite a bit of tweaking to determine the most precise method of counting lines per row and the number of lines that you can fit on a page. This will depend on the kind of data your rows contain and how it is formatted.

    To begin this process you will need to change the iCurrentPageRowCount variable to iCurrentPageLineCount, change the if-statement that controls whether to start a new page, and implement a method to count the number of lines of text in each row. We do this by tracking the number of lines that fit on a page and the number of characters per line before wrapping occurs.

    To determine the number of lines per page, view the form in print preview mode and count the number of lines (not rows) that fit on the first page. In this example, we know that the description field will always have more lines than any of the other fields, so we count each line in each description field on the first page to determine 48 lines per page. We will use 47 as our value, underestimating on the side of caution.

    Determining the number of characters that will fit on each line of the description field, before wrapping to a new line, is more of an estimate. A line that wraps to a new line must be counted as two lines. In this example, our description field is 400px wide, which allows approximately 58 characters before wrapping to the next line. To determine this number we look at the one wrapping line from all of the descriptions (the last line of the last description). We then count the number of characters on the line before it breaks, which is 58. Because the wrapping behavior depends on the length of the word at the end of the line, this number is only an estimate. Therefore, to be safe, we will use 55.

    function generatePrintList()
    {
        // Number of lines we want on a page.
        var MAX_LINES_ON_PAGE = 47;

        // Number of lines we have placed on current page.
        var iCurrentPageLineCount = 0;

    . . .

        // Loop through all source nodes and add them
        // to the table we are generating.
        while(oSrc = oVehicles.nextNode())
        {   
            // We are about to put a new row on the page.
            var iLinesInRow = CountLinesInRow(oSrc);
            iCurrentPageLineCount += iLinesInRow;

            // If the row we about to put on the page
            // will not fit, create a new page.
            if(iCurrentPageLineCount > MAX_LINES_ON_PAGE)
            {
                // Update the line count of the page we are about to add.
                iCurrentPageLineCount = iLinesInRow;

                // We are creating a new page and working with it,
                // so increment the index.
                iCurrentPageIndex += 1;

    . . .

    Now, let's implement the CountLinesInRow() function to accurately count the number of lines of text in each row added to the page. Paste the following code below the GeneratePrintList() function:

    function CountLinesInRow(oRow)
    {
        // Select the field that is always vertically bigger than the other
        // fields in the row.
        var oDescription = oRow.selectSingleNode("@description ");
        var sText = oDescription.text;

        // 'Description' is formatted so that line breaks are allowed
        // and auto-wrapping occurs approximately every 55 characters   
        var iCharsPerLine = 55;

        // Lines counted so far
        var iNumLines = 0;

        // Split the field at the line breaks
        var arrLines = sText.split("\n");

        // We know that each element created from this split is at
        // least one line (hence Math.ceil()) and every 45 characters
        // is an additional line.

        for(i = 0; i < arrLines.length; i++)
        {
            iNumLines += Math.ceil(arrLines[i].length / iCharsPerLine);
        }

        return iNumLines;
    }

    Try it:

    Preview your form as before and examine the print preview. You will notice that our code forces the Jaguar row to appear not on the first page but the second, preventing the row from splitting across pages.

    When performing your code tweaks, remember to err on the side of caution—it is better to overestimate the number of lines per row, and characters per line, than to underestimate. Underestimating will cause contents of the row to carry over onto the next page—exactly what you are trying to prevent.

Copyright © 2003-2019 Qdabra Software. All rights reserved.
View our Terms of Use.