SharePoint updates hardcoded link html-tag in layout source code

September 7th, 2009

I recently stumbled upon a strange behaviour in a customers MOSS solution. We have inherited the support contract from another supplier, and I had to update their solution with a new front page template to be used for the different market sites, which in turn were linked from a global landing page.

Now, the upgrade went fairly well and all was in place after deployment except for one thing; the link from the landing page to their “global” market site was malfunctioning. The customer had created the new start page (named start.aspx) and renamed the old one to start_backup.aspx. The link to this page from the landing page was a hard-coded A HREF in the page layouts source code. The strange behavior is that SharePoint changed this hardcoded link in its database to lead to start_backup.aspx – It IS a ghosted layout, but I find it odd that SharePoint modifies it’s source. I had to open the page layout in SharePoint Designers designview and point the link to start.aspx manually to solve the problem.

peter Sharepoint, Uncategorized

Optional SPFieldLookupValue column in list and (none) yields different results!

February 5th, 2009

Suppose you have a list with a lookup field to another list, that is optional. Selecting “(none)” in the lookup yields different values in the column, depending on the layout of the lookup.

Lookup fields have different displays depending on the number of items in the list. It’s either a regular dropdown list (
<select>) or a select with 8 visible rows that pops up after you click the arrow-down icon.

Normal view:

 

Normal select-box, less than 20 items

Normal select-box, less than 20 items

 

 

Other view:

 

Irregular select-box, more than 20 items

Irregular select-box, more than 20 items

 

 

The first alternative gives the field value

null

and the second alternative gives the field value

"0;#"

The normal view is for lookups with less than 20 values.

I have not found anything about this behaviour in the documentation.

LookupField control

The control rendering the output for a lookup is the LookupField. Using reflector I found the reason in code (but not in the docs):

In CreateChildControls there is an if-statement checking if there are more than 20 items in the list, rendering the “Other view”.

Look at the DataSource property:

if (!lookup.Required && !lookup.AllowMultipleValues)
{
    DataRow row = table.NewRow();
    row[0] = 0;
    row[1] = SPResource.GetString("LookupFieldNoneOption", new object[0]);
    table.Rows.Add(row);
    num++;
}

peter Sharepoint

Remove the Title link, replacing it, in list views?

January 14th, 2009

Today a colleague of mine noticed the Title field both in the newform and list view of a custom list we are developing for WSS3. The list itself is not at all complicated, it’s an FAQ entry with two fields: Question (text) and Answer (richtext).

Replacing or removing the Title field was easy as pie, just remove it from the contenttype, with the following snippet inside the contenttype declaration:

<RemoveFieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"/>

But, removing the title field also removes the handy link to view or edit the list item. Google gave very little on the subject, but I found some goodies here (scroll down to ChrisF post). So without removing the title field, I am going to examine how we can move the view/edit link to another field in the list, looking at how the original Title field works.

I fired up Caml Viewer to take a look on the list. Point Caml Viewer to the subweb containing the list, and view the list tab:

spcamlviewer

There is xml for the actual list, and the fields LinkTitle and LinkTitleNoMenu are defined in there, copy all text into your favourite editor and do a search for “LinkTitle” and “LinkTitleNoMenu”, respectively, and copy the entire <Field> declarations to a new editor window.

Now, generate a pair of fresh new Guid’s, replacing the ones in the two fields, and give them new names. I chose to call mine LinkQuestion and LinkQuestionNoMenu to keep the naming convention. Also go through the fields replacing references to the Title field with references to the Question field, etc.

Xml code for the two fields, after replacing Title for Question:

<Field ID="{1B451C6D-8766-4292-AEDA-62AB3413A66D}" ReadOnly="TRUE" Type="Computed" Name="LinkQuestionNoMenu" DisplayName="Question" Dir="" DisplayNameSrcField="Question" AuthoringInfo="(linked to item)" EnableLookup="TRUE" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="LinkQuestionNoMenu" FromBaseType="TRUE">
<FieldRefs>
	 <FieldRef Name="Question" />
	 <FieldRef Name="LinkFilenameNoMenu" />
