We are going to disappear for a few days – Happy New Year!

​It’s the final week of the year and I told Natasha to "step away from the keyboard". We’re going to disappear for a few days to catch our breath and be able to come back full steam ahead in the new year. That means little to no email, tweets, Facebook or content updates. 

 We are busy laying out the editorial schedule and the gameplan for the first quarter of 2011 while tweaking the NothingButSharePoint.com site. There’s also a lot going on that we are participating in during  the first three months of 2011:

With that, I say have a good one. I hope you stay safe. I look forward to seeing you back here, ready to go on January 3rd.

Best regards,
Mark

Mark Miller
Founder and Editor, EndUserSharePoint.com
Chief Community Officer and SharePoint Evangelist, Global 360
Founding Member, NothingButSharePoint.com

SharePoint Plus 2010

 

Sorry guys but this one is not the announcement of a new SP template. SharePoint Plus 2010 is actually something different.

SharePoint Plus 2010 refers to the positive effect that the SharePoint Community had on me this year.

Let me mention some examples:

  1. I had the chance to make a friend, Agnes Molnar, while chatting about… that’s our secret.
  2. I moved from Slovenia to the US (NY).  I had my first SP related meeting with Mark Miller and my second one with Mark Miller and Daniel McPherson. Getting the chance to chat with them was a great experience!
  3. I learned some techniques on traveling light from the masters: Joel Oleson and Michael Noel.
  4. I learned about humility, kindness and respect when I travelled with the speakers of the first SharePoint Conference in Peru: Agnes Molnar, Joel Oleson, Michael Noel, Toni Frankola.

Would you like to share, with us, some of your SharePoint Plus 2010 highlights?

p.s. Happy holidays to all of you!

Customizing the HTML code of a Content Query Web Part

Actually – I had to bemore than just friends with the CQWP – I had to master it and force it to do my bidding when it came to customizing the HTML output. As I did some research on how to do what I wanted it to do, I kept an ever-growing file of notes on the steps I was taking. It ended up being an insanely long blog draft and I decided to throw it out there to see if more experienced SharePoint superstars could give me some tips on how I could have done this more easily or maybe to help other newbie SharePoint developers..

Some facts and disclaimers:

  • I’m working with a SharePoint 2010 Publishing site and using SharePoint Designer 2010 for modifying some of the site files.
  • I’m not going to go into CSS in this article, so I won’t talk about how I style the web part. This article is really just about getting the HTML I want to be generated from a customized Content Query Web Part.
  • At this point, I’m literally just under three weeks into learning about SharePoint branding, customizing master pages, working with XSL, etc. I have a lot to learn.

Here is the challenge that I had:

First, here’s a screenshot of my custom query web part:

2010-12-12-CustomizingHTMLCodeCQWP-01.png

  • The CQWP pulls from a list of publishing pages and displays the title, a description, an image, and links to the page.
  • Of the three dummy data items there, #1 only has a title and link, #2 has a title, description, image, and link, and #3 only has the title, description, and link.
  • The CQWP is set up to use the “image on left” Item Style.

If you view the HTML source, there is a TON of HTML code that goes into the above; there are several nested tables and stuff for allowing you to click on the header to edit it. Here is just a small sampling…

<table class="s4-wpTopTable" border="0" cellpadding="0" cellspacing="0" width="100%">
	<tr>
		<td><table border="0" cellpadding="0" cellspacing="0" width="100%">
			<tr class="ms-WPHeader">

				<td align="left" class="ms-wpTdSpace"> </td>
				<td title="Recent News - Displays a dynamic view of content from your site." id="WebPartTitleWPQ2" class="ms-WPHeaderTd"><h3 style="text-align:justify;" class="ms-standardheader ms-WPTitle"><nobr><span>Recent News</span><span id="WebPartCaptionWPQ2"></span></nobr></h3></td>

The actual data part – the list of news items – has HTML code that looks like this:

<ul class="dfwp-column dfwp-list" style="width:100%" >
<li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">New SPDevWiki site released!</a><div class="description"></div></div></div></li>
<li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">SharePoint 2010 SDK released</a><div class="description">SharePoint 2010 SDK released for RTM</div></div></div></li>
<li class="dfwp-item"><div class="item"><div class="link-item"><a href="#" title="">Announcing Community Kit for SharePoint</a><div class="description">CodePlex projects teams get together to release CKS:Dev!</div></div></div></li>
</ul>

There are plenty of classes to work with… but if I were to modify the .dfwp-xxx classes, those would affect all of the web parts using that type of display, and in my specific situation, I couldn’t throw the web part into my own custom <div> with an ID or class that would allow me to target only that web part.

But I’m getting ahead of myself. Let me show you what I was shooting for. Here’s my HTML that I would ideally want:

<div class="blogfeed-wrapper">
	<div class="blogfeed">
		<h2>Recent News</h2>
		<ul>
			<li>
				<p class="date">Jun 5, 2010</p>
				<p class="image"><img src="images/pic.png" alt="image" width="50" height="50" /></p>
				<h3><a href="#">Article title</a></h3>
				<p class="blurb">Article blurb</p>
			</li>
			<!-- repeat LI for each data item -->
		</ul>
	</div>
</div>

To accomplish this, I would have to:

  1. Figure out how to wrap the DIVs and UL around the repeated list items, along with the H2 heading
  2. Modify the code for the repeated list items to use the HTML code I want (and to show the additional “article date” field)

