Intranoggin

Blither, Blather, Web Content Management.
Blog » Creating a Members Acknowledgment Card

Creating a Members Acknowledgment Card

This is the topic I'm speaking about at COSPUG this month. 

It's a how to for creating a members acknowlegment card (greeting card, thank you card) application. The app is created entirely "out of the box", which is to say it's all javascript and custom xsl using only the browser and SharePoint Designer.  It will take you through creating the content type, to the custom list, custom data entry form, custom display form, and workflow.

Sorry about the enormous post. But after creating it, I'm not too lazy to back and break it apart.

WHERE IS IT?

Check out the My Local Broadband web site for the list template and setup instructions: Get It

First, create the content type.

I have 3 fields that are used for the user's message:

  • DisplayTo displays at the top of the card.
  • CardMessage is the body of the card.
  • Signed is the 'from' line displayed at the bottom of the card.

These fields are used for the card's mechanics:

  • SendTo – this will be the user that gets the email and will see the card in the 'my cards' in the gallery.
  • CardType – allows the user to choose between personal and public. Only public cards are displayed in the gallery

    • Note: personal cards are not security protected in any way more than public cards. They are only obscured
  • BackgroudImage – holds the name of the background image.

    • Note we will code it to assume this images exists in /siteimages/cardbackgrounds
  • Title – comes from the base type, but we'll put it to use, you'll see.

Now create a custom list.

  • In the list settings, turn on content types and add your new type
  • Remove the standard item type.
  • Disable attachments
  • Set it so users can only edit their own items
  • I also turn on version history.

Advanced settings

Now open your site in SharePoint Designer

Create the Custom Display Form

Start by copying the existing DispForm.aspx and naming it DispCardForm.aspx

I do it this way because the copy keeps the top & left nav from the master page in place.

To set our new form as the default display form, right click on the Acknowledgement list and go to properties

Then, in the supporting files tab, choose our dispcardform.aspx as the display item form.

Now, whenever anybody goes to the original DispForm.aspx, they will instead receive our DispCardForm.aspx.

Now open DispCardForm.aspx and delete the listform web part

Never mind that it says new item at the top it's only this way in designer. (I don't know, don't ask)

Insert a custom list form

Choose our current list, content type, and the display form. I don't want the toolbar on my display form, so I unchecked that box too.

<time warp>

I want the card display to look pretty simple so I:

  • Wipe out the close buttons.
  • Move DisplayTo, Message, and Signed into a formatted table.
  • Move CardType below said table.
  • Deck it all out with style sheets (classes went at the top, just inside the content tag)
  • Use the BackgroundImage field to set a background image in the table.

And you end up with code like this:

<style type="text/css">

        .Card_TableWrapper{

            width:700px;

            height:450px;

            background-repeat:no-repeat;

            margin-bottom:15px;

        }

        .Card_Table{

            border:4px gray inset;

            width:700px;

            height:450px;

            background-repeat:no-repeat;

            margin-bottom:15px;

            background-color:white;

        }

        .Card_TopSpacer{

            height:300px;

            width:100%;   

        }

     .Card_To{

            width:100%;

            font-family:Arial, Helvetica, sans-serif;

            font-size:25px;

            vertical-align:top;

            padding-left:5px;

        }

        .Card_Message{

            vertical-align:top;

            height:125px;

            padding:0 5px 0 5px;

        }

        .Card_Signed{

            vertical-align:top;

            text-align:right;

            font-family:Arial, Helvetica, sans-serif;

            font-size:20px;

            padding:5px 5px 0 5px;   

        }

        .Card_Details{

            font-family:Arial, Helvetica, sans-serif;

            font-size:10px;

        }

