HiddenNetwork.com Banner
Showing posts with label Code Sample. Show all posts
Showing posts with label Code Sample. Show all posts

Monday, April 21, 2008

Checking the current user's permission on a web site or a list by code

in answer to a question in the forums:

SPWeb has a "CurrentUser" attribute which gives you a SPUser object. the GetAssignmentByPrincipal function of the RoleAssignments collection of the SPWeb object will get you what roles the current user has.

 

here is an example:

SPWeb web = SPControl.GetContextWeb(this.Context);

SPRoleAssignment assignment = web.RoleAssignments.GetAssignmentByPrincipal((SPPrincipal) web.CurrentUser);

StringBuilder sb = new StringBuilder("");

foreach (SPRoleDefinition role in assignment.RoleDefinitionBindings)

{

sb.AppendLine(role.Name);

sb.AppendLine(role.BasePermissions);

sb.AppendLine("--------------------------------------");

}

 

 

This sample will give you a string with all the information. if you want to check if a use is allowed to add list items

 

note - don't dispose of the SPWeb object in the example above, since it comes from the context.

 

another note - this checks the web site permissions - not the permissions for a specific list. if you want to check a list you will have to get the roles from an SPList object. in the following example I am doing the same for the announements list in the current site:

 

SPList list = SPControl.GetContextWeb(this.Context).Lists["Announcements"];

SPRoleAssignment assignment = list.RoleAssignments.GetAssignmentByPrincipal((SPPrincipal) web.CurrentUser);

StringBuilder sb = new StringBuilder("");

foreach (SPRoleDefinition role in assignment.RoleDefinitionBindings)

{

sb.AppendLine(role.Name);

sb.AppendLine(role.BasePermissions);

sb.AppendLine("--------------------------------------");

}

 

How to find the default SSP with code

