I have a repeater that lists customer items, one of which is a button for more information. When the user clicks that button, a user control appears immediately below it. I'd like for the user control to then close when the button is clicked again. I initially toggled visibility server-side, but I now can't use this because the user control needs to be loaded on the button click due to parameters that need to bepassed through. I also can't used show and hide by jquery because that would involve downloading all the data first for the whole page which could make the page too cumbersome.
So currently in each repeater I have a linkbutton with command arguments and an OnCommand event which fires this event:
protected void uxPolicyDocsButton_Command(object sender, CommandEventArgs e)
{
//Find Policy Summary control within repeater item in order to toggle visibility
LinkButton button = sender as LinkButton;
if (button != null)
{
RepeaterItem ri = button.Parent as RepeaterItem;
if (ri != null)
{
PlaceHolder policySummaryPlaceHolder = (PlaceHolder)ri.FindControl("uxPolicySummaryPlaceHolder");
Control policyDocuments = (Control)PolicySummaryPlaceHolder.FindControl("uxPolicyDocumentsControl");
foreach (Control c in policySummaryPlaceHolder.Controls)
{
//FindControl action goes in here. Have stepped through though and it doesn't appear
}
if (policyDocuments != null)
{
policySummaryPlaceHolder.Controls.Remove(policyDocuments);
}
else
{
policyDocuments uxPolicyDocumentsControl = (PolicyDocuments)LoadControl("~/Controls/Home/PolicyDocuments.ascx");
uxPolicyDocumentsControl.PolicyNumber = button.CommandArgument;
policySummaryPlaceHolder.Controls.Add(uxPolicyDocumentsControl);
}
}
}
}
My plan for the toggle was that if the PolicyDocuments control was null, then load the control, and if not then remove the control, but it always came back null. It did load the control correctly though.
Here is the section of the repeater:
<ItemTemplate>
<tr>
<td>
<%#Eval("StartDate","{0:d}")%>
</td>
<td class="center-cell-ctrl">
Postcode:<br />
<%#Eval("Postcode")%>
</td>
<td id='<%#Eval("PolicyNumber")%>' class="button-cell">
<asp:LinkButton ID="uxPolicyDocsButton" CommandName="PolicyNumber" CommandArgument='<%#Eval("PolicyNumber")%>' OnCommand="uxPolicyDocsButton_Command" runat="server" Visible="false">Policy<br />Documents</asp:LinkButton>
</td>
</tr>
<asp:PlaceHolder ID="uxPolicySummaryPlaceHolder" runat="server"></asp:PlaceHolder>
<asp:PlaceHolder ID="uxPolicyDocumentsPlaceHolder" runat="server"></asp:PlaceHolder>
</ItemTemplate>
I've had a look around for people who have had similar problems, and a common answer is that you need to load controls in the Page_Init event. Does that mean that this way won't work? Any ideas for an alternative if this is the case?
Many thanks
Every time your page is loaded (either initially, or via PostBack) the Control Tree needs to be re-created.
The controls declared in the ASPX part of your page are added by the framework (kind of), but any controls that you add "dynamically" need to be placed back in the control tree too. If they aren't then, as you've disovered, they simply can't be found again.
Complex controls, like the datagridview, rebuild their control tree by storing (a huge amount of) data in the ViewState.
It might be simpler, rather than have your page add/remove items from a PlaceHolder, to create a user control that you can and hide, and load it's data the first time its shown. This also helps your controls and pages adhere to the Single Repsonsibility Principal
I dont' understand, why you can't add the PolicyDocuments with visible = false and no datasource instead of Placeholder, like that:
<uc:PolicyDocuments ID="uxPolicyDocuments" runat="server" Visible="false"></asp:PolicyDocuments>
and during the command use such code:
if (ri != null)
{
var policyDocuments = ri.Controls.OfType<PolicyDocuments>().FirstOrDefault();
if (policyDocuments == null)
return;
if (policyDocuments.Visible)
{
policyDocuments.Visible = false;
}
else
{
policyDocuments.PolicyNumber = button.CommandArgument;
policyDocuments.Visible = true;
}
}
In this case you don't need to use some hacks in Init event of the page.
Anyway, you always can use the .Controls.OfType<PolicyDocuments>() extension method to get all the user controls you need.
Related
I have a page that have the following code for a custom control:
<SiteControls:Announcements runat="server" id="UserAnnouncements" />
Let's also say I have a GridView control, just so I can cover multiple scenarios. I need to check if the user has permission to view this control by checking the Boolean:
PermissionsManagement.DoesUserHavePermission(userId, permissionId)
Which is defined as:
public static class PermissionsManagement
{
public static bool DoesUserHavePermission(int userAccountId, int permissionId)
{
// Code Goes Here
}
}
If the user doesn't have permission, DoesUserHavePermission will return false. I have the ASP.NET WebForms page laid out as if the user has full control (meaning I have all the controls on the page and want to remove them if they don't have permission vs adding every single control to the page).
I can set the control's visibility to false on Page_Load function if the user doesn't have permission, but that doesn't stop my control from loading or in the case of a GridView from loading its data. How do I stop a control (User control or standard control) from loading any data if the user doesn't have permission to use (view) the control? I have tried the following inline code which doesn't work:
<% if(PermissionsManagement.DoesUserHavePermission(1, 1))
{ %>
<SiteControls:Announcements runat="server" id="UserAnnouncements" />
<% } %>
But that doesn't work as the control Page_Load still fires for the control and I assume any other control will load data if it is data-bound or acts similar to my control.
Without knowing much of your code, it is a little difficult to figure out the exact answer. However, as much as I understood your question, here's my answer.
Loading data for Announcements or GridView should still be in your control. I would expose a method in Announcements control that actually loads data for it. For the GridView you should simply defer the binding of DataSource until the permission check is performed. Of course these things need to be done in addition to hiding (setting visibility) of these controls.
See the code below, not complete, but enough to express an idea:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Check permissions here
if (allowed)
{
// For custom/user control
UserAnnouncements.GetAnnouncements();
// For grid view
GridView1.DataSource = GetGridviewData(); // GetGridviewData would return DataSet or anything valid.
GridView1.DataBind();
}
else
{
// Hide the controls
}
}
}
I have a custom control which is basically a Gridview and its first column is a TemplateField with a checkbox in the header and a checkbox for each of the rows. Clicking on the header checkbox should call some javascript to toggle each of the checkboxes in every row on display... it's a common requirement as we know.
My problem is (I'll go into more detail later) that when I have 2 of these controls on the same page, and I click the checkbox that selects all the chkboxes, the page calls the wrong bit of javascript (which only appears once on the rendered html page) and checks all the checkboxes on the wrong grid !
Here's my code:
<asp:TemplateField>
<HeaderTemplate>
<input type="checkbox" ID="chkSelectAll" onclick="SelectAllCheckboxes(this)"/>
</HeaderTemplate>
<ItemTemplate>
<asp:CheckBox runat="server" ID="chkSelected"></asp:CheckBox>
</ItemTemplate>
</asp:TemplateField>
Javascript:
<script>
function SelectAllCheckboxes(chk) {
$('#<%=gridPublishers.ClientID%>').find("input:checkbox").each(function ()
{
if (this != chk) { this.checked = chk.checked; }
});
}
</script>
So, the parent page has 2 of these controls on it. The first grid shows All the publishers and the other shows the publishers that have been selected from the first...
<h2>Selected Publishers</h2>
<cac:GridPublishers id="GridSelectedPublishers" runat="server" CssClass="GridSelectedPublishers" BindSource="DynamicFromSession" ShowMultiSelectCol="true" ShowFilterControls="false" NoRecordsMessage="No Publishers have been selected yet." />
<br /><br />
<h2>All Publishers</h2>
<cac:GridPublishers id="GridPublishers" runat="server" ShowMultiSelectCol="true" CssClass="GridPublishers" />
As I said earlier, the javascript only appears once on the rendered html page (and I understand why) but how can I get each instance of the custom control calling it's own javascript (or an alternative method) so that it only toggles its own checkboxes ??
...
I've also tried adding 2 javascript events and on grid bind trying to find the master checkbox and assign it the correct JS function, but I've searched each cell of the header row, and col 0 (where the control should be) holds 0 controls.
...
I've also tried adding a hidden button that on page load, I can assign it the correct javascript function (that will affect the correct gridview) and then the master checkbox fires the hidden button onClientClick event, but as the page reloads, it gets confused and fires the click event twice and from both grids apparently !
Please help !!
So I guess you realise that this line of code is causing the root problem of selecting the checkboxes in the wrong datagrid:
#<%=gridPublishers.ClientID%>').find
It's always going to pickup gridPublishers, and not GridSelectedPublishers.
So this is the area to fix. What you need to do is make this function a bit more abstract:
<input type="checkbox" ID="chkSelectAll" onclick="SelectAllCheckboxes(this)"/>
The onclick event passes 'this', but that's only a reference to the checkbox which isn't much help.
I'd suggest you try and make it something like this:
<input type="checkbox" ID="chkSelectAll" onclick="SelectAllCheckboxes(this,'GridSelectedPublishers')"/>
And then use the 2nd argument in the javascript function to grab the right datagrid.
Your problem now, is how to get that 2nd argument in there....you may be able to think of your own solution for this, but I would be tempted to make that checkbox an ASP checkbox, and find it during datagrid render and assign it the onClick with Attribute.Add
Making sense?
I've already marked "ben_the_builder's" answer as correct because it got me along the right line.
When I bind my grids I call this function:
private void Register_CheckAllControl_JScript()
{
// THIS IS A WORKAROUND FOR WHEN TWO OF THE SAME CUSTOM CONTROL LIVE ON THE SAME PAGE, EACH CONTROL'S JAVASCRIPT MUST SPECIFY THE ID OF THE CONTROL TO AFFECT
if (gridPublishers.HeaderRow != null)
{
CheckBox chkAll = gridPublishers.HeaderRow.FindControl("chkSelectAll") as CheckBox;
if (chkAll != null)
{
if (this.BindSource == Enumerators.BindSource.DynamicFromSession)
{
chkAll.Attributes.Add("onclick", "SelectAllCheckboxes(this,'GridSelectedPublishers');");
}
else
{
chkAll.Attributes.Add("onclick", "SelectAllCheckboxes(this,'GridPublishers');");
}
}
}
}
To access the "master" checkbox from code behind - it had to be an ASP control. an input control just wasn't recognised when iterating through the header cell collection.
My Javascript needed a little tweaking for the IDs to be correct. The control name I'm passing had to be name it was given on the parent page which belongs in the middle of the three tier final html output name (see the example, it'll make sense...)
My Javascript looks like this now:
<script>
function SelectAllCheckboxes(chk, ctrlName) {
//if ctrlName = "
$("#MainContent_" + ctrlName + "_gridPublishers").find("input:checkbox").each(function () {
if (this != chk) { this.checked = chk.checked; }
});
}
</script>
You want to use parameters. Instead of hard coding #<%=gridPublishers.ClientID%> into your javascript function, use the this parameter you pass to the function to determine the name of the grid view that contains the checkbox, then check everything inside that grid view.
I have created a usercontrol (.ascx) for use in my asp.net website (not mvc). The control has a few text boxes and allows the user to enter a new customer. It has two buttons, one to add the customer and one to cancel the control. I call display the control by using a “ModalPopupExtender “ with the code shown below:
This is in an updatePanel:
<asp:ModalPopupExtender ID="ModalPopupExtender2" runat="server" TargetControlID="hiddenButton"
PopupControlID="Panel3" BackgroundCssClass="modalBackground" DropShadow="true"/>
Here is the Panel3 markup:
<asp:Panel ID="Panel3" runat="server" Style="display: none" CssClass="modalPopup" Width="100%" Height="100%">
<uc3:CustomerForm runat="server" ID="CustomerForm1" ParentUpdatePanel="UpdatePanel1" ErrorDisplayControl="lblErrorMessage"/>
</asp:Panel>
When I click a button on the main page the code behind calls:
ModalPopupExtender2.Show();
This shows my control and when I add a new customer the database is updated so this side of things works fine. What I need to do is enhance the code so that when a customer is added and the usercontrol closes, the customer dropdownlist on the main page is loaded with the updated customer list and the customer that the user enter is selected. The dropdownlist is databound so my question is, how do I know when my usercontrol has been closed so I can work out if a new customer has been added and then re databind my list and select the correct item? Working with asp.net and javascript is not an area I have a lot of skill in so please forgive if I have used the wrong terminology.
I have found a solution to my problem. In my usercontrol, after a new customer has been added I call:
RaiseBubbleEvent(this, new CustomerArgs {OrganisationId = organisationId});
In the calling page’s code behind I listen for the bubble event:
protected override bool OnBubbleEvent(object sender, EventArgs e)
{
if (sender is CustomerForm && e is CustomerArgs)
{
_organisationID = (e as CustomerArgs).OrganisationId.ToString();
DoOrganisationListDataBind();
return true;
}
return false;
}
This works fine for what I’m after. Not sure if this is a good way to do it but it works.
I'm using nested controls for a series of forms and having problem with the onclick event.
I'm just using textboxes and a manual code-behind insert rather than Gridview/Details view structure.
The scenario is that I have a car insurance form where users can add additional named drivers. At the top is a section where you add your own details (which is a seperate user control) and there is a section underneath where you can add up to 4 named drivers (which is also a seperate usercontrol). What I wanted to do was use my 'Add Driver' linkbutton onclick event to add the details into the database, then clear the form fields so the user could then add another driver. This all works fine until you go to click the Add Driver button again and discover it doesn't work.
This is the Parent control that houses the Add Driver user control:
<h2>Add Permanent Driver</h2>
<asp:ValidationSummary ID="uxValidationSummary" runat="server" ShowSummary="true" ValidationGroup="ChangeDetails" />
<div class="clear"></div>
<h3>
Please enter your details below as they are on your insurance documents</h3>
<uc1:UserDetails ID="uxUserDetails" runat="server" />
<uc2:AddDriver ID="uxAddDriver" EntryType="Permanent" runat="server" />
This is a cutdown version of the onclick event for the Add Driver submit button on the AddDriver control
protected void uxSubmit_Click(object sender, EventArgs e)
{
Page.Validate("AddDriver");
if (Page.IsValid)
{
DriverDAL.DriverTableAdapters.DriverTableAdapter taDriver = new DriverDAL.DriverTableAdapters.DriverTableAdapter();
taDriver.DriverInsert(StartDate, EndDate, Title,
FirstName, Surname, DateOfBirth, Gender);
uxDriverList.DataBind();
uxDate.Text = string.Empty;
uxEndDate.Text = string.Empty;
uxTitle.SelectedIndex = 0;
uxFirstName.Text = string.Empty;
uxSurname.Text = string.Empty;
uxDateOfBirth.Text = string.Empty;
uxGender.SelectedIndex = 0;
}
}
Is there anything I can do to reactivate this linkbutton after the first OnClick (or remove it and recreate it) so I can add multiple drivers? I tried to databind the button after the submit, but as expected, that had no effect, but that is the kind of thing I'm looking for. I can't redirect the page, because as mentioned earlier in the post, it is using nested controls and I arrive at this form by using the SelectedIndexChanged event of a DropDownList.
Any ideas appreciated - thanks. Let me know if more info is required.
It sounds like you might have some validation that's preventing the button from doing a postback. Try setting CausesValidation="false" and see if that makes a difference.
If validation is causing the problem, I would either separate the validation into groups and assign the button to a specific group, or toggle the enabled state of the validators based on some condition, for example:
RequiredFieldValidator1.Enabled = Panel1.Visible;
I have a listbox control. When the user clicks it, it tells a custom control to use a certain ID to use.
The custom control draws the same thing everytime(dynamically), just loads different content depending on this ID(it's loaded from a database into a dynamic form like control).
Ok, Now I'm having trouble with viewstate spillage. When you click the listbox to load say ID #1, it'll all look good. Then, you click on ID #2 and all the textbox controls created inside the custom control has the same thing that was put in ID #1. So when the listbox index changes I need to clear the view state, but I can't get this to work.
All of the controls are created at Page_Load also.
I tried ViewState.Clear() at Page_Load but that didn't do anything.
I had the custom control derive from INamingInterface, but I guess the IDs still match for viewstate.
I've tried changing the custom controls ID to something unique(like "CONROL_"+id.ToString()) I've also tried doing the same thing with the panel containing the custom control.
I can not seem to get rid of this view state!
EDIT
Ok here is code that demonstrates the problem
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (ddl.SelectedValue == "1")
{
Create("ID #1");
}
else if (ddl.SelectedValue == "2")
{
Create("ID #2");
}
}
void Create(string text)
{
TextBox t = new TextBox();
t.Text = text;
pnl.Controls.Add(t);
}
}
the markup:
<div>
<asp:Panel ID="pnl" runat="server">
<asp:DropDownList ID="ddl" runat="server" AutoPostBack="True">
<asp:ListItem Text="id 1" Value="1">
</asp:ListItem>
<asp:ListItem Text="id 2" Value="2"></asp:ListItem>
</asp:DropDownList>
</asp:Panel>
</div>
If you run this code you'll notice that if you change what is in the textbox and then you change the dropdown list, then what you typed earlier will be kept in there instead of it being overwritten..
My basic goal with this is to get it so that when you change to ID #2, it puts "ID #2" in the textbox no matter what(preferably without disabling viewstate altogether)
If I set the ID of the Text control then it doesn't retain the old value. Are you giving all controls a unique id?
void Create(string text)
{
TextBox t = new TextBox();
t.ID = text;
t.Text = text;
pnl.Controls.Add(t);
}
You can't do it that way.
For viewstate to work properly all controls must be created before it is loaded and with the same id's. So you must store the control definitions in session and recreate then with the same ids to ASP.NET load their properties from the view state. Page_load is too late, do it at PreLoad.
But it is easier to have all controls created at design time with visible set to false, and alternate their visibility so viewstate will work properly.
Actually this is no longer relevant. We fixed it by just disabling viewstate for our dynamically created controls. This would not work in all instances, but in our case there are two buttons the user can push(that are to do with the dynamic controls) or a list box to switch forms. The two buttons both save the state of the controls to database, so viewstate is not actually needed.(I always get confused when thinking about viewstate and how it interacts with controls.. )
So basic advice: If your having trouble controlling the viewstate, be sure you actually need it.