NavLink updating URL but does not reloading page in Blazor - asp.net-core

I have a ProjectBase.razor page that is used to create, view & edit projects. The following routes all take you to this page:
/project/view/{projNum}
/project/create/
/project/edit/{projNum}
I also have a Navlink in my navigation menu that allows you to create a new project:
<NavLink class="nav-link" href="/Project/Create" Match="NavLinkMatch.All" >
<span aria-hidden="true">New Project</span>
</NavLink>
If I click on that link while on the view/edit features of the same page, the URL changes to "/Project/Create," but the page itself doesn't refresh or reload. Is there a way to force this through the NavLink? Or do I need to add an OnClick function to do this?

Create and use the OnParametersSetAsync task in your code block for the page. This event will fire when parameters change.
#code
protected override async Task OnParametersSetAsync()
{
// This event will fire when the parameters change
// Put your code here.
}

Yes, using something like Microsoft.AspNetCore.Components.NavigationManager and its NavigateTo function with forceLoad set to true will accomplish what you're looking for.
Of course yes, this will require you to set up an onclick function, but this is the way I ended up accomplishing something similar for a site-wide search page which never technically had its URL change outside of the query string search value I was passing it.
That being said, there may be a decent way of doing it with only NavLinks. I'll update my answer when I'm not on mobile.

In my component I had already overridden OnInitializedAsync in order to make an API call to get my data.
My solution looks like this:
protected override async Task OnInitializedAsync()
{
// Make your API call or whatever else you use to initialize your component here
}
protected override async Task OnParametersSetAsync()
{
await OnInitializedAsync();
}

I had same problem. Solution I have is...
Create new page
#page "/project/create/"
<ProjectBase></ProjectBase>
That's it! remove #page directive for(/project/create/) from ProjectBase page
Everything will work as expected... now do it for all pages you have.

In your case you have to make below changes as mention by Rod Weir, I am just extending the answer.
/project/view/{projNum}
/project/create/
/project/edit/{projNum}
For above query parameter you have to define [Parameter] in your code.
[Parameter]
public string projNum {get;set;}
Then add method
protected override async Task OnParametersSetAsync()
{
var projectDetail = await getProjectDetails(projNum); // ProgNum will change as it get changes in url, you don't have to do anything extra here.
}
Force page to reload will land you in some other problems, it will get the correct result but the page behavior will change. There are other components on the page like header/left Nav/ etc these will not changes if they are dynamic. It will force you to make changes and hanlde force reload in all the components. Also user experience is affected.
Hope this help.

That is by design.The page itself doesn't refresh or reload because the <NavLink> does not send request to the server (F12 to check) and it redirect to the same page on the client, so nothing updates.
If you enter those URLs in the browser,they will send requests and then refresh page.
A workaround is that you could display different content based on the current route.
#page "/project/view/{projNum}"
#page "/project/create/"
#page "/project/edit/{projNum}"
#using Models
<h3>ProjectBase</h3>
#if (projNum == null)
{
<EditForm Model="#createModel" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" #bind-Value="createModel.Name" />
<button type="submit">Create</button>
</EditForm>
}
else
{
<EditForm Model="#exampleModel" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" #bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
}
#code {
[Parameter]
public string projNum { get; set; }
private ExampleModel createModel = new ExampleModel();
private ExampleModel exampleModel = new ExampleModel();
protected override void OnInitialized()
{
exampleModel.Name = projNum;
}
private void HandleValidSubmit()
{
//your logic
Console.WriteLine("OnValidSubmit");
}
}

Related

In Razor Pages, how can I preserve the state of a checkbox which is not part of a form?

I have page that has checkbox that is used to expand/collapse some part of the page. This is client-side logic done in JavaScript.
I want to preserve the state of this checkbox for this particular page. Can Razor Pages do this automatically?
I tried by adding bool property with [BindProperty(SupportsGet = true)] in PageModel but it doesn't work - when I check the checkbox and reload (HTTP GET) the checkbox is always false.
Guessing that this toggle feature is user-specific, and that you want to persist their choice over a number of HTTP requests, I recommend setting a cookie using client-side code, which is user- or more accurately device-specific and can persist for as long as you need, and can be read on the server too.
https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
https://www.learnrazorpages.com/razor-pages/cookies
I want to preserve the state of this checkbox for this particular page. Can Razor Pages do this automatically?
No, since you don't send it to the backend it will not show it.
As Mike said, it better we could store it inside the client cookie or storage.
More details, you could refer to below codes:
<p>
<input type="checkbox" id="cbox1" checked="checked">
<label >This is the first checkbox</label>
</p>
#section scripts{
<script>
$(function(){
var status = getValue();
if(status === "true"){
$("#cbox1").attr("checked","checked");
}else{
$("#cbox1").removeAttr("checked");
}
})
$("#cbox1").click(function(){
var re = $("#cbox1").is(":checked")
alert(re);
createItem(re);
});
function createItem(value) {
localStorage.setItem('status', value);
}
function getValue() {
return localStorage.getItem('status');
} // Gets the value of 'nameOfItem' and returns it
console.log(getValue()); //'value';
</script>
}

