How to add blazor component loaded to page from jquery and ajax call - razor

I have a new ASP .NET Razor pages app that I've configured for Blazor.
In the program.cs I've added
builder.Services.AddServerSideBlazor();
...
app.MapBlazorHub();
In the Layout I've added:
<head>
<base href="~/" />
...
</head>
...
<body>
...
<script src="_framework/blazor.server.js"></script>
...
</body>
I added a Components folder with a Test.razor component:
#using System.Net.Http
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.Authorization
#using Microsoft.AspNetCore.Components.Forms
#using Microsoft.AspNetCore.Components.Routing
#using Microsoft.AspNetCore.Components.Web
#using Microsoft.AspNetCore.Components.Web.Virtualization
#using Microsoft.JSInterop
#namespace RazorPagesApp.Components
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
[Parameter]
public int InitialValue { get; set; }
private void IncrementCount()
{
currentCount++;
}
protected override void OnParametersSet()
{
currentCount = InitialValue;
}
}
In my Pages folder, I added a Test.cshtml page:
#page
#{
ViewData["Title"] = "Test";
}
<h1>#ViewData["Title"]</h1>
<button onclick="getComponentPartial();">Add Component Via Jquery</button>
<div id="componentPlaceholder"></div>
And a ComponentPartial.cshtml page:
#page
#{
Layout = null;
}
<div id="PartialComponentDiv">
<p>ServerPrerendered</p>
<component type="typeof(Components.Test)" render-mode="ServerPrerendered" param-InitialValue="20" />
</div>
Finally, in my site.js I added my ajax call:
function getComponentPartial() {
try {
$.get({
url: 'ComponentPartial',
dataType: 'HTML',
success: function (data) {
$('#componentPlaceholder').html(data);
}
});
}
catch (e) {
console.log(e.message);
}
}
After running the project and navigating to https://localhost:7048/Test , I press the button to load the ComponentPartial page onto my test page. The Test.razor component renders but is not interactive, I believe because the blazer.server.js does not have this listed as a component. Is there a way to get this functional, i.e. have this component that was loaded via ajax and jquery be responsive via the blazor.server.js?

Related

Routing to named element in Blazor (use anchor to navigate to specific element)

