zevenseas


 

Isolator for SharePoint

Unit testing with SharePoint becomes much more easy with the Isolator for SharePoint, develop and test SharePoint code without a SharePoint server! ;)

Typemock Isolator is a great way to open up the world of unit testing
to SharePoint developers. Unit testing SharePoint applications is an
important part of our soon to be released P&P SharePoint Guidance
and Typemock Isolator is integral to our unit testing guidance.”

Ajoy Krishnamoorthy, Lead Product Planner for Patterns & Practices,
Developer Division at Microsoft

Typemock are offering their new product for unit testing SharePoint called Isolator For SharePoint, for a special introduction price. it is the only tool that allows you to unit test SharePoint without a SharePoint server. To learn more click here.

The first 50 bloggers who blog this text in their blog and tell us about it, will get a Full Isolator license, Free. for rules and info click here.

Let’s see what this is all about eh? Can’t wait! ;)

Charts for SharePoint using .NET3.5 SP1 ;)

Ever wondered what happened after Microsoft acquired Dundas’s data visualization components ? Somehow it ended up in the SP1 update of .NET 3.5 together with the Charting Add-on! This means that we can use it in SharePoint as well! Woohoow! :)

And the best thing is that Wictor Wilén has already created a webpart that let’s you connect to a list, define the X and the Y values, choose a chart type and displays a chart! :)

Go to www.codeplex.com/chartpart and download this webpart !

 

Technorati Tags: ,

Tagged Links available for free

As my mate Daniel posted, we decided to put Tagged Links out for free.. if you are unaware of what this thing actually is, here is a snippet

It’s social bookmarking for SharePoint, taking the standard “Links List” to a new level via browser integration, one-click tagging, advanced graphical views and global tags that allow you to define tags across an entire site collection. Take a look at the new “Walkthrough”.

In short, there is no simpler way to collect and share links to interesting web site content using SharePoint.

So it’s a solution which you can use to collect useful links and share these with your colleagues. Internally we use it to filter through all of our individual RSS feeds so we can see what actually are the most useful ones and every friday@seas, when we come together, we discuss the ones that we all ‘tagged’. While eating our own dogfood we came up with new ideas and incorporated those in the V1.1 version (like aggregating through the taggedlinks lists and a better and more generic tagcloud).

The only left to do for you is to download it or try it here (username: “DemoUser” password: “DemoUser”) and let us know what you think of it! ;)

QuickTask WebPart

Because a picture tells more than thousand words, here is what the webpart looks like..

So what do we see? A custom webpart that displays all the non completed tasks from a given tasklist and hereby giving the user the ability of speeding up the administration of those tasks by :

  1. Complete one or several tasks at once
  2. Assign a task to an user using the context menu

Let me explain how it all works.. first of all we have to setup the custom toolbar using the Microsoft.SharePoint.WebControls.Toolbar and adding a ToolBarButton to it.

//Defining a button 
checkAllTasksToolBarButton = (ToolBarButton)Page.LoadControl("/_controltemplates/ToolBarButton.ascx");
checkAllTasksToolBarButton.Click += new EventHandler(checkAllTasksToolBarButton_Click);

checkAllTasksToolBarButton.Text = "Complete selected tasks";
checkAllTasksToolBarButton.ImageUrl = "/_layouts/images/CheckNames.gif";

//Creating the toolbar and adding the button           
webPartToolBar = (ToolBar)Page.LoadControl("/_controltemplates/ToolBar.ascx");
webPartToolBar.Buttons.Controls.Add(checkAllTasksToolBarButton);

this.Controls.Add(webPartToolBar);

Secondly we are going to put a hidden inputsection in the page so we can store all the checked items and retrieve them when the button is clicked

//Adding the .ascx with all the javascript and the hidden input form
scriptcontrolpage = (UserControl)Page.LoadControl("/_layouts/zevenseas/controltemplates/hiddeninputsection.ascx");
scriptcontrolpage.ID = "scriptcontrolpage";
this.Controls.Add(scriptcontrolpage);

Next is to define the SPDataSource

datasource = new SPDataSource();
datasource.DataSourceMode = SPDataSourceMode.List;
datasource.UseInternalName = true;
datasource.SelectCommand = "<Query><Where><Neq><FieldRef Name=\"Status\" /><Value Type=\"Text\">Completed</Value></Neq></Where><OrderBy><FieldRef Name=\"ID\" /></OrderBy></Query>";

And after that the SPGridView with it’s columns

//Defining and building the SPGrid to display the datasource
grid = new SPGridView();
grid.AutoGenerateColumns = false;

