How to update HREF after anchor gets clicked in Blazor? - c#

Trying to replace HREF upon click on Anchor with Blazor.
So in pseudo;
click => calls function => replaces HREF => triggers open event, but targets new HREF
couldn't make it happen, what should be the syntax?
#context.FileName
Side note
Please do not reference https://github.com/aspnet/AspNetCore/issues/5545. I'm asking how to replace the HREF value with an external address which is irrelevant to that issue since I'm OK to use javascript: void(0) at this point.

Thanks to #daniherrera I started to look into the right places and figured it out the answer in here
[Inject] public NavigationManager NavigationManager { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
public async Task NavigateToUrlAsync(string url, bool openInNewTab)
{
if (openInNewTab)
{
await JsRuntime.InvokeAsync<object>("open", url, "_blank");
}
else
{
NavigationManager.NavigateTo(url);
}
}

Related

Blazor RenderFragment to String

I'm developing a code block component using .Net 6 Blazor wasm. I need to display the RenderFragment as string and also render the component in my html.
Here is my code block component,
<pre class="language-html">
<code class="language-html">
#("Some way to get non rendered html from #ChildContent")
</code>
</pre>
#ChildContent
#code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}
I'm using the above component as,
<CodeBlock>
<Chat></Chat>
</CodeBlock>
Expected Output:
<Chat></Chat>
<!-- This is the input I passed inside as RenderFragment and I need the exact Render Fragement Code;
not the Rendered Html code of the RenderFragment -->
Component gets rendered as expected. But unable to get non rendered html of my RenderFragment
One option is to pass the RenderFragment content as string parameter to CodeBlock component. But this results in duplicate and non readable HTML. Also this becomes difficult to maintain when the ChildContent has multiple lines of code.
<CodeBlock Html="#("<Chat></Chat>")">
<Chat></Chat>
</CodeBlock>
Any hints/suggestions on how to achieve this?
This is kind of a tough question. From what I understand you want to be able to render the ChildContent of your component but also be able to view the ChildContent data as plaintext razor code, or HTML if that's what's in your ChildContent, right?
So an option would be to surround your #ChildContent renderer tag with a <div> and assign that div a unique id when the parent component is initialized. Then you create another variable, let's call it private string RawContent { get; set; }. Then write a javascript function that takes an id value and gets the element by id from the DOM and returns the element's innerHTML.
I created a test project to try it and it works. Here are the snippets that are relevant.
In your Component.razor file:
#inject IJSRuntime JS
<pre class="language-html">
<code class="language-html">
#RawContent
</code>
</pre>
<div id="#ElementId">
#ChildContent
</div>
#code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
private ElementReference DivItem { get; set; }
private string RawContent { get; set; }
private Guid ElementId { get; set; } = Guid.NewGuid();
protected async override Task OnInitializedAsync()
{
base.OnInitializedAsync();
RawContent = await JS.InvokeAsync<string>("GetElementHtmlText", ElementId.ToString());
}
}
Then in your index.html file add this in side of a <script> tag, or to one of your javascript files:
async function GetElementHtmlText(elementID) {
await setTimeout(() => { }, 1);
console.log(elementID);
var element = document.getElementById(elementID);
console.log(element);
var result = element.innerHTML;
console.log(result);
return result;
}
Then anywhere you wish to use this component you will render the HTML markup and have the Raw Text available as well. However this function will return all of the html markup raw text, meaning that you get the razor tags and the empty comment elements that Blazor inserts as well (ie. <!--!-->). But as long as you know that you can work around them.
I didn't do this but you could modify this to call the JSInterop function inside the RawContent's getter instead of just when the component initializes.

Convert static html page to Blazor Page

I have build a static website, a long time ago, with Nicepage.
But i want to convert this project into a Blazor project, so i can use some C# code.
To achieve this, i copy/paste the code of a html page, into a razor file, except the script tags (i put them into index.html).
The issue is that elements are not well render.
For example, i have an image that i want to be visible only on desktop, and not on mobile. After i switch the project on Blazor, the image is not visible on desktop and mobile. I saw that nicepage as a global css file, and a specific css file for the html page. So i add them into the razor page
<link rel="stylesheet" href="nicepage.css" media="screen"> <link rel="stylesheet" href="home.css" media="screen">
But nothing happened.
So i would like to know if anyone has managed to create html page with Nicepage, and then use it in a Blazor project?
Thank you.
Blazor can be evil :). It works best with CSS (switching classes according to that field etc.) in my opinion. So if you want to display something on desktop do:
1. code in CSS like
#media (max-width: 992px){
.nameOfClass {
display:none;
}
}
2. Javascript:
Do simple model, inject IJSRuntime, invoke it, create function and use it as property in html part
Model -
public class WindowDimension
{
public int Height { get; set; }
public int Width { get; set; }
}
Inject IJSR -
#inject IJSRuntime JS at top of .razor page
Invoke e.g. in OnInitialize also be aware of naming. I had some issues when I had it like HeightOfWindow etc.
#code{
int Height { get; set; }
int Width { get; set; }
protected override async Task OnInitializedAsync()
{
var dimension = await JS.InvokeAsync<WindowDimension>("getWindowDimensions");
Height = dimension.Height;
Width = dimension.Width;
}
}
Create function in JS (can be only code in some JS file + do not forget to put in index file.
window.getWindowDimensions = function () {
return {
width: window.outerWidth,
height: window.outerHeight
};
};
HTML
<div style:"width: #Height">.....</div>

Event for click outside a div or element to close it on Blazor

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);
}