I looked around for information about customizing the Content Query Web Part and found this article, “Display Content Query Web Part Results in a Grid/Table” by Paul Galvin which seemed like it might do the trick. He references Heather Solomon’s blog post, “Customizing the Content Query Web Part and Custom Item Styles.” Here are the step-by-steps of what I did:

  1. I followed Heather Solomon’s article, steps 5-11, and Paul Galvin’s step 4 to create a test item style in ItemStyle.xsl in order to list out the data already being pulled into the web part. I named my custom style “TestList” – I’ll keep it around for testing other web parts.
    1. In SharePoint Designer 2010, open Style Library/XSL Style Sheets/ItemStyle.xsl. Note that this file is only available in a root or top-level SharePoint site, not in subsites.
    2. Click the “Edit” link (in the “Customization” section).
    3. In ItemStyle.xsl, you’ll see a few <xsl:variable /> tags at the top, followed by sections of <xsl:template /> code. Each <xsl:template /> has a name associated with it which corresponds to the Item Style dropdown in the Web Part editor.
    4. Here’s the code I added – just to keep it easy to find, I put it under the “Default” template section. Also notice that the value for name and @Style must be iden
    5. tical.
    6. <
      xsl:template name="Default" match="*" mode="itemstyle">
      ....
      </xsl:template>
      <!-- custom item style template for listing out fields -->
      <xsl:template name="TestList" match="Row[@Style='TestList']" mode="itemstyle">
      	<xsl:for-each select="@*">
      		<xsl:value-of select="name()" /><br />
      	</xsl:for-each>
      </xsl:template>
      
    7. Back in the browser, click to edit the web part. In the web part editor, under Presentation > Styles, pick the new “TestList” from the list of Item Styles:

      2010-12-12-CustomizingHTMLCodeCQWP-02.png

    8. Here’s what I ended up with:

      2010-12-12-CustomizingHTMLCodeCQWP-03.png

    9. If you want, copy and paste the list into a text editor for reference just in case.
  2. Now, following steps 12-13 in Heather’s article, I went to find the specific column “Internal Names” for fields that I wanted to add to the web part.
    1. Go to Site Settings > Site Administration: Site Libraries and Lists. This page shows a list of the various types of lists and libraries in your site.
      2010-12-12-CustomizingHTMLCodeCQWP-04.png
    2. I picked the “Pages” list because that’s what my Content Query Web Part is pulling, but you’ll want to go to whichever list you’re trying to pull data from. This takes you to the List Information page.
    3. Scroll down to the Columns section to view all the columns that are available for that type of content.
      2010-12-12-CustomizingHTMLCodeCQWP-05.png
    4. Hover or click on the link for the columns that you want to add to the Content Query Web Part results. I’m primarily interested in the “Article Date” column. At the very end of the link is the “Field” parameter, followed by the internal column name. In my case, the Article Date column’s internal name is ArticleStartDate.
      2010-12-12-CustomizingHTMLCodeCQWP-05a.png
  3. Following steps 16-18 in Heather’s article, I export the CQWP, modify the code to include the extra column(s), then import it back in as a new web part.
    1. Back on the web site, expand the edit menu and choose “Export.”
    2. 2010-12-12-CustomizingHTMLCodeCQWP-06.png
    3. Save the .WEBPART file to your computer, then open it in a text editor.
    4. Look for “Co
    5. mmonViewFields” and you’ll see something like this:
    6. <property name="CommonViewFields" type="string" />
      
    7. Following Heather’s instructions, I modify the tag to include my additional ArticleStartDate column.
      <property name="CommonViewFields" type="string">ArticleStartDate, DateTime</property>
      
      Note: From this MSDN article, “How to Display Custom Fields in a Content by Query Web Part,” there’s an additional step (Step 4) to map the new column to existing/available fields in the web part. This only works if your custom field can replace the Title, Description, LinkURL, or ImageURL web part fields – which could save you a few steps if, say, you wanted to show a different text column in place of the “description” field. In this case, though, since I want to display the article date, I’m going to have to do some actual customization to the XSL files, described below.
    8. Save the file. Back in the browser, click the “Edit” button to edit the page so that you can add more web parts to the page.
      2010-12-12-CustomizingHTMLCodeCQWP-07.png
    9. Click “Add a Web Part”, then click “Upload a Web Part” and browse to your .WEBPART file, then click Upload.
      2010-12-12-CustomizingHTMLCodeCQWP-08.png
    10. You’ll have to click “Add a Web Part” again, but this time you’ll see an additional folder for “Imported Web Parts” with your new web part listed. Add this to your page. (Mine is called “Recent News” because it exported my original Content Query Web Part, which had “Recent News” as the title.)
      2010-12-12-CustomizingHTMLCodeCQWP-09.png
    11. The new web part will show up on the page. Notice that the new column is included with the other data:
      2010-12-12-CustomizingHTMLCodeCQWP-10.png
    12. Delete the original web part just to get it off the page.
  4. Now, I wanted to figure out how to modify the HTML to display what I wanted: Some outer wrapper DIVs for styling, an H2 heading for the “Recent News” title, a wrapper UL for the list items, and then using a simple LI for each list item. Bear with me as I explain the overall approach:
    1. First – I’m going to get rid of the existing “Recent News” heading by changing the Chrome of the web part to hide the current title and ONLY show the list itself. This will allow me to get rid of the funky tables and code that are really difficult to style. (I’m going to be inserting my own heading in the HTML instead.)
    2. Then – I’m going to do the “easy” part, which is to edit ItemStyle.xsl with my HTML for each list item (the <LI> and everything in between from the top of this page in my “ideal” HTML code). This will allow me to get rid of the various DIVs from the default “Image on Left” code, remove displayed fields that I don’t need, and add in the additional fields that I want to display (in this example, ArticleStartDate). Both Paul and Heather’s articles cover this part. Note: As mentioned in Heather’s article, ItemStyle.xsl “repeats” itself for each data item, which is why I would only put in the <LI> code. If I added <UL>, I would get a separate UL list for each item.
    3. Finally – the “hard” part, which is to edit ContentQueryMain.xsl to put in my wrapper DIVs, H2 heading, and UL, which will go around the stuff being generated in ItemStyle.xsl and make everything look pretty. This is where I turned to Paul’s article.
  5. Here we go! Starting with 4a: Hide the web part title bar by editing the web part and changing the Chrome Type to “None.”
    2010-12-12-CustomizingHTMLCodeCQWP-11.png

    Note: If you were to save/close or check in the page, you’ll just see the list of data fields without the title bar.
    2010-12-12-CustomizingHTMLCodeCQWP-12.png

    If you’re like me, you may freak out initially: “What have I done?! Now I can’t edit the web part!” No worries – as soon as you click “Edit page,” you’ll see your web part zones and the title bar will reappear for editing.
  6. Pulling from Heather’s article (steps 19-24), I open ItemStyle.xsl (again) in SharePoint Designer for editing.
    1. At this point, I already have an idea of the Item Style that I want to start from – the “Image on Left” style, because it already will contain the code I need for the image, title, and description, so all I need to do is move things around and add the date. You will need to figure out which Item Style you want to start with and find the corresponding code.
    2. In this case, “Image on Left” happens to be the same as the “default” template, so I find the 60 or so lines of code that make up the Default template, copy, and paste it under my TestList
    3. template.
    4. <xsl:template name="Default" match="*" mode="itemstyle">
      ....
      </xsl:template>
      
    5. My first step is to replace <xsl:template name=”Default” match=”*” mode=”itemstyle”> with something that looks more like my TestList template line, but with yet another custom Item Style name. I’ll call mine “News”:
      <xsl:template name="News" match="Row[@Style='News']" mode="itemstyle">
      
      In other words, replace name=”YourCustomStyleName” and match=”Row[@Style=’YourCustomStyleName‘]” in your <xsl:template> tag.
    6. Now, eac
    7. h xsl:template section has some variables or parameters at the top, followed by recognizable HTML code mixed in with various xsl tags for testing conditionals or displaying data. I copy the “ideal HTML” that I want to have and paste it into the top of that section to start me off.
      <li>
      	<p class="date">Jun 5, 2010</p>
      	<p class="image"><img src="images/pic.png" alt="image" width="50" height="50" /></p>
      	<h3><a href="#">Article title</a></h3>
      	<p class="blurb">Article text</p>
      </li>
      
      
    8. Then, start moving the relevant XSL code into your HTML tags.You can see what I did below. I had to add the ArticleStartDate field (see Step 20 in Heather’s blog) and formatted it by following the steps at Josh Gaffey’s blog for Custom Date Formats in SharePoint so that the date is displayed as m/d/yyyy. (Note that I put stuff I don’t really care about… or don’t understand what they do… in a comment tag to hide them.)
      <!-- custom item style template for recent news -->
      <xsl:template name="News" match="Row[@Style='News']" mode="itemstyle">
          <xsl:variable name="SafeLinkUrl">
              <xsl:call-template name="OuterTemplate.GetSafeLink">
                  <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
              </xsl:call-template>
          </xsl:variable>
          <xsl:variable name="SafeImageUrl">
              <xsl:call-template name="OuterTemplate.GetSafeStaticUrl">
                  <xsl:with-param name="UrlColumnName" select="'ImageUrl'"/>
              </xsl:call-template>
          </xsl:variable>
          <xsl:variable name="DisplayTitle">
              <xsl:call-template name="OuterTemplate.GetTitle">
                  <xsl:with-param name="Title" select="@Title"/>
                  <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
              </xsl:call-template>
          </xsl:variable>
      
      	<li>
      		<!-- Here is where I add in my ArticleStartDate field. It would typically look like
      		<xsl:value-of select="@ArticleStartDate" />
      		but the extra stuff is to format the date. -->
      		<p class="date"><xsl:value-of select="ddwrt:FormatDate(@ArticleStartDate,1033,1)" /></p>
      
      		<xsl:if test="string-length($SafeImageUrl) != 0">
                  <p class="image">
                      <a href="{$SafeLinkUrl}">
                        <xsl:if test="$ItemsHaveStreams = 'True'">
                          <xsl:attribute name="onclick">
                            <xsl:value-of select="@OnClickForWebRendering"/>
                          </xsl:attribute>
                        </xsl:if>
                        <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'">
                          <xsl:attribute name="onclick">
                            <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/>
                          </xsl:attribute>
                        </xsl:if>
                        <img class="image" src="{$SafeImageUrl}" title="{@ImageUrlAltText}">
                          <xsl:if test="$ImageWidth != ''">
                            <xsl:attribute name="width">
                              <xsl:value-of select="$ImageWidth" />
                            </xsl:attribute>
                          </xsl:if>
                          <xsl:if test="$ImageHeight != ''">
                            <xsl:attribute name="height">
                              <xsl:value-of select="$ImageHeight" />
                            </xsl:attribute>
                          </xsl:if>
                        </img>
                      </a>
                  </p>
              </xsl:if>
      
      		<h3><a href="{$SafeLinkUrl}" title="{@LinkToolTip}"><xsl:value-of select="$DisplayTitle"/></a></h3>
      		<p class="blurb"><xsl:value-of select="@Description" /></p>
      
      		<!--
      		<xsl:call-template name="OuterTemplate.CallPresenceStatusIconTemplate"/>
                  <a href="{$SafeLinkUrl}" title="{@LinkToolTip}">
                    <xsl:if test="$ItemsHaveStreams = 'True'">
                      <xsl:attribute name="onclick">
                        <xsl:value-of select="@OnClickForWebRendering"/>
                      </xsl:attribute>
                    </xsl:if>
                    <xsl:if test="$ItemsHaveStreams != 'True' and @OpenInNewWindow = 'True'">
                      <xsl:attribute name="onclick">
                        <xsl:value-of disable-output-escaping="yes" select="$OnClickTargetAttribute"/>
                      </xsl:attribute>
                    </xsl:if>
      
                  </a>
                  -->
      	</li>
      </xsl:template>
      
    9. When you’re done, go back into the browser to edit the web part. You should see your new custom item style in the Item Style select box. In the screenshot below, you can see that “News” (the name of my custom Item Style) showed up properly, so I was able to pick it and apply it. Of course, I’m still missing the wrapper HTML code (for the UL, etc.) so I moved on to the next step…
      2010-12-12-CustomizingHTMLCodeCQWP-13.png
  7. Now I want to get the wrapper HTML code (below) around the LIs.
    <div class="blogfeed-wrapper">
    	<div class="blogfeed">
    		<h2>Recent News</h2>
    		<ul>
    		...
    		</ul>
    	</div>
    </div>
    
    At first glance, Paul’s article seems perfect for what I need to do. His method is to edit ContentQueryMain.xsl to pass in parameters that identify the “current position” and “last row” so that ItemStyle.xsl can put in special code at the start of the list (when current position = 0) and the end of the list (current position = last row). Unfortunately, after following his detailed and clear instructions, I found that it didn’t work for me. I’m not sure if it’s because of a difference in SharePoint 2007 and 2010, but basically what was going on was that the .dfwp-xxxx UL and LIs were still being wrapped around my code. So while Paul’s technique “worked” in the sense that the wrapper code showed up before the first item and after the last item, the other stuff was getting in the way and creating havoc with the code:
    <ul class="dfwp-column dfwp-list" style="width:100%" >
    <li class="dfwp-item">
    	<div class="blogfeed-wrapper">
    		<div class="blogfeed">
    			<h2>Recent News</h2>
    			<ul>
    			<li>...</li>
    </li>
    <li class="dfwp-item">
    			<li>...</li>
    </li>
    <li class="dfwp-item">
    			<li>...</li>
    			</ul>
    		</div>
    	</div>
    </li>
    </ul>
    
    Diving deep into the ContentQueryMain.xsl file, I was able to locate various variable definitions that hold the HTML code for the UL and LI tags. There were some variables near the top of the file and more inside the OuterTemplate.Body template.
    <xsl:variable name="BeginList" select="string('<ul class="dfwp-list">')" />
    <xsl:variable name="EndList" select="string('</ul>')" />
    <xsl:variable name="BeginListItem" select="string('<li class="dfwp-item">')" />
    <xsl:variable name="EndListItem" select="string('</li>')" />
    
    <xsl:template name="OuterTemplate.Body">
    	...
    	<xsl:variable name="BeginColumn1" select="string('<ul class="dfwp-column dfwp-list" style="width:')" />
    	<xsl:variable name="BeginColumn2" select="string('%" >')" />
    	<xsl:variable name="BeginColumn" select="concat($BeginColumn1, $cbq_columnwidth, $BeginColumn2)" />
    	<xsl:variable name="EndColumn" select="string('</ul>')
    " />
    
    If you do a quick search in the code for $BeginList, $EndList, etc., you’ll see that those variables are printed out several times in different places. Not being at all an expert, though, it was pretty hard for me to figure out what was going on, so I resorted to an elegant solution to find the places to target. And by “elegant,” I mean “amateurish and hackish.” If you aren’t too embarrassed to follow my example, here’s what you do…
  8. Go through all of the code, looked for all the instances where those variables are printed out, and add test text before them that will print out in the HTML and show which conditionals were being fulfilled. Here’s one example:
    TEST1
    <xsl:value of disable-output-escaping="yes" select="$BeginColumn" />
    

    You’ll want to repeat that for each time you see an <xsl:value /> tag for $BeginList, $EndList, $BeginListItem, $EndListItem, $BeginColumn, and $EndColumn.

    When I saved and refreshed my browser, this is what I saw:

    2010-12-12-CustomizingHTMLCodeCQWP-14.png

  9. Now you can go through the code, find the places where your test code was added, and replace them with your custom code. Below are the steps that I took, but you’ll need to figure out what will work for you.
    1. My first step was to set up my own custom variables for the wrapper. Inside the OuterTemplate.Body, under the existing variable definitions for BeginColumn1, BeginColumn2, etc., I added this custom code below. Since I didn’t feel like dealing with lots of &lt; and &gt;, I put my HTML code inside a CDATA section:
      <!-- my custom variables for the news section -->
      <xsl:variable name="newsStart">
      	<![CDATA[
      		<div class="blogfeed-wrapper">
      			<div class="blogfeed">
      				<h2>Recent News</h2>
      				<ul>
      
      	]]>
      </xsl:variable>
      <xsl:variable name="newsEnd">
      	<![CDATA[
      				</ul>
      			</div>
      		</div>
      
      	]]>
      </xsl:variable>
      
    2. Okay, NOW I’m ready to replace my “TEST#” stuff with actual useful code. I looked for TEST1 (start of list), TEST9 (start of list item), TEST10 (end of list item), and TEST6 (end of list) in the ContentQueryMain.xsl file. I added <xsl:choose /> and <xsl:when /> statements to put in my custom variables when the Item Style = “News” (the name of my custom item style).Here is the original code (with the first TEST1 stuck in there):
      <xsl:choose>
          <xsl:when test="$cbq_isgrouping != 'True'">
              <xsl:if test="$CurPosition = $FirstRow">
      		TEST1
      		<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
              </xsl:if>
          </xsl:when>
      
      
    3. And here’s my edited code:

      <xsl:choose>
          <xsl:when test="$cbq_isgrouping != 'True'">
              <xsl:if test="$CurPosition = $FirstRow">
              <!-- customized stuff -->
      		<xsl:choose>
              	<xsl:when test="@Style='News'">
              		<xsl:value-of disable-output-escaping="yes" select="$newsStart" />
      			</xsl:when>
      			<xsl:otherwise>
      				<!-- this was the original code -->
      				<xsl:value-of disable-output-escaping="yes" select="$BeginColumn" />
      			</xsl:otherwise>
              </xsl:choose>
              <!-- end customized stuff -->
      
              </xsl:if>
          </xsl:when> 
    4. I repeated the process for the other sections. Because my <li> and </li> is inside the ItemStyle.xsl file, I didn’t need to define variables for those and left those “when” statements empty.
    5. <!-- customized stuff -->
      <xsl:choose>
      	<xsl:when test="@Style='News'">
      		<xsl:value-of disable-output-escaping="yes" select="$newsEnd" />
      	</xsl:when>
      	<xsl:otherwise>
      		<!-- this was the original code -->
      		<xsl:value-of disable-output-escaping="yes" select="$EndColumn" />
      	</xsl:otherwise>
      </xsl:choose>
      <!-- end customized stuff -->
      
      ...
      
      <xsl:template name="OuterTemplate.CallItemTemplate">
          <xsl:param name="CurPosition" />
          <xsl:param name="LastRow" />
          <!-- customized stuff -->
      	<xsl:choose>
          	<xsl:when test="@Style='News'" />
      	<xsl:otherwise>
      		<!-- this was the original code -->
      		<xsl:value-of disable-output-escaping="yes" select="$BeginListItem" />
      	</xsl:otherwise>
          </xsl:choose>
          <!-- end customized stuff -->
      
      ...
      
      <!-- customized stuff -->
      <xsl:choose>
      	<xsl:when test="@Style='News'" />
      	<xsl:otherwise>
      		<!-- this was the original code -->
      		<xsl:value-of disable-output-escaping="yes" select="$EndListItem" />
      	</xsl:otherwise>
      </xsl:choose>
      <!-- end customized stuff -->
      
    6. And after saving the page, going back to the browser, and refreshing, I finally have my desired result! (Note that the colors and layout are all part of my stylesheet, which I’m not delving into for this article.)
    7. 2010-12-12-CustomizingHTMLCodeCQWP-15.png

      Here is what the HTML looks like (I took out the URLs and added line breaks for legibility). It’s much cleaner than the original generated HTML and includes the classes that I need to hook in my CSS!

      <div class="blogfeed-wrapper">
      	<div class="blogfeed">
      		<h2>Recent News</h2>
      		<ul>
      			<li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
      				<p class="date"></p>
      				<h3><a href="..." title="">New SPDevWiki site released!</a></h3>
      				<p class="blurb"></p>
      			</li>
      			<li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
      				<p class="date">8/3/2010</p>
      				<p class="image"><a href="..."><img class="image" src=".../image.png" title="" /></a></p>
      				<h3><a href="..." title="">SharePoint 2010 SDK released</a></h3>
      				<p class="blurb">SharePoint 2010 SDK released for RTM</p>
      			</li>
      			<li xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
      				<p class="date">6/4/2010</p>
      				<h3><a href="..." title="">Announcing Community Kit for SharePoint</a></h3>
      				<p class="blurb">CodePlex projects teams get together to release CKS:Dev!</p>
      			</li>
      		</ul>
      	</div>
      </div>
      
    Whew! That was really long. Here’s a summary of the steps that I took:
    1. Created a custom “test” Item Style to list out the fields being passed to the web part, which also helped after Step 3 to see if the new fields were added in properly or not.
    2. Figured out the internal column name(s) for the additional field(s) to add to the web part.
    3. Exported the Custom Query Web Part that was on the page, modified the code to add the fields, and imported the modified web part back in and added it to the page.
    4. Changed the Chrome type to “none” to hide the title bar.
    5. Created a custom “News” Item Style and modified the code to get the HTML that I wanted to display for each item.
    6. Hacked ContentQueryMain.xsl to figure out where to modify the code, then added custom code specific to the “News” Item Style for my wrap-around code.
    I definitely appreciate any comments that more experienced SharePoint developers have about how to modify the ContentQueryMain.xsl file more elegantly or other ways to accomplish this! If you have questions, please feel free to post them but remember that I’m new to this as well and may not be able to answer them. 2010-12-12-CustomizingHTMLCodeCQWP-16.gif