//CheckBox
BoundField checkbox = new BoundField();
checkbox.DataField = "Id";
checkbox.DataFormatString = "<input type='checkbox' group='siteCheckboxes' name='{0}' onclick='checkBoxClick();'>";
checkbox.HtmlEncode = false;
checkbox.HeaderText = "<input type='checkbox' id='selectAllCheckBox' onclick='selectAllCheckBoxClick(this);' />";
grid.Columns.Add(checkbox);

//Title menu with ECB
SPMenuField titleMenu = new SPMenuField();
titleMenu.HeaderText = "Title";
titleMenu.TextFields = "Title";
titleMenu.MenuTemplateId = "TaskListMenu";

titleMenu.TokenNameAndValueFields = "EDIT=ID";
titleMenu.SortExpression = "Title";

MenuTemplate itemListMenu = new MenuTemplate();
itemListMenu.ID = "TaskListMenu";

MenuItemTemplate editItemMenu = new MenuItemTemplate("Edit Item", "/_layouts/images/edititem.gif");
editItemMenu.ClientOnClickNavigateUrl = SPContext.Current.Web.Url + "/Lists/" + 
    taskList + "/editform.aspx?id=%EDIT%&Source=" + SPContext.Current.Web.Url;
itemListMenu.Controls.Add(editItemMenu);

SubMenuTemplate listSettings = new SubMenuTemplate();
listSettings.Text = "Assign Task to";
listSettings.Description = "Quickly assing this task to an user";
listSettings.ImageUrl = "/_layouts/images/ALLUSR.gif";
                
foreach (SPUser user in SPContext.Current.Web.Users)
{
    MenuItemTemplate listItem = new MenuItemTemplate();
    listItem.Text = user.Name.ToString();
    listItem.Description = string.Format("Assign to task to {0}", user.Name);

    string url = string.Format("{0}/_layouts/zevenseas/taskassign.aspx?List={1}&Item=%EDIT%&UserId={2}&source={3}", 
        SPContext.Current.Web.Url, taskList, user.ID.ToString(), SPContext.Current.Web.Url);
    listItem.ClientOnClickNavigateUrl = url;
    listSettings.Controls.Add(listItem);
}

itemListMenu.Controls.Add(listSettings);
this.Controls.Add(itemListMenu);

grid.Columns.Add(titleMenu);

//Status column
SPBoundField status = new SPBoundField();
status.DataField = "Status";
status.HeaderText = "Status";
grid.Columns.Add(status);

//AssignedTo Column
SPBoundUserField assigned = new SPBoundUserField();
assigned.DataField = "AssignedTo";
assigned.HeaderText = "Assigned To";
grid.Columns.Add(assigned);

this.Controls.Add(grid);

Notice the SPBoundUserField, this is a custom SPBoundField that takes care of the value returned by a User/Group Column. Check out my guest post at Bamboo’s Community blog on how to extend the SPBoundField to act like you want it to ;) Next is to describe the event of when the ToolbarButton is clicked

void checkAllTasksToolBarButton_Click(object sender, EventArgs e)
{
    //Getting a reference to the hiddenInput field to get the values
    string controlId = "ctl00" + "$" + this.Parent.ID + "$" + this.ID + "$scriptcontrolpage" + "$HiddenSiteSelections";
    System.Web.UI.HtmlControls.HtmlInputHidden inputsection = (System.Web.UI.HtmlControls.HtmlInputHidden)this.Page.FindControl(controlId);

    if (inputsection != null)
    {
        string[] ids = inputsection.Value.Split(new Char[] { '#' });
        SPList list = SPContext.Current.Web.Lists[taskList];

        foreach (string id in ids)
        {
            if (!string.IsNullOrEmpty(id))
            {
                SPListItem item = list.GetItemById(Convert.ToInt32(id));
                item["PercentComplete"] = 1;
                item["Status"] = "Completed";
                item.SystemUpdate();
            }
        }
    }

    DataBind(taskList);
}

And of course the DataBind function

void DataBind(string listName)
{
    if (!string.IsNullOrEmpty(listName))
    {
        datasource.List = SPContext.Current.Web.Lists[listName];
        try
        {
            grid.DataSource = datasource;
            grid.DataBind();
        }
        catch (Exception error)
        {
            Literal literal = new Literal();
            literal.Text = error.Message.ToString();
            this.Controls.Add(literal);
        }
    }
}

Now if you are wondering where the variable ‘taskList’ is coming from.. don’t worry.. here it is ;)

[Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Select TaskList"), WebDescription("Select TaskList")]
public string taskList { get; set; }

If you look closely on the creating of the ECB menu, you see that I link to a page called ‘taskassign.aspx’. I couldn’t figure out how to use the ClientOnClickUsingPostBackEvent method of the MenuItemTemplate class in a webpart so I used the ClientOnClickNavigateUrl and pointed to a custom aspx page which does nothing than accepting all the variables and modifying the task and redirecting back to the page. Below is the code and the markup of the associated .aspx page

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>

<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" Inherits="zevenseas.Community.Web.UI.WebControls.WebParts.UITaskAssign" %>
<asp:Content ID="Content5" ContentPlaceHolderID="PlaceHolderMain" runat="server">
</asp:Content>  
public class UITaskAssign: Microsoft.SharePoint.WebControls.LayoutsPageBase
{
    protected override void CreateChildControls()
    {
        int listItem = Convert.ToInt32(Page.Request.QueryString["Item"]);
        string list = Page.Request.QueryString["List"];
        int userId = Convert.ToInt32(Page.Request.QueryString["UserID"]);
        string requestUrl = Page.Request.QueryString["Source"];

        using (SPSite site = new SPSite(SPContext.Current.Web.Url))
        {
            using (SPWeb web = site.OpenWeb())
            {
                web.AllowUnsafeUpdates = true;
                try
                {
                    SPListItem taskListItem = web.Lists[list].GetItemById(listItem);
                    SPFieldUserValue userValue = new SPFieldUserValue(web, userId.ToString());
                    taskListItem["AssignedTo"] = userValue;
                    taskListItem.Update();
                }
                catch (Exception error)
                {
                    Literal output = new Literal();
                    output.Text = error.Message.ToString();
                    this.Controls.Add(output);
                }
                finally
                {
                    web.AllowUnsafeUpdates = false;
                }
            }
        }

        SPUtility.Redirect(requestUrl, SPRedirectFlags.Default, Context);
        base.CreateChildControls();
    }       
}
 

And last but not least.. here is the code of the hiddeninputsection.ascx. Please note that I didn’t took the time to find out what custom javascript needs to be in there and what not. I just copied this stuff from MS’s very own Administration Toolkit ;)

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Import Namespace="Microsoft.SharePoint" %> <%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<script type="text/javascript">
function    Visascii(ch)
{
    return (!(ch.charCodeAt(0) & 0x80));
}
function Visspace(ch)
{
    return (ch.charCodeAt(0) == 32) || ((9 <= ch.charCodeAt(0)) && (ch.charCodeAt(0) <= 13));
}
function stripWS(str)
{
    var b = 0;
    var e = str.length;
    while (str.charAt(b) && (Visascii(str.charAt(b)) && Visspace(str.charAt(b))))
        b++;
    while ((b < e) && (Visascii(str.charAt(e-1)) && Visspace(str.charAt(e-1))))
        e--;
    return ((b>=e)?"":str.substring(b, e ));
}
var L_NoFieldEmpty_TEXT = "<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,common_nofieldempty_TEXT%>' EncodeMethod='EcmaScriptStringLiteralEncode'/>";
function CheckForEmptyField(text_orig,field_name)
{
    var text = stripWS(text_orig);
    if (text.length == 0)
    {
        alert(StBuildParam(L_NoFieldEmpty_TEXT, field_name));
        return false;
    }
    return (true);
}
function CheckForEmptyFieldNoAlert(text_orig)
{
    var text = stripWS(text_orig);
    if (text.length == 0)
    {
        return false;
    }
    return (true);
}
var L_WrongEmailName_TEXT = "<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,common_wrongemailname_TEXT%>' EncodeMethod='EcmaScriptStringLiteralEncode'/>";
function CheckForAtSighInEmailName(text_orig,field_name)
{
    var text = stripWS(text_orig);
    if (!CheckForEmptyField(text_orig,field_name)) return false;
    var indexAt = 0;
    var countAt = 0;
    var countSpace = 0;
    var len = text.length;
    while(len--)
    {
        if (text.charAt(len) == '@')
        {
            indexAt = len;
            countAt++;
        }
        if (text.charAt(len) == ' ')
            countSpace ++;
    }
    if ((countAt == 0) ||
        (indexAt == 0) ||
        (indexAt == (text.length-1))
        )
    {
        alert(StBuildParam(L_WrongEmailName_TEXT, field_name));
        return false;
    }
    if (countSpace !=0 )
    {
        alert(L_TextWithoutSpaces1_TEXT + field_name);
        return false;
    }
    return (true);
}
    var checkBoxGroupName = "siteCheckboxes";
    function checkBoxClick ()
    {
        var checkBoxHidden = (document.getElementById("<%= HiddenSiteSelections.ClientID %>"));
        checkBoxHidden.value = "";
        var selectAllCheckBox = document.getElementById ("selectAllCheckBox");
        if (selectAllCheckBox != null)
            selectAllCheckBox.checked = false;
        var i;
        for (i = 0; i < theForm.length; i ++)
        {
            curGroup = theForm[i].group;
            if (checkBoxGroupName==curGroup && theForm[i].checked==true)
            checkBoxHidden.value += theForm[i].name + "#";
        }
    }
    function selectAllCheckBoxClick (selectAllCheckBox)
    {
        var newCheckBoxState = selectAllCheckBox.checked;
        for (i = 0; i < theForm.length; i ++)
        {
            curGroup = theForm[i].group;
            if (checkBoxGroupName==curGroup)
                theForm[i].checked = newCheckBoxState;
        }
        checkBoxClick ();
        selectAllCheckBox.checked = newCheckBoxState;
    }
    function _spBodyOnLoad()
    {
        var checkBoxHidden = (document.getElementById("<%= HiddenSiteSelections.ClientID %>"));
        checkBoxHidden.value = "";
    }