Answer: (update - thanks to Gary Lapointe who pointed out that my last post used internal objects, I actually tested my code and did more research and found that there is no way in the API to get to the default. If someone knows of a way I'm happy to learn. for now, here is a workaround that I have found - using the STSADM command line)

Process stsadmProcess;
stsadmProcess = new Process();
stsadmProcess.StartInfo.WorkingDirectory = @"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN";
stsadmProcess.StartInfo.FileName = "stsadm.exe";
stsadmProcess.StartInfo.Arguments = "-o enumssp -default";
stsadmProcess.StartInfo.UseShellExecute = false;
stsadmProcess.StartInfo.RedirectStandardOutput = true;
stsadmOutput = new StringBuilder("");
stsadmProcess.Start();
stsadmProcess.WaitForExit();
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(stsadmProcess.StandardOutput.ReadToEnd());
MessageBox.Show(xdoc.SelectSingleNode("//Ssp[@Default='true']").OuterXml);

Wednesday, February 06, 2008

Sample event handler to set a field as a pr imary key (enforce no duplicates)

Got this as a request from a reader- how to prevent users from adding items with same titles as ones that already exist in the list.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace SPSTIPS.SharePoint.EventHandlers
{
    public class TitlePrimaryEventHandler:SPItemEventReceiver
    {
        const string TITLE_QUERY = @"<Query><Where><Eq><FieldRef Name=""Title"" /><Value Type=""Text"">{0}</Value></Eq></Where></Query>";
        public override void ItemAdding(SPItemEventProperties properties)
        {
            if (properties.AfterProperties["Title"] != null)
            {
                //get the title of the new item
                string currentTitle = properties.AfterProperties["Title"].ToString();
                //get the web site object
                using (SPWeb web = properties.OpenWeb())
                {
                    //get the current list
                    SPList list = web.Lists[properties.ListId];
                    //query the list to check if there are items with the same title
                    SPQuery q = new SPQuery();
                    q.Query = string.Format(TITLE_QUERY, currentTitle);
                    SPListItemCollection itemsWithSameTitle = list.GetItems(q);
                    //if there are items, cancel the add, and show an error to the user.
                    if (itemsWithSameTitle.Count > 0)
                    {
                        properties.Cancel = true;
                        properties.ErrorMessage = "There is already an item with the title \"" + currentTitle + "\ in this list".";
                    }
                }
            }
        }
    }
}

Thursday, January 10, 2008

Getting user profile values that support multiple value

I noticed today that the MSDN article "User Profiles Object Model Overview" has a wrong code sample for the "correct way of retrieving a user profile property value". The code sample there will not work, because the object model in sharepoind doesnt have the methods and objects the sample code is using.
Therefore - here is my correct way:

if (Profile[this.UserProfilePropertyName].Count == 1)
    return Profile[this.UserProfilePropertyName].Value.ToString();
else
{
    StringBuilder ret = new StringBuilder("");
    UserProfileValueCollection values = Profile[this.UserProfilePropertyName];
    System.Collections.IEnumerator allValues = values.GetEnumerator();
    while(allValues.MoveNext())
    {
        ret.Append(allValues.Current.ToString());
        ret.Append(";");
    }
    return ret.ToString();
}

Tuesday, November 27, 2007

Avoiding CSS caching issues

I had a problem in several projects where we changed the CSS and deployed it to the load balanced server environment, and still the old CSS file was cached somewhere, and we couldnt roll out the changes to the users.
Another problem on the same line are browsers that cache the css files, and users do not see the changes we make to the css until we tell them to clear their browser's cache.

To resolve this, we took a trick from Microsoft - notice how Microsoft always links to the css with a version number in sharepoint? this is done to avoid caching when you put a new version.

My trick was to develop a custom web control (that I call the "CssInjector") that gets as a property the link to the css file, and renders that link with a random querystring. this forces the browsers (as well as IIS or other caching applications) not to cache the file.
The problem with this solutions is that it will have performance implications - slower loading of pages, so what I'd really like to do is what MS did - rely on a version number. However, I have yet to come up with the architecture that will let me manage version numbers in my solution package without having to manually setting the version every time. I thought of reading the version number from the CSS file itself, but that will introduce performance issues again - as the code will have to load and read the css file each time a page is opened.

So until I get a better solution, here is the code I am using for my web control:

using System;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
namespace SharePointTips.SharePoint.WebControls{
public class CssInjector : WebControl
    {
        private const string CSS_LINK_FORMAT = @"<link rel=""stylesheet"" type=""text/css"" href=""{0}"">";
        private string _CSSFileLink = "";
        public string CSSFileLink
        {
            get { return _CSSFileLink; }
            set { _CSSFileLink = value; }
        }
        protected override void Render(HtmlTextWriter writer)
        {
            if (this.CSSFileLink.Length > 0)
            {
                writer.Write(string.Format(CSS_LINK_FORMAT, this.CSSFileLink + "?k=" + GetUniqueKey(8)));
            }
        }
        public static string GetUniqueKey(int length)
        {
            //create an array of acceptable characters
            char[] chars = new char[62];
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
            StringBuilder result = new StringBuilder(length);
            Random rnd = new Random();
            //create the random string in the specified length
            for (int i = 0; i < length; i++)
            {
                result.Append(chars[rnd.Next(chars.Length)]);
            }
            return result.ToString();
        }
    }
}
And here is how I use it in the master page:
<%@ Register Tagprefix="SharePointTipsWebControls" Namespace="SharePointTips.SharePoint.WebControls" Assembly="SharePointTips.SharePoint.WebControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxx" %>
<SharePointTipsWebControls:CssInjector runat="server" ID="SharePointTipsCSS1" CSSFileLink="/_layouts/SharePointTips/SharePointTipsCustomCss.css" />

Wednesday, October 31, 2007

Another simple code snippet - using the user field

A user field in a list allows people to choose a user for each list item.

When looking at the value that is stored, we can see it is a lookup field - and is stored using the format ID#useraccount. For example:
listitem["User Name"] will have "1#development\ishai"
Click on the image below to see it in action in visual studio:

Where does this number come from? well, everytime you create a site in sharepoint, sharepoint creates a list for users. Each user gets an ID in the list, and when you add a user column to a list in that site, it is infact a special case of a lookup column into the users list. So in each site the same user will have a different ID.

So how do we use this field in code?

Get a user from a list item:


To get a user we use the SPFieldUserValue class, which accepts a SPWeb and a string value as parameters. The string value is the value from the list that contains the ID and the account name. Once we have that, we can use the SPFieldUserValue to get information about the user.
Example:

 using (SPSite site = new SPSite("http://portal"))

            {

                using (SPWeb web = site.OpenWeb())

                {

                    SPList list = web.Lists["Example For Users"];

                    SPListItem item = list.Items[0];

 

                    SPFieldUserValue userValue = new SPFieldUserValue(web, item["User Name"].ToString());

                    if (userValue.User.LoginName == web.CurrentUser.LoginName)

                    {

                        //do something!

                    }

                }

            }

Adding a value to a user field


This is the same, but in reverse. We use the same class (SPFieldUserValue ) and give it the values we want for the user.

using (SPSite site = new SPSite("http://portal"))

            {

                using (SPWeb web = site.OpenWeb())

                {

                    SPList list = web.Lists["Example For Users"];

                    SPListItem item = list.Items[0];

 

                    SPFieldUserValue userValue = new SPFieldUserValue(web, web.CurrentUser.ID, web.CurrentUser.LoginName);

                    item["User Name"] = userValue;

                    item.Update();

                }

            }


The image below shows the list after I updated the list item:

Wednesday, October 24, 2007

Code Practices - getting\setting values from\to the lookup and the hyperlink fields

How to get data from a field that is a lookup? how to set data into a field that is a hyperlink?
I don't believe I didn't write about this yet. It's so common!

For both cases, sharepoint object model exposes classes to help us get or set the data we want. These are the SPFieldLookupValue and the SPFieldUrlValue.

Example 1: Set the url field of a link


Use the SPFieldUrlValue class to create an object that holds the url to link to, and the title to display:

SPList list = web.Lists["Links"];

SPListItem newLink = list.Items.Add();

SPFieldUrlValue value = new SPFieldUrlValue();

value.Description = "test";

value.Url = "http://www.microsoft.com/sharepoint";

newLink["URL"] = value;

newLink.Update();

Example 2: Get the url field of a link


Use the SPFieldUrlValue class to create an object that gets the url and description:

SPList list = web.Lists["Links"];

SPListItem existingLink = list.Items[0];

SPFieldUrlValue value = new SPFieldUrlValue(existingLink["URL"].ToString());

string linkTitle = value.Description;

string linkURL = value.Url;

Example 3: Set the value of a lookup field for a known title and ID


In the following example I am using SPFieldLookupValue to set the value of a lookup field ("Group Name") to item "Program Operations", whose ID is 14:

SPList list = web.Lists["Branches"];

SPListItem newBranch = list.Items.Add();

newBranch["Title"] = "A New Branch";

SPFieldLookupValue newValue = new SPFieldLookupValue(14,"Program Operations");

newBranch["Group Name"] = newValue;

newBranch.Update();

Example 4: Get the value of a lookup field from an item


Here I am reading the value of the group name field (which is a lookup field in the branches list):

SPList list = web.Lists["Branches"];

SPListItem existingBranch = list.Items[0];

SPFieldLookupValue group = new SPFieldLookupValue(existingBranch["Group Name"].ToString());

int lookedUpItemID = group.LookupId;

string lookedUpItemTitle = group.LookupValue;

Wednesday, October 17, 2007

Using the SharePoint People Picker

A long time ago (beta2 time) I published a post about the people picker control (MOSS 2007 controls - have a bit of fun with the "people editor" form control!). Lately I had many comments on that post from people who have been having issues getting the value the user selected, and asked for another demo of using the control.
So here it is:

The code creates three controls - a PeopleEditor (from the Microsoft.SharePoint.WebControls namespace), a button and a textbox. The button has an click event that itirates over the users picked in the PeopleEditor and writes them to the text box. Simple!



public class PeoplePickerWebPart : System.Web.UI.WebControls.WebParts.WebPart

    {

        PeopleEditor pe;

        TextBox t;

        Button b;

 

        protected override void CreateChildControls()

        {

            base.CreateChildControls();

            pe = new PeopleEditor();

 

 

            this.Controls.Add(pe);

            b = new Button();

            b.Text = "Click me to see the users";

            this.Controls.Add(b);

            b.Click += new EventHandler(b_Click);

            t = new TextBox();

            t.TextMode = TextBoxMode.MultiLine;

            this.Controls.Add(t);

        }

 

        void b_Click(object sender, EventArgs e)

        {

            foreach(string ent in pe.CommaSeparatedAccounts.Split(','))

            {

                t.Text += ent + Environment.NewLine;

            }

        }

Results:
The web part as the page loads


The web part does "check names"


The web part after the click of the button

Using the SharePoint date control in a web part

After a lot of requests in comments in this blog, I figured this must be very hard to implement. So I tried, and found it quite easy. Here is a code sample of a web part that uses the sharepoint date control

The code is simple - three controls - a DateTimeControl from the Microsoft.SharePoint.WebControls namespace, a button and a text box.
The button has a click event. On click, the event sets the text box to display the date that was selected in the DateTimeControl.


public class DatePickerWebPart : System.Web.UI.WebControls.WebParts.WebPart

    {

        DateTimeControl dtc;

        TextBox t;

        Button b;

 

        protected override void CreateChildControls()

        {

            base.CreateChildControls();

            dtc = new DateTimeControl();

            dtc.ID = "dtc" + this.UniqueID;

            this.Controls.Add(dtc);

            b = new Button();

            b.Text = "Click me to see the date";

            this.Controls.Add(b);

            b.Click += new EventHandler(b_Click);

            t = new TextBox();

            this.Controls.Add(t);

        }

 

        void b_Click(object sender, EventArgs e)

        {

            t.Text = dtc.SelectedDate.ToLongDateString();

        }

    }

Here is the result:
This is the web part when the page first loads


This is the web part when you click on the date picker


This is the web part when you click on the button

See? it is simple!

Friday, September 28, 2007

Wrokaround - "Error" in navigation when creating a publishing site from code

Since I started working with sharepoint 2007 I was puzzled by this bug, and recently I asked more people how said they have that too. The problem happens when you use the API to create a site out of a template that has the publishing feature activated in it. The site is created ok, but the first user to open the site will see a very odd tab and quick-launch:

So far, I have no explanation, but I have a workaround. Make a call to the site in the code. I know - this adds process time and resources, but it beats having your users ask you about the "error tab".
In your code, add the following function, and call it just after the site creation code:

public static void OpenUrl(string url)
{
    try
    {
        HttpWebRequest request = (HttpWebRequest)
        WebRequest.Create(url);
        request.UseDefaultCredentials = true;
        HttpWebResponse response = (HttpWebResponse)
        request.GetResponse();
    }
    catch(Exception ex) {
    //do something with the error
    }
}
Here is an example how to use this function:
public static void CreateSite(SPWeb parentWeb)
{
    try
    {
        //get the template for the site using the GetWebTemplate function I wrote
        SPWebTemplate template = 
                SharePointFunctions.GetWebTemplate(parentWeb,
                        this.SiteTemplateName, 
                        ConfigVariables.SitesLCID);
        //create the site using the class properties
        newWeb = parentWeb.Webs.Add(this.ShortName, 
                this.SiteTitle, 
                this.SiteDescription, 
                ConfigVariables.SitesLCID, 
                template, 
                false, 
                false);
        OpenSiteHomePage(newWeb.Url);   
    }
    catch(Exception ex) {
    //do something with the error
    }
}

Tuesday, September 04, 2007

Just for me - excel macro to generate an XML

If you are here for a sharepoint tip, disregard this post- I am posting this to myself so I will not lose this code. Basically, it is an excel macro that genenrates an xml file from the current sheet. If you do want to use it, add a reference to microsoft xml in the VBA environment, and paste the code below to a module.
There are many way to improve it, but I just wrote this to quickly achieve a small task that I needed done.

Sub makeXml()
    ActiveCell.SpecialCells(xlLastCell).Select
    Dim lastRow, lastCol As Long
    lastRow = ActiveCell.Row
    lastCol = ActiveCell.Column
    
    Dim iRow, iCol As Long
    
    Dim xDoc As New DOMDocument
    Dim rootNode As IXMLDOMNode
    Set rootNode = xDoc.createElement("Root")
    Dim rowNode As IXMLDOMNode
    Dim colNode As IXMLDOMNode
    
    'loop over the rows
    For iRow = 2 To lastRow
        Set rowNode = xDoc.createElement("Row")
        'loop over the columns
        For iCol = 1 To lastCol
            If (Len(ActiveSheet.Cells(1, iCol).Text) > 0) Then
                Set colNode = xDoc.createElement(GetXmlSafeColumnName(ActiveSheet.Cells(1, iCol).Text))
                
                colNode.Text = ActiveSheet.Cells(iRow, iCol).Text
                rowNode.appendChild colNode
            End If
        Next iCol
        rootNode.appendChild rowNode
    Next iRow
    xDoc.appendChild rootNode
    xDoc.Save ("c:\temp\temp.xml")
    set xDoc = Nothing
    
End Sub
Function GetXmlSafeColumnName(name As String)
    Dim ret As String
    ret = name
    ret = Replace(ret, " ", "_")
    ret = Replace(ret, ".", "")
    ret = Replace(ret, ",", "")
    ret = Replace(ret, "&", "")
    ret = Replace(ret, "!", "")
    ret = Replace(ret, "@", "")
    ret = Replace(ret, "$", "")
    ret = Replace(ret, "#", "")
    ret = Replace(ret, "%", "")
    ret = Replace(ret, "^", "")
    ret = Replace(ret, "*", "")
    ret = Replace(ret, "(", "")
    ret = Replace(ret, ")", "")
    ret = Replace(ret, "-", "")
    ret = Replace(ret, "+", "")
    
    GetXmlSafeColumnName = ret
End Function

Wednesday, August 29, 2007

Web Services on SharePoint - making F5 Work

I was getting many questions on best practices to write custom web services for sharepoint, and I wanted to write an article about it for some time now. Also, I just had a chance to fiddle around with making F5 (run from visual studio into debug mode) work for a web service I am testing on a sharepoint site. This involved a few tricks, so I am documenting them:
Note - only do this on your development box!
Note - code lines marked in red means that you will need to change the values to your environment values.

I hold that the best practice is not to use the web service as a web application template (that is the default with visual studio) because when deploying to sharepoint, you'r web service usualy needs to sit in the layouts folder, and you do not want to deploy your code files there. You also don't want to use the visual studio publishing mechanism that uses the frontpage server extensions.
The alternative is creating a web service that is deployed as an asmx file to the layouts folder, pointing to a DLL that is deployed to the GAC. That makes it safe and secure, and easier to deploy and track versions.

Step 1 - Create a Web Service (DLL) Project
To create the web service, use Visual Studio 2005, and click "File-New-Project" and select ASP.NET Web Service Application.

If you don't have that project type installed, you may need to change the installed features of Visual Studio on your machine. The good think about this project type is that it creates a web service as a DLL and an asmx and not as a web site.

After the project is created, change the file names, namespace and assembly names as needed (make sure the namespace of the webservice attribute is changed - you don't want "http://tempuri.org/" as your namespace...) and most importantly - sign the assembly as strong name (right click the project, properties, signing tab, sign the assembly).

Now we have to find out the key that was given to the assembly when it was signed. To do that you must first build the web service (I use ctrl-shft-B, or right click the project and select build) and then either drag and drop the DLL to the assembly folder, right click and get the key:

Or you can run a command line from the visual studio SDK (start-programs-visual studio 2005-visual studio tools-Visual Studio 2005 Command Prompt) and type

sn -T "c:\temp\WebService1\WebService1\bin\WebService1.dll"

Once we have the public key token, we can change the web service's asmx file to use the DLL that will be deployed to the gac. Right click the service1.asmx file and choose "View Markup". You need to change the markup to point to the DLL that will be in the GAC, so this is the format you need to use:
<%@ WebService Language="C#" Class="WebService1.Service1, WebService1, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=db33ac6baa259170" %>
Expalantion:
  1. The "WebService1.Service1" part is the "namespace dot classname" of your code. To get that, you will need to open your .cs file, and copy the namespace you are using, add a dot after it, and add the class name.
  2. The "WebService1" (after the comma) is the name of the DLL that you may have set in the project properties under the "application" tab (the "assembly name")
  3. The public key token is the key we got earlier.

Note - I am not sure if this is needed, but I also registered my web service as a safe control in the web.config, before I used any sharepoint code. It's worth checking if this is required or not, and I will update if I have time to test.

Step 2 - Setting up the Build Events
While in the project's properties, switch to the "Build Events" tab. Paste the following in the "post-build event command line" box:

"c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /i "$(TargetPath)" /f
copy "$(ProjectDir)\*.asmx" "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS"
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\bin\RecycleAppPools.vbs"
"c:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\disco" http://localhost/_layouts/service1.asmx /o:"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS"


Explanation:
  1. The first line puts the DLL in the GAC.
  2. The second line copies the web service asmx file to the layouts folder. You should make sure your projects do not create same names for asmx files, or they will overwrite eachother!
  3. The third line recycles the application pools. I am doing this with a vbs file that I have written, and placed in the 12 hive's "\bin" folder for my comfort. Here are the contents of the file:
    Set locator = CreateObject("WbemScripting.SWbemLocator")
    Set Service = locator.connectserver(strServer, "root/MicrosoftIISv2")
    Set APCollection = Service.InstancesOf("IISApplicationPool")
    For Each APInstance In APCollection
    APInstance.Recycle
    Next
    echo "Recycle Complete."
  4. The last line build a discovery file for the web service in the layouts folder. if you installed your visual studio 2005 in a different location, you will need to change the folder path to the disco.exe file, and if you changed the name of the asmx (or if you have more than one) you will need to modify that as well. You will also want to change the url to point to a sharepoint site on your machine if you are not using the "localhost" header. In my case, I use the header "portal", so I change is to "http://portal".

Step 3- Change the F5 behavior
To change what happens when you press F5, switch to the "Web" tab, and change the start url to the url to the web service (I use http://portal/_layouts/service1.asmx), then change the "use IIS web server" and change the project url to the rool sharepoint url (I use "http://portal") and tick the "override application root URL and type the same url as the project url:

Last Step - set up your web.config to allow debugging
If you want F5 to work, and have visual studio debug the web service when you press it, the sharepoint web.config should be changed to allow debugging. To do that, you will need to open the web.config file for the virtual server you are using (in my case "http://portal" - which means the web.config is under "c:\Inetpub\wwwroot\wss\VirtualDirectories\portal80\web.config") and find the "<compilation>" tag, and set the attribute "debug" to true:

This is it!
If you didn't miss anything, you should be able now to press F5 and the web service will launch in the sharepoint context, with debugging in the visual studio! You can set breakpoints and debug the web service.

Wednesday, August 01, 2007

Modifying Search to Add a Send Link By Email link

My customer wanted a "send link by email" next to each search result item. "Easy", I thought and went ahead to edit the xslt for the search results. But I found out a problem with links that had spaces in them - the link in outlook would appear broken. To solve this, I had to write some simple javascript, and the process is described below.

Problem:
If you want a mailto link (href="mailto") that has in the body a link to a file, you may get a link like this:
href="mailto:body=http://server/site/library/this is my file.doc"
This is a problem, because the file name contains spaces. The email will open with a broken link in it's body:

Solution:
Luckily, SharePoint has a javascript functions that solves that for us. They are "escapeProperly" and "navigateMailToLink". What I did is create a function of my own in the page:

function SendEmailWithLink(link)
{
      var link = "mailto:?body=" + escapeProperly(link);
      navigateMailToLink(link);
      return false;
}

And my link is:
href="#nowhere" onclick="javascript:SendEmailWithLink('');"

This article would'nt be complete if I didn't explain why I did this - this if for the search results xslt - the idea was to add a "send link by email" button next to each search result. The solution - change the xslt to include the script above, and a button like this:

<a title="E-mail a Link" href="#nowhere">
      <xsl:attribute name="onclick">
         javascript:SendEmailWithLink('<xsl:value-of select="$url"></xsl:value-of>');
       </xsl:attribute>
       <img border="0" src="/_layouts/images/ICMSG.GIF"/> 
</a>

We also added other actions (I will try to get them published) and came up with the following for each result item:

Tuesday, June 26, 2007

Using DDWRT in xslt-based web parts

What is DDWRT?
well, its a script that microsoft packaged for it's xslt dataviews, that gives them more xslt power.
I needed to use the ddwrt functions in my content query web part, but I guess that the following approach will work in the search web parts as well.

Why do we need it?
I needed it to format the date I was getting back from the content query. The format I was getting back was ugly to the user (2007-06-27 15:52:00) and I wanted to format it, but I didn't want to write my own function.

So how to use it?
you need to add to your xslt the following namespace where the namespaces are declared (at the top of the xsl file, in the "xsl:stylesheet" tag):
xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"

Then you can use the ddwrt's functions. For example, to format the date I used the following:
<xsl:value-of disable-output-escaping="no" select="ddwrt:FormatDate(string(@Modified), 3081, 5)" />
Note that in the FormatDate function I used 2 hard coded values - 3081 which is the LCID for Australia (so the date will be formatted to Australian date format) and 5 which specifies the what do I want to display - date, time, date and time ect. I have no idea what values give what, but I do know that 5 gives me the date and the time.

Update to ItemAdding fiasco

This is to let you know that I updated my posts about the event handlers and ItemAdding, after I did some more research and found the correct way to get and set item's properties during ItemAdding. I swear this method did not work during Beta2 when I posted my old posts, and I swear that contacts in Microsoft did tell me it was by design and not possible (I have the emails to show that).
However, it turned out that it is possible, and easy, and I posted an update and a code sample in the old posts:
Bad news - synchronous list events bug (or missing feature)
Synchronous Add List event (ItemAdding) will not give access to item properties

Wednesday, June 20, 2007

Adding custom fields to the Enhanced Content Quey Web Part (Beta 1.2)

I just got around to adding another important feature to the Enhanced Content Query Web Part and I uploaded it to codeplex.
I am sure you read my articles, or articles by other MVPS like Heather about showing custom columns in the content query webpart. This requires (as Heather's article shows) that you export the web part to a file, manually modify the file to add the fields you want, and import it back.
This annoyed me enough to add a property to my ECQWP that will allow you to add those fields without having to export-import.

In this version (1.2) you have two new properties in the propeties pane - Data Fields and Common View Fields. I have yet to find out how Data Fields are used, so you don't need to use that property - it is enough that you set the Common View Fields.
I will refer you to Heather's article to find out the format that the value of this property should be written in - how to get the field's internal name, and how to get the field's type. Just a quick reminder though - the value in that field is a pipe seperated value of fields, where each field is a coma seperated pair of field (internal) name and field type. For example, a text field with internal name "Comments" will be added like so:
Comments,Text
A choice field with internal name "Client_x0020_Name" will be added like so:
Client_x0020_Name,Choice
and if we want to add both of the above fields, it will look like so:
Comments,Text|Client_x0020_Name,Choice


As always, get the web part, installation instructions and source code from codeplex. Just be sure to download the most recent version.

Tuesday, June 19, 2007

Enhanced Content Query Web Part upgraded to Beta 1.1

I am happy to announce that the Enhanced Content Query Web Part project has progressed to Beta 1.1, with some very important fixes to major bugs.

The web part now correctly prints out the item ID, has context menus for folders (not just documents) and supports displaying icons for documents.

Be sure to read the documentation (installation instructions) and use the new XSLT that is provided there. The old XSLT will not solve any of the bugs I fixed.

Wednesday, June 06, 2007

Allowing InfoPath forms to redirect to pages in other site collections

In InfoPath 2007, when you publish a form as a web form, you have the option to specify several query string parameters. One of those parameters is the "Source" parameter, which is "The location to which the user will be redirected when the form is closed. The URL must be in the same site collection or an error will be returned."
The error mentioned is "The following location is not accessible, because it is in a different site collection:" and the url you specified.

So it seems that specifying a source from another site collection is impossible. But what if I have a site collection dedicated to forms? lets say "http://forms", to which I link from the intranet home page or another page, and I want that when the user closes the form, he will be redirected back to the page in the intranet (lets say "http://intranet") that we started from?

I had to come up with a solution, and a simple one jumped to mind - a redirect page!
The trick is simple. Instead of specifying a link to "http://intranet" as the source parameter (which will cause an error), specify a link to a special page in the layouts folder, and give that page the query string that links to the page you want to return to.

Confused? here is an example:
Instead of linking to the form with this link:

http://forms/_layouts/FormServer.aspx?XsnLocation=http://forms/myforms/Forms/template.xsn&Source=http://intranet

Link to it like this:
http://forms/_layouts/FormServer.aspx?XsnLocation=http://forms/myforms/Forms/template.xsn&
Source=http://forms/_layouts/SPSTipsFormRedirector.aspx?target=http://intranet

As you can see from the links, the first one will cause an error because we are linking to another site collection, while the second one will work, because we are linking to the current site collection.

So the only thing left for me to show you is how to create the SPSTipsFormRedirector.aspx page that will handle the redirect to the page you specified in the red section in the second link:
Open your layouts folder (C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS) and create a new text file by the name SPSTipsFormRedirector.aspx. Open the file in a text editor such as notepad, and copy the following code:

<%
string target = Page.Request.QueryString["target"];
Page.Response.Redirect(target);
%>
<%
@ Page Language="C#" Inherits="System.Web.UI.Page" %>
<html>
</
html>

Repeate this in every front end server, and you are ready to write links to forms that, when closed, will go back to a page you specify in the link!

Tuesday, June 05, 2007

Enhanced Content Query Web Part goes Beta 1!

Hey Everyone!
This is to let you know that I released beta 1 of the Enhanced Content Query Web Part Project. Make sure you read the installation documentation to the end, because that is where you will find the sample XSLT that will cause the menus to appear.

Anyone want to volunteer to draw me a logo? Contact me!

Monday, June 04, 2007

Why isn't there a default value for lookup fields?

Oh, lookup fields, how I hate you.
I just realized that it is not possible in the user inteface to set a default value for a lookup field. Is it that hard to do an interface that will show the user creating the field the options from the looked-up list and allow him to select a default value?
Apperantly it is. I guess Microsoft were worried about what happens if someone deletes the looked-up value.

Not content with my findings, I tried looking in the object model and discovered that you can set up a d