I cannot use an HTML anchor to navigate to a specific HTML element of a page in the Blazor Server. For example:
#page "/test"
<nav>
<!-- One version I've tried -->
Section2
<!-- Another version I've tried -->
<NavLink href="#section2">Section2</NavLink>
</nav>
#* ... *#
<h2 id="section2">It's Section2.</h2>
#* ... *#
When I click the link to Section2, I get redirected to the route http://localhost:5000/test#section2, however, will be at the top of the page. In my opinion, the browser should scroll down to the proper element, as specified by the Element Selector, but it can't.
Does it have to be done in a special way in Blazor?
I use Blazor 6 in .Net6 with Visual Studio 2022 (ver:17.0.2).
After loading a page, a browser automatically scrolls to the element identified by its id in the fragment part of the URL. It does the same when you click on an anchor with an href of the kind #element-id.
The page load behavior doesn't work for a Blazor Server because the element doesn't exist yet on page load.
The solution is to manually create a scroller using javascript and a razor component:
First of all, create a razor component like this
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
#implements IDisposable
#code {
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await ScrollToFragment();
}
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
private async void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
await ScrollToFragment();
}
private async Task ScrollToFragment()
{
var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// Handle text fragment (https://example.org/#test:~:text=foo)
// https://github.com/WICG/scroll-to-text-fragment/
var elementId = fragment.Substring(1);
var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
elementId = elementId.Substring(0, index);
}
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
Then add this javascript code somewhere before the Blazor script renders. You can wrap it with script tags and place it in the head.
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
Finally implement it in your pages if needed. You can also place it inside your layouts, so it will work for every page you create.
#page "/"
<PageTitle>Index</PageTitle>
<a href="#my-id">
<h1>Hello, world!</h1>
</a>
<SurveyPrompt Title="How is Blazor working for you?" />
<div style="height: 2000px">
</div>
<div id="my-id">
Hello!
</div>
<AnchorNavigation />
Source: link
You can also use an ElementReference and FocusAsync which uses the built in Blazor JS. To use it you need to use a small hack to make the component "Focusable" which is to set a tabindex. I've used a span but you can use what you like. I've used #alessandromanzini's code to get the element from the NavigationManager.
Here's a component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using System.Diagnostics.CodeAnalysis;
namespace SO75358165;
public class Bookmark : ComponentBase, IDisposable
{
private bool _setFocus;
[Inject] private NavigationManager NavManager { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? BookmarkName { get; set; }
[DisallowNull] public ElementReference? Element { get; private set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "span");
builder.AddAttribute(2, "tabindex", "-1");
builder.AddContent(3, this.ChildContent);
builder.AddElementReferenceCapture(4, this.SetReference);
builder.CloseElement();
}
protected override void OnInitialized()
=> NavManager.LocationChanged += this.OnLocationChanged;
protected override void OnParametersSet()
=> _setFocus = this.IsMe();
private void SetReference(ElementReference reference)
=> this.Element = reference;
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
if (this.IsMe())
{
_setFocus = true;
this.StateHasChanged();
}
}
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (_setFocus)
await this.Element!.Value.FocusAsync(false);
_setFocus = false;
}
private bool IsMe()
{
string? elementId = null;
var uri = new Uri(this.NavManager.Uri, UriKind.Absolute);
if (uri.Fragment.StartsWith('#'))
{
elementId = uri.Fragment.Substring(1);
return elementId == BookmarkName;
}
return false;
}
public void Dispose()
=> NavManager.LocationChanged -= this.OnLocationChanged;
}
Here's my test page:
#page "/"
<PageTitle>Index</PageTitle>
<NavLink href="#me">To me</NavLink>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
//.....
<h1>Hello, world!</h1>
<Bookmark BookmarkName="me" >
<h1 id="me">Focus on Me</h1>
</Bookmark>

How to Remove Element / Component from BlazorWebassambly

I am trying to make a Razor component that removes itself from the page after 3 seconds, after its added to the page.
I will click on a button
Than the Component will be added to the current page
After 3 seconds the Component removes itselfrom the page
<**div #ref="messageRef" style="position: absolute; margin: 0 auto; background-color: red; width: 200px; height: 80px;">**
<p>Message.....</p>
</div>
#code {
ElementReference messageRef;
private MessageComponent thisMsg;
protected override async Task OnInitializedAsync()
{
await JSRuntime.InvokeVoidAsync("MessageComponent.Remove()", messageRef);
StateHasChanged();
}
}
As JeremyW mentions in his comment, this can be done with an #if statement in the body of the page that holds the content. Using the Blazor template app as an example, it might look something like this:
#page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
#if (displayPrompt)
{
<SurveyPrompt Title="How is Blazor working for you?" />
}
#code {
private bool displayPrompt;
protected override void OnInitialized()
{
displayPrompt = true;
HideMessageAfterDelay();
base.OnInitialized();
}
private async void HideMessageAfterDelay()
{
await Task.Delay(3000);
displayPrompt = false;
StateHasChanged();
}
}
When displayPrompt evaluates to true, the prompt is added to the DOM. When is evaluates to false, it's removed from the DOM.
If you need the message component to handle this itself, then just put the equivalent code inside the component itself instead of the page.

How to reproduce #using(Html.BeginForm()){ ... } in Razor [duplicate]