</script>
<input type="hidden" id="HiddenSiteSelections" runat="server" />

There you have it.. all of this is also available on our CodePlex site.. let me know what you think of it! ;)

Technorati Tags: ,

Nintex Workflow 2007

If you want to give your SharePoint power users the ability to create workflows without (or even with) the use of SharePoint Designer then Nintex Workflow 2007 is the workflow tool to use.

What surprised me was the fact how relatively easy it is to create quite complex workflows using the webbased workflow designer of Nintex. Why is it easy? Well .. I have some experience using Visual Studio workflows, but had some difficulties along the way (Lessons learned with Windows Workflow Foundation). And I have some experience building SPD Workflows (SPD.. nice to have) and I’ve even built some custom actions for SPD (SPD Workflow activity - Creating a document library , SPD Workflow activity - Copying a listItem accros a site).

Most of the time I’ve been using workflows within SharePoint in combination with InfoPath forms. One the biggest issues I’ve had in the past was the whole task-driven way approach where there was no real information about the form you are approving. See my complete rant at (Approval) workflow thingies..

So what does Nintex does? You probably wouldn’t have guessed it but it completely eliminates almost all the issues I had using SPD! ;)

Let me illustrate how easy it is to create a relatively complex workflow using the workflow designer. The workflow itself is about the process that takes place when a new employee is hired within a company :

  1. HR Department fills in all the details about the new employee
  2. The manager of the department where the employee will work must verify all the details and has to specify all work-related details (room number, workstation, phone, etc)
  3. Now the workflow becomes a parallel workflow
    1. Facility Management gets the form and will arrange everything that the manager has requested
    2. IT Department gets the form and will arrange everything that the manager has requested
      1. HR gets a mail every time a particular component is ready
  4. HR Department archives the form

Within a couple of hours (including testing out several options) I quickly came to the following workflow :

image

Pretty impressive eh? Aside to that here’s how a task looks like :

image

So.. within the task, all the information (if you make use of the promotion of the columns in InfoPath) is there. Aside to that, it’s smart enough to paste the &OpenIn=browser if you click on the Item so the form will open in the browser (something that the OTB workflow does not do!).

What else? Well.. the ability to see what the status is of the workflow. In other words, you can visually see what the status is of the workflow :

image

Another key thing is that customers don’t need developers anymore to create this kind of workflows using Visual Studio or SharePoint Designer but instead the power now lies in the business where people can create their own workflows. Maybe this last statement is a little bit over exaggerated but you get the idea. But maintaining a Nintex workflow is much more easier and better to understand for the end-user then a SPD workflow is.

Maybe consultants, like us,  are needed to setup the initial workflow (together with the creation of forms (InfoPath or SharePoint Lists). But then with a simple guidance (check out the excellent tutorials at the Connect site of Nintex) and training, the power users can create/maintain workflows themselves and thus save your IT department a lot of money and time. The time that was once needed to complete a single SPD workflow, which btw is not very DTAP-able (Development, Test, Acceptance, Production) can now be used to complete multiple Nintex workflows which are (in a way (using the Import and Export functionality) DTAP-able). I’m still investigating if there is a way of adding Nintex workflows in a SharePoint solution so it can be part of a real SharePoint solution :)

I haven’t looked in the SDK yet but it is possible to add custom actions as it is for SPD, so don’t worry that your beautifully crafted custom actions are not available anymore when you start using Nintex. You only need to make it available in Nintex, so some extra steps are required (for creating the dialogs, validation, etc).

 

Well.. I hope you are as enthusiastic as I am about this product right now! Download a trial version over at the Nintex Workflow 2007 page


 
 
 

© 2009 Community Kit For SharePoint