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".";
}
}}}}}
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();
}
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" />
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();
}
}
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 =
newSPFieldUrlValue(existingLink[
"URL"].ToString());
stringlinkTitle = 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;
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 = newPeopleEditor();
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;
}
}
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.
publicclassDatePickerWebPart : 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();
}
}
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 templatefor the site using the GetWebTemplate function I wrote
SPWebTemplate template= SharePointFunctions.GetWebTemplate(parentWeb,
this.SiteTemplateName, ConfigVariables.SitesLCID);
//create the siteusing 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
}
}
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
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.
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
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.
Labels:
Code Sample,
Content Query Web Part,
Enhanced Content Query Web
Part,
Free Web Parts,
Open Source,
SharePoint 2007
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.
Labels:
Code Sample,
Content Query Web Part,
Enhanced Content Query Web
Part,
Free Downloads,
Free Web Parts
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!
Labels:
Code Sample,
InfoPath,
SharePoint 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!
Labels:
Code Sample,
Content Query Web Part,
Enhanced Content Query Web
Part,
SharePoint 2007
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 default value for a lookup field using the
following code:
SPList docs = w.Lists["Documents"];
SPFieldLookup fl = (SPFieldLookup)docs.Fields["City"];
fl.DefaultValue = "1;#Mexico City";
fl.Update();
I will add this option to my
Utility Pack when possible.
Using the above code solves the issue
almost totaly. When you use the web interface to add a list item or upload a document,
the default value comes up in the lookup box.
However (and this is the big one), from the office applications
(both 2003 and 2007) the default value is ignored and the lookup box always begins
at the "blank" option.
Should I waste one of my MSDN support
calls on this and ask MS for a fix? or should I just learn to live with it?
Meanings of variables in the
context menu's script
If you are building a web part that
displays context menus for list items - just like the context menus that SharePoint
builds out of the box, you need to know the meaning of the attributes that you need
to add to the table that holds the item. I couldn't find any documentation or referance
to it on the internet, so I decided to publish the results of my diggings.
If you look at the source html of the built-in web part,you will see that every
item with a context menu looks like this:
<table height="100%" cellspacing="0"
class="ms-unselectedtitle" onmouseover="OnItem(this)" id="1"
ctxname="ctx1"
url="/Documents/Sample%20Document.docx"
dref="Documents"
perm="0x7fffffffffffffff"
type=""
ext="docx"
icon="icdocx.gif|Microsoft Office Word|SharePoint.OpenDocuments"
otype="0"
couid=""
sred=""
cout="0"
hcd=""
csrc=""
ms="0"
ctype="Document"
cid="0x0101006121B9A5B6A75F49AD8620D335B47E62"
uis="1024"
surl="">
The question is asked - what do those attribute mean? what is "cout" and what is
"otype"? what values are expected there?
After some research, here is my dictionary:
-
ctxname:
Name of the context object on the page that holds the context for the list that
you are connecting to.
-
url:
link to the document (can be either relative or absolute.
-
dref:File
Directory Referance - the relative folder path the item\document is in. For example
if I have a document library called "documents" with the url "http://portal/documents",
the items in the root folder will have "documents" as the dref value, while items
in a folder called "test" will have "documents\test".
-
perm:
has to do with permissions, but I didn't figure out how it works yet.
-
type:
Seems not to be used for items - but to create menu seperators or different kind
of menus.
-
ext:
The document extension. For example "doc", "docx", "ppt" and so on.
-
icon:
This seems to control the "edit" menu item. Here you specify the icon that will
be displayed next to the "edit in..." menu item, the text that will appear as the
name of the application (edit in Microsoft Word) and the script that will be used
to edit the item (I have no idea what possible values can be used here). All of
this seperated by the "|" character.
-
otype:
Current Item FSObj Type. This seems to have something to do with the check in-check
out menus, that will not be displayed if this is 1, but I could not figure out what
is the logic here.
-
COUId:
Current Item Checked Out User Id. (ID of the user in the web site the item is from)
-
sred:
Server File Redirect. Allows you to specify a URL that will be used instead of the
file's url when the link is clicked or when a menu option is used. For example,
if you put a link to another document, all menu actions will be redirected to that
document instead.
-
cout:
Current Item Checkedout To Local. If the item is checked out to a local (offline)
folder and not to the database. 0 for false, 1 for true.
-
hcd:
Controls if a menu item to navigate to the "/_layouts/updatecopies.aspx" will be
added. I am guessing this has to do with records management (update copies of the
document when it was changed).
-
csrc:
Copy source link. If the item is a copy of an item in a records management site,
this holds the link to the source, and adds the menu item for going to the source
file (Go to source).
-
ms:
Current Item Moderation Status. Also has to do with if its checked out or not, and
if its draft or not.
-
ctype:
The type of the item. I could not find where that is used.
-
cid:
Content Type ID. The ID of the content type for the item. also seems to be used
for list menus to determine the content type to create when a user clicks on the
new item menu.
-
uis:
Current Item UI String. Seems to hold the version number, where the major version
is a multiplication of 512, and the minor version is the reminder (the script gets
the minor version number by doing "%512"). For example, a value of 512 is version
1.0 while 513 is version 1.1 and 1024 is version 2. Does this mean we have a version
limit of 511 minor versions in sharepoint? According to the "introduction to versioning",
this is only the default and the administrator can change this. This sounds fishy
considering the "512" limit is hard coded in the javascript.
-
surl:
Source Url. This dictates where the page will go back to after a menu item is clicked.
I keep forgetting this, and I think
I had to figure it out from scratch at least 4 times allready, so I may as well
blog it.
The thing is - last time I figured it out I wanted to blog about it, and then saw
that someone beat me to it - Heather Solomon already
wrote about it
in her blog, and I seem to remember someone else wrote a similar script.
The thing that throws me off every time is realizing that the XML that the content
query web part builds for each item has the values in the attributes. So what you
need to do is write xslt code to display the attributes.
Heather's article says exactly how
to modify the files, so if you need that kind of help, go there now. Below is the
code that I am using (slightly modified):
<xsl:template name="ShowXML" match="Row[@Style='ShowXML']"
mode="itemstyle">
<xsl:for-each select="@*">
</br>
Name: <xsl:value-of
select="name()" /> Value:<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
I have just managed to do something
really cool with the web part, but I have to refine it a bit before I publish it.
I promise this will knock your socks off!
skip to main |
skip to sidebar
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".";
}
}
}
}
}
}
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();
}
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" />