Here is my case:
I have a component Foo and I want it to have a blazor template called Wrapper which will be used to wrap somepart of Foo's content.
What I need to do is something like
Normal usage:
<Foo>
<Wrapper>
<div>
<p>Something in the wraper</p>
#context // context should be some content of Foo
</div>
</Wraper>
</Foo>
Inside Foo
#* Other content of Foo*#
#if(Wrapper != null){
#* Pass to Wrapper some content of Foo *#
Wrapper(#* How to render html/components here? *#)
}
#* Other content of Foo*#
But there is no way to pass html or components for the template?
How can I do this?
Your Wrapper in this case is a RenderFragment that you want to accept another RenderFragment, so it's signature would be
[Parameter] public RenderFragment<RenderFragment> Wrapper {get;set;}
Here is the complete Foo.razor
#* Other content of Foo*#
<h1>This is Foo</h1>
#if(Wrapper != null){
#* Pass to Wrapper some content of Foo *#
#Wrapper(
#<h2>This comes from Foo</h2>
)
}
<h1>There is no more Foo</h1>
#code
{
[Parameter] public RenderFragment<RenderFragment> Wrapper {get;set;}
}
Notice carefully the construct inside the call to Wrapper.
Start with #Wrapper so this is a call from Razor
The open ( after Wrapper puts you back in C# land, so -
use # to swap back to Razor code.
Now you can use Razor/HTML markup <h2>This comes from Foo</h2>
Close the call to Wrapper with a )
That is how you can use a RenderFragment that takes a RenderFragment as it's context.
The output from this would be
This is Foo
Something in the wraper
This comes from Foo
There is no more Foo
You can test it out here https://blazorfiddle.com/s/ox4tl146
Related
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.
Im trying to wrap one of my components on some specific tag based on some conditions.
To make it simple lets say that i have a
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
And if 'condition' == true then it should wrap it in another div the result should be like this
<div class="wrapper-class">
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
</div>
And before you say, use if-else method. i must say no. because the tag and the class in it is dynamic so i cant write it like that.
What i tried to do is
#if (condition)
{
#:<div class="wrapper-class">
}
<div class="inner-class">
this must be in inner-class div and wrapped in some-class div
</div>
}
#if (condition)
{
#:</div>
}
I Thought it should do the job.
But the problem is the browser closes the outer div before putting the inner div in it.
You have to use BuildRenderTree
With BuilderRenderTree you have full control to build component html.
For more information read this good article Building Components Manually via RenderTreeBuilder
I ended up writing a wrapper component similar to this to solve this problem pretty elegantly:
#if (Wrap)
{
<div id="#Id" class="#Class">
#ChildContent
</div>
}
else
{
#ChildContent
}
#code
{
[Parameter] public string? Id { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public bool Wrap { get; set; }
}
It doesn't support dynamic tag types, but that would be pretty easy to create using a component implementing BuildRenderTree as mRizvandi suggested.
If it's a simple layout like you've described than you can make use of the MarkupString. The trick is understanding that MarkupString will automatically close any tags that are left open. Therefore you just need to build the entire string properly before trying to render it as a MarkupString.
#{ bool condition = true; }
#{ string conditionalMarkup = string.Empty; }
#if (condition)
{
conditionalMarkup = "<div class=\"wrapper-class\">";
}
#{ conditionalMarkup += "<div class=\"inner-class\"> this must be in inner-class div and wrapped in some-class div </div>"; }
#if (condition)
{
conditionalMarkup += "</div>";
#((MarkupString)conditionalMarkup)
}
else
{
#((MarkupString)conditionalMarkup)
}
I do this for building simple conditional markup. Usually inside of an object iteration making use of String.Format to fill in property values.
I have an IList<IControl> that is an interface that derives from IComponent, every implementation of the interface inherits from ComponentBase. The component is instantiated from a factory dynamically (it returns a component compatible with input's type).
Now I want to render this list, but I do not know how, this is my ControlsContainer.razor:
#foreach (var control in OrderedControls)
{
<div #key=#Guid.NewGuid().ToString()>
#RenderWidget(control)
</div>
}
I want to avoid a switch/if-else if with every component type (they are loaded dynamically with reflection, I do not need to register them somewhere).
Untested, but the following should work, or at least put you on the right path ...
#foreach (var control in OrderedControls)
{
<div #key=#Guid.NewGuid().ToString()>
#RenderWidget(control)
</div>
}
#code {
RenderFragment RenderWidget(IControl control)
{
var concreteType = control.GetType();
RenderFragment frag = new RenderFragment(b =>
{
b.OpenComponent(1, concreteType);
b.CloseComponent();
});
return frag;
}
}
Code
C#
public static class MyClass
{
public static object Foo = new { #class = "foo" };
public static object Barbaz = new { #class = "bar baz" };
}
cshtml
<div #MyClass.Foo></div>
<div #MyClass.Barbaz></div>
Desired HTML result
<div class="foo"></div>
<div class="foo bar"></div>
Actual HTML result
<div {="" class="foo" }=""></div>
(works, but looks wrong)
<div {="" class="bar" baz="" }=""></div>
(doesn't work, and looks even more wrong)
How should I declare Foo and Barbaz to achieve my desired result?
I would do it a little different:
C#
public static class MyClass
{
public static string Foo = "foo";
public static string Barbaz = "bar baz";
}
VIEW
<div class="#MyClass.Foo"></div>
<div class="#MyClass.Barbaz"></div>
reusing in action link:
#Html.ActionLink("mylink","#Url.Action("Index","Home")", new {#class=MyClass.Foo})
Do not use objects but strings in properties like this:
public static class MyClass
{
public static string Foo = "foo";
public static string Barbaz = "bar baz";
}
and in view set up code:
<div class = "#MyClass.Foo"></div>
<div class="#MyClass.Barbaz"></div>
Side note: you will probably need to revise your logic as setting css classes from c# backend is not something you should do very often. Try to avoid that. Instead use flags based on which you will have appropriate css classes applied in view.
class is an html attribute, IMHO, It is not a good idea to render that attribute itself from server using a variable. The value is fine.
Use simple string type which holds just the css class name
public static class MyClass
{
public static string Foo = "foo" ;
public static string Barbaz = "bar baz" ;
}
and the view
<div class="#MyClass.Foo"></div>
Your approach in the question, will work with ActionLink helper method call as the mvc framework accepts an anonymous object and build the html attributes from that. You cannot use that as it is with your normal html markup as there is nothing to convert the anonymous object to the corresponding html markup. Although you can write some code to do that, It will make your code messy. Remember , The clean approach is to write readable code (normal html markup as much as you can in the view file). This the whole reason behind the asp net team to bring the new tag helpers so designers can build the page without worrying too much about server code / any magic helper methods etc
I would lean towards avoiding to call magic helper methods in my view code. I prefer to keep it as normal HTML code as much as i can.
I'm converting old aspx/ascx pages to now use Razor and I've run into a WebControl that can't easily become a helper method or cshtml.
The WebControl has several nested ITemplate properties like this:
[ToolboxData("<{0}:LightBox runat=server></{0}:LightBox>")]
public class Lightbox : WebControl, INamingContainer
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Header { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Body { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Footer { get; set; }
}
This is how it is formatted in the aspx/ascx file:
<cc1:Lightbox ID="LightBox1" runat="server">
<Header>Header HTML</Header>
<Body>Body HTML</Body>
<Footer>Footer HTML</Footer>
</cc1:Lightbox>
If a section exists, then the CreateChildControls() method wraps it in a div tag with certain classes. It ends up like this, but more complicated:
<div class="header">
<div class="title">Header HTML</div>
</div>
<div class="body">Body HTML</div>
<div class="footer">Footer HTML</div>
I don't know of a good way to do something like this in Razor. I could turn it into one or more helper methods. However, this means I either need pass HTML as strings or convert each ITemplate section to this "fun" format:
Func<dynamic, object> header = #<text>
<span id="logo">
<img src="#path" />
</span>
</text>;
...
#LightBox(header, body, footer) // This would contain logic from CreateChildControls
Is this the best that is possible with Razor?
The only other thing I can think of is placing the HTML as is on the page and using a JavaScript module to then modify the inner sections with new container divs, but I don't see why this work can't stay on the server side.
The best approach for you is using jQuery. In jquery you could use something like $( "p" ).addClass( "myClass yourClass" );.
If there is "p", it will add related class on that, if not, nothing will happen.
Another option is replacing other controls with lightbox, like bootstrap modal.