Why use a VSeWSS or equivalent in SharePoint development

Speed

Add New… | Content Type etc. is a wizard approach and speeds up development

Maintenance  

Every dev will write the DDF, solution manifest files differently
The structure of every project is different
 A tool like VSeWSS (which 1.3 RTM’d today) will enforce auto generation and also structure

Upgradeability

Will auto upgrade to VS2010

Ramp up

Developers in market know the VSeWSS tools over custom approach

Continuous Integration

Automatically supported by TFS for automated builds
 

Create an Organization Chart in MOSS 2007 Using a Contacts List

 

I was recently tasked with finding a way to maintain an org chart for my department. If this were a single-use resource that didn’t need to be updated frequently, I would have chosen to create it in a program such as Visio or Adobe Illustrator (even PowerPoint would work well with its SmartArt capabilities). However, this is something we want to be as maintenance-free as possible so we aren’t rebuilding it as people come and go.

The department’s operations team already maintains a list of everyone in the department by using a SharePoint Contacts list, so we can pull data from that and build the org chart dynamically. This method does not require any custom code or web parts, so there’s no need to deploy anything on the server!

 2010-12-20-MOSS2007OrgChart-01.jpg

Before I go any further, I want to point out the benefits of maintaining a department contact list on SharePoint (rather than relying on distribution lists and looking people up on their MySite page every time you need a phone number). If your department doesn’t do this already, consider these benefits:

  • Everyone in the department has a central place they can bookmark to quickly look for someone’s contact information (email, work phone, mobile phone, etc.)
  • The list can be printed if employees want to have a hard copy of all department personnel
  • The list is easy to maintain if it is incorporated with the current on-boarding workflow that the operations team manages
  • Information in the list can be used for “fun” projects like listing upcoming birthdays or anniversaries
  • Information in the list can be used for practical projects like this org chart or a “meet the team” page for site visitors

I also recommend this series of articles that explains how the XSLT works in SharePoint. It’s not required reading for this post, but it will help explain why we’re doing some of the things that make the org chart work.

A Synergy of Web Technologies

The org chart in this example uses a combination of CSS, jQuery, Google Charts, and XSLT to create the final product. The scripts that I used can be found here:

For now we can load jQuery from Google’s CDN. I also decided to simply load the Google Charts API directly from Google’s web site. For production it might be better to copy the scripts into a document library on your SharePoint site and load them that way.

The Nested List to Google Org Chart plugin is not hosted anywhere, so that script must be stored somewhere on SharePoint. I have a document library called “webresources” that I use to house all of the centralized style sheets, images, and scripts I use for our department’s SharePoint site, so I uploaded the jQuery plugin here. I also created an “orgchart.css” style sheet and uploaded it here as well (at this point it doesn’t have any styles defined, but I may decide to customize the look and feel of the org chart in the future).

Now that the resources are taken care of, it’s time to start building!

Setting Up the Contacts List

The Contacts list has several columns out of the box, but we’ll need to create at least two additional columns: Department (or Team—this will be displayed on the org chart so viewers can easily see which team or sub-department everyone belongs to) and Supervisor (make this a “Person or Group” column). I also recommend changing as many of the default columns to a “Person or Group” column type as possible. This will ensure that they show the latest information pulled from Active Directory.

To change the default columns to a “Person or Group” column type, first find out what the Internal Name of the column is. You can do this on the list settings page by clicking on a column name to edit it. In the URL look for a parameter called “Field.” The Internal Name of the column will be after the “=” sign. The Full Name column, for example, has an internal name of “FullName” with no spaces.

 2010-12-20-MOSS2007OrgChart-02.jpg

Delete the column, then create a new column and enter “FullName” (no spaces) as the new Column name, and select “Person or Group” for the column type. At the bottom of the page, select “Name (with presence)” in the Show field drop-down and click OK to save the new column.

 2010-12-20-MOSS2007OrgChart-03.jpg

Now that the new column is created, edit it again and add a space in the column name. This will keep the Internal Name the same (“FullName”), but will change the Display Name to “Full Name” with a space. If we had created the new column and called it “Full Name” with a space to begin with, the Internal Name would have been “Full_x0020_Name” which wouldn’t match the original column’s internal name (SharePoint converts spaces in column names to “_x0020_” when new columns are created).

 2010-12-20-MOSS2007OrgChart-04.jpg

Do this for all other columns that you wish to change to a Person or Group type (simply change the Show field drop-down to show the appropriate information). I recommend changing the following at a minimum if you’re using MOSS 2007 or later, because all of this information should be in Active Directory:

2010-12-20-MOSS2007OrgChart-17.png

I also changed the column order so the new columns are not at the bottom of the EditForm.aspx and DispForm.aspx pages.

 2010-12-20-MOSS2007OrgChart-05.jpg

Now it’s simply a matter of entering each person in the department into the Contacts list. This may take a while initially, but once it’s set up it only takes a few seconds to add or remove personnel from the list in the future.

Creating the Data View Web Part

The next step is to create the Data View Web Part (DVWP) that will build the nested lists of contacts. Create a new blank web part page on your site called “OrgChart.aspx.” Open the page in SharePoint Designer (you may need to detach the page from its layout), click on an empty web part zone (you’ll need to be in Design or Split view mode), and go to Data View > Insert Data View… to add a new DVWP. In the Data Source Library pane on the right, click on the Contacts list and select “Show Data”.

2010-12-20-MOSS2007OrgChart-06.jpg