Redirecting in blazor with parameter

Hello how can you redirect to another page in Blazor with a parameter?
#page "/auth"
#using Microsoft.AspNetCore.Blazor.Services;
#inject AuthService auth
#inject IUriHelper urihelper;
<input type="text" bind="#Username" />
<button onclick="#AuthAsync">Authenticate</button>
#functions{
public string Username { get; set; }
public string url = "/home";
public async Task AuthAsync()
{
var ticket=await this.auth.AuthenticateAsync(Username);
urihelper.NavigateTo(url); //i see no overload that accepts parameters
}
}
In this case i want to navigate to the /home page giving it a string as parameter.
Do this:
Create a home.cshtml file page like this:
Note that two #page directive are employed since optional parameters are not supported yet.
The first permits navigation to the component without a parameter. The second #page directive
takes the {username} route parameter and assigns the value to the Username property.
Pages/home.cshtml
#page "/home"
#page "/home/{username}"
<h1>#Username is authenticated!</h1>
#functions {
// Define a property to contain the parameter passed from the auth page
[Parameter]
private string Username { get; set; };
}
Do this in your auth.cshtml
#functions{
public string Username { get; set; }
public string url = "/home";
public async Task AuthAsync()
{
var ticket=await this.auth.AuthenticateAsync(Username);
// Attach the parameter to the url
urihelper.NavigateTo(url + "/" + Username);
}
}
Hope this helps...
You can only pass parameters in the URL at present.
So, if your home component was expecting [Parameter] string Name you would need to provide a URL of /home/fred and fred would be passed into the Name parameter of the home component.
If you are looking to pass more complex data then you would have to look at doing it via some kind of service.
Here is the link to the official docs on routing parameters: https://blazor.net/docs/routing.html#route-parameters

Get the same display using an ActionLink inside Html.BeginForm and simple <a> tag

In my ASP.NET MVC 5 website I have a menu with two types of files: actual pages returned from the controllers and pdf files that you can view in the navigator. Since my menu is created dynamically (it varies according to Active Directory rights) I have to distinguish which type is which (Document or Page), and this means that according to the file type you click, it will not result in the same action.
To show you, imagine there is a List<DocumentModel> documents and the following to treat the information:
<ul>
#foreach (var document in documents)
{
<li>
#if (document.type.Equals("Document"))
{
using (Html.BeginForm("DisplayPDF", "Navigation", new { pdfName = document.link }, FormMethod.Post))
{
#Html.ActionLink(document.docName, "DisplayPDF", null, new {id="linkStyle", #class = "saveButton", onclick = "return false;" })
}
}
else
{
//This link is different then the one above visually
<span class="glyphicon glyphicon-file"></span>#document.docName
}
</li>
</ul>
<!--This script allows to submit the form, and treat the action of DisplayPDF-->
<script>
$(document).ready(function () {
$('.saveButton').click(function () {
$(this).closest('form')[0].submit();
});
});
</script>
DocumentModel is as follows:
public class DocumentModel
{
public long idDocument { get; set; }
public long idCategory { get; set; }
public string docName { get; set; }
public string link { get; set; }
public string type { get; set; }
}
Finally my DisplayPDF method is the following:
public FileContentResult DisplayPDF(string pdfName)
{
var fullPathToFile = #"Your\Path\Here" + pdfName;
var mimeType = "application/pdf";
var fileContents = System.IO.File.ReadAllBytes(fullPathToFile);
return new FileContentResult(fileContents, mimeType);
}
Now as you can see in the Razor view I displayed, there are two types of clickable links. One is only a controller redirection, whereas the second calls the DisplayPDF method and then returns a PDF page. The problem I'm having is that these links are displayed differently, and I don't know how to have the same display for both.
To synthesize my question : how to display the same using #Html.ActionLink() and 'a' tag, knowing that currently, the 'a' tag has the proper display?
Update I modified some code as to give IDs to <a> tag and #Html.ActionLink, then I added this css in stylesheet:
#linkStyle {
margin-left: 25px;
color: antiquewhite;
}
But I still have a different display
UPDATE 2 I think that the problem of display come from the BeginForm method, any ideas to fix this?

Categories

Resources