</style>

    <xsl:template name="dvt_1.rowview">

        <tr>

            <td>

            <table cellpadding="0" cellspacing="0" class="Card_TableWrapper">

                <tr>

                    <td>

                        <table cellpadding="0" cellspacing="0" background="/SiteImages/CardBackgrounds/{@BackgroundImage}" class="Card_Table">

                            <tr>

                                <td class="Card_TopSpacer">

                                </td>

                            </tr>

                            <tr>

                                <td class="Card_To">

                                    <xsl:value-of select="@DisplayTo"/>

                                </td>

                            </tr>

                            <tr>

                                <td class="Card_Message">

                                    <xsl:value-of disable-output-escaping="yes" select="@CardMessage"/>

                                </td>

                            </tr>

                            <tr>

                                <td class="Card_Signed">

                                    Signed:<xsl:value-of select="@Signed"/>

                                </td>

                            </tr>

                        </table>

                    </td>

                </tr>

                <tr>

                    <td class="ms-toolbar" nowrap="">

                        <table>

                            <tr>

                                <td class="ms-descriptiontext" nowrap="">

                                    <xsl:value-of select="@CardType"/> acknowledgement of <xsl:value-of select="@SendTo" disable-output-escaping="yes"/><br/>

                                    <SharePoint:CreatedModifiedInfo ControlMode="Display" runat="server"/>

                                </td>

                            </tr>

                        </table>

                    </td>

                </tr>

            </table>

            </td>

        </tr>

    </xsl:template>

Now we Create the New Entry Form

Start just like the DispCardForm.aspx above, namely

  • Create a copy of NewForm.aspx and rename it NewCardForm.aspx
  • Set it as the new item form in the list properties
  • Open the form and delete the main web part
  • Add in a custom list form with these properties

Now, here's the general idea behind the next changes. I want to give the user a data entry experience that is a rough estimate of the card display. (show entry over the image). In order to do that, I break up the fields into 3 sections.

The top section holds the SendTo and CardType fields. These must be set by the user, but are not shown in the card on the display page.

The middle section holds the 3 fields that go into the card on the display page (DisplayTo, CardMessage, & Signed).

The bottom section holds the fields for title and BackgroundImage. I want to hide these and set their values via query string parameters. But if the parameters are not present in the query string, I want to default their values, but show them in case the user wants to override them.

Sorry about the <time warp>. After rearranging the parts and adding in the styles I've got this in the code:

(just inside placeholder main)

<style type="text/css">

.Card_Table {

    width: 700px;

    height: 450px;

    background-repeat: no-repeat;

    margin-bottom: 15px;

}

.Card_input {

    width: 100%;

    color: #999999;

    font-family: verdana;

    font-size: .7em;

    font-weight: bold;

}

.Card_TopSpacer{

    height:215px;

    width:100%;   

}

.Card_To {

    width: 100%;

    vertical-align: top;

    text-align: left;

    padding: 5px 5px 0 5px;

}

.Card_Message {

    vertical-align: top;

    height: 125px;

    padding: 0 5px 5px 5px;

}

.Card_Signed {

    vertical-align: top;

    text-align: right;

    padding: 5px 5px 0 5px;

}

.Card_Hide{

    visibility:hidden;

    display:none;

}

</style>