Now the Data Source Details pane will be visible. Select just one of the fields; at this point it doesn’t matter, so I went with the Last Name field. Now choose to insert the selected field as a Multiple Item View. The DVWP should now be displaying the last names of everyone in the Contacts list.

 2010-12-20-MOSS2007OrgChart-07.jpg

Hover over the DVWP and click on the small white box with the right-arrow that appears in the top-right corner of the DVWP (this is called the “Common Data View Tasks” menu). From here we need to set a few options. First, click on “Paging” and make sure that “Display all items” is selected.

 2010-12-20-MOSS2007OrgChart-08.jpg

Then click on “Change Layout…” and select the bulleted list layout. This would be a good time to save the page as well.

 2010-12-20-MOSS2007OrgChart-09.jpg

Now let’s tidy up the XSLT that SP Designer generates and remove any SharePoint-specific HTML as-needed. Switch to code view and find the <XSL> tag inside the DVWP. Right-click on the tag and select “Select Tag” to highlight the entire XSL style sheet. Now press Shift + Tab repeatedly until all of indentations are gone from the XSLT.

 2010-12-20-MOSS2007OrgChart-10.jpg

We’re going to be working with the XSL templates quite a bit, so first find each <xsl:template>…</xsl:template> and add an extra blank line before and after each template (i.e. add a blank line before <xsl:template> and after </xsl:template>). I also prefer to re-indent the XSL templates by hand to show nested elements better (use the Tab key, not the spacebar) so my XSL style sheet looks something like this:

<XSL>
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
<xsl:output method="html" indent="no"/>
<xsl:decimal-format NaN=""/>
<xsl:param name="dvt_apos">&apos;</xsl:param>
<xsl:variable name="dvt_1_automode">0</xsl:variable>

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
	<xsl:call-template name="dvt_1"/>
</xsl:template>

<xsl:template name="dvt_1">
	<xsl:variable name="dvt_StyleName">BulTitl</xsl:variable>
	<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
	<ul>
		<xsl:call-template name="dvt_1.body">
			<xsl:with-param name="Rows" select="$Rows" />
		</xsl:call-template>
	</ul>
</xsl:template>

<xsl:template name="dvt_1.body">
	<xsl:param name="Rows" />
	<xsl:for-each select="$Rows">
		<xsl:call-template name="dvt_1.rowview" />
	</xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.rowview">
	<li class="ms-vb">
		<xsl:value-of select="@Title" />
		<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
			<br /><span ddwrt:amkeyfield="ID" ddwrt:amkeyvalue="ddwrt:EscapeDelims(string(@ID))" ddwrt:ammode="view" />
		</xsl:if>
	</li>
</xsl:template>

</xsl:stylesheet>
</XSL>

Now we can see the code a bit easier. First look for the <ul> element in the “dvt_1” template and change it to <ul id="orgchart">. Then look for the <li class="ms-vb"> in the “dvt_1.rowview” template and remove the class attribute so it’s just <li>. This would be a good time to save the page now that we’ve gone to the trouble of reformatting the code and removing the SharePoint-specific class from the XSLT.

Parameters

I set up my org chart so that it uses two URL query string parameters. The first is “TopLevel” to determine which person to start the org chart with. For example, if the URL were OrgChart.aspx?TopLevel=Josh%20McCarty then the org chart would start with me at the top. This makes the XSLT re-usable to show team-specific charts (by using a team manager as the “TopLevel”) or department-wide charts (by using the Director/VP/etc. of the department as the “TopLevel”). To create this parameter, switch back to design view and open the Common Data View Tasks menu, then click on “Parameters…”

To create the parameter, click on the New Parameter button and type “TopLevel” as the Name. Select “Query String” as the Parameter Source, and enter “TopLevel” again as the Query String Variable. The Default Value field can be left blank, or it can be set to the head of the department (this will need to be manually updated if that person leaves the department). Click OK to create the new parameter.

2010-12-20-MOSS2007OrgChart-11.jpg

The second parameter I use is “Levels” to determine how many levels deep the org chart should go. For example, if the URL were OrgChart.aspx?TopLevel=Josh%20McCarty&Levels=3 then the org chart would show three levels of subordinates below me (if only I were that high up the ladder!). Create the “Levels” parameter using the same method as the “TopLevel” parameter, but in this case I would specify a default value so the org chart never shows just one person. I used 10. This would be another good time to save the page.

How This All Works

Before we dive into the XSLT and start making further changes, I want to explain how this is going to work. This is the basic logic that the XSLT will use:

  1. Select the contact whose @FullName matches the value in the “TopLevel” query string parameter, then display that contact in the <li> of the unordered list.
  2. Create a nested <ul> within the <li> of the top level contact.
  3. Select all of the contacts whose @Supervisor column matches the @FullName of the top level contact, then display those contacts as <li>’s of the nested <ul>.
  4. Create nested <ul>’s for each of the nested <li>’s of the second level contacts.
  5. Select all of the contacts whose @Supervisor column matches the @FullName of the second level contacts, then display those contacts as <li>’s of the second nested <ul>’s.
  6. Repeat until the level specified in the “Levels” parameter is reached or until there are no more contacts.

Because we are going to be repeating the same process, we can use a recursive XSL template. For each contact, the template will call itself again to display the next level of contacts until there are no more people below the previous contact. It will do this independently on each branch of the org chart, so it will always follow the branches to their lowest level (or until they reach the number of levels specified by the “Levels” parameter in the query string).

Creating the XSLT

Switch back to code view in SP Designer and find the “dvt_1.rowview” template. Below this we are going to create a new “contact_details” template that will display details about each contact in the org chart. We’ll call this template each time we need to display a contact on the org chart. Let’s keep it simple for right now while we’re still developing. This will display just the name (with presence) of the contact:

<xsl:template name="contact_details">
	<xsl:param name="Rows" />
	<xsl:param name="FullName" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $FullName]">
		<xsl:value-of select="@FullName" disable-output-escaping="yes" />
	</xsl:for-each>
</xsl:template>

Notice that <xsl:for-each> has an XPath expression. This is used to select the contact whose @FullName column matches the $FullName parameter that is passed whenever this template is called. Because this column includes presence information, we need to use the substring method to strip out the HTML and get to the raw text of the @FullName column in order to compare it to $FullName. In our “dvt_1.body” and “dvt_1.rowview” templates we are going to use <xsl:call-template> to call the “contact_details” template and pass the $FullName parameter to it.

Now find the “dvt_1.body” template. Modify it by adding a slightly different XPath expression to the select attribute of <xsl:for-each>:

<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $TopLevel]">

This will filter all of the people in our Contacts list (the “$Rows”) to select the contact whose @FullName value matches the $TopLevel parameter in the query string. We also need to create a variable to track the current level of the org chart so we can test for when the “Levels” parameter from the query string is satisfied. Do this by adding <xsl:variable name="CurLevel" select="number(0)" /> after <xsl:for-each>. This will set the top level of the org chart to zero (use the “number” method to set the variable to a number value instead of a text string).

To display this contact in the org chart, create a <li> element inside <xsl:for-each> (after the variable we just created). Inside that <li> element, call the “contact_details” template with the Rows and FullName parameters (again use XPath for the FullName to strip out the presence information):

<xsl:template name="dvt_1.body">
	<xsl:param name="Rows" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $TopLevel]">
		<xsl:variable name="CurLevel" select="number(0)" />
		<li>
			<xsl:call-template name="contact_details">
				<xsl:with-param name="Rows" select="$Rows" />
				<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
			</xsl:call-template>		
			<xsl:call-template name="dvt_1.rowview" />
		</li>
	</xsl:for-each>
</xsl:template>

After calling the “contact_details” template, the <li> element will call our “dvt_1.rowview” template to display all of the contacts below the top level. This is where we need to filter the second level of the org chart to select only the contacts whose @Supervisor column matches the @FullName column of the top level contact. We can accomplish this by creating a Supervisor parameter, setting its value to use the @FullName of the top level contact, and passing that value to the “dvt_1.rowview” template. We also need to pass the $CurLevel variable so the additional levels of the org chart can test for when the “Levels” parameter from the query string is satisfied:

<xsl:template name="dvt_1.body">
	<xsl:param name="Rows" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $TopLevel]">
		<xsl:variable name="CurLevel" select="number(0)" />
		<li>
			<xsl:call-template name="contact_details">
				<xsl:with-param name="Rows" select="$Rows" />
				<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
			</xsl:call-template>
			<xsl:call-template name="dvt_1.rowview">
				<xsl:with-param name="Rows" select="$Rows" />
				<xsl:with-param name="CurLevel" select="$CurLevel" />
				<xsl:with-param name="Supervisor" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
			</xsl:call-template>
		</li>
	</xsl:for-each>
</xsl:template>

Note: Because we are using a query string parameter to determine which contact is at the top of the org chart, the design view in SP Designer will never show any contacts (unless you specified a default value for the “TopLevel” parameter), so don’t worry if you don’t see anything. You can check progress any time by saving the page and opening it in a browser and including the query string parameters.

To create the second level of the org chart, edit the “dvt_1.rowview” template so a <ul> wraps around <xsl:for-each>, then modify <xsl:for-each> to use an XPath expression to select only the contacts whose @Supervisor column matches the $Supervisor parameter. Include a <li> inside <xsl:for-each> and call the “contact_details” template:

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="Supervisor" />
	<ul>
		<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
			<li>
				<xsl:call-template name="contact_details">
					<xsl:with-param name="Rows" select="$Rows" />
					<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
				</xsl:call-template>
			</li>
		</xsl:for-each>
	</ul>
</xsl:template>

Now we need to modify the “dvt_1.rowview” template so it calls itself (i.e. make it recursive) until the “Levels” parameter from the query string is satisfied. To do this, first add <xsl:param name="CurLevel" /> so the $CurLevel parameter is received from the “dvt_1.body” template, then create a new variable called “LevelTest” to set the second level to $CurLevel + 1 so we can increment the level of the org chart:

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="Supervisor" />
	<xsl:param name="CurLevel" />
	<xsl:variable name="LevelTest" select="number($CurLevel + 1)" />
	<ul>
		<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
			<li>
				<xsl:call-template name="contact_details">
					<xsl:with-param name="Rows" select="$Rows" />
					<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
				</xsl:call-template>
			</li>
		</xsl:for-each>
	</ul>
</xsl:template>

Next we need to test for the value of $LevelTest and only display the contacts if $LevelTest is less than or equal to the “Levels” parameter in the query string. Add this <xsl:if> around the <ul> element:

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="Supervisor" />
	<xsl:param name="CurLevel" />
	<xsl:variable name="LevelTest" select="number($CurLevel + 1)" />
	<xsl:if test="($LevelTest <= number($Levels))">
		<ul>
			<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
				<li>
					<xsl:call-template name="contact_details">
						<xsl:with-param name="Rows" select="$Rows" />
						<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
					</xsl:call-template>
				</li>
			</xsl:for-each>
		</ul>