</FieldRefs>
<DisplayPattern>
	 <IfEqual>
		  <Expr1>
			   <LookupColumn Name="FSObjType" />
		  </Expr1>
		  <Expr2>1</Expr2>
		  <Then>
			   <Field Name="LinkFilenameNoMenu" />
		  </Then>
		  <Else>
			   <HTML><![CDATA[<a onfocus="OnLink(this)" href="]]></HTML>
			   <URL />
			   <HTML><![CDATA[" ONCLICK="GoToLink(this);return false;" target="_self">]]></HTML>
			   <Column HTMLEncode="TRUE" Name="Title" Default="(no title)" />
			   <IfEqual>
					<Expr1>
						 <GetVar Name="ShowAccessibleIcon" />
					</Expr1>
					<Expr2>1</Expr2>
					<Then>
						 <HTML><![CDATA[<img src="/_layouts/images/blank.gif" class="ms-hidden" border=0 width=1 height=1 alt="]]></HTML>
						 <HTML>Use SHIFT+ENTER to open the menu (new window).</HTML>
						 <HTML><![CDATA[">]]></HTML>
					</Then>
			   </IfEqual>
			   <HTML><![CDATA[</a>]]></HTML>
			   <IfNew>
					<HTML><![CDATA[<IMG SRC="/_layouts/1033/images/new.gif" alt="]]></HTML>
					<HTML>New</HTML>
					<HTML><![CDATA[">]]></HTML>
			   </IfNew>
		  </Else>
	 </IfEqual>
</DisplayPattern>
</Field>
<Field ID="{33AF1D6E-10D6-4e55-858F-C96836777A0E}" ReadOnly="TRUE" Type="Computed" Name="LinkQuestion" DisplayName="Question" DisplayNameSrcField="Question" ClassInfo="Menu" AuthoringInfo="(linked to item with edit menu)" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="LinkQuestion" FromBaseType="TRUE">
	<FieldRefs>
		 <FieldRef Name="Question" />
		 <FieldRef Name="LinkQuestionNoMenu" />
		 <FieldRef Name="_EditMenuTableStart" />
		 <FieldRef Name="_EditMenuTableEnd" />
	</FieldRefs>
	<DisplayPattern>
		 <FieldSwitch>
			  <Expr>
				   <GetVar Name="FreeForm" />
			  </Expr>
			  <Case Value="TRUE">
				   <Field Name="LinkQuestionNoMenu" />
			  </Case>
			  <Default>
				   <Field Name="_EditMenuTableStart" />
				   <SetVar Name="ShowAccessibleIcon" Value="1" />
				   <Field Name="LinkQuestionNoMenu" />
				   <SetVar Name="ShowAccessibleIcon" Value="0" />
				   <Field Name="_EditMenuTableEnd" />
			  </Default>
		 </FieldSwitch>
	</DisplayPattern>
</Field>

These fields are then placed in the list definitions schema.xml, in /List/MetaData/Fields.

Don’t forget to update ViewFields to reflect the change:
Change

<ViewFields>
	<FieldRef Name="LinkTitleNoMenu">
  </FieldRef>
</ViewFields>

Into

<ViewFields>
	<FieldRef Name="LinkQuestionNoMenu">
  </FieldRef>
</ViewFields>

and

<ViewFields>
	<FieldRef Name="LinkTitle">
  </FieldRef>
</ViewFields>

Into

<ViewFields>
	<FieldRef Name="LinkQuestion">
  </FieldRef>
</ViewFields>

Then just remove the Title field from your contenttype and you’re set!

Result:

question_link

A word of caution. More things depend on the Title-field, for example the breadcrumb and title bar, so these fields will display (no title), after this operation:

result_missing_title

peter Example, Sharepoint , ,

Left outer linq to sql join with a parameter

December 19th, 2008

Left outer joins in linq to sql are somewhat hard to grasp, at least for me. I was very happy when I managed to figure out how to make a left outer join with a parameter, something that is a common procedure in one of the projects we are developing; the system has a lot of language-dependent data in child-tables that needs to be outer joined so that we still get the fields out even though something is missing a translation.

T-Sql

I am starting this posting by showing what I want to accomplish in T-SQL, to set the scene for the DLINQ stuff that comes down below. This is not hard at all to accomplish in T-SQL, but in order to show the example data and how it fits together, I chose to start out with this section.


The data source I will run this example on looks like the following:
diagram
Here is a script to create the sample datasource in SQLEXPRESS (I used LINQPad):

CREATE DATABASE LEFTOUTERTEST
USE LEFTOUTERTEST

CREATE TABLE Note (
	Id INT NOT NULL PRIMARY KEY,
	NoteTypeId INT NOT NULL
)
CREATE TABLE NoteType (
	Id INT NOT NULL PRIMARY KEY
)
CREATE TABLE NoteLang (
	NoteId INT NOT NULL,
	LanguageId INT NOT NULL,
	Text NVARCHAR(200),
	CONSTRAINT [PK_NoteLang] PRIMARY KEY CLUSTERED
	(
		NoteId ASC,
		LanguageId ASC
	)
)
CREATE TABLE Language (
	Id INT NOT NULL PRIMARY KEY
)
ALTER TABLE Note  WITH CHECK ADD CONSTRAINT [FK_Note_NoteType] FOREIGN KEY([NoteTypeId]) REFERENCES [NoteType] (Id)
ALTER TABLE NoteLang  WITH CHECK ADD CONSTRAINT [FK_NoteLang_Language] FOREIGN KEY([LanguageId]) REFERENCES [Language] (Id)
ALTER TABLE NoteLang  WITH CHECK ADD CONSTRAINT [FK_NoteLang_Note] FOREIGN KEY([NoteId]) REFERENCES [Note] (Id)
-- insert two note types
INSERT NoteType VALUES (1)
INSERT NoteType VALUES (2)
-- insert three notes
INSERT Note VALUES (1,1)
INSERT Note VALUES (2,1)
INSERT Note VALUES (3,2)
-- insert two languages
INSERT Language VALUES (1)
INSERT Language VALUES (2)

-- insert language-dependent data
INSERT NoteLang VALUES (1, 1, N'Note id 1, language 1')
INSERT NoteLang VALUES (1, 2, N'Note id 1, language 2')
INSERT NoteLang VALUES (2, 1, N'Note id 2, language 1')
INSERT NoteLang VALUES (3, 1, N'Note id 3, language 1')

View of the sample data, note that Notes with ID 2 and 3 are not translated into language with ID=2.

SELECT
	NoteId,
	Text,
	LanguageId
FROM
	NoteLang

Results:
resultset_allitems1

The following is what we are trying to accomplish, join all rows that lacks translation to a specific language:

SELECT
	note.Id,
	note.NoteTypeId,
	noteLang.Text,
	noteType.Id as NoteTypeId
FROM
	Note note
LEFT OUTER JOIN NoteLang noteLang on noteLang.NoteId = note.Id and noteLang.LanguageId = 2
INNER JOIN NoteType noteType on note.NoteTypeId = noteType.Id

Results:
resultset_leftouter1

Linq to sql

Using linq to sql, the method of doing a left outer join is documented here among other places. I have not found any example on how to add a parameter (as the example above; LanguageId) to the left outer join query, in order to specifically outer join some of the matching outer data.

from note in Note
join noteLang in NoteLang on note.Id equals noteLang.NoteId into noteLangs
join noteType in NoteType on note.NoteTypeId equals noteType.Id
from noteLangGroup in noteLangs.Where(l => l.LanguageId == 2).DefaultIfEmpty()
select new {
	NoteId = note.Id,
	LanguageId = (noteLangGroup != null ? noteLangGroup.LanguageId : 2),
	Text = (noteLangGroup != null ? noteLangGroup.Text : null),
	NoteTypeId = note.NoteTypeId
}

Results from the LINQ query for the same datasource:
resultset_leftouter_linq

peter Code, LINQ ,

AvailableWebTemplates – for WSS

December 17th, 2008

MOSS publishing infrastructure gives the possibility to declare which web templates that are available on subwebs, in a feature property:

<Properties>
   <Property Key="AvailableWebTemplates" Value="*-MyTemplateName#1"/>
</Properties>

But this is not possible in the same way for WSS. The object model gives us two hints: SPWeb.SetAvailableWebTemplates and SPWeb.SetAvailableCrossLanguageWebTemplates These methods can be used to achieve the same goal: controlling which web templates that are available for creation from a certain site template in the UI (although I have not got the cross language version to work). A feature receiver in a feature stapled to my site template can be used to set which web templates that are available. The code for to set which templates are available are contained in a separate method:

/// <summary>
/// Set available templates for creating subwebs
/// </summary>
/// <param name="web">the web it concerns</param>
/// <param name="siteTemplateNamePredicate">how to match the template name</param>
public static void SetAvailableSubWebTemplates(
	SPWeb web, Predicate<String> siteTemplateNamePredicate,
	bool currentLocale)
{
    Collection<SPWebTemplate> collection = new Collection<SPWebTemplate>();
    foreach (SPWebTemplate template in web.GetAvailableWebTemplates(web.RegionalSettings.LocaleId))
   {
       if (template != null &&
           template.IsSubWebOnly &&
           siteTemplateNamePredicate.Invoke(template.Name))
       {
            collection.Add(template);
       }
   }
   if (collection.Count > 0)
   {
       web.SetAvailableWebTemplates(collection, web.RegionalSettings.LocaleId);
       web.Update();
   }
}

Finally, how to use this method from the feature receiver:

Predicate<String> predicate = new Predicate<string>(s => s.StartsWith("STS"));
SetAvailableSubWebTemplates(web, predicate);

peter Sharepoint , ,