(and my row template)

    <xsl:template name="dvt_1.rowedit">

        <xsl:param name="Pos" />

        <tr>

            <td>

                <table border="0" cellspacing="0" width="100%">

                    <tr>

                        <td width="190px" valign="top" class="Card_input">

                                <nobr>Send To</nobr>

                        </td>

                        <td width="400px" valign="top" class="ms-formbody">

                            <SharePoint:FormField runat="server" id="ff2{$Pos}" ControlMode="New" FieldName="SendTo" __designer:bind="{ddwrt:DataBind('i',concat('ff2',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@SendTo')}"/>

                            <SharePoint:FieldDescription runat="server" id="ff2description{$Pos}" FieldName="SendTo" ControlMode="New"/>

                        </td>

                    </tr>

                    <tr>

                        <td width="190px" valign="top" class="Card_input">

                                <nobr>Card Type</nobr>

                        </td>

                        <td width="400px" valign="top" class="ms-formbody">

                            <SharePoint:FormField runat="server" id="ff5{$Pos}" ControlMode="New" FieldName="CardType" __designer:bind="{ddwrt:DataBind('i',concat('ff5',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@CardType')}"/>

                            <SharePoint:FieldDescription runat="server" id="ff5description{$Pos}" FieldName="CardType" ControlMode="New"/>

                        </td>

                    </tr>

                </table>

                <table id="Card_EntryTable" cellpadding="0" cellspacing="0" class="Card_Table">                        <tr>

                        <td class="Card_TopSpacer">

                        </td>

                    </tr>

                    <tr>

                        <td colspan="2" class="Card_To">

                            <div class="Card_input">

                                <nobr>To: <span class="ms-formvalidation"> *</span><SharePoint:FormField runat="server" id="ff3{$Pos}" ControlMode="New" FieldName="DisplayTo" __designer:bind="{ddwrt:DataBind('i',concat('ff3',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@DisplayTo')}"/>

                                </nobr>

                            </div>

                        </td>

                    </tr>

                    <tr>

                        <td colspan="2" class="Card_Message">

                            <div class="Card_input">

                                Message<span class="ms-formvalidation"> *</span><br/>

                                <SharePoint:FormField runat="server" id="ff4{$Pos}" ControlMode="New" FieldName="CardMessage" __designer:bind="{ddwrt:DataBind('i',concat('ff4',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@CardMessage')}"/>

                            </div>

                        </td>

                    </tr>

                    <tr>

                        <td colspan="2" class="Card_Signed">                       

                            <div class="Card_input">

                                <nobr>Signed<span class="ms-formvalidation"> *</span>

                                <SharePoint:FormField runat="server" id="ff6{$Pos}" ControlMode="New" FieldName="Signed" __designer:bind="{ddwrt:DataBind('i',concat('ff6',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Signed')}"/>

                                </nobr>

                            </div>

                        </td>

                    </tr>

                </table>

                <table border="0" cellspacing="0" width="590px" id="Card_ExtraInfoTable" class="Card_Hide">

                    <tr>

                        <td width="190px" valign="top" class="ms-formlabel">

                                <nobr>Title<span class="ms-formvalidation"> *</span></nobr>

                        </td>

                        <td width="400px" valign="top" class="ms-formbody">

                            <SharePoint:FormField runat="server" id="ff1{$Pos}" ControlMode="New" FieldName="Title" __designer:bind="{ddwrt:DataBind('i',concat('ff1',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}"/>

                            <SharePoint:FieldDescription runat="server" id="ff1description{$Pos}" FieldName="Title" ControlMode="New"/>

                        </td>

                    </tr>

                    <tr>

                        <td width="190px" valign="top" class="ms-formlabel">

                                <nobr>Background Image</nobr>

                        </td>

                        <td width="400px" valign="top" class="ms-formbody">

                            <SharePoint:FormField runat="server" id="ff7{$Pos}" ControlMode="New" FieldName="BackgroundImage" __designer:bind="{ddwrt:DataBind('i',concat('ff7',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@BackgroundImage')}"/>

                            <SharePoint:FieldDescription runat="server" id="ff7description{$Pos}" FieldName="BackgroundImage" ControlMode="New"/>

                        </td>

                    </tr>

                    <xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">

                        <tr>

                            <td colspan="99" class="ms-vb">

                                <span ddwrt:amkeyfield="ID" ddwrt:amkeyvalue="ddwrt:EscapeDelims(string(@ID))" ddwrt:ammode="view"></span>

                            </td>

                        </tr>

                    </xsl:if>

                </table>

            </td>

        </tr>       

    </xsl:template>

If you dig into that, you'll notice two peculiarities (well two that I can explain anyway)

  • There is a table with id="Card_ExtraInfoTable" class="Card_Hide". Checking the style sheet, you'll see this is not displayed to the user.
  • There is no background image displayed anywhere.

