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

June 28 2010 54 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 one drop down list, two multi selectable list boxes and one text field in the web part’s editor part. Web template is chosen from the drop down list. The first list box makes all site collection’s root webs of a web application available to be selected from and the other makes a list of sites published with a selected web template. The text box contains an integer value.

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

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

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

    private string _webTemplate = "BLOG#0";
    [WebBrowsable(false), Personalizable(PersonalizationScope.Shared)]
    public string WebTemplate
    {
        get { return _webTemplate; }
        set { _webTemplate = value; }
    }

    private int _limit = 5;
    [WebBrowsable(false), Personalizable(PersonalizationScope.Shared)]
    public int Limit { get { return _limit; } set { _limit = value; } }

    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 DropDownList WebTemplates;
    protected TextBox Limit;

    protected override void CreateChildControls()
    {

        Limit = new TextBox { CssClass = "UserInput", Width = 20 };
        // autopostback
        WebTemplates = new DropDownList
        {
            CssClass = "UserInput",
            Width = 176,
            ID = ID + "WebTemplates",
            AutoPostBack = true
        };
        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
        };

        var webTemplates = SPContext.Current.Site.GetWebTemplates(1033);
        WebTemplates.DataSource = webTemplates;
        WebTemplates.DataTextField = "Title";
        WebTemplates.DataValueField = "Name";
        WebTemplates.DataBind();
        WebTemplates.SelectedIndexChanged += WebTemplates_SelectedIndexChanged;

        // 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 Template:<br/>"));
        panel.ContentTemplateContainer.Controls.Add(WebTemplates);
        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(@"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(@"<tr><td><div
        class="
"UserSectionHead"">Limit:<br/>"));
        panel.ContentTemplateContainer.Controls.Add(Limit);
        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
    /// with the selected web template
    /// event handler is the same as below - could use the same
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void WebTemplates_SelectedIndexChanged(object sender, EventArgs e)
    {
        FillWebs(Sites.Items, Webs.Items);
        Webs.Rows = Webs.Items.Count + 2;
    }


    /// <summary>
    /// event handler to list webs under the selected site collection
    /// with the selected web template
    /// </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 void FillWebs(ListItemCollection sites,
        ListItemCollection webs)
    {
        webs.Clear();
        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
        int result;
        if (int.TryParse(Limit.Text, out result))
        {
            part.Limit = result;
        }
        part.WebTemplate = WebTemplates.SelectedValue;
        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
        Limit.Text = part.Limit.ToString();
        WebTemplates.SelectedValue = part.WebTemplate;
        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 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 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 IEnumerable<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 IEnumerable<SPWeb> GetWebs(SPSite site)
    {
        var webTemplate = WebTemplates.SelectedValue;

        // SPWeb doesn't contain the same info as SPWebTemplate.Name
        // SPWebTemplate.Name contains "TEMPLATE#N"
        // where SPWeb WebTemplate contains only the "TEMPLATE"
        return site.RootWeb.GetSubwebsForCurrentUser().Where(w =>
            webTemplate.StartsWith(w.WebTemplate)).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. Also the web template -selection causes a post back to refresh the Webs list box to match webs found created with the selected web template. The tricky part here was to get the editor part sync properties from the web part and apply changes back to the web part and to handle the post backs caused in the panel. Below are two clips of how the property setting works:

Select site web template

Web Template is available to be selected from.

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

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

Other sites created with the selected web template are also available to be selected from.

Other sites created with the selected web template are also available to be selected from.

Popularity: 4% [?]

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

  1. A & H Garage Door Service
    943 S 40th St
    Springdale, AR 72762
    (479) 636-7658
    https://www.proliftdoors.com/bentonville/

  2. threesome says:

    They loved getting fucked by transgender border hoppers.

    Sponsored by these people: threesome

  3. plumber says:

    Elite Garage Door & Plumbing Services of Northwest Arkansas
    8007 W Pine St
    Rogers, AR 72756
    (479) 354-0088
    plumber

  4. Brigida says:

    Top trending figures the Dogecoin canine, Bonk, and Pepe might be involved in the recreation.

Leave a Reply