Select characters (highlight text) in input on mousedown and mouseup with Blazor Component

Same thing as Javascript Window.GetSelection. Basically I want to be able to grab the selected text in an html input with Blazor.
<input type="text" value="" />
So whatever value is written into the input, upon mouse-selection will be stored in a string
string mySelectedText { get; set; }
So the user will do this:
and the variable will hold this:
mySelectedText = "selection is made";
Dom-manipulation should be done with #on as shown in this list but i cannot see any #onSelection in that list
I have tried this suggestion without any success.
The user-event must be mouse-selection of text from input, and the selected text must be stored or showed.
The solution is to combine Javascript with Blazor with the #inject IJSRuntime
in the Blazor-component:
#inject IJSRuntime js
<p #onmouseup="#GetSelectedText">Make selection with mouse on here</p>
<p>You highlighted: #SelectedText</p>
#code {
public string SelectedText { get; set; }
async Task GetSelectedText()
{
SelectedText = await js.InvokeAsync<string>("getSelectedText");
}
}
and the javascript funktion named getSelectedText in the wwwroot/html.html insert this below the webassembly.js
<script>
function getSelectedText() {
return window.getSelection().toString();
}
</script>
This solves the problem

Position Blazor component inside user content

I have a requirement to dynamically put a Blazor component inside user-provided content. Essentially, the component is supposed to extend user-provided markup with some UI elements.
Let's say the user provides some content that can have a "greeting-container" element in it and the component should insert a greeting button inside that element.
My current solution is to call a JavaScript function to move the DOM element in OnAfterRenderAsync (full code below). It seems to work fine, but manipulating DOM elements seems to be discouraged in Blazor since it can affect the diffing algorithm. So I have a couple of questions on this:
How bad is it to move DOM elements like this? Does it cause performance issues, functional issues or some undefined behavior?
Is there a better way to achieve the same result without using JavaScript? I was considering using the RenderTreeBuilder for this, but it seems like it might not be designed for this purpose since it's recommended to use hardcoded sequence numbers, which doesn't seem possible when dealing with dynamic content not known at compilation time.
Current solution code:
Greeter.razor
#page "/greeter"
#inject IJSRuntime JSRuntime;
<div>
#((MarkupString)UserContentMarkup)
<div id="greeting">
<button #onclick="ToggleGreeting">Toggle greeting</button>
#if (isGreetingVisible) {
<p>Hello, #Name!</p>
}
</div>
</div>
#code {
[Parameter]
public string UserContentMarkup { get; set; }
[Parameter]
public string Name { get; set; }
private bool isGreetingVisible;
private void ToggleGreeting()
{
isGreetingVisible = !isGreetingVisible;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("moveGreetingToContainer");
}
}
_Host.cshtml
window.moveGreetingToContainer = () => {
var greeting = document.getElementById("greeting");
var container = document.getElementById("greeting-container");
container.appendChild(greeting);
}
UserContentTest.razor
#page "/userContentTest"
#inject IJSRuntime JSRuntime;
<h3>Testing user content</h3>
<Greeter UserContentMarkup=#userContentMarkup Name="John"></Greeter>
#code {
private string userContentMarkup = "Some <b>HTML</b> text followed by greeting <div id='greeting-container'></div> and <i>more</i> text";
}
Expected result (after clicking "Toggle greeting"):
<div>
Some <b>HTML</b> text followed by greeting
<div id="greeting-container">
<div id="greeting">
<button>Toggle greeting</button>
<p>Hello, John!</p>
</div>
</div> and <i>more</i> text
</div>
Great question - and yes, using JS to move the dom elements is very bad as Blazor doesn't see the change you made to the dom.
What you can do is switch over to using a RenderFragment and more specifically RenderFragment<RenderFragment> which is markup that will be supplied with more markup as a parameter.
On the second line, I am invoking the UserContentMarkup method (which is a RenderFragment<RenderFragment>) and passing in the <div id=greeting> content as the context parameter.
Note: It is wrapped in a <text> element which is actually a way to embed HTML in C# in a Razor file. It does not render a <text> element to the page.
Greeter.razor
<div>
#UserContentMarkup(
#<text>
<div id="greeting">
<button #onclick="ToggleGreeting">Toggle greeting</button>
#if (isGreetingVisible) {
<p>Hello, #Name!</p>
}
</div>
</text>
)
</div>
#code {
[Parameter]
public RenderFragment<RenderFragment> UserContentMarkup { get; set; }
[Parameter]
public string Name { get; set; }
private bool isGreetingVisible;
private void ToggleGreeting()
{
isGreetingVisible = !isGreetingVisible;
}
}
UserContentTest.razor
Here you can see two ways to consume Greeter - using markup in the page, or using a code method.
<h3>Testing user content</h3>
#* Using markup to supply user content - #context is where the button goes *#
<Greeter Name="John">
<UserContentMarkup>
Some <b>HTML</b> text followed by greeting
<div id='greeting-container'>#context</div> and <i>more</i> text
</UserContentMarkup>
</Greeter>
#* Using a method to supply the user content - #context is where the button goes *#
<Greeter Name="John" UserContentMarkup=#userContent />
This code method can be confusing - it is a RenderFragment<RenderFragment> which means it has to be a method that accepts a RenderFragment as its only parameter, and returns a RenderFragment - the RenderFragment being returned in this case is markup wrapped in <text> to make it clear it is markup.
#code
{
RenderFragment<RenderFragment> userContent
=> context => #<text>Some stuff #context more stuff</text>;
}
Try it out here : https://blazorrepl.com/repl/QuPPaMEu34yA5KSl40

