Here is what I came up with for a single-source popup modal for a web application.
I put a placeholder on the master page and the code below in a separate class. The class file receives the message and builds the modal html.
This works on full postback, but not when called from within an ajax update panel.
I know why. I just don’t know of any way around it. Any ideas?
Public Class Common
Inherits SiteMaster
Public Shared Sub showModalMsg(ByVal sMsg As String)
Dim lbl As New Label
lbl.Text = sMsg
'(for simplicity, the building of the div to make the modal is omitted)
Dim pageHandler = HttpContext.Current.CurrentHandler
If TypeOf pageHandler Is System.Web.UI.Page Then
Dim ph As PlaceHolder = DirectCast(pageHandler, System.Web.UI.Page).Master.FindControl("phModalDialog")
If ph IsNot Nothing Then
ph.Controls.Add(lbl)
End If
End If
End Class
Ok, here is what I did. I added an optional variable called 'container' that can be passed when calling the dialog within an update panel.
Public Shared Sub showModalMsg(ByVal sMsg As String,
Optional ByVal container As Control = Nothing)
I posted my full solution here:
How can I use a single modal across my web application and avoid repetative html on every page?
Related
Building my first server control. Getting the "Value cannot be null. Parameter name: child" error in debug at the point indicated in my code. The textbox is created, then becomes nothing when the parent's page_load event is called. The page_load event fires between the OnInit and CreateChildControls in my control. Examples are hard to come by.
Public Class ContactForm
Inherits WebControl
#Region "Local variables"
Private _ShowFirstName As Boolean
Private tbFirstName As TextBox
#End Region
<Browsable(True), _
Bindable(True), _
Category("Appearance"), _
DefaultValue(True), _
Localizable(True), _
Description("Show First Name field")> _
Public Property ShowFirstName() As Boolean
Get
Return _ShowFirstName
End Get
Set(value As Boolean)
_ShowFirstName = value
End Set
End Property
Protected Overrides Sub OnInit(e As EventArgs)
If ShowFirstName = True Then
Dim tbFirstName As New TextBox
tbFirstName.ID = "tbFirstName"
tbFirstName.MaxLength = 30
tbFirstName.Text = "IM HERE"
End If
MyBase.OnInit(e)
End Sub
<<<<< --- NOTE: The Page_Load event of the parent fires here ->>>>>
Protected Overrides Sub CreateChildControls()
Me.Controls.Add(tbFirstName) <<---Error: Value cannot be null. Parameter name: child
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
End Class
i haven't done this before but at the top you say, DefaultValue(True); does that mean it must have a default value? maybe if you set a default value OnInit (or make the prop false).
Okay, here is how I got it working.
I basically move the OnInit code into the CreateChildControls...duh!
I created a function (buildForm) that declares and adds all controls in one step like below.
Protected Overrides Sub CreateChildControls()
buildForm()
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
SIDEBAR: Web User Control vs Custom Server Control
I spent the last 4-5 days comparing and experimenting with both controls. I've done a lot of User Controls over the years and just copied them into applications. Hated the maintenance that created.
I spent a full day trying to compile my current web controls into single dll that could be used kind of like server controls. I found it tedious and limited.
Many articles, including MSDN, had me scared of server controls saying it is much harder than a web control to create. I can say...so far...I am digging the Server Control. I wish I had done this in the beginning. True reuse. Don't fear the server control.
If my session mode is set to "StateServer", I get a serialization error because I'm storing a list of controls in a session, and adding those into a aspx placeholder control. Here's the code that I think is problematic:
Protected Sub AddCtrl(ByVal ctrl As Control)
Dim l As List(Of Control)
If Session("Ctrls") Is Nothing Then
l = New List(Of Control)
Else
l = DirectCast(Session("Ctrls", List(Of Control))
End If
End Sub
Later on in the code, I iterate through the Ctrls session object, and add each control to a placeholder in the HTML.
What can do I to fix this problem? I tried to create a custom list that inherits from list, and made it serializable and use the custom list instead, but that didn't do it:
<Serializable()> _
Public Class MyList
Inherits List(Of Control)
I tried using ViewState instead and that also didn't do it. Seeking advice...
Thanks!
The Repeater control in the following test case contains two runat="server" DIVs. Each one of them gets a TextBox appened to them through the ItemCreated event of the Repeater. Both of the have AutoPostBack=True, and TextChanged event wired to txt_TextChanged. However, only the TextBox from the first level properly points to the event on the postBack of the page. The second level TextBox also causes the postBack to occur, but its value does not persist in the VIEWSTATE as well as the event does not fire.
Here's a direct link to the test case in a .zip file (uploaded to my personal website), as well as all the code needed. The project is built in VS2010 using .NET Framework 4, but the problem also exists in 1.1, 2, and 3.5.
Anybody has any ideas on what is wrong and how to make this work?
ASPX
<asp:Repeater ID="rep" runat="server">
<ItemTemplate>
<!-- first level works -->
<div id="divOutside" runat="server">
<!-- second level doesn't work -->
<div id="divInside" runat="server"></div>
</div>
</ItemTemplate>
</asp:Repeater>
Code-Behind
Public Class WebForm1
Inherits System.Web.UI.Page
Private Sub WebForm1_Init(sender As Object, e As System.EventArgs) Handles Me.Init
If Not IsPostBack Then
Dim Table As New DataTable()
Table.Columns.Add("Column1")
Dim Row As DataRow = Table.NewRow()
Row("Column1") = ""
Table.Rows.Add(Row)
rep.DataSource = Table
rep.DataBind()
End If
End Sub
Private Sub repTest_ItemCreated(sender As Object, e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles rep.ItemCreated
' outside
Dim divOutside As HtmlGenericControl = DirectCast(e.Item.FindControl("divOutside"), HtmlGenericControl)
Dim txtInput As New TextBox
With txtInput
.ID = "txtInputOutside"
.AutoPostBack = True
.Text = "Event gets called, value persists accross postBack."
.Width = 400
End With
AddHandler txtInput.TextChanged, AddressOf txt_TextChanged
divOutside.Controls.Add(txtInput)
' inside
Dim divInside As HtmlGenericControl = DirectCast(e.Item.FindControl("divInside"), HtmlGenericControl)
txtInput = New TextBox
With txtInput
.ID = "txtInputInside"
.AutoPostBack = True
.Text = "Event NOT called, value is lost during postBack."
.Width = 400
End With
AddHandler txtInput.TextChanged, AddressOf txt_TextChanged
divInside.Controls.Add(txtInput)
End Sub
Protected Sub txt_TextChanged(sender As Object, e As EventArgs)
End Sub
End Class
Is there any reason why the textboxes have to be added to the divs dynamically? why not just put them in the aspx page with the repeater then on itemdatabound enable/disable or do whatever you need to do. That should work fine.
By the way, if you were to use panels instead of divs your on the fly approach will work.
Here's a link to Microsoft Connect, where I reported this is an official bug:
https://connect.microsoft.com/VisualStudio/feedback/details/652655/asp-net-bug-in-event-linking-of-2nd-level-dynamic-controls
It contains 2 PARTIAL workarounds that work in the uploaded files section, if anybody is interested or encounters the same problem, as well as details to what I found in the Workarounds tab.
To keep it short, it is the order of getting references to the containers and the order the TextBoxes are appended to their appropriate containers which either causes the issue or works as expected.
But, not to forget the most important point, the only reason the first-level TextBox is there in the first place is to showcase what I want as functionality. If the first-level TextBox does not get appended at all, then both workarounds fail to provide any sort of fix. Please keep that in mind when reviewing that problem - finding a workaround that revolves around changing the order of the items is not an actual solution or a fully working workaround!
Thanks everyone for the input!
I have a custom control that inherits from .NET's CompositeControl class. This control overrides the CreateChildControls in order to build its child controls dynamically. I need the page to post back after a couple different javascript events occur on the client side.
In order to accomplish this, I create two hidden controls on the page so I can set their values with javascript, submit the page, and read the values out on server side. Here's is the code I use to create these two hiddens:
Protected Overrides Sub CreateChildControls()
hdEventName = New HiddenField()
Controls.Add(hdEventName)
hdEventName.ID = "hdEventName"
hdEventArgs = New HiddenField()
Controls.Add(hdEventArgs)
hdEventArgs.ID = "hdEventValue"
' other controls
' ...
End Sub
When a javascript event occurs I set the value attribute of the two hiddens and submit the page, like so:
hdEventName.value = 'EventName';
hdEventArgs.value = 'arg1,arg2';
document.forms[0].submit();
In the OnLoad method of my control, I attempt to check the Value property of the hdEventName and hdEventArgs controls, but it is always empty. However, Page.Request.Form(hdEventName.UniqueID) and Page.Request.Form(hdEventArgs.UniqueID) return correct values. The actual HTML in the markup also shows correct values after the page posts back.
Why is the Value property of the HtmlInputHiddens disconnected from the actual value that appears on the client?
Update
It appears that a control's properties get loaded from the form sometime after OnLoad occurs. Thus I was able to solve my problem by either moving the code that checks the two hidden fields into the OnPreRender method, or adding the following method to my code -
Private Sub Event_Handler(ByVal sender As Object, ByVal e As EventArgs)
Handles hdEventName.ValueChanged
' do stuff with hiddens
' ...
' reset the values back
hdEventName.Value = String.Empty
hdEventArgs.Value = String.Empty
End Sub
when the page posts back there's nothing to link the variable hdEventName to the control you previously created. what you're doing is akin to having an integer declared at the class level and setting it to 5 when you're creating child controls. there's nothing to maintain that value in that variable across postbacks.
if you want to get a reference to the control you created previously, you'd have to use
hdEventName = CType(Page.FindControl("hdEventName") , HiddenField)
(i'm guessing at this) or Request if you're only concerned with the value.
It appears that a control's properties get loaded from the form sometime after OnLoad occurs. Thus I was able to solve my problem by either moving the code that checks the two hidden fields into the OnPreRender method, or adding the following method to my code -
Private Sub Event_Handler(ByVal sender As Object, ByVal e As EventArgs)
Handles hdEventName.ValueChanged
' do stuff with hiddens
' ...
' reset the values back
hdEventName.Value = String.Empty
hdEventArgs.Value = String.Empty
End Sub
I have a base master page that specifies the main layout template of a website. It also handles some logic that changes tabs depending on the section, and also sets page meta information.
I'm dynamically loading nested master pages by looking at the querystring, loading up a record from the database, and setting the nested master page dynamically based on a value found in that record. I need to load dynamic nested master pages for layout and functional differences.
There is additional information in that record that I want to use in the base master page and in the dynamically loaded master page so I can avoid additional database calls.
Currently, I have set up a class that inherits MasterPage to act as the base class for the base master page. I have a shared (static) property that holds the object representing the database call that I want to share between the base master page and the nested, dynamically called master page.
It works, but it seems a little ugly. Are there any other better solutions?
You could always pass the record in the HttpContext.Items collection. Once it is in the Items collection it is available to every thing that can reach the HttpContext for the duration of the request.
Ok, I had to sleep on this one a bit, but I came up with a cleaner solution. I ended up using a base class for the page, instead of a base class for the master page. The base page sets the meta that I was going to set in the base master page.
Public Class PageBase
Inherits Page
Private _DocDetails As FolderDocument
Public Overridable ReadOnly Property DocDetails() As FolderDocument
Get
Return _DocDetails
End Get
End Property
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack() Then
SetMeta()
End If
End Sub
Protected Sub SetMeta()
If DocDetails IsNot Nothing Then
Page.Title = DocDetails.MetaTitle
If DocDetails.MetaKeywords <> String.Empty Then
Dim metaKeywords As New HtmlMeta()
metaKeywords.Name = "Keywords"
metaKeywords.Content = DocDetails.MetaKeywords
Page.Header.Controls.Add(metaKeywords)
End If
If DocDetails.MetaDescription <> String.Empty Then
Dim metaDescription As New HtmlMeta()
metaDescription.Name = "Description"
metaDescription.Content = DocDetails.MetaDescription
Page.Header.Controls.Add(metaDescription)
End If
End If
End Sub
End Class
..And then the aspx page inherits this base page and dynamically sets the master page.
<%# Page Language="VB" Inherits="PageBase" %>
<script runat="server">
Private _DocDetails As FolderDocument
Public Overrides ReadOnly Property DocDetails() As FolderDocument
Get
Return _DocDetails
End Get
End Property
Protected Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs)
_DocDetails = FolderDocuments.GetFolderDocument()
If _DocDetails IsNot Nothing Then
If _DocDetails.MasterPage <> "" Then
Me.MasterPageFile = String.Format("~/templates/{0}.master", _DocDetails.MasterPage)
End If
End If
End Sub
</script>
...and in the dynamically called master page I can reference the page's base class by casting:
Dim parentPage As PageBase = DirectCast(Page, PageBase)
Response.write(parentPage.DocDetails.Title)