document.getElementById() in blazor - c#

I need to change a text of a div, and I only have the id of the div. Without #bind (as I only have the id), how can I do that? I searched for a bit and some articles that may or may not be outdated stated that blazor doesn't allow access to DOM elements / API, but even so, I don't an access to the entire DOM element, I only want to be able to change the text inside a div or, if that's not possible, even the value property.

You can use ref attrubute. Source page Example:
#page "/"
#inject IJSRuntime JSRuntime
Enter your name
<input #ref=ReferenceToInputControl />
#code
{
ElementReference ReferenceToInputControl;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await JSRuntime.InvokeVoidAsync("BlazorUniversity.setFocus", ReferenceToInputControl);
}
}

First of all, try and use Blazor as it was intended. I've only come across a few scenarios where I need to call Javascript with an Id and manipulate the DOM.
If you still NEED to run Javascript then you can invoke the Javascript with a parameter which will be your id.
await jsRuntime.InvokeVoidAsync("javascriptMethodWithIdParameter", Id);
Then use this in your Javascript:
window.javascriptMethodWithIdParameter = function(id)
{
document.getElementById(id).innerHTML = 'This is the id: ' + id;
}
Here is my simple sample:
https://blazorfiddle.com/s/u6stbg0y
.NET 6 Example above
ASP.NET Core Blazor JavaScript interoperability (JS interop) official documentation

Related

Blazor Dynamic Custom Elements in application

I'm developing a webapp built in C# with Blazor WASM that is Asp.Net hosted. I'm making a blazor component that through the use of a library already in production, will generate a HTML fragment (or full embed) that is then displayed in this way
...
<div>
#((MarkupString)document)
</div>
...
with document containing the markup generated by the library.
As long as we're doing it with static content all is fine and dandy, but now we need to have some input in there that will then be sent back to the server to execute some actions.
In a MarkupString there is no way to include <InputFile /> or <InputText /> components in such a way that they are shown in the fragment and I can read their contents, and I can find no way to actually interact with the standard HTML tags, especially regarding the file upload.
Moreover we'll probably soon need to have a specific image uploader with preview which would be a custom Blazor component and this led me to the CustomElements .NET 7 feature that looks like what I need for both problems.
However I couldn't find how to actually implement this in my app, and the documentation I found is still very partial in that way. Is there a way to do what I need?
EDIT: Managed to fix this partially, with Chen's answer. I still have trouble with the binding though, as the #bind-Value directive is not working with
Unhandled exception rendering component: Microsoft.AspNetCore.Components.Forms.InputText requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'.
with this markup:
...
<custom-input-text #bind-value="$field1" name="$field1"></custom-input-text>
...
(the capital V in bind-Value becomes lowercase all by itself)
Am I doing something wrong again?
CustomElements should meet your requirements, you can create your own logic in Blazor components, and then use it in your application.
To use the component, you need to add the following JavaScript script references to your host app in this specific order.
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
You also need to add the corresponding middleware:
app.UseBlazorFrameworkFiles();
And use app.UseWebAssemblyDebugging(); for debugging.
Then you need to register the corresponding component in the Blazor program:
builder.RootComponents.RegisterCustomElement<Counter>("my-counter");
Then you can call this component in your application, including passing parameters, etc.
<my-counter title="Khalid" increment-amount="2" />
Here is a complete example with detailed explanation, you can use it as a reference.
Helpful links:
Blazor Custom Elements.
Using .NET 7's Blazor Custom Elements to render dynamic content.
ASP.NET Core Razor components.
Hope this can help you.
Edit1:
It looks like you can't do two-way binding between Razor Page and Razor Component. The official document mentions that parameters can be passed through JavaScript properties, but it doesn't seem to be able to read the parameters.
For example:
<my-counter ></my-counter>
<button onclick="Test()">click</button>
<script>
function Test()
{
const elem = document.querySelector("my-counter");
//get parameter failed, it's undefined
var text = elem.incrementAmount;
//successfully set parameter
elem.incrementAmount = "test";
}
</script>
So I'm guessing that CustomElements only have writable properties in Razor Page.
Edit2:
I found that JQuery can be used to detect the value of the input box. When getting this value and performing certain operations, can it also achieve the same effect as two-way binding?
<my-counter ></my-counter>
<button onclick="Test()">click</button>
<script>
function Test()
{
const elem = document.querySelector("my-counter");
//get the value of input
var value = $("my-counter").find("input").val();
elem.incrementAmount = "test";
}
</script>

