Search code examples
asp.netdynamic-data

New button for Foreign Key fields in Dynamic Data


In a scaffolded page in ASP.NET Dynamic Data, if the entity has a foreign key field, and the value you seek is not in the the primary key table, i.e. is not in the drop-down, you have to abandon your edits to the entity, add the sought foreign key value to its table, and return to your original entity.

How could I go about adding a 'New' link/button to the foreign key field template, that would open a new window (make a Panel visible) where you can add the sought value, and then refresh the drop-down?


Solution

  • You mean like in the django admin ui ;). I'm currently trying to implement that feature, i'll post the code here if I get it to work.

    EDIT:

    Ok, I got that to work, complete django style... It's kinda long to explain but simple in fact.

    Files to create:

    A admin_popup.master to have a nice popup page (copy the admin.master without the header).

    A popup_Insert.aspx with admin_popup.master as master. (copy the Insert.aspx)

    Modifications

    To your admin.master.cs: add this:

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
    
        System.Web.UI.ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "refresh_fks", @"
        var fk_dropdown_id;
        function refresh() {
            __doPostBack(fk_dropdown_id,'refresh');
        };", true);
    }
    

    In your admin_popup.master, add these attributes to the body tag (it's used to resize the poup)

    <body style="display: inline-block;" onload="javascript:resizeWindow();">
    

    In your admin_popup.master.cs

    protected override void  OnInit(EventArgs e)
    {
        base.OnInit(e);
    
        System.Web.UI.ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "refresh_fks", @"
        var fk_dropdown_id;
        function refresh() {
            __doPostBack(fk_dropdown_id,'refresh');
        };", true);
    
        System.Web.UI.ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "resize_window", @"function resizeWindow() {
            window.resizeTo(document.body.clientWidth + 20, document.body.clientHeight + 40);
            window.innerHeight = document.body.clientHeight + 5;
            window.innerWidth = document.body.clientWidth;
        }", true);
    
        System.Web.UI.ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "update_parent", @"function updateParent() {
            window.opener.refresh();
        }", true);        
    }
    

    In popup_Insert.aspx.cs, replace these two functions:

    protected void DetailsView1_ItemCommand(object sender, DetailsViewCommandEventArgs e) {
        if (e.CommandName == DataControlCommands.CancelCommandName)
            System.Web.UI.ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "Close_Window", "self.close();", true); 
    }
    
    protected void DetailsView1_ItemInserted(object sender, DetailsViewInsertedEventArgs e) {
        if (e.Exception == null || e.ExceptionHandled) {
            System.Web.UI.ScriptManager.RegisterClientScriptBlock(this, this.GetType(), "Close_Window", "window.opener.refresh(); self.close();", true); 
        }
    }
    

    In ForeignKey_Edit.ascx, add a LinkButton (ID=LinkButton1) and in ForeignKey_Edit.ascx.cs, replace that function

    protected void Page_Load(object sender, EventArgs e) {
        if (DropDownList1.Items.Count == 0)
        {
            if (!Column.IsRequired) {
                DropDownList1.Items.Add(new ListItem("[Not Set]", ""));
            }
    
            PopulateListControl(DropDownList1);
            LinkButton1.OnClientClick = @"javascript:fk_dropdown_id = '{0}';window.open('{1}', '{2}', config='{3}');return false;".FormatWith(
                DropDownList1.ClientID,
                ForeignKeyColumn.ParentTable.GetPopupActionPath(PageAction.Insert),
                "fk_popup_" + ForeignKeyColumn.ParentTable.Name, "height=400,width=600,toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,directories=no,status=no");
        }
        if (Request["__eventargument"] == "refresh")
        {
            DropDownList1.Items.Clear();
            if (!Column.IsRequired)
            {
                DropDownList1.Items.Add(new ListItem("[Not Set]", ""));
            }
    
            PopulateListControl(DropDownList1);
            DropDownList1.SelectedIndex = DropDownList1.Items.Count - 1;
        }
    }
    

    And finally the two extentions functions I use (put it where you want to):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using System.Web.DynamicData;
    using System.Web.UI;
    
    public static class Utils
    {
        [DebuggerStepThrough]
        public static string FormatWith(this string s, params object[] args)
        {
            return string.Format(s, args);
        }
    
        public static string GetPopupActionPath(this MetaTable mt, string action)
        {
            return new Control().ResolveUrl("~/{0}/popup_{1}.aspx".FormatWith(mt.Name, action));
        }
    }
    

    In your global.asax, register the new route by changing that line:

    Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert|popup_Insert" }),
    

    Ok I hope i didn't forgot anything... It certainly could be improved, but it works. Ok I hope some people will fint that useful, it makes ASP.NET Dynamic Data a lot more better ;). I'm goind to take a look at many-to-many relationships now.