</xsl:if>
</xsl:template>

We don’t want to display anything if there are no contacts that match the $Supervisor parameter, so we need to nest another <xsl:if> inside the <xsl:if> for the $LevelTest parameter and test if any contacts exist whose @FullName column matches the $Supervisor parameter:

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="Supervisor" />
	<xsl:param name="CurLevel" />
	<xsl:variable name="LevelTest" select="number($CurLevel + 1)" />
	<xsl:if test="($LevelTest <= number($Levels))">
		<xsl:if test="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
			<ul>
				<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
					<li>
						<xsl:call-template name="contact_details">
							<xsl:with-param name="Rows" select="$Rows" />
							<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
						</xsl:call-template>
					</li>
				</xsl:for-each>
			</ul>
		</xsl:if>
</xsl:if>
</xsl:template>

Finally we need to make the template recursive by calling itself. Call the “dvt_1.rowview” template after calling the “contact_details” template, and pass all the same parameters that we used when calling “dvt_1.rowview” from the “dvt_1.body” template, except use $LevelTest when selecting the “CurLevel” parameter instead of using the number zero. This means that when “dvt_1.rowview” is called again, it will continue to increment the value of $CurLevel by +1 each time, allowing the template keep calling itself until it satisfies the “Levels” parameter in the query string:

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="Supervisor" />
	<xsl:param name="CurLevel" />
	<xsl:variable name="LevelTest" select="number($CurLevel + 1)" />
	<xsl:if test="($LevelTest <= number($Levels))">
		<xsl:if test="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
			<ul>
				<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]">
					<li>
						<xsl:call-template name="contact_details">
							<xsl:with-param name="Rows" select="$Rows" />
							<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
						</xsl:call-template>
						<xsl:call-template name="dvt_1.rowview">
							<xsl:with-param name="Rows" select="$Rows" />
							<xsl:with-param name="CurLevel" select="$LevelTest" />
							<xsl:with-param name="Supervisor" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
						</xsl:call-template>
					</li>
				</xsl:for-each>
			</ul>
		</xsl:if>
	</xsl:if>
</xsl:template>

Save the page, then open it in your browser and take a look (don’t forget to use include the query string in your URL). You should see a bulleted list of contacts, grouped according to supervisor. Try changing the “Levels” parameter in the query string to see the org chart change in depth.

Note: A value of “0” for the “Levels” parameter will show just the person in the “TopLevel” parameter. A value of “1” will show one level below the person at the top; a value of “2” will show two levels below the person at the top, and so on.

2010-12-20-MOSS2007OrgChart-12.jpg

Now that our nested lists are working, let’s add additional information to the “contact_details” template. All of our changes will take place inside <xsl:for-each>. Go ahead and add any additional columns that you want displayed. For example, if you wanted to display name, job title, work phone, and email (with a mailto link) your template might look something like this:

<xsl:template name="contact_details">
	<xsl:param name="Rows" />
	<xsl:param name="FullName" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $FullName]">
		<xsl:value-of select="@FullName" disable-output-escaping="yes" /><br />
		<xsl:value-of select="substring-before(substring-after(substring-after(@JobTitle, '?ID='), '>'), '<')" /><br />
		<xsl:value-of select="substring-before(substring-after(substring-after(@WorkPhone, '?ID='), '>'), '<')" /><br />
		<a href="mailto:{substring-before(substring-after(substring-after(@Email, '?ID='), '>'), '<')}"><xsl:value-of select="substring-before(substring-after(substring-after(@Email, '?ID='), '>'), '<')" /></a>
	</xsl:for-each>
</xsl:template>

Note how we took a substring of most of the columns. By default, all columns that are a “Person or Group” type include presence information. We don’t need that displayed more than once, so we can remove it from all columns except the @FullName column. Also be sure to include the disable-output-escaping="yes" attribute to the @FullName column, otherwise the actual HTML code will be output as text on the page. Save the page and take a look in your browser. The result will look like this:

 2010-12-20-MOSS2007OrgChart-13.jpg

The Data View Web Part is now completed. Here is the entire XSL style sheet if you want to copy and paste this into your DVWP:

<Xsl>
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
<xsl:output method="html" indent="no"/>
<xsl:decimal-format NaN=""/>
<xsl:param name="dvt_apos">&apos;</xsl:param>
<xsl:param name="ListID">47FC6EED-1305-4425-B6BF-3818D6298ECE</xsl:param>
<xsl:param name="TopLevel" />
<xsl:param name="Levels" />
<xsl:variable name="dvt_1_automode">0</xsl:variable>

<xsl:template match="/" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
	<xsl:call-template name="dvt_1"/>
</xsl:template>

<xsl:template name="dvt_1">
	<xsl:variable name="dvt_StyleName">BulTitl</xsl:variable>
	<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
	<ul id="orgchart">
		<xsl:call-template name="dvt_1.body">
			<xsl:with-param name="Rows" select="$Rows" />
		</xsl:call-template>
	</ul>
</xsl:template>

<xsl:template name="dvt_1.body">
	<xsl:param name="Rows" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $TopLevel]"> 
		<xsl:variable name="CurLevel" select="number(0)" />
		<li>
			<xsl:call-template name="contact_details">
				<xsl:with-param name="Rows" select="$Rows" />
				<xsl:with-param name="CurLevel" select="$CurLevel" />
				<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
			</xsl:call-template>
			<xsl:call-template name="dvt_1.rowview">
				<xsl:with-param name="Rows" select="$Rows" />
				<xsl:with-param name="CurLevel" select="$CurLevel" />
				<xsl:with-param name="Supervisor" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
			</xsl:call-template>
		</li>
	</xsl:for-each>
</xsl:template>

<xsl:template name="dvt_1.rowview">
	<xsl:param name="Rows" />
	<xsl:param name="CurLevel" />
	<xsl:param name="Supervisor" />
	<xsl:variable name="LevelTest" select="number($CurLevel + 1)" />
	<xsl:if test="($LevelTest <= number($Levels))"> 
		<xsl:if test="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]"> 
			<ul>
				<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@Supervisor, '?ID='), '>'), '<') = $Supervisor]"> 
					<li>
						<xsl:call-template name="contact_details">
							<xsl:with-param name="Rows" select="$Rows" />
							<xsl:with-param name="FullName" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
						</xsl:call-template>
						<xsl:call-template name="dvt_1.rowview">
							<xsl:with-param name="Rows" select="$Rows" />
							<xsl:with-param name="CurLevel" select="$LevelTest" />
							<xsl:with-param name="Supervisor" select="substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<')" />
						</xsl:call-template>
					</li>
				</xsl:for-each>
			</ul>
		</xsl:if>
	</xsl:if>
</xsl:template>

<xsl:template name="contact_details">
	<xsl:param name="Rows" />
	<xsl:param name="CurLevel" />
	<xsl:param name="FullName" />
	<xsl:for-each select="$Rows[substring-before(substring-after(substring-after(@FullName, '?ID='), '>'), '<') = $FullName]"> 
		<div>
			<xsl:value-of select="@FullName" disable-output-escaping="yes" /><br />
			<xsl:value-of select="substring-before(substring-after(substring-after(@JobTitle, '?ID='), '>'), '<')" /><br />
			<xsl:value-of select="substring-before(substring-after(substring-after(@WorkPhone, '?ID='), '>'), '<')" /><br />
			<a href="mailto:{substring-before(substring-after(substring-after(@Email, '?ID='), '>'), '<')}"><xsl:value-of select="substring-before(substring-after(substring-after(@Email, '?ID='), '>'), '<')" /></a>
		</div>
	</xsl:for-each>
</xsl:template>

</xsl:stylesheet>
</Xsl>

Adding Scripts to Generate the Org Chart

The next step is to load the scripts onto the page that generate the org chart. Minimize your browser and go back to SP Designer and switch to design view. Insert a Content Editor Web Part (CEWP) on the page, switch to code view, and find the content section of the CEWP:

<Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[]]></Content>

Create a few blank lines between the “[]” brackets so you have some room to work:

<Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor">
	<![CDATA[
	
	
	]]>
</Content>

All of the content in a CEWP goes between those two brackets. Add the following scripts inside the CEWP, making sure to change the source attribute accordingly (I’m loading jQuery and the org chart visualization from Google for development purposes; you may want to save a copy of the scripts to your server and load them from there when the org chart is in production; note the Nested List to Google Org Chart jQuery plugin must be saved to your server because it’s not hosted anywhere else, so I have it in a “webresources” document library):

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>

This loads the jQuery library.

<script type='text/javascript' src='http://www.google.com/jsapi'></script>

This loads the Google charts API.

<script type="text/javascript">
	google.load('visualization', '1', {packages:['orgchart']});
</script>

This loads the org chart visualization.

<script type="text/javascript" src="http://servername/site/WebResources/jquery.g_orgchart.js"></script>

This loads the Nested List to Google Org Chart jQuery plugin from my “webresources” library.

<script type="text/javascript">
	$(document).ready(function(){
		$("#orgchart").g_orgchart({ 'size' : 'small' , 'allowCollapse' : 'true' });
	});
</script>

This tells the Org Chart jQuery plugin which nested list to use for the org chart and specifies any options to use for the org chart such as size/p>

The entire CEWP content section should look like this:

<Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[
	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
	<script type='text/javascript' src='http://www.google.com/jsapi'></script>
	<script type="text/javascript">
		google.load('visualization', '1', {packages:['orgchart']});
	</script>
	<script type="text/javascript" src="http://newsource/extaffairs/WebResources/jquery.g_orgchart.js"></script>
	<script type="text/javascript">
		$(document).ready(function(){
			$("#orgchart").g_orgchart({ 'size' : 'small' , 'allowCollapse' : 'true' });
		});
	</script>
]]></Content>

Save the page and view it in your browser. You should now have an org chart!

 2010-12-20-MOSS2007OrgChart-14.jpg

Most likely you’re viewing the org chart in Internet Explorer, so the actual nodes for each person will not look very appealing (white background with a light-blue border). If you view the org chart in a browser with CSS3 support (Firefox, Chrome, Safari, etc.) you should see a much nicer version of the chart with rounded corners, a background color gradient, and drop-shadows:

 2010-12-20-MOSS2007OrgChart-15.jpg

To make the chart a little more visually appealing in Internet Explorer, add the following CSS below the scripts in the CEWP to give the nodes a background color:

<style type="text/css">
.google-visualization-orgchart-node {
	background-color: #edf7ff;
}
.google-visualization-orgchart-nodesel {
	background-color: #fff7ae;
}
</style>

There, that looks better!

2010-12-20-MOSS2007OrgChart-16.jpg

Internet Explorer also renders the nodes in different sizes; I assume it’s just the way IE handles tables (the org chart is actually created using table cells and cell borders).

