While the ModalPopup control is nice, it comes short in pretty common usage scenario, especially when wanting to pop it from a DataControl like a GridView, Repeater, DataList and you have what. Specifically what i want to address in this post is being able to effectively use it from a DataControl while reducing bloat and make usage easier. As part of this post, I have included source code for a custom control with a small example page. It's an attachment to this post, you'll find it below.
Unzip the Website application project and open it in visual studio by browsing to it from the Open Website dialog...
So, i'm back to using the Ajax Extentions again yayy \o/
Today i wanted to use the ModalPopup control in the AjaxToolKit library. It was indeed working nicely for basic things until my application started to become more demanding. Specifically what i needed to do was to be able to popup the control via a button click, but then i didn't want to nest the modalpopup in every row of my gridview, Since i'd have one button per row, launching the same modalpop, while the data in the modalpopup changed based on what row i was clicking. I found it totally overkill and un-necessary to being forced to have 1 ModalPopup per row to launch my popup!
To make things even worse, I was having issues with the ModalPopup dissappearing when a postback was instantiated by a control in the ModalPopup. The fix was ofcourse to call the Show method of the ModalPopup serverside to keep the popup displayed or else the postback wouldof refreshed it and keep it hidden again (as it would go back to its inital state, invisible and hidden).
Again I had issues with this quick fix, since it just didn't work as advertised and became problematic. So, i set out to fix 2 things :
1) I wanted 1 ModalPopup if all i had was a single ModalPopup to display via separate target controls, each on individual rows of a DataControl like a Gridview and reduce un-necessary code bloat.
2) I wanted the ModalPopup to stop the dissappear act if a postback was initialized when the ModalPopup was in view.
I ended up ofcourse writing a server control that is easy to use, and reuse since i'd be needing this functionality on several other pages, but above all, i didn't really want to write too much code and above all, i wanted to use the ModalPopup, while fixing the problems it had, not reinventing the control. So here is how i tacked the problem. Firstly, this is how you use the control declaratively :
<MyControl:DataControlModalPopup ID="DataControlModalPopup1" runat="server"
ModalPopupBehaviourId="DataControlModalPopupBehavior1" TargetControls="GridViewLinkButtonSelect,DetailsViewLinkButtonUpdate,DetailsViewLinkButtonCancel,DetailsViewLinkButtonDelete" />
As you can note above, the control expects 2 properties.
1) ModalPopupBehaviourId : This is the behaviour id you passed to the ModalPopup in your page. This is also how this control can find the ModalPopup js class instance created by the modalPopup control
2) TargetControls : Expects a comma delimited list of postback control ids that you may need to
a) Launch the control
b) Make postbacks once the popup control is shown(we want to fix the dissappearing act)
One thing to note is that if you have a button on every row of your DataControl that is launching the ModalPopup, you only need to take the id you provided to the control. So say you provided the button an id of GridViewLinkButtonSelect, the final client id is an autogenerated id with NamingContainer namespaces prefixed : MyGridView_ctl01_GridViewLinkButtonSelect ; In this manner asp.net is able to autogenerate a unique id for the button repeated on each row, which will change eg :
MyGridView_ctl01_GridViewLinkButtonSelect
MyGridView_ctl02_GridViewLinkButtonSelect
MyGridView_ctl03_GridViewLinkButtonSelect
etc..
You do not need to bother with the autogenerated id. all you need to do is pass the id of the control once, so in this case we pass only GridViewLinkButtonSelect as one of the control ids to TargetControls eg :
TargetControls="GridViewLinkButtonSelect,DetailsViewLinkButtonUpdate,DetailsViewLinkButtonCancel,DetailsViewLinkButtonDelet"
Note how we have included GridViewLinkButtonSelect in the list, while this is not the autogenerated id, this is all we need.
Ok, enough with the rant already, lets get to see a proper working code example on usage :
1) A simple gridview nested in an UpdatePanel, with a select button on each row
2) Upon selecting the button, trigger a postback and popup a DetailsView in a ModalPopup
3) Make a postback from the detailsview when trying to go in edit mode and update the record and not experience the ModalPopup dissappearing act.
Ok, time for a small example, well, i was hoping to make it minimum, looks a bit long but compared to real world usage scenario this is nothing and quite minimal :p
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Panel ID="PanelDetailsView" BackColor="Gray"
style="display:inline;" runat="server">
<asp:LinkButton ID="LinkButtonClose" Text="Close"
runat="server"></asp:LinkButton>
<asp:DetailsView ID="DetailsView1" SkinID="DetailsView1"
DataSourceID="ObjectDataSourceDetailsView"
runat="server" OnModeChanging="DetailsView1_ModeChanging">
<Fields>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="ButtonEdit" CommandName="Edit"
runat="server" Text="Edit" />
</ItemTemplate>
<EditItemTemplate>
<asp:Button ID="ButtonUpdate" CommandName="Update"
runat="server" Text="Update" />
</EditItemTemplate>
</asp:TemplateField>
</Fields>
</asp:DetailsView>
</asp:Panel>
<asp:GridView ID="GridView1" SkinID="GridView1"
DataSourceID="ObjectDataSourceGridView"
runat="server" OnRowCommand="GridView1_RowCommand">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="ButtonSelect" CommandName="Select"
CommandArgument='<%# Container.DataItem %>'
runat="server" Text="Select" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<Ajax:DataControlModalPopup runat="server" ID="DataControlModalPopup1"
ModalPopupBehaviourId="ModalPopup1"
TargetControls="ButtonSelect,ButtonEdit,ButtonUpdate,LinkButtonClose" />
<asp:HiddenField ID="HiddenFieldDummy1" runat="server" />
<AjaxToolKit:ModalPopupExtender runat="server" ID="ModalPopup1"
BehaviorID="ModalPopup1"
DropShadow="true" TargetControlID="HiddenFieldDummy1"
CancelControlID="LinkButtonClose"
PopupControlID="PanelDetailsView"
BackgroundCssClass="modalBackground"
RepositionMode="RepositionOnWindowScroll">
</AjaxToolKit:ModalPopupExtender>
</ContentTemplate>
</asp:UpdatePanel>
Now, one thing to note here is how the ModalPopupExtender's TargetControlID is set to a dummy hiddenField. Unfortunately I had to resort to this trick since not setting a targetID would cause the ModalPopupExtender to throw an error message making this propery obligatory.
Another thing to also note is that we passed the ID of the controls to our custom controls TargetControls property (but we did not pass the autogenerated client ID) ; what this means is that internally the custom control is written to fix the problem by checking who is posting back and matching it to the list of id's supplied to TargetControls.
The matching is done using the indexOf method, which for example can find : LinkButtonSelect in an id that was MyGridView_ctl01_GridViewLinkButtonSelect, so while we could pass the clientID, we didn't(for convenience) which means you are left to making sure that the ID you give your postback buttons do not collide with other buttons in the page. This is not a problem for me in 99.9% of the cases in my usage scenario and problem in most real world usage scenario. If it were then make sure you pass the ClientID :-)