Convert Blazor HTML markup portion to string for email body

I have a simple Blazor component that take some inputs (some lists of object and few strings) and formats them into simple HTML for display, (tables generated from the lists of objects, simple text, etc.).
This HTML is a report that is intended to be both displayed to users in the app and also emailed to various people (via SendGrid). For compatibility, we are keeping the email HTML as simple as possible.
The component works fine, however I am not sure how to translate a component's markup portion into a simple string of escaped HTML so that I can pass the string to SendGrid and fire off an email.
I am aware of MarkupStrings, but I have only used them in reverse--to write a string containing HTML tags that will be properly displayed in my app. I can't find any suggestions for doing the conversion the way that I need it done.
Is there any simple way to have a component write all of its markup into a string so that I can email it out?
Or, would I be better off writing a .cs file with a static method that takes in the parameters in question, renders it into a MarkupString, and then passes the string both to SendGrid for email and also to a Blazor component for in-app display?
The simplest way to do that is to employ JSInterop to retrieve the Html markup for the component, produced by the browser. Let's say You've defined a child component, and you want to retrieve its html source. You can do that like this:
Define the child...
SelectGender.razor
<div id="selectGender">
<h1>Select Gender</h1>
<select>
#foreach (var gender in genders)
{
<option>#gender</option>
}
</select>
</div>
#code {
private List<string> genders = new List<string> { "Male", "Female", "Other" };
}
Usage
#page "/"
#inject IJSRuntime JSRuntime
<div>#((MarkupString) html)</div>
<SelectGender />
<button #onclick="GetHtml">Get Html</button>
#code{
private string html;
protected async Task GetHtml()
{
html = await JSRuntime.InvokeAsync<string>("myJsFunctions.getHtml");
}
}
_Host.cshtml
<script>
window.myJsFunctions =
{
getHtml: function () {
return document.getElementById("selectGender").innerHTML;
}
};
</script>
Is there any simple way to have a component write all of its markup into a string so that I can email it out?
No, your C# code has no simple way to do this - you could use JS Interop to get the rendered HTML from the dom, but nothing built in for it.
Or, would I be better off writing a .cs file with a static method that takes in the parameters in question, renders it into a MarkupString, and then passes the string both to SendGrid for email and also to a Blazor component for in-app display?
That is a possibility - I can't comment on the value of that to you, but it is a technique that could work if the component you are rendering is static,

Can you link to a Razor component without hard-coding its URL?

I'm trying to create a github pages website with the new Blazor WASM. I use C# almost every day for work, but I've never used Blazor/Razor/ASP.NET before, so it's very possible that I'm trying to use server-side techniques that won't work here.
I have a group of pages, and I want to show a little preview of each page with a link to that page on the homepage. All of these pages implement the same component base class. For example, Sample.razor might look like
#page "/sample"
#inherits GroupBase
<!-- html here -->
#code {
public override string Name { get; } = "Sample";
}
Index.razor might look like
#page "/"
#foreach (GroupBase p in mPagesToPreview)
{
#p.Name
}
#code {
List<GroupBase> mPagesToPreview = new List<GroupBase> { new Sample() };
}
Is there any way to route to the Sample page without explicitly putting in "/sample"? Or even using a normal link (I've checked both a and NavLink) with a function similar to nameOf() or getType()?
My best option right now is to add a URL property to the base, but it seems like I shouldn't have to type out "/sample" twice. (It's not hard; it's the principle of the thing!)
I've also seen some examples of people injecting a NavigationManager and then using that to deduce the URL, but that would also have to be repeated for each page. I've seen some examples that get the page directive from RouteData.Values, but those were Razor pages. I couldn't figure out how to create a RouteData object or use it as a static class.
You can use class RouteAttribute to find the route.
It looks like this:
[Microsoft.AspNetCore.Components.RouteAttribute("/counter")]
public partial class Counter : Microsoft.AspNetCore.Components.ComponentBase
See How do I read an attribute on a class at runtime?

NavigationManager.NavigateTo in razor pages c# sometimes don't work

i've a very simple question about asp.net core and razor pages.
Inside c# razor code I want to redirect to another route in certain cases.
If there's an async (awaiting) webservice call before, NavigationManager.NavigateTo doesn't work correctly, but no exception is thrown either.
async Task Cancel()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated){
// if there's some async webservice call action here,
// NavigationManager.NavigateTo does neither work nor an exception is thrown
NavigationManager.NavigateTo("/Project", true);
}
}
Do you have any idea what could be the cause?
What are the alternatives to NavigationManager.NavigateTo?
P.S. Please don't propose Response.Redirect in that way (because, that's not suitable for me).
var context = new Microsoft.AspNetCore.Http.HttpContextAccessor();
context.HttpContext.Response.Redirect("/Project", true)
Kind regards,
-he.
Bug is already fixed now.
The problem with NavigationManager.NavigateTo(...) occurred only, because I used a button inside a posting form in my Razor page. Replaced form tag with div and now everything works fine.
Kind regards,
-he
<div>
<p>
<label>Project name: #projectName</label>
</p>
<p>
<!-- [...] -->
<span class="btn-cancel">
<button class="btn-primary" #onclick="#(async () => await Cancel())">Cancel</button>
</span>
</p></div>
#code {
// ...
async Task Cancel()
{
// here're other async action calls
NavigationManager.NavigateTo("/Project", true);
}
}
Another possible solution is using <form #onsubmit="Cancel"> with a <button type="submit">. This preserves advantages of forms, for example the handling of the enter-key.
little late here, but thought to share my case here. For me, NavigationManager.NavigateTo() was not working for some of the target blazor components even though they were simple components with only HTML contents like below, without any C# code in it.
#page "/unauthorized"
<h3>You are not authorized.</h3>
I noticed that "Build Action" property of the components which are not working here aren't set as "Content". So once I made this property as "Content", it started working.

