In my Blazor server-side project, I need to close a pop-up menu by clicking outside the menu.
I use a simple If statement to show/hide the pop-up by triggering the onClick event. but there is no such event to close the pop-up by clicking outside the pop-up menu. so the user should only close it by click on the element with the onClick event.
so my question is how we can resolve this issue better without using JS?
Thank you in advance.
Here is what I do with a div to fire the onfocusout event where the ShowSelectBox variable is used by your if-statement to show or hide something:
<div tabindex="0" #onfocusout="#(() => ShowSelectBox = false)">
...
</div>
I used this in a blazor webassambly application, but should be the same for server-side.
Add a <div> element that is the size of the screen and is z-indexed 1 lower than the popup menu, but higher then the rest of the application. You will need to use position: fixed in the CSS class, and then width: 100%; height: 100%; top: 0%; left: 0%; so it will fill up the page. Initial CSS display value should be display: none;. When you open the popup, also change the display property of the div to display: flex to add it to the DOM, then add an #OnClick click handler on this floating div that will close the popup AND set the floating div display property back to display: none;. The div should be clear so you can still see the rest of the app behind it, but allows you to click outside the popup for the behavior you are looking for, as anywhere outside the popup will be on the Div covering the rest of the screen.
Note that on the popup, you will also need to set the floating Div display value back to 'none' when you close the popup from inside the popup, otherwise it will stick around and require an extra click to get it to go away.
You can also slightly shade the Div element with transparency to provide a highlighting effect for your popup when it opens.
I came up with a solution without using JS, using the onmouseover event we can set a value to a bool var as true and with a onmouseout event we can set as false and so, verifying if the mouse clicked in or out a tag element
<button #onclick="Show">Click</button>
<div #onmouseover="MouseIn" #onmouseout="MouseOut">...</div>
#code{
bool IsInside;
void Show()
{
if(!IsInside)
{
...
}
}
void MouseIn()
{
IsInside= true
};
void MouseOut()
{
IsInside= False;
}
}
I had the same situation and I ended up using css :hover state. Assuming you have a drop down menu popup, doing this way when you hover the div element the menu popup will get displayed and when you hover out the menu popup will be closed automatically.
or
You can do something like,
<div class="dropdown is-right is-hoverable #(PopupCollapsed ? "is-active" : null)"
#onclick="e => this.PopupCollapsed = !this.PopupCollapsed"
#onfocusout="e => this.PopupCollapsed = false">
</div>
#code {
public bool PopupCollapsed { get; set; }
}
The #onmouseout event inside of my div tag does work, but it was a bit too touchy for what I needed because my div has several children so it kept closing when I didn't want it to. I found this solution to be more reliable.
I created an event handler class
[EventHandler("onmouseleave", typeof(EventArgs), true, true)]
public class EventHandlers
{
}
and then inside my razor page:
<div class="menu" #onmouseleave="MouseLeave"></div>
#code {
private void MouseLeave(EventArgs args)
{
IsVisible = false;
}
}
My solution is use blazor only.
All dropdown is from one component which contols click on it. if user click on closed dropdown to open it then actualy opened dropdown is closed. Or if user click on main div of body page or any object on this div (not dropdown), then actualy opened dropdown is closed too. Attribute stopPropagation is set to true on dropdown for block main div click event to block close dropdown actualy opening itself.
Dropdown has its own click event because it also contains an input element for searching dropdown items and clicking on it would close the dropdown.
It should also work for DatePisker, ColorPicker and other elements that actually open a div.
I try this solution and it looks like it could work well
Create class for all dropdowns
public class DropDownState
{
public string OpenedDropDownId { get; private set; }
public event Action OnChange;
public void SetOpened(string Id)
{
OpenedDropDownId = Id;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
To Program.cs add
builder.Services.AddScoped<Data.DropDownState>();
Dropdown component looks like this, stop propagation is important for this solution
#inject Data.DropDownState DropDownStateService
#implements IDisposable
<div class="form-group" #onclick="ObjectClick"
#onclick:stopPropagation="true">
<div class="dropdown">
...
// each dropdown needs own unique id
[Parameter]
public string ComponentId { get; set; }
bool showResults = false;
protected override async Task OnInitializedAsync()
{
DropDownStateService.OnChange += DropDownOdherOpened;
}
public void Dispose()
{
DropDownStateService.OnChange -= DropDownOdherOpened;
}
async Task DropdownShow(bool _showResults)
{
showResults = _showResults
if (showResults)
{
// this informs other dropdowns that this dropdown is opened an other dropdown go to closed state
DropDownStateService.SetOpened(ComponentId);
}
}
public void DropDownOdherOpened()
{
if (DropDownStateService.OpenedDropDownId != ComponentId && showResults)
{
// close dropdown
DropdownShow(false);
StateHasChanged();
}
}
// this closes other dropdowns on click, this replaces main div click event functionality
public async Task ObjectClick()
{
DropDownStateService.SetOpened(ComponentId);
}
And in MainLayout.razor add onclick
#inject Data.DropDownState DropDownStateService
<div class="container-fluid" #onclick="ObjectClick">
#Body
</div>
public async Task ObjectClick()
{
DropDownStateService.SetOpened("Body");
}
I had this very same issue with a custom dropdown menu I made, and I felt that the standard behavior of anything that looks like a dropdown should be to close itself if you click outside of it.
I solved it with a custom component that puts a fullscreen div just below the dropdown (z-index wise), and added an onclick eventcallback to that div. It feels a bit unorthodox to do it like this, and it may not fit all cases, but hey, it works, and no JavaScript needed!
On my .razor page:
#if(showUserDD)
{
<OutsideClickDetector MethodToCallOnClick="#ToggleUserDD" LowestInsideComponentZIndex="1000" />
<div id="UserDDContent" style="z-index: 1000" class="list-group position-absolute shadow">
... some content ...
</div>
}
#code {
private async Task ToggleUserDD() => showUserDD = !showUserDD;
}
OutsideClickDetector.razor
<div #onclick="OnClick" class="vh-100 vw-100 position-fixed"
style="top: 0; left: 0; z-index: #(LowestInsideComponentZIndex-1);
background-color: #(TransparentOutside ? "none" : "rgba(0,0,0,0.5)");"
/>
#code
{
[Parameter] public int LowestInsideComponentZIndex { get; set; }
[Parameter] public bool TransparentOutside { get; set; } = true;
[Parameter] public EventCallback MethodToCallOnClick { get; set; }
private async Task OnClick() => await MethodToCallOnClick.InvokeAsync(null);
}
For debugging purposes you can set TransparentOutside to false if you want to see the fullscreen div.
It's a bit of a faff but managed it with some JS interop. I'm new to Blazor but managed to cadge this together from various other posts;
Add an id to your popup - called mine profile-popup
<div id="profile-popup">
<RadzenProfileMenu #ref="profileMenu" Style="width: 200px;" >
<Template>
<div class="row">...
Create a JS file to attach a handler to the click event of the document - if the source of the click is in your popup ignore it, otherwise fire a helper method from your helper class
window.attachHandlers = (dotnetHelper) => {
document.addEventListener("click", (evt) => {
const profileElement = document.getElementById("profile-popup");
let targetElement = evt.target;
do {
if (targetElement == profileElement) {
//return as not clicked outside
return;
}
targetElement = targetElement.parentNode;
} while (targetElement);
dotnetHelper.invokeMethod("InvokeAction");
});
};
Create the helper class
public class ProfileOutsideClickInvokeHelper
{
Action _action;
public ProfileOutsideClickInvokeHelper(Action action)
{
_action = action;
}
[JSInvokable]
public void InvokeAction()
{
_action.Invoke();
}
}
Attach the handler in the OnAfterRender override. I have a component containing the popup. You need to dispose of the object reference
public partial class TopBanner : IDisposable
{
[Inject]
IJSRuntime JSRuntime { get; set; }
public void CloseProfileMenu()
{
profileMenu.Close();
}
DotNetObjectReference<ProfileOutsideClickInvokeHelper> _objRef;
protected override void OnAfterRender(bool firstRender)
{
_objRef = DotNetObjectReference.Create(new ProfileOutsideClickInvokeHelper(CloseProfileMenu));
JSRuntime.InvokeVoidAsync("attachHandlers", _objRef);
}
public void Dispose()
{
_objRef?.Dispose();
}
}
Not sure if this is the best solution, or even a good one, but it seems to work ok.
Here is my solution. This is Blazor/CSS only and will work with most popular exsisting pop-up/slider/offcanvas type css (eg: bootstrap).
The button to open the pop-up/slider etc:
<a #onclick="ToggleUserPanel">Open</a>
The pop-up/slider etc:
<div id="quick_user" class="offcanvas offcanvas-right p-10 #UserPanelClass">
<a href="javascript:;" class="btn btn-primary" id="quick_user_close">
<i #onclick="ToggleUserPanel"></i>
</a>
</div>
Important part, the Overlay. Most base css libraries will use an Overlay with a pop-up, the beauty of this is you don't need to worry about styling a div for the "non" pop-up part of the screen. If you aren't using a library it's easy enough to write your own styles for a z-indexed overlay.
#if (UserPanelClass != "")
{
<div class="offcanvas-overlay" #onclick="ToggleUserPanel"></div>
}
BLAZOR STUFF
#code{
private string UserPanelClass = "";
//This will either show the User Panel along with it's overlay
//or close the User Panel and remove the overlay.
//It can be triggered by any buttons that call it or (the solution to this
//post) clicking on the "Non Pop-Up" part of the screen which is the
//overlay div
private void ToggleUserPanel()
{
if (String.IsNullOrEmpty(UserPanelClass))
UserPanelClass = "offcanvas-on";
else
UserPanelClass = "";
}
}
And that's it.
Found a solution in a Github repository.
<OutsideHandleContainer OnClickOutside=#OnClickOutside>
...
<div>Sample element</div>
...
</OutsideHandleContainer>
#functions{
void OnClickOutside()
{
// Do stuff
}
}
I was running into a similar, if not the same, issue. In my case, I have a shared component from an RCL, that exists as a child component on either a page or other component. I'm creating a custom dropdown menu and I needed a global event that would close all of those components if I clicked anywhere else on the page.
Originally, I resorted to creating a global click event with Javascript. I decided to look back into it and came up with a solution that has zero Javascript and I wanted to share and it's actually pretty simple.
I have my component
<div class="dropdown" #onclick="toggleDropdown" #onclick:stopPropagation>
#if(_item.Expanded)
{
<div class="dropdown-list">
<ul>
<li>Item</li>
</ul>
</div>
}
</div>
#code{
[CascadingParameter]
protected MyService Service { get; set; }
[Parameter]
protected Item Item { get; set; }
private Item _item;
protected override void OnParametersSet()
{
_item = Item;
if(!Service.Items.Contains(_item))
Service.Items.Add(_item);
base.OnParametersSet();
}
private void toggleDropdown() => _item.Expanded = !_item.Expanded;
}
Then I have my service
public class MyService : IMyService
{
private List<Item> _items;
public List<Item> Items
{
get => _items ?? new List<Item>();
set => _items = value;
}
public void CloseDropdowns()
{
if(Items.Any(item => item.Expanded))
{
foreach(var i in Items)
i.Expanded = false;
}
}
}
Finally, I have the MainLayout of my actual application
<CascadingValue Value="MyService">
<div class="page" #onclick="MyService.CloseDropdowns">
<div class="main">
<div class="content px-4">
#Body
</div>
</div>
</div>
</CascadingValue>
Hope this helps.
Update
I failed to mention an issue that occurs when there are multiple of the same component on a page, for example, two or more dropdowns. The above code works as designed since the mainlayout click event is bubbled up, but we don't want this to close everything every time, we could have one dropdown open and all the others closed.
For this, we need to stop the event bubbling to the mainlayout and provide a callback to the dropdown's parent component that will update the child component.
In the service
public void ToggleDropdown(bool expanded, Item item)
{
foreach(var i in _items)
i.Expanded = false;
item.Expanded = expanded;
}
In the component
[Parameter]
public EventCallback<(bool, DSCInputConfig)> ExpandCallback { get; set; }
private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender;
private void toggleDropdown()
{
_expanded = !_expanded;
ExpandCallback.InvokeAsync((_expanded, _config));
_shouldRender = true;
}
Finally, in the parent component/page
<Dropdown Options="#component" ExpandCallback="dropdownCallback" />
<Dropdown Options="#component" ExpandCallback="dropdownCallback" />
#code{
private void dropdownCallback((bool expanded, Item config) item) => MyService.ToggleDropdown(item.expanded, item.config);
}
I've search a lot for this issue but nothing came up.
My problem is the following: I have a main view in which I want to load a user control with parameters when I click on a button but the user control won't show. The constructor of the user control is called and the parameters are set, even the page load event from the user control is called. What am i doing wrong?
Main.aspx:
<%# Page Title="Main page" Language="C#" AutoEventWireup="true" CodeBehind="Main.aspx.cs" Inherits="MainApp.Main" MasterPageFile="~/Site.master" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"></asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<%-- some other fields which i send to the user control on click --%>
<div class="col-lg-1 col-sm-12 col-md-1">
<asp:Button runat="server" CssClass="btn btn-primary" CausesValidation="false" ID="generateData" OnClick="generateData_Click" Text="View info" />
</div>
<div runat="server" id="contentDiv">
<p>No info available atm.</p>
</div>
</asp:Content>
Main.aspx.cs button click event:
protected void generateData_Click(object sender, EventArgs e)
{
UserControl1 uc = (UserControl1 )this.LoadControl(typeof(UserControl1 ), new object[] { param1, param2, param3});
contentDiv.Controls.Clear();
contentDiv.Controls.Add(uc);
}
UserControl1.aspx.cs:
public partial class UserControl1: System.Web.UI.UserControl
{
public string Param3 { get; set; }
public string Param1 { get; set; }
public string Param2 { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
Page.DataBind();
}
public UserControl1(string param1, string param2, string param3)
{
Param1 = param1;
Param2 = param2;
Param3 = param3;
}
}
UserControl1.ascx:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="UserControl1.ascx.cs" Inherits="MainApp.UserControls.UserControl1" %>
<div>
<p style="color: red">Under construction</p>
</div>
The user control isn't visible on the main page and I don't know why.
PS: I know that i can send parameters as seen below but I don't understand why I cannot use the method described above.
UserControl1 uc = (UserControl1)this.LoadControl("~/UserControls/UserControl1.ascx");
uc.Param1 = "val1";
...
Here is the full explanation of why second method with LoadControl by type will not work: Asp.net Usercontrol LoadControl Issue.
The reason there is a difference is because in the second instance you
are bypassing the binding mechanism of the the two partial classes.
When you include the ascx control in the page as a normal declaration,
the controls declared within it (the label in your example) are
automatically instantiated by the framework as the class is
instantiated. This is a binding mechnism that reads the "front-end"
ascx file and instantiates objects in the class.
When you use LoadControl(string) - you are doing exactly the same
thing.. it uses a virtual path to get the "front-end" file and ensure
controls within it are instantiated.
However, when you use the new (to 2.0) LoadControl(type, object)
version, there is no virtual path available and it rightly so makes no
assumptions (as it cannot ensure that a type has a front end file).
I'm trying to create a custom Context Menu as a user control. The code behind for this user control looks like this:
public partial class UCContextMenu : UserControl
{
private List<ContextMenuItem> m_menuItems = new List<ContextMenuItem>();
protected void Page_Load(object sender, EventArgs e)
{
}
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<ContextMenuItem> Items
{
get { return m_menuItems; }
set { m_menuItems = value; }
}
public event EventHandler Command;
}
Then, I defined the ContextMenuItem type in the same namespace as the user control (in the same file actually) which is essentially a menu item:
public class ContextMenuItem
{
public string Name { get; set; }
public string Text { get; set; }
}
The ascx page for the user control includes a repeater control which is defined like this:
<div id="contentHolder">
<ul id="ulContextMenu" style="display: none; z-index: 1000; border-width: 0.8px;
font-size: 13px;" class="ui-corner-all">
<asp:Repeater runat="server" DataSource="<%# Items %>">
<ItemTemplate>
<li><a href='#<%# DataBinder.Eval(Container.DataItem, "Name") %>'>
<%# DataBinder.Eval(Container.DataItem, "Text") %></a>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</div>
And finally to use it in a page after registering it, I have:
<uc:ContextMenu runat="server" OnCommand="SomeMethod">
<Items>
<uc:ContextMenuItem Name="SomeName" Text="SomeText" />
</Items>
</uc:ContextMenu>
Now when I run this, I get a NullReference parse error on line
<uc:ContextMenuItem Name="SomeName" Text="SomeText" />
When not running, Visual Studio also says Element 'ContextMenuItem' is not a known element. It doesn't catch the exception at runtime either so maybe that way I could look at the stack trace to see what's going on. I was just wondering if anyone has encountered similar issues like this. Any help is appreciated.
Create ContextMenuItem as a normal user control. The fact that you are defining it in the same namespace as you have emphasized doesn't help you in any way.
So, just add ContextMenuItem.ascx to your project and then register and add the control on page or other control.
I have a base usercontrol in my ASP.Net app, There are HTML markup within this usercontrol that are set to runat="server" and id's. The problem I am having is when the usercontrol is loaded, the HTML markup is being returned as null
ASP.Net C# code:
public partial class webonlinecustombase : System.Web.UI.UserControl
{
public Event Event { get; set; }
public OnlineSystemPageCustom custompage { get; set; }
public OnlineSystemPageCustom.OnlineSystemPageHdr.OnlineSystemPageModule custommodule { get; set; }
public void Page_Load(object sender, EventArgs e)
{
string typeName = custommodule.ModuleInternetFile;
inpagelink.HRef = "#" + custommodule.ModuleName.Replace(" ", "").Replace("/", "");
modtitle.InnerText = custommodule.ModuleName;
Type child = Type.GetType(typeName);
UserControl ctl = Activator.CreateInstance(child) as UserControl;
if (ctl != null)
{
this.modsection.Controls.Add(ctl);
}
}
}
Here is the HTML Markup:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="webonlinecustombase.ascx.cs" Inherits="IPAMIntranet.IPAM_Controls.webtemplatecontrols.webonlinecustombase" %>
<a id="inpagelink" runat="server"></a>
<span id="modtitle" runat="server" style="width:100%;text-align:left">Scientific Overview</span>
<div id="modsection" runat="server" style="width:100%;">
</div>
<p>Back to Top</p>
Why is the inpagelink and modtitle being returned as null?
I have seen this happen in web applications (not web sites) when changes are made to the source (especially setting runat=server on items that were not previously runat=server), but you don't switch to the designer view.
The way that I resolve this issue is to switch to design view and dirty a property in one of the fields. This usually causes VS to update the designer code-behind file.
You can double-check this file to ensure the controls have been added. If you check it prior to doing this, you should see that they are missing.
asp.net does'n have span class, so you cant do anything in code behind with it.
use LinkButton or HyperLink instead of
the other solution is to create span or a in code, something like this
var span = new HtmlGenericControl("span");
span.InnerHtml = "From<br/>Date";
span.Attributes["class"] = "blue";
placeholder.Controls.Add(span);
hope I helped :))
I have a gridview bound to a sql server datasource. I am currently moving selected items to a ListBox to show chosen records. I'd like to switch to the ReOrderList from the AJAX Toolkit to be able to reorder the selected items. Unfortunately, the ReorderList needs a true datasource to bind to. What is the best practice for creating some sort of temporary table that the user would use?
OK, what you could do is persist your temporary list datasource in viewstate. Here's a rough example:
<asp:ScriptManager ID="SM1" runat="server"></asp:ScriptManager>
<ajaxToolkit:ReorderList ID="RList" runat="server"
DragHandleAlignment="Left" ItemInsertLocation="End"
AllowReorder="true" ShowInsertItem="true" PostBackOnReorder="false">
<ItemTemplate>
<p><%# Eval("ID") %> = <%# Eval("Name") %></p>
</ItemTemplate>
</ajaxToolkit:ReorderList>
<asp:Button ID="ButtonAdd" runat="server" OnClick="ButtonClick_AddItem" Text="Add New" />
Then in codebehind:
public partial class SortList : System.Web.UI.Page
{
[Serializable]
public class MyItem
{
public Guid Id { get; set; }
public string Name { get; set; }
public MyItem(Guid id, string name)
{
Id = id;
Name = name;
}
}
protected List<MyItem> MyList
{
get
{
if (ViewState["myClass"] == null)
ViewState["myClass"] = new List<MyItem>();
return (List<MyItem>)ViewState["myClass"];
}
}
protected void AddItem(Guid id, string name)
{
MyList.Add(new MyItem(id, name));
RList.DataSource = MyList;
RList.DataBind();
}
protected void ButtonClick_AddItem(object sender, EventArgs e)
{
AddItem(Guid.NewGuid(), DateTime.Now.Ticks.ToString());
}
}
Obviously you would substitute MyItem class with whatever you want to store and replace the button with the GridView select item event. But hopefully the principle is there?
I don't follow. The ReOrderList has a DataSourceID property to which you can just point at your existing SqlDataSource. Or do you not actually have an SqlDataSource control?
If not, how are you binding the data to your GridView? If you are binding to some kind of object colllection then you could use an ObjectDataSource instead, so long as it implements IList interface. Perhaps have a read of ReorderList - bind to DataTable if you are trying to bind to a DataTable instead.