Monday, April 30, 2007

SharePoint Designer Article 1 - how to edit a page?


In SharePoint 2003 (WSSv2) with FrontPage, it was so easy to edit a page. Just open the page you want to edit in Internet Explorer and hit the "Edit in FrontPage" icon at the toolbar.

Well, the good news are that in SharePoint 2007 (WSSv3) the same applies, but only for some of the pages, while other pages will tell you "This page cannot be edited in SharePoint Designer".

This article will explain what is going on, and how to get each type of page to be editable in SharePoint Designer.





Lets start easy- when you create a WSSv3 web site from one of the default template such as team site, blank site etc...you will have no problems using the easy button - edit in SharePoint Designer:



You can also open the SharePoint site or page for editing from within SharePoint Designer using the "Open Site" or "Open.." dialogs within the application:







When you open such a page from such a site directly from Internet Explorer, or from SharePoint Designer, you will see the page in SharePoint Designer, and be able to modify it using the designer mode or the code mode:




So far so good right?

So lets figure out why some of the pages give us the following message when we try to edit them in SharePoint Designer:

"This page cannot be edited in SharePoint Designer. You can edit the content in the browser, or edit the corresponding page layout in SharePoint Designer"

This page cannot be edited in SharePoint Designer. You can edit the content in the browser, or edit the corresponding page layout in SharePoint Designer



Why is that? well, simply because the page you are trying to edit is under the publishing feature. It is a publishing page, and as such, by default, gets it's layout from the layout page and the master page.

The only change you should be doing on such a page is edit it in the browser and add\remove\change web parts in it.


However, there is a way to work around this - detach it from it's layout page. This is similar to the ghosting\unghosting process that we had back in SharePoint 2003 (WSSv2), but with the added benefit that we can allways roll back the change.
It also means that in the first time you will edit that page, you will have to start from SharePoint Designer to do it:


Open the site that contains the page in SharePoint Designer (use "File>Open Site") and browse to the page you want to edit in the folder list.

Right click the file, and choose "Detach from page layout". This will unghost the file - copying it's layout from the layout page into the database, and so allowing you to edit it, just like you used to do in SharePoint 2003:




Unlike sharepoint 2003, you can take it back, and reattach the file:






Best practice:

Although unghosting is no longer a big bad wolf since it can be undone, you still shouldn't be doing it. It means that the page is disconnected from the layout page that defines the layout for all of the pages in the site. If you will want to make a change to the layout template in the future, this page will not change, unless you reattach the file, but in that case your changes to the file's layout will be lost.


So the best practice from SharePoint 2003 remains - only change a file using SPD if you must. There are ways to avoid editing a file in SPD and still making the changes you thought you had to do using SPD. For example - create a new layout page template, and change the page to use that layout.

Now, I hear you asking - "what about dataviews? we can only add them using SPD!". And I will cover that in my next article on the best practice of how to add a dataview using SPD to a page, without detaching it from its layout page.

If you cannot wait until I find the time for this article, just click on the "frontpage" tag in my blog - and see how I did it with sharepoint 2003 and frontpage. The method is the same.






One of more field types are not installed properly

Another FAQ that I see a lot from developers - they get the error "One of more field types are not installed properly" in their applications.
No, don't blame your database in being corrupt, and don't go beat up the guy who create the list and the fields. The problem is simple:
You'r code is doing a CAML query, and uses a field name that does not exist.

I hear you say "but I checked my CAML query, and the field names are correct", and I have to remind you - field names in CAML queries should be internal names, not display names.
This is why, if you have a field called "Parent Task" (for example), in CAML you should refer to it as "Parent_x0020_Task", because the internal name is encoded to avoid special characters.
Also, the internal name may be totaly different from the display name - this will happen if you created a field and then renamed it. So the internal name remains the same as the old name, but the display name changed.
Another scenario for this is when you created your own list definition, and specified different internal and display names. (this happened to me recently)

So, how to know what is the internal name for your field? simple - just click on it!
Let me elaborate - create a view in the list that has that field, and click on the field to sort the list by it. In the web address (url) of the page you are directed to, the internal name of the field should be displayed, after the query string "SortField=".
If the field does not allow sorting, you can go to the list settings and click on the field, as if you want to change it's settings. Again, the internal name of the field will be in the URL address of the page.
Please note, that the field names in the URLs are encoded again, and you will have to compensate for that. for example, a field with a space like "Parent Task" will not be displayed like I said before in the address bar as "Parent_x0020_Task", but instead as "Parent%5fx0020%5fTask" (the underscore character was replaced by the unicode equivalent "%5f").

So you want an easier way to find the internal name (without coding)?
Just do yourself a huge favour and download U2U CAML Builder which is a terrific application to help you build CAML queries.
Since the tool knows it has to use internal names, it just does it for you!.
I cannot live without this tool
U2U CAML Builder screen

Explanation for sharepoint begginers - the difference between web parts and lists (my two web parts are showing the same data)

Did you try adding another web part to a page and it displays the same data as another web part on the page? Here is the answer.

As part of my habit of posting in this site an answer for any question I see more than once in the forums, I want to share with you an explanation I use in the forums to let sharepoint begginers realize what is the difference between a web part and a list.

The confusion sounds like this:
"I'm a newbie to the SharePoint world so if this is a really amateur question, you know why. I am currently tasked with setting up the SharePoint site for my department. I have tried to put two instances of the Links web part on the top level of my site. It will allow me to do this but when I make a change to one of the web parts the change also occurs in the other Links web part." (taken from an msdn forum post)

To which I reply:
You are confusing "web parts" and "lists"
A "web part" is a mechanism to display data, while a "list" is like a small database - a mechanism to store data.

What you did, is have one list (links, or contacts) and two web part displaying the same list - same data.

If you want two different sets of data then the easy way would be to create another list, and add a web part to look into that list.

Another option is to create a field in the list by the name (for example) "show in web part 1" of type boolean, and then configure the two webparts that show the same information to show based on that field (this method is known as a filter).