From this point on you can customize the org chart even further. For example, by altering the “contact_details” template and using some additional CSS and JavaScript, we can create pop-up windows that display additional information about the user when clicked.

An Alternative Org Chart with No JavaScript

If you’d prefer not to use any JavaScript for the org chart, you can get a fair approximation of the org chart (minus the connecting lines) by using just CSS in the CEWP:

<style type="text/css">
#orgChart {
	font-size: 10px;
	font-family: arial, helvetica, sans-serif;
	line-height: 1.1em;
	text-align: center;
}
#orgChart, #orgChart ul {
	float: left;
	list-style-type: none;
	margin: 5px 0 0 0;
	padding: 0;
	text-align: center;
}
#orgChart li {
	display: inline;
	float: left;
	margin: 10px 5px;
	padding: 0;
}
#orgChart li div {
	-webkit-box-shadow: rgba(0, 0, 0, 0.496094) 3px 3px 3px;
	background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#EDF7FF), to(#CDE7EE));
	background-color: #EDF7FF;
	border: 2px solid #CDE7EE;
	border-radius: 5px;
	cursor: default;
	display: block;
	height: 60px;
	width: 150px;
	margin: 0 auto;
	padding: 5px;
}
</style>

Note: Using the CSS-only method will cause your org chart nodes to wrap below each other if the width of the page is too small to include all of the nodes side-by-side. You can avoid this by specifying a width for #orgChart that is large enough to include all org chart nodes.

That just about covers everything I can think of for creating an org chart in MOSS 2007 without deploying any custom web part code. If you come up with any cool ways to enhance the org chart (like adding functionality with the “contact_details” template), be sure to let me know!

SharePoint Flash Guide

  

Recently my Director and I discussed developing an internal SharePoint training site and having brief 1-2 minute videos walking users through individual topics.  I pinged the SharePoint community to find out what folks consider the best screen capture program and received recommendations for Camtasia Studio.  Camtasia is a great product (30 day trial is available) and saves videos in multiple formats but I was most interested in using Flash.  However I couldn’t really find any definitive guides on how to get my Camtasia videos into a player to be used on SharePoint.  So here it goes, my first SharePoint article detailing my steps to placing a flash video on SharePoint!
 
I’m not going to walk through how to create a movie in Camtasia as they’ve created a great Learning Center so I will make the assumption we have produced a video.  However now that we have produced a flash video we need to have a player that will present our file and allow the user to control the playback inside of SharePoint.  First, we download a Flash player to be hosted on SharePoint.  There are several free and paid variations out there of Flash Players however I went with JW Player primarily because they have a great Wizard tool to assist in getting the code to embed (more on this later).  Download the latest version of JW Player and save the contents of the Zip file.

We will need to take several of the JW Player files and upload them to a dedicated document library.  I simply called mine ‘JWPlayer’ and I placed these files at the highest site level I had access to.
2010-12-12-FlashGuide-01.png
Important point:  Be sure to make that library Read Only for the audience who will be watching these videos as they just need to be able to call these files.  Upload the following files from the JW Player files into our new ‘JWPlayer’ library and make note of the URLs to each file as we will need that shortly:
 
player.swf (this is the actual Flash player that is used to play the videos)
swfobject.js (file that controls the Flash player and corresponding video)
 
Next I created a new ‘Videos’ document library at my SharePoint Training site to house my Flash videos and preview images of each video from Camtasia (which are defaulted to PNG format).  Important point:  Be sure to make that library Read Only for the audience who will be watching these videos as they just need to be able to call these files.  Upload the following files from the JW Player files into our new ‘JWPlayer’ library and make note of the URLs to each file as we will need that shortly.
 
Access the JW Player Wizard site as this will assist in building the code.  Expand the options for ‘Embed parameters’ and paste the URL to the player.swf file in the ‘source’.
 
2010-12-12-FlashGuide-02.png

Next expand the ‘File properties’ area and paste the URL to the produced flash video in the ‘file’ section and paste the URL to the preview image in the ‘image’ field.
 
2010-12-12-FlashGuide-03.png

Click on the ‘Update Preview & Code’ button and once the screen refreshes we will have our custom code in the ‘Copy Your Code*’ box.  Highlight all that code and copy its contents for use in the next step.
 
 2010-12-12-FlashGuide-04.png
 
2010-12-12-FlashGuide-05.png
 
 
 
Go to the site where the video will be displayed and add a Content Editor Web Part:

 
2010-12-12-FlashGuide-06.png

Open the tool pane:
2010-12-12-FlashGuide-07.png

 

Click on the Source Editor… button
 
2010-12-12-FlashGuide-09.png
 

 
Paste in the code from the JW Player Wizard into the ‘Source Editor’ window.  Once that information is pasted in we will need to update the first <script type> of the code to point to the swfobject.js file that was previously uploaded.  So the final code will look like:
 


<script type='text/javascript' src='/URL/to/your/JWPlayer folder/swfobject.js'></script>
 
<div id='mediaspace'>This text will be replaced</div>
 
