This is a very minor behavior when compared with the entire scope, but it is one that I'd like to put a stop to.
I have created a very, very simple SharePoint Feature. It has two elements in its manifest: an aspx webpart page, and an elements xml. I'll paraphrase my elements xml, which just adds a module, below.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Pass" Url="" Path="">
<File Url="pasq.aspx" NavBarHome="True" Type="Ghostable">
<AllUsersWebPart WebPartZoneID="Left" WebPartOrder="0">
<![CDATA[
WARGH THIS PART DOESN'T MATTER FOR THIS QUESTION!
]]>
</AllUsersWebPart>
</File>
</Module>
</Elements>
Now, on the first time I deploy and activate this feature, it works properly. But if I have to deactivate and then reactivate the feature in order to fix some properties in the webpart, then I find myself with a second webpart on the page. Naturally, each similar cycle will just add more and more. I understand that a new webpart must be created in order to employ the changes I just made, but why is the old webpart still on the page? Can I make this older webpart go away automatically as part of the feature activation/deactivation process without needing to employ a Receiver class?
EDIT
Based on the fact that I've already employed a Receiver to solve this issue, I ended up just adding the webpart removal as part of feature deactivation. But I was still hoping that maybe there's something I'm missing.
Unfortunately the content will be repeated unless you do one of the following :
Solution 1 : you can add a similar code to the activate feature to add webparts in the pages as follows:
SPFile file = page.File;
using (SPLimitedWebPartManager WebPartManager =
file.GetLimitedWebPartManager(PersonalizationScope.Shared))
{
try
{
SPLimitedWebPartCollection webparts = WebPartManager .WebParts;
if (webparts.Count == 0)
{
//set your web part
WebPartManager.AddWebPart(webpart, "MomoZone", 1);
}
}
catch(Exception ex)
{
throw;
}
finally
{
WebPartManager.Web.Dispose();
}
}
Solution 2 : you can use site definition with and Onet file that runs only once the site is created so you would have no more of this problem while setting your efatures to hidden
Cheers
SharePoint doesn't offer a declarative way (xml) to remove feature elements automagically. You'll have to add some code in a FeatureDeactiving method on the feature receiver to remove it programatically, sorry.
Related
I have a custom document library template with content types. This works fine but the only thing that I would like to add is that when a user instantiates a new document library based on that template, that is has a predefined folder structure already in place.
I have tried adding Module tags in my schema.xml but this doesn't seem to work.
I know that it is possible to provision a document library with files and folders with a ListInstance feature but this is not possible in this case. I would like that the predefined folder structure is part of the document library template.
Is this possible?
Thanks
Maarten
If You want to achieve this using Document Library Definition. I don't think that would be achievable. What you can do is take help of list /document library templates.
1 Create a custom Doclibary the way you want.
2. create the desired folder structure. without uploading any documents.
3, Save the doc library as template by going to Doclibray settings ( make sure you store the template along with content stored into it)
Another method (which I must blog on soon) is to fake a list creation event. I add an empty view definition with a custom aspx page to the list template. The custom page simply executes some custom functionality on the list, deletes the initialisation view, then redirects to the normal view. It's a little messy, and it will only work if the list is created through the UI, but it works.
Here is a very quick example. You already have your list template. In the schema.xml file, add a new View to the Views element like so:
<Views>
<!-- Below is a blank view used to kick of initialisation after list creation. -->
<View DisplayName="Initialise" Type="HTML" DefaultView="TRUE" WebPartZoneID="Main" SetupPath="pages\Scratch\init.aspx" Hidden="TRUE" Url="_init.aspx">
<Toolbar Type="Standard" />
<ViewHeader />
<ViewBody />
<ViewFooter />
<ViewEmpty />
<ViewFields />
<ViewData />
<Query />
</View>
<!-- standard views would be here -->
</Views>
You may be able to go without the empty elements in there. That was something I was going to test further before blogging on it. But this will get the job done. The important things are:
This view is the first view and DefaultView is set to TRUE.
The SetupPath is set to a custom page that you will provision with your solution.
For the custom page (init.aspx in my example), I just made a copy of ...\12\TEMPLATE\Pages\viewpage.aspx and changed what the page inherits from. You could do this with inline code, but I used a codebehind assembly. So first line of that file becomes:
<%# Page language="C#" MasterPageFile="~masterurl/default.master" Inherits="SharePointScratch.InitPage,SharePointScratch,Version=1.0.0.0,Culture=neutral,PublicKeyToken=xxxxxxxxxxxxxxxx" %>
And then the codebehind:
using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
namespace SharePointScratch
{
public class InitPage : System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
SPList list = SPContext.Current.List;
list.ParentWeb.AllowUnsafeUpdates = true;
// Create you folder structure here...
// Fix the views by deleting the initialisation view.
SPView view = SPContext.Current.ViewContext.View;
list.Views.Delete(view.ID);
list.Update();
list.ParentWeb.AllowUnsafeUpdates = false;
// Redirect to the new default view.
SPUtility.Redirect(list.DefaultViewUrl, SPRedirectFlags.Default, this.Context);
}
}
}
Basically, we are relying on the SharePoint default behavior to display the default view of a list after creation. A custom view is inserted in the schema with the sole intention of firing off some custom code. The custom code does, well, whatever you want. After this, you clean up by deleting the special view from the template and redirecting back to the view.
I'm working on a sharepoint 2010 publishing site that has many subsites. I've set up a custom master page, and several custom page layouts.
I've discovered how to set the default page layout used for newly created pages in a subsite (found at /_Layouts/AreaTemplateSettings.aspx), but I can't seem to figure out how to specify the default page layout used to create ~/Pages/default.aspx when I create a new subsite.
Right now it selects WelcomeLinks.aspx, and that's not what I want.
Is this only available if I deploy custom master pages / layouts via code, and if so, does anyone have any good examples?
Thanks.
The Page Layout of a newly created subsite is determined by the site definition. For example, if you create a subsite using the Publishing Site with Workflow template, then that site is created using Configuration ID="2" from 14\TEMPLATE\SiteTemplates\BLANKINTERNET\XML\onet.xml. Within that configuration is a module section that points to SubWebWelcome:
<Module Name="SubWebWelcome" Url="$Resources:osrvcore,List_Pages_UrlName;" Path="">
<File Url="default.aspx" Type="GhostableInLibrary" Level="Draft" >
<Property Name="Title" Value="$Resources:cmscore,IPPT_HomeWelcomePage_Title;" />
<Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/WelcomeLinks.aspx, $Resources:cmscore,PageLayout_WelcomeLinks_Title;" />
<Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
</File>
</Module>
As you can see, SubWebWelcome provisions the default.aspx using the WelcomeLinks Page Layout.
If you want a different Page Layout for the default page, you have two options:
Create a custom site definition based on BLANKINTERNET that uses your desired Page Layout.
Continue to use the out of the box site definition with custom code (launched by either feature stapling or event receivers) that changes the Page Layout from WelcomeLinks.
You don't need to deploy a custom page layout but you do need to use code. The way we have solved this is to create an Event Receiver for the WebProvisioned event which will fire after a new SPWeb has been created.
What you can do is to update the PublishingPage in the new web with the Page Layout that you want. This allows users to create new webs but you to set the default Page Layout of each new web.
This is the event receiver code:
public override void WebProvisioned(SPWebEventProperties properties)
{
try
{
if (PublishingWeb.IsPublishingWeb(properties.Web))
{
PublishingWeb curPubWeb = PublishingWeb.GetPublishingWeb(properties.Web);
foreach (PageLayout curLayout in curPubWeb.GetAvailablePageLayouts())
{
if (curLayout.Name == "DefaultPageLayout.aspx")
{
foreach (PublishingPage curPage in curPubWeb.GetPublishingPages())
{
curPage.CheckOut();
curPage.Layout = curLayout;
curPage.Update();
curPage.CheckIn("");
}
break;
}
}
}
}
catch (Exception ex)
{
/* Handle exception here */
}
}
And this is the code to register the event receiver (this can be run when your feature is activated or can be run once from a PowerShell script or console application):
using (SPSite topSite = new SPSite("[Site Collection URL]"))
{
SPEventReceiverDefinition webEventDef = topSite.EventReceivers.Add();
webEventDef.Name = "Web Adding Receiver";
webEventDef.Synchronization = SPEventReceiverSynchronization.Synchronous;
webEventDef.Type = SPEventReceiverType.WebProvisioned;
webEventDef.SequenceNumber = 4001;
webEventDef.Assembly = "MyCustomAssembly, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=123456789";
webEventDef.Class = "MyCustomAssembly.CustomEvents";
webEventDef.Data = "Adding publishingwebfeatures";
webEventDef.Update();
}
If the Publishing feature is enabled at the site, it should be as simple as going to:
Site Settings, Look and Feel, Welcome Page and selecting the page
It seems that when you add publishing sub webs to SharePoint it doesn't seem to inherit the parent webs default page layout. What is more is even if you call SetDefaultPageLayout passing true to reset all sub-sites, this setting still does not stick.
After scaffolding my entire site structure (sub-webs), I had to implement the following recursive function to ensure the top most default page layout is being inherited, hopefully this helps someone.
// Recursively update sub-webs to inherit the default page layout.
Action<PublishingWeb> updateWebRecursive = null;
updateWebRecursive = new Action<PublishingWeb>((parentWeb) =>
{
PublishingWebCollection childWebs = parentWeb.GetPublishingWebs();
if (!parentWeb.Web.IsRootWeb)
{
parentWeb.InheritDefaultPageLayout();
parentWeb.Update();
}
foreach (PublishingWeb childWeb in childWebs)
{
updateWebRecursive(childWeb);
}
});
updateWebRecursive(pubWeb);
I have a webpart that displays HTML output within the RenderWebPart method and also creates controls within the CreateChildControls both methods are declared as overridden in the webpart.
My question is how to I control the order of the display of the controls and html output?
At the moment I call EnsureChildControls() within the RenderWebPart mthod to ensure all controls within the CreateChildControls are created and then the html out is rendered.
What if I wanted to display a control on the page then html output and then another control below in that order?
I would recommend moving all of your static HTML out of the Render function and into the CreateChildControls function. If you need to, you can add regular old HTML using Labels, WebControls, or even better... LiteralControls. Then you can just add them to your Controls collection.
Example:
WebControl container = new WebControl(System.Web.UI.HtmlTextWriterTag.Div);
StringBuilder sb = new StringBuilder();
sb.Append("<ul>");
foreach (Node child in this.Children)
{
sb.AppendFormat("<li>{1}</li>", url, name);
}
sb.Append("</ul>");
LiteralControl l = new LiteralControl();
l.Text = sb.ToString();
container.Controls.Add(l);
I'm actually relatively new to SharePoint and I was very astounded initially that one had to create individual controls manually, without a GUI interface like any normal ASP.NET web development involves. However, I had a very very good tip that allowed me to still use a GUI interface, and use it to create webparts for Sharepoint. It involves wrapping WebUserControls in webparts.
The link I used is here: http://www.a2zdotnet.com/View.aspx?id=95
You basically create WebUserControls (.ascx file) in a website, and so you can just add your controls like any normal .aspx page. You can also have a normal code-behind file (.ascx.cs). You then drag the .ascx file onto an .aspx file, so that it will then use your WebUserControl. When your .ascx files are ready and built, you copy them to the Layouts directory in the 12-Hive of your SharePoint Server. Best to create a sub-directory in there, to avoid clashing with other files in there already.
You then need to create a separate class Library project, that will have your WebPart code on. You then tell your WebPart to use your .ascx files in the layouts directory. Something like this:
protected override void CreateChildControls()
{
base.CreateChildControls();
try
{
this.Controls.Clear();
_myControl = this.Page.LoadControl("\\_layouts\\MyFolder\\WebUserControl.ascx");
this.Controls.Add(_myControl);
}
catch (Exception e)
{
err = e.Message;
}
The link I provided above provides more information, but basically you compile the webpart project and then add the DLL to the BIN directory of your sharepoint server (c:\inetpub\wwwroot\wss\ etc). You don't have to compile it to the GAC, by the way.
Then you add a entry to the web.config of your sharepoint server:
<SafeControl Assembly="MyWebUserControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5" Namespace="MyWebUserControl" TypeName="*" Safe="True" />
If you didn't compile the DLL to the GAC, but the BIN instead, you just need:
<SafeControl Assembly="UserControl" Namespace="UserControl" TypeName="*" Safe="True" />
Again, the link I posted above has it written in the code, a reference to the GUID, which is only needed if you placed the DLL in the GAC. You don't need the GUID part of the code if you only placed in the BIN directory.
Hope that helps.
Ash
I have an event receiver for a content type to validate some data in the ItemUpdating event. If I cancel the event (some of the data isn't valid for example), I set the properties cancel to true:
properties.Cancel = true;
properties.ErrorMessage = "...";
SharePoint cancels the updating event ok, but shows the standard SharePoint error page (with the specified message). Only problem is, I've got a complaint that this isn't actually very useful - we should return to the EditForm page so the details can be updated.
Has anyone done this, is there an easy way? The only suggestion I've had is that I can implement my own error page, but that's sounding quite a heavy solution to a (theoretically) simple process.
You could try to output HTML code (which includes javascript as well) in the ErrorMessage. BUT even if you do, the problem is that you have no safe way back to the data the user has entered. Either you make a HTTP/301 redirect and then it's a new page load, or you make the client go history.back() with JavaScript and then the browser may reload the page.
The official way of doing this is that you create a list definition and customize the list template. Then you edit the edit form template and include as many ASP.Net validator controls as needed. Then, implement the server side logic as you need. This article explains the technique: http://msdn.microsoft.com/en-us/library/aa543922.aspx
EDIT: To attach a custom control for editing of a specific contenttype, you add an XmlDocuments section to your ContentType definition. For instance, like this
<ContentType
..........
<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<Display>ContentTypeName_DispForm</Display>
<Edit>ContentTypeName_EditForm</Edit>
<New>ContentTypeName_NewForm</New>
</FormTemplates>
</XmlDocument>
</XmlDocuments>
.......
Then you create your own yoursolution_controltemplates.ascx file, which contains as well such blocks:"
<SharePoint:RenderingTemplate ID="ContentTypeName_DispForm" runat="server">
<Template>
<!-- put whatever controls you need here, we typically create a
separate custom control which implements everything-->
</Template>
</SharePoint:RenderingTemplate>
You can try to redirect using CopyUtil : http://weblogs.asp.net/jan/archive/2008/02/26/copyutil-aspx-a-little-sharepoint-gem.aspx
link = "http://yoursite/_layouts/CopyUtil.aspx?Use=id&Action=dispform&ItemId=X&ListId=X&WebId=X&SiteId=X";
Page.Response.Redirect(link)
maybe this will work
I want to use the MultipleLookupField control in a web page that will run in the context of SharePoint. I was wondering if anyone would help me with an example, which shows step by step how to use the control two display two SPField Collections.
I'm not entirely sure I understand your question, especially the bit about displaying two SPField collections. Sorry if this turns out to be the answer to a completely different question!
Anyway here's a quick demo walkthrough of using the MultipleLookupField in a web part.
Create a team site. Add a few tasks to the task list. Also put a document in the Shared Documents library. Create a new column in the Shared Documents library; call it "Related", have it be a Lookup into the Title field of the Tasks list, and allow multiple values.
Now create a web part, do all the usual boilerplate and then add this:
Label l;
MultipleLookupField mlf;
protected override void CreateChildControls()
{
base.CreateChildControls();
SPList list = SPContext.Current.Web.Lists["Shared Documents"];
if (list != null && list.Items.Count > 0)
{
LiteralControl lit = new LiteralControl("Associate tasks to " +
list.Items[0].Name);
this.Controls.Add(lit);
mlf = new MultipleLookupField();
mlf.ControlMode = SPControlMode.Edit;
mlf.FieldName = "Related";
mlf.ItemId = list.Items[0].ID;
mlf.ListId = list.ID;
mlf.ID = "Related";
this.Controls.Add(mlf);
Button b = new Button();
b.Text = "Change";
b.Click += new EventHandler(bClick);
this.Controls.Add(b);
l = new Label();
this.Controls.Add(l);
}
}
void bClick(object sender, EventArgs e)
{
l.Text = "";
foreach (SPFieldLookupValue val in (SPFieldLookupValueCollection)mlf.Value)
{
l.Text += val.LookupValue.ToString() + " ";
}
SPListItem listitem = mlf.List.Items[0];
listitem["Related"] = mlf.Value;
listitem.Update();
mlf.Value = listitem["Related"];
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
EnsureChildControls();
}
Granted, this is borderline ridiculous -- everything is hard-coded, there is no error-handling at all, and it serves no useful purpose -- but it's only meant as a quick demo. Now build and deploy this web part and add an instance of it to your team site's homepage; it should allow you to get and set the tasks which are associated with the first document in the library.
The strange bit towards the end of the button Click handler, where we read a value from mlf.Value and then write it back again, appears to be required if you want the UI to stay in sync with the actual list values. Try omitting the last line of bClick to see what I mean. This has been driving me nuts for the last hour or so, and I'm hoping another commenter can come up with a better approach...
Hm. Works fine on mine, so let's see if we can work out how your setup is different...
It looks as though it's having trouble populating the control; my first guess would be that this is because the code makes so many assumptions about the lists it's talking to. Can you check that you've got a plain vanilla Team site, with (assume these names are case-sensitive):
A list called Tasks, with several items in it
A library called Shared Documents with at least one document
A column called Related in the Shared Documents library
The Related column is a Lookup field into the Title column of Tasks, and allows multiple values.
The first document in Shared Documents has a value for Related
Then add the webpart. Fingers crossed...
Hm. OK, I'm still trying to break mine... so I went to the layouts directory and created a file foo.aspx. Here it is:
<%# Page Language="C#" Inherits="System.Web.UI.Page" MasterPageFile="~/_layouts/simple.master" %>
<%# Register Tagprefix="foo" Namespace="Foople" Assembly="Foople, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5"%>
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<foo:WebPart1 id="fred" runat="server" />
<foo:WebPart1a id="barney" runat="server" />
</asp:Content>
WebPart1 is the webpart from before. WebPart1a is the exact same code, but in a class that inherits directly from WebControl rather than from WebPart.
It works fine, apart from a security validation problem on the postback that I can't be bothered to debug.
Changing the masterpage to ~masterurl/default.master, I uploaded foo.aspx to the Shared Documents library, and it works fine from there too -- both the WebControl and the WebPart behave properly, and the security problem is gone too.
So I'm at a loss. Although I did notice this page with an obscure might-be-bug which is also in SPFolder.get_ContentTypeOrder(): http://forums.msdn.microsoft.com/en-US/sharepointdevelopment/thread/63baf273-7f36-453e-8293-26417759e2e1/
Any chance you could post your code?