Saturday, April 28, 2007

Content Query Web Part, with a marquee

Meron Fridman has released a free web part - a version of the content query web part that displays the content it found as a marquee. Read about the web part in Meron's blog.
The content query with marquee webpart's properties pane - allows you to set how the marquee works.

Thursday, April 26, 2007

Announcing a new series of articles - SharePoint Designer DataViews

I see a growing amount of questions about the SPD dataviews, or questions that can be answered by "use the dataview web part", so I thought I will start a new series of articles of "how-to"s to show how to do common actions using the dataview web part.

I will start by an article on best practices of using the dataview web part - how you should approach using it. Then I will start showing examples of modifying it, using either the built-in menus, or custom xslt samples.
This is actually an upgrade to some articles I have written in the past on how to use the xslt dataview in frontpage, on this blog and in MSD2D.com.

While I am always open to suggestions on what to show, please keep in mind that I will not do your projects for you. My aim is to show how this cool feature can make your life easier, so don't bother posting complex scenarios of things you want to achieve. Simple questions like "can you explain how to use conditional formatting" or "can you give an example on how to transpose a list, so that the rows are columns" will be (probably) accepted.
Also, if you are asking for something, leave your email address (will not be published) so I can notify you when it's published.

So, keep your eyes opened on this blog (you mean you are not registered by RSS? not even by email? well, it's time!)

Content Query limitations, and Object module access rights requirements

Some new articles were published just now by Microsoft about sharepoint limits that were undocumented until now:
  1. The Content Query Web Part does not support more than 1000 lists at the same time.
    This is actually not a content query web part problem, but a SPSiteDataQuery problem, which the content query web part uses. If you are using SPSiteDataQuery in your code, or a Content Query web part in your page, you may see the error "The query cannot be completed because the number of lists in the query exceeded the allowable limit".
    So you have to be extra careful that your content query web part in not set up to query more than that amount.Read the Microsoft Article (opens in new window).
    Possible error message:
    "The query cannot be completed because the number of lists in the query exceeded the allowable limit. For better results, limit the scope of the query to the current site or list or use a custom column index to help reduce the number of lists."
  2. Error Message when running an application that accesses the object module.
    This is actually something I have encountered with my Utility pack and answered some people in the forums about this, and apperantly MS saw fit to document it properly now.
    When running an appliation on the server that tries to use the object module, you have to run with a user account that has sufficient permissions. Not just on the site, and not just on the server!:
    1. The user is a server farm administrator.
    2. The user has permissions to access the content database.
    3. The user is a site collection administrator.
    4. The user has permissions to access the Windows SharePoint Services site or the SharePoint Server 2007 site through which the code iterates.

    Notice requirement #2? this is where I failed with my utility pack on a client server - I was running the tool with an account that had local administration rights on the server, but the account didn't have any permissions on the database.
    This is also true for running stsadm operations such as backup and restore, or feature deployments... (something they don't say in the article...).
    Read the Microsoft Article (opens in new window).
    Possible error messages:
    "Unhandled Exception: System.IO.FileNotFoundException"
    "Access Denied" (The Microsoft article does not mention this, but I have seen this error for the same issue)

Wednesday, April 25, 2007

Error: "An item with the same key has already been added." When modifying the search results web part's XSLT

When you modify the xslt in the people core results web part, you may get the following error (I get it everytime) when you click "OK" in the web part settings:
System.ArgumentException: An item with the same key has already been added. 
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 
at Microsoft.Office.Server.Search.WebControls.PeopleCoreResultsWebPart.SetSortedRefinementDataOnHiddenObject() 
at Microsoft.Office.Server.Search.WebControls.PeopleCoreResultsWebPart.GetXPathNavigator(String viewPath)
This is very annoying and I do not know how to solve it. But if you get it, don't worry - your change has been made, and you just need to check-in\publish the page to see the effect. The problem ofcourse is that you may not want to check-in the page without seeing the result first, and there is no way around this...

Monday, April 23, 2007

While I am linking, here is CAML.NET

Another cool tool - go to the the CAML.NET codeplex site and install it.
Now, why didn't I think of that?
Thanks John Holliday.

Using a SharePoint list as an authentication provider

I normally don't post links to other peoples' articles, but when something makes me say "wow" or "why didn't I think of that". This tip is really cool.
It shows in an easy to follow step-by-step process how to make a "users" list in sharepoint, and then make sharepoint (or any other .net application) authenticate users based on that list.
The possibilities are endless. Thank you Willie Rust.

Sunday, April 15, 2007

Changing a list schema in a feature requires IIS reset to take effect

This is more a note to myself than anything smart I have to say:
When you build a list definition, and deploy it as a feature, if you make a change to the schema, the change will not take effect until you do IISRESET.
Deactivating the feature and uninstalling it, then installing it and activating it will not do the trick - you must either recycle the application pool or do IISReset.

Thursday, April 12, 2007

Fixing the Lookup fields in List Definition Schema

[Update 24/04/07]
I tried Donal's solution and it didn't work for me. Did it work for anyone else?.
Also, I should mention that this is for WSS3 (2007)

[Update 14/04/07]
A commenter by the name of Donal McWeeney updates me that it is possible to refer to other lists by using the relative path in the "List" attribute in the schema. That will make most of the code below unnessecary - which is good. but will not solve the List="Self" problem (when we want the lookup to look into the list we are creating), because we want the user to be able to create many lists with different relative paths. So my code is still useful, but please read the comment by Donal before starting. If his method solves your problem you should use it!
[End Update 14/04/07]

Here is the first output out of the nested tasks project - a solution for the challange of createing list definitions with lookups.

The Problem
If you want to create a feature that will deploy two (or more) lists, with a lookup field connecting them, you are in trouble.
The schema for defining a lookup field requires you to put the ID (Guid) for the list you are referancing. This is a problem since you don't know the ID - this is randomly generated when the list is created.

The Solution

  1. Define the lookup list as you would:
    <Field Type="Lookup" DisplayName="Parent Task" Required="FALSE" List="Self" ShowField="Title" UnlimitedLengthInDocumentLibrary="FALSE" ID="{94f89715-e097-4e8b-ba79-ea02aa8b7abc}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="ParentTask" Name="ParentTask" ColName="int3" RowOrdinal="0" />
  2. Install my "LookupFixer" web control. Here is the Source code (I will create this as a download as part of the Nested Tasks project when I can):
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Xml;
    using System.Text;
    using System.Web;
    using System.Web.UI.WebControls;
    using System.Xml.Serialization;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;



    namespace SharePointTips.SharePoint.WebControls
    {
        
    public class LookupFixer : WebControl
        {
            
    private string lookupFieldName = "";        
            
    public string LookupFieldName
            {
                
    get
                {
                    
    return lookupFieldName;
                }
                
    set
                {
                    lookupFieldName = 
    value;
                }
            }
            
    private string targetListName = "";
            
    public string TargetListName
            {
                
    get
                {
                    
    return targetListName;
                }
                
    set
                {
                    targetListName = 
    value;
                }
            }
            
            
    protected override void OnLoad(EventArgs e)
            {
                
    SPList currentList = SPContext.Current.List;
                FixLookUps(currentList);
                
    base.OnLoad(e);
            }
            
    public void FixLookUps(SPList list)
            {
                
    string fieldName = this.LookupFieldName;
                
                
    if (list.Fields.ContainsField(fieldName))
                {
                    list.ParentWeb.AllowUnsafeUpdates=
    true;                
                    
    SPFieldLookup field = (SPFieldLookup)list.Fields[fieldName];
                    
    SPList targetList = GetTargetList();
                    
    //check if the field link to the list is broken. 
                    if (field.SourceId != targetList.ID.ToString())
                    {
                        
    //create the new lookup field from the old field
                        string newFieldXml = field.SchemaXml;
                        
    string title = field.Title;
                        list.Fields.Delete(field.InternalName);
                        list.Update();
                        
                        
    //put the ID of the current list as the source for the lookup
                        newFieldXml = newFieldXml.Replace("http://schemas.microsoft.com/sharepoint/v3", targetList.ID.ToString());
                        newFieldXml = newFieldXml.Replace(
    "Self", targetList.ID.ToString());
                        
    //change the ID of the field to create it as a unique new field.
                        newFieldXml = newFieldXml.Replace(field.Id.ToString(), System.Guid.NewGuid().ToString());
                        
    //TODO - check if the old field was in the default view, and set it in the optionns
                        string newFieldID = list.Fields.AddFieldAsXml(newFieldXml);
                        list.Update();
                        
                        
                    }
                }
            }
            
    private SPList GetTargetList()
            {
                
    if (TargetListName.Length == 0)
                    TargetListName = 
    SPContext.Current.List.Title;
                
    return SPContext.Current.Web.Lists[TargetListName];
            }
        }
    }
  3. Modify the "AllItems.aspx" to include this control in the following format:
    <%@ Register Tagprefix="SharePointTips" Namespace="SharePointTips.SharePoint.WebControls" Assembly="LookupFixer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=074b83dc3e6008ae" %>
    <SharePointTips:LookupFixer ID="LookupFixer1" runat="server" TargetListName="Announcements"  LookupFieldName="Parent Task" />

What did we just do?
The web control has two properties that allow you to configure the lookup. TargetListName can remain empty if you want the lookup to be to the same list (self referencing list) - like we want it to work in nested tasks, and LookupFieldName is the name of the lookup field that we are trying to create and is broken.
The control, since it is embedded in the "AllItems.aspx" view, will get triggered when the list is created from the list definition, because the AllItems.aspx is the default view.
(*If you change the default view in the schema, make sure you also embedd the control in that aspx file*)
The code in the control reads the properties, loads the list, loads the field, gets the field settings from the schema, and then - because it is impossible to change the broken field - we delete the field, and create a new one from the schema.

The only thing missing here is changing the code to also add the new field to the views that the old field was part of. I didn't have time to look into that yet.

Note!
This is an ugly workaround that works. If anyone has a better solution - please let me know!

Wednesday, April 11, 2007

SharePoint Tips Utility Pack version 1.2 is releaed


I thought that I may as well clean up the utility pack before I start the nested tasks, so here it is - version 1.2 of the SharePoint Tips Utility Pack.


The new version has all the features of the old one, plus my event handler registration utility that allows you to register an event handler, to a specific list, or to all lists with the same name in all the sites under a site.

Notice 3 interesting things about this screen - one is the "Register in sub sites" checkbox, which I explained what it does, the second is the "browse..." button - which allows you to pick your event handler DLL file, and the application will automatically fill the "assembly" box and will show you the classes list in the class dropdown. Finally, the "Data" text box allows you to specify data for the event handler, which can then be access in the event handler code from the event properties by using the "ReceiverData" property of the properties object. I find that very useful, do you?







I also improved some of the multithreading functions in the delete sites screen, and added recursion to the "field settings" screen, which has also been improved by letting you rename the field. Together with the recursion this is a very powerful tool - just imagine you deployed a solution with 100 sites and suddenly realize you misspelled one of the field names...



So here is the link again- version 1.2 of the SharePoint Tips Utility Pack. You can dowload the code, the application or even a setup file - that I recommend you use since in future versions the setup can replace and upgrade if you use the setup now.



P.S. - I am looking for a logo for the application. This will appear in the Help-About screen and should be 131px wide and 259px tall. As you can see, the logo I painted...just sucks. Also if you have better ideas for an Icon (32x32 and 16x16) for the application...








Tuesday, April 10, 2007

STP Language converter - free download

My friends in Kwizcom have done it again.
Below is a link to their free STP language converter which "translates a site template file (.stp) to a desired language".
This would be very useful if you want to install the Microsoft templates (the "fantastic 40") but you are running a sharepoint server in alanguage that Microsoft didn't release the templates in.
See this in the Kwizcom blog or just download the tool from their site.
If you want to know how they do it - they are telling you that too!

Good work guys!

Monday, April 09, 2007

Defining a Lookup field in the schema is impossible?

I have been trying to get around a problem that has been troubling me with the Nested Tasks design.

You see, I wanted to put the whole list configuration into a list definition, which is basically an xml file which is easy to maintain. However, since we have a need for a lookup field, it looks like we are going to need to write code (a feature event receiver) to add that field, instead of having it in the list definition.

Why?

To define a lookup field, you need to specify at least two things:

  1. The GUID of the list its connecting to
  2. The name of the field in the list it connects to
  3. You may also specify (optional) the type of join and which field to display, so you can potentially connect to the ID field, but display the title field.

My problem is with item number 1 above – we don't have the GUID of the list, and there seems to be no way in the XML to simple tell it "I want you to connect to this list!".

When I was writing the design I was aware of this issue, but I was hoping research will uncover a way to accomplish that. I thought "there must be a built in feature that defines a lookup!", but I was wrong.

So unless any one of my readers knows how to define a lookup field in a list's schema, I will start working on a feature event receiver that will add the field to the list.

Come to think of it – this may actually be better, because now we can create a feature that just changes the existing lists, and not create a new list.

What do you think?

Sunday, April 08, 2007

How to lose customers with a publicity stunt? (off topic)


Take a lookm at what UbiSoft are doing...It started as a nice promotion for their games for the PS3, offering a free playstation draw. The terms and all the pages say that the winners will be declared on April 5th...

Here is a screen shot from today (April 9th) of the winner page:



It's hard to imagine what the people in UbiSoft were thinking. Is this how they want their potential customers view them?







SharePoint Nested Tasks Project is published



What does this mean? That you can go to codeplex and view the source code as it changes (there is nothing there but skeletons right now) and most importantly - read the specs as a word document, add to the wish list or just give any kind of feedback in the discussion forum.







Saturday, April 07, 2007

SharePoint Nested Tasks Architecture Draft

I was invited by Lawrence Liu from Microsoft to merge my project for nested sharepoint tasks into the community kit for sharepoint 3 he is working on. I agreed, but I will still be working on the feature seperatly with all the volunteers who contacted me so far (sorry guys - you don't get credit until you actually do something...).


So I had a fine Saturday writing the first draft for the architecture of the solution. Here is is, open for discussion. Sorry about the formatting, but publishing from word is never perfect.




 


  1. Table of Contents


 

1.    Table of Contents    2

2.    Table of Figures    2

3.    Project Details    3

3.2.    Purpose    3

3.3.    Project Description – What are "Nested Tasks"    3

3.4.    Why SharePoint?    3

4.    Specifications    4

4.2.    The Built-In SharePoint Tasks List    4

4.3.    Nested Tasks Requirements    4

4.4.    Out of Scope    5

5.    High Level Design    6

5.2.    Solution Architectural Concept    6

5.3.    Components    7

5.3.1.    Customized list definition    7

5.3.2.    Sub Tasks Hierarchy Web Part    7

5.3.3.    An event handler or workflow (pending research)    8

5.3.4.    A SharePoint Feature Definition for Deployment    8


 


 

  1. Table of Figures

Figure 1 The built-in SharePoint task item    4

Figure 2 Sample user interface for creating sub tasks from an item's actions drop-down menu    6

Figure 3 Sample user interface to add sub tasks from the item's edit menu    6

Figure 4 Sample sub tasks hierarchy web part user interface    7

Figure 5 Sample user interface for showing the task tree for the current task    8


 


 


 

  1. Project Details

    1. Purpose

    The purpose of this document is to outline the specifications for the SharePoint Nested Tasks project, and supply a breakdown of the project to tasks for developers.

    1. Project Description – What are "Nested Tasks"

    SharePoint Nested Tasks is meant to provide SharePoint users with the ability to create sub tasks in a (potential infinite) hierarchy.

    Currently, tasks in both SharePoint and Outlook do not support sub tasks that branch from a parent, and the need for this tracking of a hierarchy of tasks is apparent.

    For example, a decision maker may assign a task to a team leader, who will in turn assign sub tasks to people in his team. The team leader wants to see the status of the sub tasks aggregated into the view of the task that is assigned to him, and he also wants to make changes to the parent task that will affect the entire tree of tasks.

    1. Why SharePoint?

    Windows SharePoint Services version 3 is a web collaboration tool that supports collaboration throughout the organization, allowing users to quickly create solutions for collaboration using custom lists and easy to define workflows. Since it includes built-in support for item level security (each task's security can be set separately), and allows easy customization of lists, views and environments it is the perfect platform to develop a collaborative solution such as this.


 

  1. Specifications

    1. The Built-In SharePoint Tasks List

    Windows SharePoint Services 3 comes with a built-in definition for a task list, which allows users to specify data such as Title, Priority, Status, % Completed, Assigned to, Start and Due dates.


    Figure 1 The built-in SharePoint task item

    This structure can then be customized in each site to rename the fields, add additional ones or remove unwanted ones.

    The built-in tasks list also comes with a feature that sends email to the assignee of each task to notify them about the task when it is created.

    This however is not a complete standard tasks workflow to remind the assignee about the task as its due date draws near. It is not part of the scope of this project to answer this need, but we acknowledge the need and if possible to include such a workflow as part of the solution, it may get added.

    1. Nested Tasks Requirements

    In addition to the default tasks functionality in SharePoint are as follows:

  • Each task may have multiple sub tasks.
  • A sub task may not have a due date beyond the due date of its parent, nor have a start date before the start date of its parent.
  • A task cannot be marked as complete before all its children are marked as complete.
  • If a task is deleted, all its children will be deleted as well.
  • It will be possible to assign sub tasks to users with no access (permissions) to the parent task.
  • The user will have a view allowing him to see his tasks in a hierarchical manner, while maintaining security through the hierarchy (if the user is not allowed to see a task, he should not be shown that task).
  • The user will have an easy and intuitive way to create sub tasks, from the parent task.
  • The solution should be built in a manner that will allow administrators to choose if it will replace the built-in tasks functionality on the server, or will allow choosing to activate the solution in a site to live side by side with the built-in tasks list.
  • None of the modifications should break default SharePoint functionality such as list customization, workflows, event handlers, backup, recycle bin and security.
  1. Out of Scope

This iteration will not deal with:

  1. Recycle bin restore for a task with its sub tasks
  2. Roll back of a task with its sub tasks to history versions
  3. Customized email notifications beyond the built-in ones.


 

  1. High Level Design

    1. Solution Architectural Concept

    A customized list definition will be created to include a field called "Parent Task" that will be a lookup into the same list. This will allow specifying a parent for each task, with root tasks having no parent.

    The field will be hidden from the user in the "Edit Task Form" and "New Task Form", so as not to let users create sub tasks without the customized user interface. This is mainly to avoid confusion, since a task list may contain numerous items, some of them with the same title, and it would be hard for the user to manually pick the parent.

    This means that the only way to create a sub task is to start from the parent. User interface will be developed to allow doing so.





    Figure 2 Sample user interface for creating sub tasks from an item's actions drop-down menu






    Figure 3 Sample user interface to add sub tasks from the item's edit menu

    1. Components

    The solution will be broken into the following components:

    1. Customized list definition

      This list definition will inherit from the built-in tasks list definition, but will include some modifications:

      1. A "Parent Task" lookup field that will look into the same list, into the "title" field.
      2. An embedded web part in the "New", "Edit" and "Display" forms (see hierarchy web part for more details)
      3. A customized view to display the tasks as a hierarchy using the hierarchy web part component.


       

    2. Sub Tasks Hierarchy Web Part

      This web part will be the user interface to display the tasks list as a hierarchy of tasks and sub tasks.

      The web part should employ techniques such as AJAX to show the user the root tasks and their children without loading the entire list when the page is loaded, in order to avoid slowing down the page.

      The web part must be security-sensitive, showing the user only the tasks he is allowed to see.

      The web part will also allow the site manager to specify which fields it will display, and will have a button to create a new root task, as well as an option for creating sub tasks from an existing task.






      Figure 4 Sample sub tasks hierarchy web part user interface

      The web part will also support a task-centric view, starting from the current task that the user is currently viewing. This is to give the user easy access from the current task to the children tree.





      Figure 5 Sample user interface for showing the task tree for the current task

      Finally, this web part, since it will be embedded in the "New Task Form", will also be in charge of setting the parent task in the hidden "parent task" field.

      Another option for this web part (optional for this iteration) is to provide the user with a button to set status for all tasks in the hierarchy at once. This is to allow a user to close all tasks from one place.

    3. An event handler or workflow (pending research)

      This component will ensure the following:

      1. Prevent the user from creating or modifying sub tasks to have due date later than the parent's due date.
      2. Prevents the user from creating or modifying sub tasks to have start date before the one for the parent.
      3. If a task's changes its due date or start date, makes sure its children values correspond to those values (if the task has a child with a due date that is after the new value, change the due date on the child to be equal to the new due date value of the parent. The same goes for start dates)
      4. Prevent the user from marking the status of a task as complete before all its children are complete.
    4. A SharePoint Feature Definition for Deployment

      The feature will include the list definition, as well as the definitions linking the tasks list definition with the workflow\event handler. Tests should be made to determine and document the best way to replace the built-in SharePoint "TaskList" feature with the Nested Tasks List feature.

Wednesday, April 04, 2007

WebControl, post backs and INamingContainer

Ok, here is the first proof that since I got an MVP I didn't get any smarter...
I spent the last hour trying to figure out why the button in my control (inheriting from System.Web.UI.WebControls.WebControl) is not triggering the click event.

If you remember, a few days ago I posted a long post about how to properly handle server controls inside a web part and I lectured on the use of CreateChildControls and when to create the control. But in my WebControl it just didn't work!
Changing the inheritance to WebPart solved the issue, but I wanted a web control!

Finally a co-worker (Thanks Adam) reminded me that I had to implement the INamingContainer interface for the WebControl to support child controls properly. So I just added "WebCustomControl1 : WebControl, INamingContainer" to the class definition, and that's it!
I can't believe I forgot that, since I once found it the hard way, and I am almost sure I posted a tip about this before.

SharePoint Nested Tasks Team Needs Beta Testers & Tech Writers

First thing first - Thanks to Coskun Cavusoglu (check out his sharepoint blog) we now have a logo (still needs to be adjusted to 100px height as per codeplex requirements).





I had so many people contact me about this, and I am really excited to start working with all of them, but I will only have time on Saturday to start the project, so please be patient if you emailed me and I didn't reply.

What we really could use is future beta testers and technical writers to do the documentation. The technical writers should be involved from the start (that's now) and have to have excelent english and writing skills to produce our administrator and user guides. The beta testers will be assigned the task of finding our bugs before we release the final version.
Again, if you think you can help, let me know (my email - FirstNameLastName@gmail.com)


Ishai Sagi







Tuesday, April 03, 2007

Blog Roll fixed

A fellow blogger wrote a comment to me a few days back that my link to his blog is pointing to my own blog. I checked, and indeed it was true, and not just his link - a lot of links didn't work.
After some research I found the culprit - my online blog tracking service didn't write the urls to some of the sites for some reason. I emailed them, and got a very quick response (considering) after 3 days!

"Hello, Thank you for the bug report. We believe we have corrected the invalid URL problems you reported and regret any inconvenience this may have caused. Should it happen again, please let us know."
So now I can relax and apologize to any of you who didn't get the proper link-up that you deserve. And I can still recommend bloglines as my favorite online rss reader and blogroll provider.

If you are interested in other things I am reading (beside sharepoint blogs), check out my bloglines share and enjoy.

Monday, April 02, 2007

SharePoint Hierarchical Tasks project is starting!

I have started a project in codeplex, and I am happy to announce that Liam Cleary (MVP - Microsoft Office SharePoint Server) has joined forces with me on this and will help. I still could use a third or maybe even a fourth person so that each one takes on one aspect of the project, so if you know web part development and\or creating features for MOSS, contact me and join the project!
I also need a logo for the project, so if you have any graphical skills - send me a proposed logo for the project (project name is "SharePoint Hierarchical Tasks", but there is no requirement that the name is on the logo).
Finally, I will be writing up the specs and scope for the project, so this is your best chance to influence what goes in the project. Liam already asked for an event handler that will make sure sub tasks have due dates before the parent task. What do you think? Leave comments on this post.

P.S. - as always I remind you that if you want me to be able to talk to you, you should leave me your email address. Don't worry - I never publish comments with email addresses in them so you won't get exposed to spam crawlers. Also, you can contact me directly using the obsufacated email address below:
FirstNameLastName@gmail.com
Your's Ishai Sagi

Tasks and Sub tasks application coming up!

I got a lot of responses lately to my old article where I boasted about a "tasks-sub tasks" solution I helped implement, and it seems a lot of people want the code.
Unfortunately, I am not free to give away that code - and anyway, it was done before I learned some new tricks. So now I will focus on doing this properly as an open source project, with a step-by-step installation instructions.
This will take some time, so please be patient, and don't stop reading this blog - I am not ignoring you - I am just busy...

Any developer out there want to join my effort and do some of the work? I can write a pretty good spec for you - I need people who either know good web part development (preferably with Ajax knowledge, but I can live without) or people with good knowledge of site and list definitions creation.
If you are interested, contact me at the following email address (if you cant figure out the email address, you can't be in the project!) FirstNameLastName@gmail.com

Your's Ishai Sagi

Sunday, April 01, 2007

Another Live MOSS internet site goes live and it looks amazing!




Check it out!

My company Unique World have just released another live MOSS site that looks even better then the one I blogged about before.

The site is for Komatsu - a big company that makes heavy machinery - and that gave our graphic guys a lot of room for fun with yellow pictures:






Check out the textual pages:





This always amazes me. Good work guys!







A MVP is born

This may be an April's fools joke, but it looks too legitimate to be one!
I seem to be the newest MVP in Microsoft SharePoint Server, since I just got the email now. I have known I was nominated for the last month or so (I still have no idea who nominated me) and I guess this is what you get for answering way too many questions in the forums, and posting way too many articles in a blog!

So, will I stop now that I have this award? no way! I hope I will not offend my readers by telling them "this blog is not for you! - it is for me!". I actually do all of this so I won't forget it - every time I find out something that I want to remember, I post about it. I also use it like I use functions in programming - if I hear a question too many times, I post about it once to stop people from asking me that. So you can expect more tips at the regular intervals, maybe even more now that I have access to more resources!

Thank you Microsoft for awarding this to me. I am excited and flattered, and I hope to meet all the gang in the next MVP summit!

Server side controls and data binding in web parts

One of the most common mistakes (or questions) that developers encounter when starting with web part development is how to use server side controls such as buttons, data grids or drop-down boxes in a webpart.


This is because web part development is not the usual, easy (lazy) drag-and-drop development that most of us MS developers take for granted. Unless you use smartpart (which you shouldn't - article coming soon), there is no way to develop a web part in a drag-and-drop environment, and you are stuck with writing manual server side code.


The drag-and-drop environment usually takes care of the declaration of the objects we are drag-and-dropping, and the instantiation of the objects in the right time in the life cycle of the web page. With web parts, you don't have access to page events, just control (web part) events, and you have to know yourself how, when and where to do what with the server control you want to use in your web part.


This leads to several common mistakes:


  • Controls are rendered, but do not respond to events (you click on a button, the page posts-back but nothing happens)


  • Controls are rendered, but the data they are supposed to show is not showing


  • Controls are rendered with data in them, but if you change a property value in the web part, the data is not changed in the control until I refresh the page (or click "apply" twice)


All of these are easy to avoid, and should not baffle you as a developer. In this article I will show how to use a server side control, with data binding and event triggering in a web part.




My button is not triggering the click event


This is always for the same reason. You created the button in the wrong place in the web part life cycle.


Your code probably looks like this:


public class MyFirstWebPart : WebPart

{

    Label userName = 
new Label();

    Button changeUserButton = 
new Button(); //this is wrong!

    protected override void CreateChildControls()

    {

    

    userName.Text = 
"Ishai";

    changeUserButton.Text = 
"Change Label Text To 'Sagi'";

    changeUserButton.Click+=
new EventHandler(changeUserButton_Click);

    
this.Controls.Add(userName);

    
this.Controls.Add(changeUserButton);

    }

    
void changeUserButton_Click(object sender, EventArgs e)

    {

        userName.Text = 
"Sagi";

    }



}








Now - why doesn't this code work? you get a label with "Ishai", you get a button with some text, and when you click on the button the page does a post back but the label's text doesn't change. Basically the changeUserButton_Click event isn't getting triggered.
The answer is - the button is getting recreated every time you load the page. This is because the "= new Button();" instantiation is done in the wrong place. It is supposed to be done in the CreateChildControls event, so that the control doesnt get recreated every time the page is loaded.
If the control gets recreated, it looses its connection to the event handler function, and the event never gets triggered.

A bigger mistake I see often in the forums is when developers create the button in the web part in the render() event, which reconnects the control every time the web part is rendered.



The correct way to do the same thing is using the following code:



public class MyFirstWebPart : WebPart

{

    Label userName = 
new Label();

    Button changeUserButton;

    
protected override void CreateChildControls()

        {

    changeUserButton = 
new Button();

    userName.Text = 
"Ishai";

    changeUserButton.Text = "Change Label Text To 'Sagi'";

    changeUserButton.Click+=
new EventHandler(changeUserButton_Click);

    
this.Controls.Add(userName);

    
this.Controls.Add(changeUserButton);

        }

    
void changeUserButton_Click(object sender, EventArgs e)

    {

        userName.Text = 
"Sagi";

    }



}




Notice the difference(marked in bold)?

The button is instanciated in the CreateChildControls event, and will not get rebuilt each time the page is loaded. This means the click event will work when the button is clicked.



Controls are rendered, but data isn't rendered.


The major differance between the drag-and-drop interface and the code-your-control interface that we use for web parts, is that you don't get visual studio to automagically configure your data connection and binding for you. I know a lot of people who get really confused when tasked with using a data control manually, because they are not aware what should be done to connect the control to the data. They are used to doing it using the menus that come out of the box with visual studio when you design a page.

If you are such a developer, you need to learn the minimum of what automatic actions visual studio takes when you drag-and-drop a data control on the page, and then use menus to connect it. In a web part you do it manually.
The following example shows how to connect a data grid to a data table and show them on a web part. While the following code works, it is still not the best practice this article wants to point out - since it still has one little thing that can be done better - and avoid the last common mistake. But I will tackle that after I explain this code:


public class MyFirstWebPart : WebPart

{

    DataGrid grid;

    
protected override void CreateChildControls()

    {

        DataTable dt = 
null;



        
using (SPSite site = new SPSite(Page.Request.Url.ToString()))

        {

            
using (SPWeb web = site.OpenWeb())

            {

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

                
string q = @"

                                <Where>

                                    <Gt>

                                        <FieldRef Name=""ID"" />

                                        <Value Type=""Counter"">0</Value>

                                    </Gt>

                                </Where>"
;

                SPQuery query = 
new SPQuery();

                query.Query = q;

                SPListItemCollection items = list.GetItems(query);

                dt = items.GetDataTable();

            }

        }





        grid = 
new DataGrid();

        grid.DataSource = dt;

        grid.AllowPaging = 
true;

        grid.AllowSorting = 
true;

        grid.GridLines = GridLines.None;

        grid.PageSize = 5;



        grid.PagerStyle.NextPageText = 
"Next";

        grid.PagerStyle.PrevPageText = 
"Previous";



        grid.AutoGenerateColumns = 
true;

        grid.DataBind();





        
this.Controls.Add(grid);





        
base.CreateChildControls();

    }





}






So, what is going on here?
  1. Like in the first example, most of my code is in the CreateChildControls event. As we are going to see later, this is not always the right place to put all of the code.


  2. Using the code from my commond coding tasks article, I connect to the sharepoint site that the web part is hosted in, and connect to the "Contacts" list.



  3. Using a simple CAML query to return all items in the contacts list (this is a sample on how to use queries), I get a datatable of all contacts in the list.
    (this is on the line that says dt = items.GetDataTable();)



  4. I create the datagrid object ("grid") in the createchildcontrols event. This is (as I mentioned before) the correct place to create server side controls.



  5. I set the datasource property of the DataGrid to point to the DataTable. This is where we define the connection between the two (this is what you used to do using a wizard in the drag-and-drop .net applications).
    I also set some default settings for the grid - like paging, sorting and that is should Auto Generate Columns. This is where I see a lot of mistakes in the forums - people assuming that just connecting the datasource to the datagrid and calling DataBind() should be enough to get something on the page, and then don't understand why there is nothing rendered. If you don't set AutoGenerateColumns to true, you will have to define (manually) which columns from the data table to display.



  6. Finally, I call the DataBind() method which is another thing people tend to forget. Without this call, you will not get information in the grid!.




Changing web part properties require me to click apply twice for the data to change!


This is a usual mistake that I have sinned my self in doing and has everything to do with where\when you get the data. If the data should change based on something that happens like changing the web part properties (or getting input from a connected web part), if you get the data before the change in the property is registered, then you will have to refresh the page to see the changes!


So how do we do it?

The answer may be complicated. It all depends on the control you are using and how you want to use it. In the following example I will still use the same datagrid code I used above, but will add a property to the web part to allow the user to specify which list to render. If I only add the property and change the code in the "SPList list = web.Lists["Contacts"];" line, the user will change the value of the property to another list, but still see the same values from the contacts list, until he refreshes the page. So I will move the code that builds the DataTable into the OnPreRender event.




public class MyFirstWebPart : WebPart

{

    DataGrid grid;

    DataTable dt = 
null;

    
private const string c_Default_List_Name = "Contacts";

    
private string listName = c_Default_List_Name;

    [WebBrowsable(
true),

    Personalizable(
true),

    Category(
"Web Part List Connection"),

    DisplayName(
"List Name"),

    WebDisplayName(
"List Name"),

    Description(
"Defines the list the web part will read from."),

    DefaultValue(c_Default_List_Name)]

    
public string ListName

    {

        
get { return listName; }

        
set { listName = value; }

    }



    
protected override void CreateChildControls()

    {

        grid = 
new DataGrid();



        grid.AllowPaging = 
true;

        grid.AllowSorting = 
true;

        grid.GridLines = GridLines.None;

        grid.PageSize = 5;



        grid.PagerStyle.NextPageText = 
"Next";

        grid.PagerStyle.PrevPageText = 
"Previous";



        grid.AutoGenerateColumns = 
true;



        
this.Controls.Add(grid);



        
base.CreateChildControls();

    }

    
protected override void OnPreRender(EventArgs e)

    {

        
using (SPSite site = new SPSite(Page.Request.Url.ToString()))

        {

            
using (SPWeb web = site.OpenWeb())

            {

                SPList list = web.Lists[
this.ListName];

                
string q = @"

                                <Where>

                                    <Gt>

                                        <FieldRef Name=""ID"" />

                                        <Value Type=""Counter"">0</Value>

                                    </Gt>

                                </Where>"
;

                SPQuery query = 
new SPQuery();

                query.Query = q;

                SPListItemCollection items = list.GetItems(query);

                dt = items.GetDataTable();

            }

        }

        grid.DataSource = dt;

        grid.DataBind();

        
base.OnPreRender(e);

    }

}





What is happening here?


The web part now has a property called ListName, which allows the user to select a different list to display in the grid.
The major change is that while
the DataGrid is still getting created and added to the controls collection in the CreateChildContorls event, which is where you should do that, it is only getting connected to the DataTable in the OnPreRender event - just before the page starts rendering the html. This means that changes made to the web part like setting the ListName property have already taken place.





Finally, although it is not part of this article's original scope, I will give a final example on how to specify to the web part which fields to display (since I know I will get questions on that).

Basically, what you need to do is to remove the code line that says AutoGenerateColumns = true and manually add columns. The problem is that columns may or may not have internal names. What I do is use the AutoGenerateColumns to see the field names, and then specify the column names as they are returned.

The following code sample will allow the user to select a list, and specify which fields to display:




    public class MyFirstWebPart : WebPart

    {

        DataGrid grid;

        DataTable dt = 
null;

        Label errLabel = 
new Label();

        #region properties

        #region ListName

        
private const string c_Default_List_Name = "Contacts";

        
private string listName = c_Default_List_Name;

        [WebBrowsable(
true),

        Personalizable(
true),

        Category(
"Web Part List Connection"),

        DisplayName(
"List Name"),

        WebDisplayName(
"List Name"),

        Description(
"Defines the list the web part will read from."),

        DefaultValue(c_Default_List_Name)]

        
public string ListName

        {

            
get { return listName; }

            
set { listName = value; }

        }

        #endregion



        #region
 AutoGenerateColumns

        
private const bool c_Default_AutoGenerateColumns = true;

        
private bool autoGenerateColumns = c_Default_AutoGenerateColumns;

        [WebBrowsable(
true),

        Personalizable(
true),

        Category(
"Web Part List Connection"),

        DisplayName(
"Show All Columns?"),

        WebDisplayName(
"Show All Columns?"),

        Description(
"Defines if the web part will display all the columns. If false, the columns have to be defined in the 'Columns To Display' property."),

        DefaultValue(c_Default_AutoGenerateColumns)]

        
public bool AutoGenerateColumns

        {

            
get { return autoGenerateColumns; }

            
set { autoGenerateColumns = value; }

        }

        #endregion



        #region
 ColumnsToDisplay

        
private const string c_Default_Columns_List = "";

        
private string columnsToDisplay = c_Default_Columns_List;

        [WebBrowsable(
true),

        Personalizable(
true),

        Category(
"Web Part List Connection"),

        DisplayName(
"Columns To Display (CSV)"),

        WebDisplayName(
"Columns To Display (CSV)"),

        Description(
"Defines a Comma Seperated Value list of the list's fields that will be displayed, if 'Show All Columns' is turned off."),

        DefaultValue(c_Default_Columns_List)]

        
public string ColumnsToDisplay

        {

            
get { return columnsToDisplay; }

            
set { columnsToDisplay = value; }

        }

        #endregion

        #endregion



        #region
 events

        
protected override void CreateChildControls()

        {



            grid = 
new DataGrid();



            grid.AllowPaging = 
true;

            grid.AllowSorting = 
true;

            grid.GridLines = GridLines.None;

            grid.PageSize = 5;



            grid.PagerStyle.NextPageText = 
"Next";

            grid.PagerStyle.PrevPageText = 
"Previous";



            
this.Controls.Add(grid);



            
base.CreateChildControls();

            errLabel.Text = 
"";

            
this.Controls.Add(errLabel);

        }

        
protected override void OnPreRender(EventArgs e)

        {

            errLabel.Text = 
"";

            
try

            {

                
using (SPSite site = new SPSite(Page.Request.Url.ToString()))

                {

                    
using (SPWeb web = site.OpenWeb())

                    {

                        SPList list = web.Lists[
this.ListName];

                        
string q = @"

                                <Where>

                                    <Gt>

                                        <FieldRef Name=""ID"" />

                                        <Value Type=""Counter"">0</Value>

                                    </Gt>

                                </Where>"
;

                        SPQuery query = 
new SPQuery();

                        query.Query = q;

                        SPListItemCollection items = list.GetItems(query);

                        dt = items.GetDataTable();

                        
if (this.AutoGenerateColumns)

                        {

                            grid.AutoGenerateColumns = 
true;

                        }

                        
else if (this.ColumnsToDisplay.Length > 0)

                        {

                            grid.AutoGenerateColumns = 
false;

                            
string[] columnNames = this.ColumnsToDisplay.Split(',');

                            
foreach (string columnName in columnNames)

                            {

                                
if (list.Fields.ContainsField(columnName))

                                {

                                    SPField field = GetField(list, columnName);

                                    
if (field != null)

                                    {

                                        System.Web.UI.WebControls.BoundColumn col = 
new BoundColumn();

                                        col.DataField = field.InternalName;

                                        col.HeaderText = field.Title;

                                        grid.Columns.Add(col);

                                    }

                                }

                            }

                        }

                    }

                }



                grid.DataSource = dt;

                grid.DataBind();

            }

            
catch (Exception ex)

            {

                errLabel.Text = ex.ToString();

            }

            
base.OnPreRender(e);

        }

        #endregion

        /// <summary>

        /// A function to get a field from a list, supports both internal names and display names

        /// </summary>

        /// <param name="list">The list that contains the field</param>

        /// <param name="name">The name of the field (internal or display names supported)</param>

        /// <returns>An SPField object if the field is found, null if not found.</returns>

        private SPField GetField(SPList list, string name)

        {

            SPField field = 
null;

            
try

            {

                field = list.Fields.GetFieldByInternalName(name);

            }

            
catch { }

            
try

            {

                field = list.Fields.GetField(name);

            }

            
catch { }

            
return field;

        }

    }







The final code has two more properties - allowing you to select if the web part should Auto Generate Columns, or, if not, which columns to display.
A custom function I wrote allows you to get a referance to the fields specified, even if the user used an internal name or a display name.
Finaly, I moved the column creating code into the OnPreRender event as well, so that if you change the properties and define columns, you dont have to refresh to see the changes.





I hope this answers any unanswered questions you may have had on how to use server controls and data binding in sharepoint web parts.