Monday, January 30, 2012

Incomplete SharePoint 2010 SPWebPartManager and its events that are never raised

What?

Incomplete SharePoint 2010 SPWebPartManager and its events that are never raised

Why?

The SPWebPartManager in SharePoint 2010 is incomplete.
Some of its events are never raised and even if they are raised they don't work as expected.

Recently I was working on a scenario where I had to restrict users from being able to add certain web parts to certain web part zones in the page.

There is no such property in the web part zone that we can use to achieve this kind of functionality.

However there are other useful properties that a web part zone offers such as the below:

<WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Left" Title="loc:Left" AllowCustomization="false"   AllowLayoutChange="false" AllowPersonalization="false" LayoutOrientation="Vertical" LockLayout="true"  /> 

None of the properties that the web part zone offers can fulfill my requirement.

So as a quick solution, I have decided to use some events that are available in the SPWebPartManager. These events are supposed to be raised as soon as a web part is being added to a zone, after a web part is added to a zone, a web part is being deleted from a zone, after a web part is deleted from a zone, a web part is being moved from a zone to a different zone, after a web part is moved from a zone to another zone.

SPWebPartManager.WebPartAdding
SPWebPartManager.WebPartAdded
SPWebPartManager.WebPartDeleting
SPWebPartManager.WebPartDeleted
SPWebPartManager.WebPartMoving
SPWebPartManager.WebPartMoved

So I started off with developing a web control that has a very basic delegate that gets SPWebPartManager object for the current page & stops the web part  of type "ContentByQueryWebPart" from being added if it is being added to the zone "ZoneRight" in my custom publishing page layout.


protected override void OnInit(EventArgs e)
{
    WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(this.Page);
    if (wpm != null)
    {
        wpm.WebPartAdding += delegate(object sender, WebPartAddingEventArgs we)
        {
            System.Diagnostics.Debugger.Break();
            String webPartZoneId = we.Zone.ID;
            Type type = we.WebPart.GetType();

            if (webPartZoneId == "bodyAreaRightRail")
            {
                if (type.FullName.Equals("ContentByQueryWebPart"))
                {
                    we.Cancel = true;
                }
            }
        };
    }
}

Soon it became apparent that the code block inside the delegate is never reached by the compiler.

I have tried calling the delegate in various other ways.

wpm.WebPartAdding += (sender, we) =>
                {
                    System.Diagnostics.Debugger.Break();
                    String webPartZoneId = we.Zone.ID;
                    Type type = we.WebPart.GetType();

                    if (webPartZoneId == "ZoneRight")
                    {
                        if (type.FullName.Equals("ContentByQueryWebPart"))
                        {
                            we.Cancel = true;
                        }
                    }
                };

As none of them worked, I was intrigued to test the other events as well.

 wpm.WebPartAdding += delegate(object sender, WebPartAddingEventArgs we)
{
    we.Cancel = true;
};

wpm.WebPartAdded += delegate(object sender, WebPartEventArgs we)
{
                   
};

wpm.WebPartDeleting += delegate(object sender, WebPartCancelEventArgs we)
{
    we.Cancel = true;
};

wpm.WebPartDeleted += delegate(object sender, WebPartEventArgs we)
{

};

wpm.WebPartMoving += delegate(object sender, WebPartMovingEventArgs we)
{
    we.Cancel = true;
};

wpm.WebPartMoved += delegate(object sender, WebPartEventArgs we)
{

};
Except the WebPartAdded & WebPartDeleting events, none of the others were raised.

Even though the WebPartDeleting event was raised, the "we.Cancel = true;" did not do anything. The web part got deleted.

As my next trial, I have tried to create a custom SPWebPartManager control that is inherited from the OOB SPWebPartManager control. Very quickly I realized that the SPWebPartManager is a sealed control and therefore cannot be inherited. Excellent!!

Some one suggested that the code should run in the master page / page layout not from a web control and so I moved the above code snippet to OnInit event of the master page. Same results !!

How?

So the SPWebPartManager is buggy with incomplete events that are never raised.
The other quickest way to handle the OnWebPartAdding event for my requirement is through JavaScript / JQuery.

Saved the below JS into a file called RestrictWebPartAdditionForPageLayoutX.js and copy it into the folder /Style Library/Scripts/

ExecuteOrDelayUntilScriptLoaded(HandleWebPartAddition, "wpadder.js");

function HandleWebPartAddition() {
    //Get the add button from the web part adding panel
    var btnAdd = $("div.ms-wpadder-buttonArea button:first-child");
    //Remove the inline click event handler
    btnAdd.removeAttr('onclick');
    //Attach your own event handler using JQUERY!!
    btnAdd.click(function () {
        //Get the webpart of object, this comes from the wpadder.js, where the WPAdder object instance is found giving all //properties on the selected webpart item
        var selectedWp = WPAdder._getSelectedItem();
        //Using the same object instance you can get the selected zone id, the zone you have selected for adding the webpart
        var zoneId = WPAdder._getZoneSelect().value;

        //Prepare a collection of web parts that you do not want added
        var webParts = new Array("Content Query", "Search Core Results", "Announcements", "Calendar", "Documents", "Shared Documents", "Team Documents", "Pictures", "Pages", "Images");

        //Prepare a collection of web part zones that you do not want the above web parts added
        var webPartZoneIDs = new Array(-1,1,2,3,4);

        //Check the title of the selected webpart for whatever name you want. You can also use id of the web part
        var canAddWebPart = true;

        for (var i = 0; i < webParts.length; i++) {
            if (selectedWp.title.indexOf(webParts[i]) >= 0) {
                //if the webpart is the one you don't want user to add in specific location, check the zone next
                for (var j = 0; j < webPartZoneIDs.length; j++) {
                    if (zoneId == webPartZoneIDs[j]) {
                        //if the zone is not where the web part should be, give the message and return
                        alert('This web part is not allowed here');
                        canAddWebPart = false;
                        break;
                        return false;
                    }
                }
            }
        }

        if (canAddWebPart == true) {
            //If all is correct just let the webpart be added to the page gracefully!!
            WPAdder.addSelectedItemToPage();
            return false;
        }
    });
}
Then I opened up the page layout where I wanted this functionality from SharePoint Designer by right clicking the page layout and by choosing "Edit in advanced mode".

Added the below line to include my custom JS file.

<script type="text/javascript" src="/Style Library/Scripts/RestrictWebPartAdditionForPageLayoutX.js" />

VoilĂ !! Now users see the below when they try to add any of the web parts that I do not want them to add. (to the zones I do not want them to add to)



If you really want to do something while the web part is being deleted or moved or added, you might want to take the approach of developing a custom HttpModule as described in this super nice post

No comments:

Post a Comment