SharePoint 2010 – Ajax Panel in Web Part’s Editor Part

June 28 2010 2 comments

Have you ever wanted to have conditional property setting possibilities in your custom web parts? I have. The scenario has been that I would have wanted to render a control to web part property setting based on some other property selection – i.e. if user selects something from web part’s properties, something else would emerge to be selected from or filled in. When developing over SharePoint 2007 I had an idea how to do it with Ajax but never had the time to make it happen as in SharePoint 2007 you wouldn’t have ASP.NET 3.5 and Ajax web server extensions available in your web application by default.

In SharePoint 2010 you don’t have that limitation any more so I gave my thought a go and below is a sample what I came up with – a web part with two multi selectable list boxes in the web part’s editor part. The first list box makes all site collection’s root webs of a web application available to be selected from and the other makes available a list of sites published with a certain site template under the selected site collections.

Firstly, here’s the web part’s property definitions and editor part definition:

[ToolboxItemAttribute(false)]
public class DemoWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
        [WebBrowsable(false), Personalizable(PersonalizationScope.Shared)]
        public List<string> RootWebs { get; set; }

        [WebBrowsable(false), Personalizable(PersonalizationScope.Shared)]
        public List<string> Webs { get; set; }

        public override EditorPartCollection CreateEditorParts()
        {
            var editor = new DemoEditorpart
                             {
                                 ID = ID + "_Editor",
                                 Title = "Demo Editor Part"
                             };
            return new EditorPartCollection(new EditorPart[] { editor });
        }
}

Secondly, here’s the Editor Part implementation:

public class DemoEditorpart : EditorPart
{
    // list box to select site collection's root webs from
    protected ListBox Sites;
    // list box to select webs from
    protected ListBox Webs;

    protected override void CreateChildControls()
    {
        // autopostback
        Sites = new ListBox
                    {
                        CssClass = "UserInput",
                        Width = 176,
                        ID = ID + "Sites",
                        SelectionMode = ListSelectionMode.Multiple,
                        AutoPostBack = true
                    };
        Webs = new ListBox
                   {
                       CssClass = "UserInput",
                       Width = 176,
                       ID = ID + "Webs",
                       SelectionMode = ListSelectionMode.Multiple
                   };

        // get web applications site collection root webs to display
        var sites = GetRootWebs();
        if (sites != null)
        {
            Sites.DataSource = sites;
            Sites.DataTextField = "Title";
            Sites.DataValueField = "Url";
            Sites.Rows = sites.Count + 2;
            Sites.DataBind();
            Sites.SelectedIndexChanged += Sites_SelectedIndexChanged;
        }
        var panel = new UpdatePanel { ID = ID + "UpdatePanel" };
        panel.ContentTemplateContainer.Controls.Add(
            new LiteralControl(@"<table cellspacing=""0"" border=""0""
                    style="
"border-width:0px;width:100%;
                    border-collapse:collapse;"
"><tr><td>
                    <div class="
"UserSectionHead"">"));

        panel.ContentTemplateContainer.Controls.Add(
            new LiteralControl(@"Site Collections:<br/>"));
        panel.ContentTemplateContainer.Controls.Add(Sites);
        panel.ContentTemplateContainer.Controls.Add(
            new LiteralControl(@"</div></td></tr>
                    <tr><td>
                    <div style="
"width:100%"" class=""UserDottedLine"">
                    </div></td></tr><tr><td>
                    <div class="
"UserSectionHead"">"));

        panel.ContentTemplateContainer.Controls.Add(
            new LiteralControl("Sites:<br/>"));
        panel.ContentTemplateContainer.Controls.Add(Webs);
        panel.ContentTemplateContainer.Controls.Add(
            new LiteralControl(@"</div></td></tr><tr><td>
                <div style="
"width:100%"" class=""UserDottedLine""></div>
                </td></tr></table>"
));

        Controls.Add(panel);
        base.CreateChildControls();

    }

    /// <summary>
    /// event handler to list webs under the selected site collection
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Sites_SelectedIndexChanged(object sender, EventArgs e)
    {
        FillWebs(Sites.Items, Webs.Items);
        Webs.Rows = Webs.Items.Count + 2;
    }

    /// <summary>
    /// fill the Webs list box according to the selected site collection
    /// </summary>
    /// <param name="sites">site collections</param>
    /// <param name="webs">webs</param>
    private static void FillWebs(ListItemCollection sites,
        ListItemCollection webs)
    {
        foreach (ListItem item in sites)
        {
            var newsWebs = GetWebs(item.Value);
            foreach (var web in newsWebs)
            {
                var listItem = new
                    ListItem(web.Title,
                    item.Value + ";" + web.ServerRelativeUrl);
                if (item.Selected && !webs.Contains(listItem))
                {
                    webs.Add(listItem);
                }
                else if (!item.Selected && webs.Contains(listItem))
                {
                    webs.Remove(listItem);
                }
            }
        }
    }

    /// <summary>
    /// apply changes to web part
    /// </summary>
    /// <returns></returns>
    public override bool ApplyChanges()
    {
        var part = (DemoWebPart)WebPartToEdit;
        // apply values back to web part from editor part
        part.RootWebs = ApplyRootWebs(Sites.Items);
        part.Webs = ApplyWebs(Webs.Items);
        return true;
    }

    /// <summary>
    /// sync changes from web part
    /// </summary>
    public override void SyncChanges()
    {
        EnsureChildControls();
        var part = (DemoWebPart)WebPartToEdit;
        // sync values from web part to editor part
        SyncRootWebs(Sites.Items, part.RootWebs);
        SyncWebs(Sites.Items, Webs.Items, part.Webs);
        Webs.Rows = Webs.Items.Count + 2;
    }