kendo editor not responding after multiple requests to the same page in IE

I have a very weird bug. I have a page on MVC that displays two editors and gets passed a model with the value for both editors. The model is as follows:
public class BulletinsModel
{
[AllowHtml]
[Display(Name = "Some Bulletin")]
public string SomeBulletin { get; set; }
[AllowHtml]
[Display(Name = "Other Bulletin")]
public string OtherBulletin { get; set; }
}
I then, defined a view which receives this view model and maps it to two kendo editors.There is also some javascript code to make a post to update the information.
#model BulletinsModel
<div id="settings">
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(m => m.SomeBulletin, new { #class = "col-md-6 text-left" })
#(Html.Kendo().EditorFor(m => m.SomeBulletin).Encode(false).Name("Some_Bulletin"))
#Html.LabelFor(m => m.OtherBulletin, new { #class = "col-md-6 text-left" })
#(Html.Kendo().EditorFor(m => m.OtherBulletin).Encode(false).Name("Other_Bulletin"))
</div>
</div>
</div>
My code for my action method that renders this view is as follows (nothing fancy):
[HttpGet]
public PartialViewResult Index()
{
ViewBag.ActiveSectionName = "Bulletins";
var bulletinModel = GetBulletinsModel();
return PartialView("_Bulletins",bulletinModel);
}
However, my issue is that after hitting the Index action a couple of times, the editors become non responsive and I cannot edit the information on them. This only happens on IE, as I have not been able to replicate the issue in other browsers.
EDIT: I have just noticed that the editor is frozen. In order to be able to edit what's inside of the editor I need to click on any option of the toolbar to make it responsive once again. Why is that?
Turns out that the issue is happening with IE as detailed in this post:
Adding, removing, adding editor -> all editors on page become read only in IE. The problem is with the iframes inside the editor. I was loading my page with an Ajax request to which I had to add the following code before making the request to make it work.
function unloadEditor($editor) {
if ($editor.length > 0) {
$editor.data('kendoEditor').wrapper.find("iframe").remove();
$editor.data('kendoEditor').destroy();
}
}
unloadEditor($('#myEditor'));

Passing textbox value from View to action without using Html.begin form and Submit button

I have created a texbox. When user give some input in the textbox and click the actionlink below, the value of the textbox will get pass to the actionResult(FWMenu) in the controller. I can not use html.begin form and submit button in the view. And i can not even use [httppost] in my controller.
Is it possible in that way? If yes then please help me how.
I have not used any class in model.
Below is my Controller.
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult FWMenu(string username)
{
return View();
}
}
This is my View.
<div>
#Html.TextBox("txtUserName")
#Html.ActionLink("Login", "FWMenu", new { username = #Html.TextBox("txtUserName") })
</div>
You need to use javascript/jquery to build the url and redirect. From your comments you mentioned you wanted to use a image rather than a button or link, and that you will have multiple items, so assuming you html is
<div>
<input type="text" name="username">
<img class="submit scr=....>
<div>
<script>
var urlBase='#Url.Action("FWMenu");
$('.submit').click(function() {
var userName = $(this).prev('input').val();
location.href = urlBase + '/' + userName;
}
</script>
Side note: No real point using #Html.TextBox("txtUserName") and if you have multiple instance of this it would generate invalid html (duplicate id attributes) and in any case the name of the parameter is username so it would have needed to be #Html.TextBox("username")`