Both of these lead me to a nice chunk of Javascript:

<script type="text/javascript">

    _spBodyOnLoadFunctionNames.push("fillDefaultValues");

    function fillDefaultValues()

    {   

        var qs = location.search.substring(1, location.search.length);   

        var args = qs.split("&");   

        var vals = new Object();   

        for (var i=0; i < args.length; i++)

        {   

            var nameVal = args[i].split("=");   

            var temp = unescape(nameVal[1]).split('+');   

            nameVal[1] = temp.join(' ');

            nameVal[0]=nameVal[0].toLowerCase();   

            vals[nameVal[0]] = nameVal[1];   

        }    

        if (vals["cardimage"] == undefined)

        {

            vals["cardimage"]="GreatWork";

            ShowExtraInfo();

        }

        setTextFromFieldName("Title", vals["cardimage"]);   

        setTextFromFieldName("BackgroundImage", vals["cardimage"]+ ".jpg");

        SetCardBackground(vals["cardimage"]);

    }

    function getTagFromIdentifierAndTitle(tagName, identifier, title)

    {   

        var len = identifier.length;   

        var tags = document.getElementsByTagName(tagName);   

        for (var i=0; i < tags.length; i++)

        {   

            var tempString = tags[i].id;   

            if (tags[i].title == title && (identifier == "" || tempString.indexOf(identifier) == tempString.length - len))

            {           

                return tags[i];   

            }   

        }

        return null;

    }

    function setTextFromFieldName(fieldName, value)

    {       

         var theInput = getTagFromIdentifierAndTitle("input","TextField",fieldName);   

        theInput.value=value;

    }

    function SetCardBackground(value)

    {

        var tab = document.getElementById("Card_EntryTable");

        tab.background = "/SiteImages/CardBackgrounds/" + value + ".jpg";

    }

    function ShowExtraInfo()

    {

        var tab = document.getElementById("Card_ExtraInfoTable");

        tab.className = "";

    }

</script>

This is clients side javascript, but it uses a bit of SharePoint magic (_spBodyOnLoadFunctionNames.push("fillDefaultValues");) to register the fillDefaultValues function to fire on body load. You can Google that line and see that I've taken a fairly widely used example and made a few tweaks to it.

The general description of what's going on here is that the function, fillDefaultValues, grabs the query string and creates an array of key/value pairs of any parameters it finds there.

If it does not find a parameter called 'cardimage', it inserts one with a value of 'GreatWork' AND it also locates the table with ID= Card_ExtraInfoTable and removes the style that is hiding it by default.

It then goes on to set the title field to the value of the cardimage parameter and the BackgroundImage field equal to the cardimage parameter +".jpg" . The upshot here is that the user can override these values if they are creating a card via the 'new' toolbar button, but not if they were brought to this page via a link that passed the proper parameter.

And the final action is to locate the table with id= Card_EntryTable and set its background property to

/siteimages/cardbackgrounds/<cardimage parameter>.jpg

And what all that means is that to make this work, you need to

Create a picture library called SiteImages

(technically this could also be a plain old document library)

In the library, create a folder called CardBackgrounds.

Put your .jpg background images in that folder. If you are using the styles as I've defined them here, your images should be 700x450 with a fade to white happening around 300 pixels from the top.

NOTE: if you don't have an image called GreatJob.jpg, make sure to adjust your default value in the javascript.

Now we can create the cards, but they need to be sent, let's create a workflow.

In Designer, go to new>workflow and name it Send Card.

In actions, choose "Send an Email"

Click the email link to begin setting up the email.

For the to, click the address book and then insert a workflow lookup of the current item and pick the sendto value:

For subject, just type in some text such as "You've Been Sent a Card".

Fill in the body as desired, using the Add Lookup to Body button to fill in fields from the current card. My final email looks like this:

Gotcha/Trick Warning: When you set up your SendTo site column, if you choose to allow multiple users, you cannot use that column now in the workflow in the email To lookup. However, if you change the site column to only allow a single user, then set up this workflow email, and NOW go back and change it to allow multiple users, the workflow email will continue to work and WILL send to all the chosen users.

I also like to email the link to the person that created the card. That email looks like this:

And so your final workflow looks like this just before you click finished:

So, what's left? Create the public Gallery

Create a new view called Gallery and make it the default view. Just leave title as the sole field to be displayed.

Open Gallery.aspx in Designer.

Close, but don't delete the web part. You can do this in the browser or in designer. In designer, it looks like this:

Now, below that, insert an Acknowledgements dataview web part. Configure it to:

  • Filter – show only Cardtype=Public
  • Sort – by id descending
  • Paging – sets of 10
  • Fields – BackgroundImage, DisplayTo, Signed

In the design view of the table, select the row with the field headers and delete it. Then pick the data and go to code view. Arrange the display in code view to look like this:

<td class="ms-vb">

                <table>

                    <tr>

                        <td>

                            <a href="dispform.aspx?id={@ID}"><img src="/siteimages/cardbackgrounds/{@BackgroundImage}" style="width:60px;"/></a>

                        </td>

                        <td class="ms-vb">

                            To: <xsl:value-of select="@DisplayTo" /><br/>

                            Signed: <xsl:value-of select="@Signed" /><br/>

                            <a href="dispform.aspx?id={@ID}">View</a>

                        </td>

                    </tr>

                </table>

                </td>

Its output will then look like this:

Go into the web part's properties and rename it Public Gallery and turn on its title bar.

To the right of the public gallery, I want to display all the cards I've sent, and all the cards that were sent to me. To do this I'll need to

Add another Web Part Zone

Above the main web part zone, insert a new table, 1row x 2 cells. The table width should be 100% and each column 50%.

Now select the main web part zone and drag it into the left cell. Select the public gallery web part and copy it. Then paste two copies in the right cell. Rename the right two web parts "Cards I've Sent" and "Sent To Me".

Adjust the filter so that Cards I Sent shows cards created by the current user

Then Adjust the filter on Sent To Me so that it only shows items where SendTo equals the current user.

NOW Create the Creation Gallery

I wanted to create a nice, dynamic web part that showed thumbnails from the picture gallery, but I ran out of time. (And it required a couple teaks which meant rewriting and new screen captures). So I hardcoded in a row at the top of the gallery.aspx page with a couple thumbnails. All it needs to do is hyperlink to the newcardform.aspx page and pass the cardimage parameter.

<tr>

            <td width="50%" valign="top" class="ms-vb">

                <a href="newform.aspx?cardimage=greatwork">

                Great Work!<br/>

                <img src="/siteimages/cardbackgrounds/greatwork.jpg" width="60px"/><br/>

                Click to Create</a>

            </td>

            <td width="50%" valign="top" class="ms-vb">   

                <a href="newform.aspx?cardimage=special_thanks">   

                Special Thanks<br/>   

                <img src="/siteimages/cardbackgrounds/special_thanks.jpg" width="60px"/><br/>

                Click to Create</a>

            </td>

        </tr>

Sooo, what would I do different the next time around?

Instead of just passing in the name of the image and assuming it's a .jpg at a specific location, I'd set it up to pass the full path to the image. That would make it easier to create a dynamic web part that sends the parameter to the new card form.

I'd like to create a postcard version with the image in the left side and message in the right side.

I've also been thinking about giving the option to enter an external email address in addition to choosing from the members list.

Instead of the images residing external to the list, it would be cool if they could be attached to the new card and then displayed to the side of the message. This would be a nice postcard type of application.


Posted: 4/29/2008 8:19:00 AM by Ryan Miller | with 0 comments
Filed under: SharePoint