    /// <summary>
    /// apply selected site collections to web part
    /// </summary>
    /// <param name="listItems">Sites items</param>
    /// <returns>list of selected site collections</returns>
    private static List<string> ApplyRootWebs(IEnumerable listItems)
    {
        return (from ListItem listItem in listItems
                where listItem.Selected
                select string.Format("{0};{1};{2}", listItem.Text,
                listItem.Value, string.Empty)).ToList();
    }

    /// <summary>
    /// apply selected webs to web part
    /// </summary>
    /// <param name="listItems">Webs items</param>
    /// <returns>list of seleted webs</returns>
    private static List<string> ApplyWebs(IEnumerable listItems)
    {
        return (from ListItem listItem in listItems
                where listItem.Selected
                select string.Format("{0};{1};{2}", listItem.Text,
                listItem.Value.Split(';')[0],
                listItem.Value.Split(';')[1])).ToList();
    }

    /// <summary>
    /// sync site collections from web part
    /// </summary>
    /// <param name="rootWebItems">Sites items</param>
    /// <param name="rootWebs">selecetd site collections from web part</param>
    private static void SyncRootWebs(ListItemCollection rootWebItems,
        IEnumerable<string> rootWebs)
    {
        if (rootWebs == null) return;

        foreach (var siteInfo in rootWebs.Select(rootWeb =>
            rootWeb.Split(';')))
        {
            var rootWebItem = rootWebItems.FindByValue(siteInfo[1]);
            if (rootWebItem != null) rootWebItem.Selected = true;
        }
    }

    /// <summary>
    /// sync webs from web part
    /// </summary>
    /// <param name="rootWebItems">Sites items</param>
    /// <param name="webItems">Webs items</param>
    /// <param name="webs">selected webs from web part</param>
    private static void SyncWebs(ListItemCollection rootWebItems,
        ListItemCollection webItems,
        IEnumerable<string> webs)
    {
        if (webs == null) return;

        FillWebs(rootWebItems, webItems);

        foreach (var siteInfo in webs.Select(web => web.Split(';')))
        {
            var webItemValue = string.Format("{0};{1}", siteInfo[1],
                siteInfo[2]);
            var webItem = webItems.FindByValue(webItemValue);
            if (webItem != null) webItem.Selected = true;
        }
    }

    /// <summary>
    /// get every root web from web application - display also the
    /// names of those site collections user has no right to
    /// </summary>
    /// <returns>list of webs</returns>
    private static List<SPWeb> GetRootWebs()
    {
        var webs = new List<SPWeb>();

        SPSecurity.RunWithElevatedPrivileges(delegate
        {
            foreach (SPSite site in
                  SPContext.Current.Site.WebApplication.Sites)
            {
                webs.Add(site.RootWeb);
            }
        });

        return webs;
    }

    /// <summary>
    /// get sub webs published with a certain template
    /// </summary>
    /// <param name="siteName">site name</param>
    /// <returns>list of webs</returns>
    private static List<SPWeb> GetWebs(string siteName)
    {
        SPSite site = null;
        SPSecurity.RunWithElevatedPrivileges(delegate
        {
            site = SPContext.Current.Site.WebApplication.Sites[siteName];
        });
        return GetWebs(site ?? SPContext.Current.Site);
    }

    /// <summary>
    /// get sub webs published with a certain template
    /// </summary>
    /// <param name="site">site</param>
    /// <returns>list of webs</returns>
    private static List<SPWeb> GetWebs(SPSite site)
    {
        return site.RootWeb.GetSubwebsForCurrentUser().Where(w =>
            w.WebTemplate.Equals("MyCustomSiteTemplate",
            StringComparison.OrdinalIgnoreCase)).ToList();
    }
}

The result is that we can select site collections from web part properties and the underlying webs selection is filled dynamically causing post back within the Ajax panel in the editor part. The tricky part here was to get the editor part sync properties from the web part and apply changes back to the web part. Below are two clips of how the property setting works:

Select site collections

Site collections are available to be selected from.

Sub webs will fill the second list box according to the selection of the site collections

Sites under the selected site collections published with MyCustomSiteTemplate are available to be selected from.

Bookmark and Share

Popularity: 75% [?]

2 comments to “SharePoint 2010 – Ajax Panel in Web Part’s Editor Part”

  1. [...] SharePoint 2010 – Ajax Panel in Web Part’s Editor Part (SharePoint Blues)Have you ever wanted to have conditional property setting possibilities in your custom web parts? I have. The scenario has been that I would have wanted to render a control to web part property setting based on some other property selection – i.e. if user selects something from web part’s properties, something else would emerge to be selected from or filled in. When developing over SharePoint 2007 I had an idea how to do it with Ajax but never had the time to make it happen as in SharePoint 2007 you wouldn’t have ASP.NET 3.5 and Ajax web server extensions available in your web application by default. [...]

  2. [...] SharePoint 2010 – Ajax Panel in Web Part’s Editor Part (SharePoint Blues)Have you ever wanted to have conditional property setting possibilities in your custom web parts? I have. The scenario has been that I would have wanted to render a control to web part property setting based on some other property selection – i.e. if user selects something from web part’s properties, something else would emerge to be selected from or filled in. When developing over SharePoint 2007 I had an idea how to do it with Ajax but never had the time to make it happen as in SharePoint 2007 you wouldn’t have ASP.NET 3.5 and Ajax web server extensions available in your web application by default. [...]

Leave a Reply