'Neatest' way of adding javascript to a page footer in asp.net

Whenever i want to add a javascript library programatically, say jquery for example, it generally involves making sure there is a placeholder at the footer of my page, then calling a codebehind method that will take a link to the src as a parameter and return an htmlgeneric control, which is then added to this placeholder.
Is this still the neatest way to do it, even with .net 4.0 out?
I think a better way is to use the RegisterStartupScript method:
http://msdn.microsoft.com/en-us/library/z9h4dk8y.aspx
And even better in your case RegisterClientScriptInclude:
http://msdn.microsoft.com/en-us/library/kx145dw2.aspx
EDIT:
Here's a sample of RegisterClientScriptInclude:
if (!Page.ClientScript.IsClientScriptIncludeRegistered("myJsInclude"))
Page.ClientScript.RegisterClientScriptInclude("myJsInclude", "myJsFile.js");
EDIT2:
Here's a sample of an include with RegisterStartupScript:
string jsBlock = "<script src='myJsFile.js'></script>";
if (!Page.ClientScript.IsStartupScriptRegistered("myJsInclude"))
Page.ClientScript.RegisterStartupScript(typeof(string), "myJsInclude", jsBlock, false);
You should add things like language="text/javascript" to the script tag, but for readability I didn't add them.
Sorry... I decided to move my comment to an answer.
I personally add all of my JS to the ScriptManager. It helps lower the number of Http calls that the page has to make.
ScriptManager1.CompositeScript.Scripts.Add(New ScriptReference("~/Page/To/Jquery.js"))
But this is only if you're already using a ScriptManager on your page
Also, if you don't want to add it from CodeBehind, you can do it right in your page.
<ScriptManager>
<CompositeScript>
<Scripts>
<-- your scripts in here -->
</Scripts>
</CompositeScript>
</ScriptManager>
So by doing this, you're able to add all of your JS to a single HTTP Request rather than having a bunch of different requests all at once.
Then in the ScriptManager tag, you can add LoadScriptsBeforeUI="false" to have them put to the bottom of the page.
Sorry but that was never the cleanest way to inject script into an asp.net page.
Look at the ClientScript object. There are several methods that will suit your needs without resorting to placeholders.
ScriptManager is a good way to do this, as mentioned above. If you are not using MS Ajax and ScriptManager, then I suggest you write your own control. It should be very simple control at that. Add a public variable List and override RenderContents method to walk through your list of strings and render on the page. Sample code:
public class CustomScriptManager : WebControl
{
private List<string> scripts = new List<string>();
public List<string> Scripts
{
get { return scripts; }
set { scripts = value; }
}
protected override void RenderContents(HtmlTextWriter writer)
{
foreach (string script in scripts)
{
writer.Write("<script language=\"JavaScript\" type=\"text/javascript\" src=\"" + script + "\"></script>");
}
}
}
P.S. I haven't verified above code, but I thing you get the idea.

Categories