<WebPartPages:WebPartZone PartChromeType="None" ID="Zone1" runat="server" Title="Zone1">
<ZoneTemplate>
<MyTag:MyWebPart runat="server" PartOrder="1" ></MyTag:MyWebPart>
</ZoneTemplate>
</WebPartPages:WebPartZone>
<WebPartPages:WebPartZone PartChromeType="None" ID="Zone1" runat="server" Title="Zone1">
<ZoneTemplate>
<MyTag:MyWebPart runat="server" PartOrder="1" ></MyTag:MyWebPart>
</ZoneTemplate>
</WebPartPages:WebPartZone>
SharePoint 2007 offers a nice and easy way for elevating your permissions using delegates.
The main catch is to remember that you cannot use any of the Context objects such as SPContext to directly retrieve your objects. But this can be easily overcome by using the ID attribute of the Site object in the Context to retrieve a copy of the site.
protected void MyMethod()
{
SPSecurity.CodeToRunElevated elevatedMethod = new SPSecurity.CodeToRunElevated(MyElevatedMethod);
SPSecurity.RunWithElevatedPrivileges(elevatedMethod);
}
private void MyElevatedMethod()
{
// do not use the Context object directly since it uses the logged in credentials
// instead create a new object using the site id retrieved from the Context
using (SPSite site = new SPSite(SPContext.Current.Site.ID))
{
// do your stuff here
}
}
Modifying the web.config file for SharePoint 2007 is more complicated than a typical ASP.NET web application. Although modifying the file by hand does work, if your production environment is a large farm, or if you need your solution to be easily re-deployable, manual modification is not the answer. So SharePoint provides us with an SPWebConfigModification object to handle changes.
My example deals with adding appSetting keys. Typically, these keys should be added when your feature is installed or activated. I chose the FeatureActivated event for this example.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{using (SPSite site = properties.Feature.Parent as SPSite)
{SPWebApplication webApp = site.WebApplication;
SPWebConfigModification configMod = new SPWebConfigModification();configMod.Name = "add[@key='" + key + "']";
configMod.Path = @"configuration/appSettings";configMod.Value = @"<add key=""MyKeyName"" value=""MyValue"" />";
// you can put whatever you want for the owner, but using the feature id is a good standardconfigMod.Owner = properties.Feature.DefinitionId.ToString();
configMod.Sequence = 0;
configMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
if (webApp.WebConfigModifications.Contains(configMod))webApp.WebConfigModifications.Remove(configMod);
webApp.WebConfigModifications.Add(configMod);
// I have seen several examples that only call ApplyWebConfigModifications // or that make the Update call after calling ApplyWebConfigModifications. // But the only way it works for me is to call Update first.webApp.Update();
webApp.WebService.ApplyWebConfigModifications();
}
}
The ApplyWebConfigModifications works through a timer job, so there may be a slight delay in the update.
I stole this removal routine from Vincent Rothwell. I like it because it keys off the owner and since I use the feature id as the owner, it knows exactly which entries to remove. It also helps to clean up any goofs I may have made in the file earlier.
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{using (SPSite site = properties.Feature.Parent as SPSite)
{RemoveAppSettingsByOwner(site, properties.Feature.DefinitionId.ToString());
}
}
public static void RemoveAppSettingsByOwner(SPSite site, string owner)
{SPWebApplication webApp = site.WebApplication;
Collection<SPWebConfigModification> modifications = webApp.WebConfigModifications;
int modCount = modifications.Count; for (int c = modCount - 1; c >= 0; c--)
{SPWebConfigModification modification = modifications[c];
if (modification.Owner == owner)modifications.Remove(modification);
}
if (modCount > modifications.Count) {webApp.Update();
webApp.WebService.ApplyWebConfigModifications();
}
}
In my SharePoint 2007 project, I created a workspace template that I wanted the customers to always use when they created their meeting workspace. So I needed to bypass the standard site creation page and programmatically add the sub site with the desired template.
Step 1 was to create my own custom list template based on the Calendar list. I need to do this so I could hide the Workspace column from the user. But more importantly, so I could add an event receiver that would do the work of creating the workspace site.
Step 2 was to create the event receiver for the ItemAdded event via a feature.
The feature.xml file is standard. Being sure to set the scope to "Web" as receivers cannot be scoped at the Site Collection level.
<?xml version="1.0" encoding="utf-8"?>
<Feature Id="YOURGUIDHERE"
Title="CalendarEventListEventReceiver"
Description="Description for CalendarEventListEventReceiver"
Version="12.0.0.0"
Hidden="FALSE"
Scope="Web"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml"/>
</ElementManifests>
</Feature>
For the elements.xml ensure that the ListTemplateId attribute references the type of your list template.
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Receivers ListTemplateId="10002">
<Receiver>
<Name>ItemAdded</Name>
<Type>ItemAdded</Type>
<SequenceNumber>10000</SequenceNumber>
<Assembly>Events.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=747f099a9bd1475c</Assembly>
<Class>Events.Web.EventListEventReceiver</Class>
<Data></Data>
<Filter></Filter>
</Receiver>
</Receivers>
</Elements>
Now create the receiver and override the ItemAdded method. Remember that the ItemAdded event is an asynchronous event, so you might not see your new site right away.
using System;using System.Collections.Generic;using System.Text;using Microsoft.SharePoint;using Microsoft.SharePoint.Meetings;namespace MyNamespace{public class EventListEventReceiver : SPItemEventReceiver
{public override void ItemAdded(SPItemEventProperties properties)
{ CreateWorkSpace(properties); base.ItemAdded(properties);}
private void CreateWorkSpace(SPItemEventProperties properties)
{ using (SPWeb web = properties.OpenWeb()) { try { //find the parent siteusing (SPSite site = web.Site)
{
// get a list of the custom templates so we can find our event template SPWebTemplateCollection templates = site.GetCustomWebTemplates(1033); SPWebTemplate template = templates["EventWorkspaceTemplate.stp"]; // create the new web siteSPWeb newWeb = web.Webs.Add(properties.ListItemId.ToString(), properties.ListItem["Title"].ToString(), properties.ListItem["Location"].ToString(), 1033, template, false, false);
// cast the web as a meeting so we can link it back to the event itemSPMeeting meeting = SPMeeting.GetMeetingInformation(newWeb);
meeting.LinkWithEvent(web, properties.ListId.ToString(), properties.ListItemId, "WorkspaceLink", "Workspace");
}
}
catch (Exception ex) { properties.Cancel = true;properties.ErrorMessage = ex.Message;
}
}
}
}
}
After installing the feature, be sure it is activated in the web site that your custom list exists.
Sheetal Jain again comes to my rescue by providing a very quick and easy workaround to an apparent SharePoint 2007 bug regarding tokens in custom actions. If you use the {ListId} token, it works fine if you are viewing the actual list. But if you view the list through a WebPart, it does not work. I found some other posts that came up with workarounds that were quite complicated. So I was very excited to find his solution.
To sum up, MOSS adds a context object (ctx) to the javascript side of each page. The object has a property called ListName which is actually the Guid for the list. So instead of using {ListId}, just concatenate "+ctx.ListName" to your url instead.
Update: This does NOT work if you are in a meeting workspace. I'll try to post my workaround later.
This example creates a very simple grid that as a checkbox on each row. Allowing you to select multiple rows at a time.
Note: You can do it in Html for simple controls, but if you need to add features such as grouping, you will probably have to build your grid programmatically. (see Oscar Medina's post for tips on how to do that)
1. Register the SharePoint web controls on the aspx page
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=11.0.0.0,
Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<SharePoint:SPGridView id="grid" allowsorting="true" autogeneratecolumns="false" runat="server">
<Columns>
<asp:TemplateField headertext="Select"><ItemTemplate>
<input id="select" type="checkbox" runat="server" />
<input id="itemID" type="hidden" runat="server" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</SharePoint:SPGridView>
3. If you know the data columns, you can declare them here too. Or you can build them dynamically at step 5 (see my previous post)
4. In your code-behind page, add a reference to your grid
public class MycodeBehind : System.Web.UI.Page
{protected SPGridView grid;
5. Create a method to build the data fields dynamically (see my previous post)
6. Wire up an event handler for the RowDataBound event.
grid.RowDataBound += new GridViewRowEventHandler(grid_RowDataBound);
7. Create your Event Handler method. This method will set the itemID field value to the ID of the ListItem. You can use this field on the post back to identify the ListItem.
private void grid_RowDataBound(object sender, GridViewRowEventArgs e)
{ if (e.Row.RowType == DataControlRowType.DataRow) { HtmlInputHidden itemID = (HtmlInputHidden)e.Row.FindControl("itemID");if (itemID != null)
{DataRowView data = (DataRowView)e.Row.DataItem;
itemID.Value = data["ID"].ToString();}
}
}
8. Add this code to the post back event (OnClick most likely). This will loop through your grid and find all the rows that are checked. Once you have a checked row, you can use the itemID value to quickly find the corresponding ListItem.
for (int i = 0; i < grid.Rows.Count; i++)
{ HtmlInputCheckBox selectCtl = (HtmlInputCheckBox)grid.Rows[i].FindControl("select"); HtmlInputHidden itemIDCtl = (HtmlInputHidden)grid.Rows[i].FindControl("itemID");if (selectCtl.Checked && itemIDCtl.Value != "")
{ // find the row so we can retrieve the values SPListItem myListItem = myList.Items.GetItemById(Convert.ToInt32(itemIDCtl.Value));
// do whatever you need to do}
}
Sheetal Jain points out something that no one else seems to mention when trying to create a custom action that pops up a new window in SharePoint 2007.
The standard method that all the samples show looks something like this:
However, this does not work. The new form does indeed popup, but the original window will then display just "[Object]". The solution is to add the keyword "void" in front of the window.open call:
Since the SharePoint 2007 SPGridView controls do not support autogeneration of columns, they typically have to be manually constructed by looping through the datasource and building a column for each field. This is straightforward enough if the datasource is an SPList. For an SPList, you can simple iterate through the Fields collection and add BoundField objects to the grid using the Title and InternalName properties for the HeaderText and DataField respectively.
In the case of an SPView, this is not so straightforward. The challenge is that the view object does not contain the display names of the fields it represents.
So here is how I have worked it out. The SPView object does have a ViewFields property which gives us a collection of the internal names for the fields. So we can use this collection to point to the fields in the parent list to find the other attributes such as the display name (or Title).
private void BuildColumns(SPView myView, SPGridView grid)
{SPViewFieldCollection viewFields = myView.ViewFields;
for (int i = 0; i < viewFields.Count; i++)
{foreach (SPField field in myView.ParentList.Fields)
{ if (field.InternalName == viewFields[i]) { if (!field.Hidden) { BoundField col = new BoundField();col.HeaderText = field.Title;
col.DataField = field.InternalName;
grid.Columns.Add(col);
}
break;}
}
}
}