In WebForms, I could create a component where I could embed my own content
Example
<uc:BootstrapModal Title="Hello World" Size="Large">
<h1>Hello World</h1>
</uc:BootstrapModal>
<!--generates this...-->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<h1>Hello World</h1>
</div>
</div>
</div>
How can I do this in MVC?
You can create a HtmlHelper extension method to generate the enclosing html, similar to the way BeginForm() generates enclosing <form></form> tags.
using System;
using System.Web.Mvc;
namespace YourAssembly.Html
{
public class Dialog : IDisposable
{
private readonly ViewContext _viewContext;
private bool _disposed;
public Dialog(ViewContext viewContext)
{
if (viewContext == null)
{
throw new ArgumentNullException("viewContext");
}
_viewContext = viewContext;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
DialogExtensions.EndDialog(_viewContext);
}
}
public void EndDialog()
{
Dispose(true);
}
}
public static class DialogExtensions
{
public static Dialog BeginDialog(this HtmlHelper htmlHelper)
{
return DialogHelper(htmlHelper);
}
private static Dialog DialogHelper(this HtmlHelper htmlHelper)
{
TagBuilder div = new TagBuilder("div");
div.AddCssClass("modal fade bs-example-modal-lg");
div.MergeAttribute("tabindex", "-1");
div.MergeAttribute("role", "dialog");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-dialog modal-lg");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-content");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
Dialog modal = new Dialog(htmlHelper.ViewContext);
return modal;
}
public static void EndDialog(this HtmlHelper htmlHelper)
{
EndDialog(htmlHelper.ViewContext);
}
internal static void EndDialog(ViewContext viewContext)
{
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
}
}
}
and in the view use it as
#using (Html.BeginDialog())
{
// add the content to be rendered in the dialog here
}
Note: In the web.config file add the namespace of your assembly so that you do not have to include #using statements in the view.
<namespaces>
<add namespace="System.Web.Mvc" />
....
<add namespace="YourAssembly.Html" /> <!--add-->
</namespaces>
And you can then extend this by creating additional overloads, for example you might also have parameters for string title and a ButtonType buttons (an enum) to render a title bar and footer buttons in the dialog

Linking to another .cshtml

I need to link to Login.cshtml page from Home.cshtml and it can't seem to find the link.
Home.cshtml
#{
Layout = "~/Shared/Master.cshtml";
ViewBag.Title = "Home";
}
#section menu
{
Login
}
HomeController.cs
public class HomeController : Controller
{
//
// GET: /Home/Home
public ActionResult Home()
{
return View();
}
//
// GET: /Home/Login
public ActionResult Login()
{
return View();
}
}
RouteConfig
routes.MapRoute(
name: "Default",
url: "",
defaults: new { controller = "Home", action = "Home" }
);
Project Structure:
Your Login Page would be at /Home/Login
if you are wanting a page for /Login you need to create a new controller called LoginController with an action result method of Index.
You need to have this to serve the pages in your RouteConfig:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/",
defaults: new { controller = "Home", action = "Home" }
);
This should also sort out your default home page

How to postback a checkbox in MVC

I have a controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication5.Models;
namespace MvcApplication5.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var car = new Car { Sold = true };
return View(car);
}
[HttpPost]
public ActionResult Index(Car car)
{
return View(car);
}
public ActionResult About()
{
return View();
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication5.Models
{
public class Car
{
public bool Sold { get; set; }
}
}
Here's the view:
#model MvcApplication5.Models.Car
#using (Html.BeginForm())
{
#Html.CheckBoxFor(m => m.Sold, new { #class = "sold" })
#Html.HiddenFor(m => m.Sold)
<div class="disable">Disable</div>
<button type="submit" value="Submit">Submit</button>
}
<script type="text/javascript">
$(function () {
$('.disable').click(function () {
$('.sold').attr("disabled", "disabled");
});
})
</script>
So what I'm trying to achieve here is you come in and the checkbox is checked. If you click submit the value is posted back fine. However if you click disable the checkbox becomes disabled. You then click submit. I was expecting because the checkbox is still checked and I've added a hidden field the value posted back for sold would be true. That's the whole point of why I added the hidden field.
Can anyone tell me why the checkbox when checked but disabled posts back false for Sold when there is a hidden field that should be maintaining the value?
disabled=disabled doesn't send values, what you want to preserve the value by to disable it from being updated is readonly=readonly. i.e.
$('.sold').attr("readonly", "readonly");
You'll also need to apply some CSS to make it look disabled.