<script type='text/javascript'>	
  var so = new SWFObject('/URL/to/your/JWPlayer folder/player.swf','mpl','470','320','9');
  so.addParam('allowfullscreen','true');
  so.addParam('allowscriptaccess','always');
  so.addParam('wmode','opaque');
  so.addVariable('file','/URL/to/your/Videos folder/video.mp4');
  so.addVariable('image','/URL/to/your/Videos folder/preview.png);
  so.write('mediaspace');
</script>

 

Here is a video that captures the entire process

Done!  I am using this solution explicitly for the use of training end-users to ultimately support better adoption and a better sense of community with our users that is inherent with SharePoint.  What other ideas could be envisioned for this simple solution?  Would love to hear your feedback, leave a comment!

 

EUSP Disclaimer:  This solution has been proven for WSS and MOSS only.  I’m working on getting the flash videos to render correctly in 2010 and should have an update to the article soon.  Thanks in advance for your patience.

 

  

ExtendingSharePointSocialExpwBCS

This post was published to SharePoint Fabian Blog at 1:48:39 AM 12/20/2010
  
Account             SharePoint Fabian Blog
Category            SharePoint Designer 2010
 

Storyline

So if you have ended up here then you are searching like nothings business looking for resources on how to use SharePoint Business Connectivity Services (BCS) to Extend User Profile Services to enhance the Social Experience of the End User, or you saw a Tweet to come see it J That  said, what are we going to be doing?  Here is our problem and solution.

Problem Stated

In our company we use Active Directory to as our Source of Record (SOR), we also use a ERP System (call it Siebel, CRM, SAP… doesn’t matter) to store other related data to our employees, vendors, clients, etc.  We want to capture information from that ERP system to include into our MySite and also make this information searchable, tag-able, off-line able, viewable/consumable in the Browser UX and Fat Clients. How do we do that?

Resolution

The process we will undertake will follow the chronological process below; but before we get there, we have a few Assumptions of the Farm [these assumptions are based on what should already be configured on the Farm as in mine]
·         Assumptions
o   User Profile Service has been started
o   User Profile Synchronization Service has been started
o   Synchronization Connections are present from Active Directory (AD)
o   Profile Synchronization has occurred from the AD  Source
o   MySites are provisioned
·         Process
o   Create an External Content Type in either SharePoint Designer 2010 or Visual Studio 2010 representative of the ERP system data
§  Set the permissions accordingly so that the data is available to the system
§  Create a Profile Page for Search
o   Create a Managed User Property field which maps an Identifier in both AD & the ERP System
o   Create a Synchronization Connection for the ERP System
o   Create as many Managed User Property fields which maps to elements that you want to bring in from the ERP System
o   Determine how you want those fields to be consumed in the Profile Store, MySites, Searchability, etc.
o   Perform a Full User Profile Synchronization
§  Review the process using the “Synchronization Service Manager” in the 14 hive – basically a FIM tool
o   Review your User Profile
o   Review your MySite
So what does our ERP system look like…
 
122010_0648_ExtendingSh1.png

 
 Tee it up!
The first thing we will need to do is make that relationship between our SOR and the ERP system, in our case we are going to have the sAMAccountName attribute in Active Directory set to a newly created Managed User Property; we do this in order to have a Property to map to our ERP System.
 
122010_0648_ExtendingSh2.png

We map it to the Attribute below
122010_0648_ExtendingSh3.png

 
A few notables…
1.       After naming the Property I gave it a Type of String
2.       Under Policy Setting I changed the Policy String to Optional and the Default Privacy Setting to Everyone. I did this because I wanted the widest parameters
3.       Under “Add New Mapping” I selected the Data Source of my AD, attribute of the sAMAccoutnName and clicked Add
In the end it looks like this
122010_0648_ExtendingSh5.png

After that was complete we need to set up a Connection with a Type set to “Business Connectivity Services” and the mapping Field set to the previously created CustomSSAMID (Custom AS sAMAccountName created earlier)
122010_0648_ExtendingSh6.png

The configured setting are below
122010_0648_ExtendingSh7.png

The next step is to create additional Managed Property fields that will surface/map to the ERP system that the External Content Type is responsible to surface.
122010_0648_ExtendingSh10.png

Run your User Profile

Next fire off a Full Profile Sync
122010_0648_ExtendingSh11.png

Now there is a wonderful tool located under the UIShell folder, whatever it is called now that allows you to monitor the User Profile experience; it is a part of Forefront Identity Manager (FIM), if you have issues with this process working or are just interested in how it works, go take a peek.
122010_0648_ExtendingSh12.png

This tool is located here
122010_0648_ExtendingSh13.png

Trust but Verify

Now if you have ever been on my blogs before you will know that I have a testing phase; in this phase we look at individual profiles and we also look at the MySite experience
122010_0648_ExtendingSh14.png

The newly created properties are below…
122010_0648_ExtendingSh15.png

One of the things I did when I created the Properties is to set a few Policy Settings different so we can see the experience in MySites
On the Twitter Handle
122010_0648_ExtendingSh16.png

122010_0648_ExtendingSh17.png

 
I set Policy Setting I set Privacy to everyone and under Display Setting I enabled everything
As it relates to Facebook…
122010_0648_ExtendingSh18.png

122010_0648_ExtendingSh19.png

 
Now that we get the boring part out of the way.. here is how wonderful it looks
122010_0648_ExtendingSh20.png

You will notice that I get to see both Twitter Handle and Facebook page because Nique is a direct report and under SharePoint rules, that makes here a colleague
Now lets find someone that is not a direct report and that I didn’t add as an explicit colleague
122010_0648_ExtendingSh21.png

That’s it folks, I hope this helped… Im going to bed…
 
 

Extending SharePoint UserProfile and MySites with Business Connectivity Services (BCS)

So if you have ended up here then you are searching like nothings business looking for resources on how to use SharePoint Business Connectivity Services (BCS) to Extend User Profile Services to enhance the Social Experience of the End User, or you saw a Tweet to come see it. That  said, what are we going to be doing?  Here is our problem and solution.

Problem Stated

In our company we use Active Directory to as our Source of Record (SOR), we also use a ERP System (call it Siebel, CRM, SAP… doesn’t matter) to store other related data to our employees, vendors, clients, etc.  We want to capture information from that ERP system to include into our MySite and also make this information searchable, tag-able, off-line able, viewable/consumable in the Browser UX and Fat Clients. How do we do that?

Resolution

The process we will undertake will follow the chronological process below; but before we get there, we have a few Assumptions of the Farm [these assumptions are based on what should already be configured on the Farm as in mine]
·         Assumptions
o   User Profile Service has been started
o   User Profile Synchronization Service has been started
o   Synchronization Connections are present from Active Directory (AD)
o   Profile Synchronization has occurred from the AD  Source
o   MySites are provisioned
·         Process
o   Create an External Content Type in either SharePoint Designer 2010 or Visual Studio 2010 representative of the ERP system data
§  Set the permissions accordingly so that the data is available to the system
§  Create a Profile Page for Search
o   Create a Managed User Property field which maps an Identifier in both AD & the ERP System
o   Create a Synchronization Connection for the ERP System
o   Create as many Managed User Property fields which maps to elements that you want to bring in from the ERP System
o   Determine how you want those fields to be consumed in the Profile Store, MySites, Searchability, etc.
o   Perform a Full User Profile Synchronization
§  Review the process using the “Synchronization Service Manager” in the 14 hive – basically a FIM tool
o   Review your User Profile
o   Review your MySite
So what does our ERP system look like…
 
122010_0648_ExtendingSh1.png

 
 Tee it up!
The first thing we will need to do is make that relationship between our SOR and the ERP system, in our case we are going to have the sAMAccountName attribute in Active Directory set to a newly created Managed User Property; we do this in order to have a Property to map to our ERP System.
 
122010_0648_ExtendingSh2.png

We map it to the Attribute below
122010_0648_ExtendingSh3.png

 
A few notables…
1.       After naming the Property I gave it a Type of String
2.       Under Policy Setting I changed the Policy String to Optional and the Default Privacy Setting to Everyone. I did this because I wanted the widest parameters
3.       Under “Add New Mapping” I selected the Data Source of my AD, attribute of the sAMAccoutnName and clicked Add
In the end it looks like this
122010_0648_ExtendingSh5.png

After that was complete we need to set up a Connection with a Type set to “Business Connectivity Services” and the mapping Field set to the previously created CustomSSAMID (Custom AS sAMAccountName created earlier)
122010_0648_ExtendingSh6.png

The configured setting are below
122010_0648_ExtendingSh7.png

The next step is to create additional Managed Property fields that will surface/map to the ERP system that the External Content Type is responsible to surface.
122010_0648_ExtendingSh10.png

Run your User Profile

Next fire off a Full Profile Sync
122010_0648_ExtendingSh11.png

Now there is a wonderful tool located under the UIShell folder, whatever it is called now that allows you to monitor the User Profile experience; it is a part of Forefront Identity Manager (FIM), if you have issues with this process working or are just interested in how it works, go take a peek.
122010_0648_ExtendingSh12.png

This tool is located here
122010_0648_ExtendingSh13.png

Trust but Verify

Now if you have ever been on my blogs before you will know that I have a testing phase; in this phase we look at individual profiles and we also look at the MySite experience
122010_0648_ExtendingSh14.png

The newly created properties are below…
122010_0648_ExtendingSh15.png

One of the things I did when I created the Properties is to set a few Policy Settings different so we can see the experience in MySites
On the Twitter Handle
122010_0648_ExtendingSh16.png

122010_0648_ExtendingSh17.png

 
I set Policy Setting I set Privacy to everyone and under Display Setting I enabled everything
As it relates to Facebook…
122010_0648_ExtendingSh18.png

122010_0648_ExtendingSh19.png

 
Now that we get the boring part out of the way.. here is how wonderful it looks
122010_0648_ExtendingSh20.png

You will notice that I get to see both Twitter Handle and Facebook page because Nique is a direct report and under SharePoint rules, that makes here a colleague
Now lets find someone that is not a direct report and that I didn’t add as an explicit colleague
122010_0648_ExtendingSh21.png

That’s it folks, I hope this helped… Im going to bed…
 
 

Sharepoint Anchors Made Easy with jQuery

 

Why Anchors?      What is the Big Deal?      How: HTML      The Script      How I Use It      Last Notes     

 

Why Anchors? Because no one wants to scroll!  

My quest to find an easy way to add anchors to a page in Sharepoint was entirely fueled by our use of the wiki. In our division, the wiki is a one-stop shop for everything staff need to know to do their jobs; an ever-expanding and evolving procedure manual. All their resources are linked into this one page, so no one is left wondering where to find anything. Using the method described in a previous article, Create an HTML Generator for Quick Wiki Creation, we are seeing an explosion of wiki content being created… and that is precisely the problem.

 ‘Findability’ is a challenge with the wiki. The assets of the wiki may also be its undoing. Creating multiple titles for entries and linking in dozens of outside or related resources can quickly stretch the navigational limits of a page. Organizing the information in an index style works well for staff to find what they need, but scrolling through several hundred titles was getting out of control. I really needed a way to jump staff quickly to the information they were after.

What’s the big deal? Create some anchors already…  <back to top>

I explored several options, but everything I found pointed to the need to open the Source Editor and search through the HTML code to change the hyperlinks. The problem with the WYSIWYG (What You See Is What You Get) editors in Sharepoint is that they save on the edit page, not the .aspx so anchor-hyperlinks either send an error or link back to the page in edit mode; hardly a good outcome. There had to be better way! Luckily just as I was getting desperate for a reasonable solution, I ran across Larry Pfaff at Sharepoint Hacker. Larry came up with a jQuery script (with a bit of help from Alexander Bautz) that makes it all possible. Hannah Zimbeck, our brilliant Sharepoint summer intern, built dozens of Larry’s solutions and by the end of August she had very nearly perfected what I was after. Hannah was fabulous, all that was needed was a bit of script and HTML cleanup, and then exploring how to make it upgrade to SP2010. 
 


How: HTML Source Code   <back to top>

Anchors can be implemented on any page and easily moved, so all that is needed is a page with an HTML editor, either on an existing wiki/publishing page,or a newly created one. Edit the page and paste the below code into the Text Entry – Webpage Dialog using the Edit HTML Source icon:

 

For Sharepoint 2007 wiki:

2010-12-20-SPAnchorsMadeEasy-02.png

For Sharepoint 2010:

2010-12-20-SPAnchorsMadeEasy-06.png

 

The HTML code below will create an A-Z index similar to this picture:

2010-12-20-SPAnchorsMadeEasy-01.png
 
 

A-Z Index HTML:


<a name="top"><P class=MsoNormal style="MARGIN: 0in 0in 0pt" align=center><BR><B><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Verdana','sans-serif'"><A href="#a"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">A</SPAN></A><SPAN>    </SPAN><A href="#b"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">B</SPAN></A><SPAN>    </SPAN><A href="#c"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">C</SPAN></A><SPAN>    </SPAN><A href="#d"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">D</SPAN></A><SPAN>    </SPAN><A href="#e"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">E</SPAN></A><SPAN>    </SPAN><A href="#f"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">F</SPAN></A><SPAN>    </SPAN><A href="#g"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">G</SPAN></A><SPAN>    </SPAN><A href="#h"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">H</SPAN></A> <SPAN>   </SPAN><A href="#i"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">I</SPAN></A><SPAN>    </SPAN><A href="#j"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">J</SPAN></A><SPAN>    </SPAN><A href="#k"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">K</SPAN></A><SPAN>    </SPAN><A href="#l"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">L</SPAN></A><SPAN>    </SPAN><A href="#m"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">M</SPAN></A><SPAN>    </SPAN><A href="#n"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">N</SPAN></A><SPAN>    </SPAN><A href="#o"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">O</SPAN></A><SPAN>    </SPAN><A href="#p"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">P</SPAN></A><SPAN>    </SPAN><A href="#q"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">Q</SPAN></A><SPAN>    </SPAN><A href="#r"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">R</SPAN></A><SPAN>    </SPAN><A href="#s"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">S</SPAN></A><SPAN>    </SPAN><A href="#t"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">T</SPAN></A><SPAN>    </SPAN><A href="#u"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">U</SPAN></A><SPAN>    </SPAN><A href="#v"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">V</SPAN></A><SPAN>    </SPAN><A href="#w"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">W</SPAN></A><SPAN>    </SPAN><A href="#x"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">X</SPAN></A><SPAN>    </SPAN><A href="#y"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">Y</SPAN></A><SPAN>    </SPAN><A href="#z"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">Z</SPAN></A></SPAN></B></P><P>
<P><A name=a></A><FONT color=#ffcc00 size=3>A</FONT> <BR></P>
<P><A name=b></A><FONT color=#ffcc00 size=3>B</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P></SPAN></P><A name=c></A><FONT color=#ffcc00 size=3>C</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=d></A><FONT color=#ffcc00 size=3>D</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=e></A><FONT color=#ffcc00 size=3>E</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=f></A><FONT color=#ffcc00 size=3>F</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=g></A><FONT color=#ffcc00 size=3>G</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A> </SPAN> </P>
<P><A name=h></A><FONT color=#ffcc00 size=3>H</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=i></A><FONT color=#ffcc00 size=3>I</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=j></A><FONT color=#ffcc00 size=3>J</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=k></A><FONT color=#ffcc00 size=3>K</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=l></A><FONT color=#ffcc00 size=3>L</FONT><FONT color=#ffcc00 size=3> </FONT> <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=m></A><FONT color=#ffcc00 size=3>M</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=n></A><FONT color=#ffcc00 size=3>N</FONT><FONT color=#ffcc00 size=3> </FONT> <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=o></A><FONT color=#ffcc00 size=3>O</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=p></A><FONT color=#ffcc00 size=3>P</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=q></A><FONT color=#ffcc00 size=3>Q</FONT><FONT color=#ffcc00 size=3> </FONT> <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=r></A><FONT color=#ffcc00 size=3>R</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=s></A><FONT color=#ffcc00 size=3>S</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=t></A><FONT color=#ffcc00 size=3>T</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=u></A><FONT color=#ffcc00 size=3>U</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=v></A><FONT color=#ffcc00 size=3>V</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=w></A><FONT color=#ffcc00 size=3>W</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=x></A><FONT color=#ffcc00 size=3>X</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=y></A><FONT color=#ffcc00 size=3>Y</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>
<P><A name=z></A><FONT color=#ffcc00 size=3>Z</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN></P>



After an anchor solution with heading titles like this article?  Use the below code. 

2010-12-20-SPAnchorsMadeEasy-07.png
 
Anchor Heading/Titles HTML:


<a name="top"><P class=MsoNormal style="MARGIN: 0in 0in 0pt" align=center><BR><SPAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Verdana','sans-serif'">
<A href="#title1"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 1</SPAN></A><SPAN>      </SPAN>
<A href="#title2"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 2</SPAN></A><SPAN>      </SPAN>
<A href="#title3"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 3</SPAN></A><SPAN>      </SPAN>
<A href="#title4"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 4</SPAN></A><SPAN>      </SPAN>
<A href="#title5"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 5</SPAN></A><SPAN>      </SPAN>
<A href="#title6"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 6</SPAN></A><SPAN>      </SPAN>
<A href="#title7"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 7</SPAN></A><SPAN>      </SPAN>
<A href="#title8"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 8</SPAN></A> <SPAN>      </SPAN>
<A href="#title9"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 9</SPAN></A><SPAN>      </SPAN>
<A href="#title10"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">TITLE 10</SPAN></A><SPAN>      </SPAN></B></P><P>
<P><BR>
<P><A name=title1></A><FONT color=#ffcc00 size=3>TITLE  1</FONT> <BR></P>
<P><A name=title2></A><FONT color=#ffcc00 size=3>TITLE 2</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P></SPAN></P><A name=title3></A><FONT color=#ffcc00 size=3>TITLE 3</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title4></A><FONT color=#ffcc00 size=3>TITLE 4</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title5></A><FONT color=#ffcc00 size=3>TITLE 5</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title6></A><FONT color=#ffcc00 size=3>TITLE 6</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title7></A><FONT color=#ffcc00 size=3>TITLE 7</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A> </SPAN> </P>
<P><A name=title8></A><FONT color=#ffcc00 size=3>TITLE 8</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title9></A><FONT color=#ffcc00 size=3>TITLE 9</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>
<P><A name=title10></A><FONT color=#ffcc00 size=3>TITLE 10</FONT>  <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>back to top</FONT></SPAN></A></SPAN> </P>


Now don’t get anxious about how long that bit of code is, really it is simple, here’s what it contains:

<A href="#b"><SPAN style="COLOR: windowtext; TEXT-DECORATION: none; text-underline: none">B</SPAN></A>
 

The first section of code creates the horizontal anchors to each of the vertical letters/titles below.

 

<A name=b></A><FONT color=#ffcc00 size=3>B</FONT>&nbsp; <A href="#top"><SPAN style="FONT-SIZE: 7.5pt; TEXT-DECORATION: none; text-underline: none"><FONT color=#ff0000>&lt;back to top&gt;</FONT></SPAN></A

 

The second section completes the link to the anchor and includes the <back to top> link. Each of these sections needs to include the formatting styles to keep the anchors and links from underlining in the wiki.

Note: Testing in SP2010 lead to the addition of the top anchor <a name=”top”> which is not needed in SP07. It was included as a consideration for upgrade-ability, omitting it in SP2007 does have a nicer result in taking the viewer to the top of the page, not just the top of the index. In SP2010 the links to top do not function without the anchor.

The Script: Makes it all Work  <back to top>

The script uses jQuery to reiterate through the hyperlinks with the # sign in them so that they save on .aspx page and link properly.  In a wiki library in SP2010, the script works exactly the same as in 2007.  However the new Enterprise Wiki Sites are in the same class as the Publishing Site Pages, both having the same wiki-like linking functionality.  Changing the class from ms-wikicontent to ms-rtestate-field is all that is needed
 

Wiki Libraries, 2007 or 2010, use this script:


 

<script type="text/javascript"  src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

<script type="text/javascript">
$(".ms-wikicontent a").each(function(){

	var thisHref = $(this).attr('href');
	var index = thisHref.indexOf('#');
	if(index>0){
		$(this).attr('href',thisHref.substring(index));
	}
});	
</script>


For SP2010 Publishing or Enterprise Wiki, use this one:

 
<script type="text/javascript"  src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

<script type="text/javascript">
$(".ms-rtestate-field a").each(function(){

	var thisHref = $(this).attr('href');
	var index = thisHref.indexOf('#');
	if(index>0){
		$(this).attr('href',thisHref.substring(index));
	}
});	
</script>


The script is linked to the Google hosted JQuery, which provides the opportunity to instantly see it work on the page, but I strongly recommend using a Scripting Resource Center or a Centralized Scripting Library (Document Library dedicated to holding scripts and code) for your site.  Store the most current version of jQuery there along with the script above saved as a .txt file.  Replace the call to jQuery in the script with the version stored in the local library, this allows the script to be re-used on multiple pages within the site. The script can then be linked using a Content Editor Web Part, Content Link field:

 2010-12-20-SPAnchorsMadeEasy-04.png

The 2010 Publishing Pages have multiple web zones, so adding a Content Editor to the page to link the script from the library is no problem.  On the Enterprise Wiki it worked the same to include the Content Editor directly within the wiki web zone, and hide the Title. 

Now all that is needed it to copy information into the index/anchor page to see it work properly.  The real advantage to this solution is that it can be created on any page and when all the information/links have been added the entire source code can be copied and pasted to a final destination page.  Simply apply a CEWP with the script linked to the final destination page before the paste and the end users will never experience an interruption in their work, they will however love the additions of quick links to their page! 

 

How I use it   <back to top>

I have encouraged each department to identify their most common processes or tasks. We use those as top navigation items to ‘bucket’ our information under that particular topic. The top section shown here illustrates that this is nothing more than a table created in Word and pasted into the Wiki. When a question arises users can explore the related topic and all information about that process is linked in to that wiki page. This may be organized in any manner that makes the most sense to those using it (blocks of text/extensive picture how-to/series of links to other pages/lists or libraries on the site.) However, as we know, not everyone thinks the same and may not recognize that their question actually falls into one of those top buckets. That is where the index is most valuable. Every topic within each bucket is always listed in the index as well, and it may be linked to that topic page with as many as four different titles, depending on how many ways it might be identified (after all that is how an index is meant to work, right?)

 
2010-12-20-SPAnchorsMadeEasy-05.png
  

Last Notes   <back to top>

Keep in mind that the Titles across the top are hyperlinks to the Titles below.  The fact that they don’t underline might cause some confusion as to how they actually work in the HTML.  When editing the names, put the additions within the active hyperlink so as not to break it, then delete the portions of the old title from either end (highlighted in picture below):

 2010-12-20-SPAnchorsMadeEasy-08.png

The linked vertical titles can be edited in any fashion, but the anchors are directly in front of the text, even though they can’t be seen, so keep that in mind when editing.  Adjusting the formatting, color, size, or font will not break the hyperlinks.  Information can be added and deleted to the page in the normal ‘edit page’ fashion. 

My focus in testing here was the SP2007 and 2010 Wiki Libraries, and SP2010 Publishing and Enterprise Wiki pages.  I didn’t test in a Content Editor web part, but I’m certain if the correct class is identified for the script it would work in the same manner.

Perhaps this doesn’t qualify as a ‘no code’ solution, but I think it is obviously born from a "minimize code exposure" intent!  Hopefully you were able to maximize the anchor use in this article to skip over all the boring stuff, and get right to the solution.  For that we can all extend a great big thank you to Larry Pfaff for his help with the jQuery, and Hannah Zimbeck for her faithful dedication to my project during her summer internship! 
 

Why Anchors?      What is the Big Deal?      How: HTML      The Script      How I Use It      Last Notes   

 

 

 

 

How logic flaws in SharePoint’s Element activation process can break Lookup fields

I came about this issue while I was – with as little markup as possible – deploying a Lookup field with a relative List reference. The list this field referenced was deployed with another feature in the wsp, upon which the field’s feature depended. All in all a very simple setup, with dependencies that make sense.

With Lookup and LookupMulti fields, you have two options when binding them to a second list. You can either specify the guid, or a relative url. Here’s an excerpt from MSDN, describing the "List" attribute:

Optional Text. Used to identify the list that is the target of a lookup field (Type="Lookup").

If the target list already exists, the value of the List attribute should be the string representation of the GUID (including braces) that identifies the target list. If the target is the same list as the one that the field belongs to, you can specify "Self".

If the target list does not yet exist, the value of the List attribute can be a web-relative URL such as "Lists/My List" but only if the target list is created in the same feature as the one that creates the lookup field. In this case, the value of the List attribute on the Field element must be identical to the value of the Url attribute on the ListInstance element that creates the target list.

In my case, seeing as I have no idea what the target list’s id is as the field’s feature is activating, I’d use the relative url. But wait a minute – the quote above states "[…] can be a web-relative URL such as ‘Lists/My List’ but only if the target list is created in the same feature as the one that creates the lookup field" – What?

That would be bad for two reasons.

  1. You’d be stuck putting a whole lot of functionality in one feature – something that’d generally make dependencies difficult to express, and impossible to get right.
  2. Lists deployed already; the guids of which you’d have no way of getting into your element markup, unless you either wrote supporting code to update the xml dynamically, or create and deploy the field entirely by code .. Both of which defeat the purpose of the xml markup in the first place.

So we can all agree that this limitation would have been awful – had what’s noted in the MSDN text actually been true. Because it most certainly isn’t.

After deploying a plethora of lookup fields – some exported from SharePoint, others written by hand – with varying luck referencing lists relatively, I got annoyed, and turned to Reflector.

Element activation process in detail

Long story short, SPFieldElement – which represents a <Field /> block in an elements.xml – has a method called PerformFixUpIfLookUpField. It is responsible for looking up a referenced web (through the WebId attribute) and list (through the List attribute), based on either guids, relative references or – in the case of webid – the special "~sitecollection". If the list reference is a relative one, and the method manages to find the list in question, the list reference will be updated with an actual guid.

So that’s all fine and dandy. If PerformFixUpIfLookUpField is called for a Field element, relative references will be dealt with. If it’s called. And this is where the catch is.

PerformFixUpIfLookUpField is called from two places, and both are in the same method: SPFieldElement‘s ElementActivated. From that method, PerformFixUpIfLookUpField is called if, and only if, either of these statements hold true:

  1. .. A field with the deployed field’s id already exists on the site and the field’s Overwrite attribute is "true" and the existing field isn’t sealed or readonly.
  2. .. The deployed field is in a sandboxed solution or its Overwrite attribute is "true".

This means that unless the Lookup field is deployed in a sandboxed solution, relative list references will never be resolved if the Overwrite attribute is anything but "true". That makes no sense. At all.

This makes me wonder, though – and do correct me if I’m wrong here. The Feature activation process strikes me as a central piece in the SharePoint code base, and judging by this logic flaw: not a single unit test was written to ensure its correctness.

In Summary

So long as you specify Overwrite="true" for Field elements of type Lookup, LookupMulti, TaxonomyFieldType, etc., relative references will work. And you can completely disregard the "created in the same feature" statement in the MSDN reference.​