Aspnetcore.docs: Create sample showing how to ref JavaScript/CSS inside a View Component

Created on 20 Nov 2015  Â·  26Comments  Â·  Source: dotnet/AspNetCore.Docs

see http://docs.asp.net/projects/mvc/en/latest/views/view-components.html#comment-2369574517

Is there a mechanism for referencing JavaScript or CSS files from inside the component? It would ideal to be able to add to the "sections" of the host views. Right now, with Partial Views, we have to use a workaround of registering scripts and CSS in the HttpContext and then pulling them out later when the layout renders the script section. Same thing for CSS.

The host view shouldn't need to know anything about the components script requirements ahead of time.

P1 P2

Most helpful comment

View Components are lacking here because this wasn't a scenario they were originally designed for. Could you get creative enough to extend things to get something working? Probably and I'm sure some folks already have. But it's not something that View Components have first class support for. The long term vision is to provide a component model that actually has a designed way to interact with the layout and enable composition. I'm sorry to disappoint on this one but I'm trying to be realistic here.

All 26 comments

@rynowak Is this scenario possible to do today? What would this sample look like?

+1 for this feature

+1 as well

+1

@rynowak Is this scenario possible to do today? What would this sample look like?
@rynowak if you can send me some code, I'll get this article updated ASAP.

@Rick-Anderson - FWIW, here is what I ended up hacking together. Works for ViewComponents and Partials. You can find variants of this around the net. Would be awesome if comparable functionality was part of the framework.

I opted for an OrderedDictionary so that a.) items that are added to the resource cache more than once don't get rendered more than once and b.) items are rendered in the order that they are added to the resource cache so that you don't get bungled JS references.

Utility Code:

``` C#
public enum PageResourceType
{
JavaScript,
CSS
}

public static bool IsAjaxRequest(this HttpRequest request)
{
if (request == null)
throw new ArgumentNullException("request");

        if (request.Headers != null)
            return request.Headers["X-Requested-With"] == "XMLHttpRequest";
        return false;
    }
IHtmlHelper extensions:

``` C#
 public static HtmlString AddResource(this IHtmlHelper HtmlHelper, PageResourceType resourceType, Func<object, HelperResult> Template)
        {
            /* Usage
                        @Html.AddResource(PageResourceType.JavaScript,
                        @<script type="text/javascript">
                            $(document).ready(function ()
                            {
                                //awesome code
                            });
                        </script>
                        )
            */
            // If it's an Ajax request, then we add the data inline, immediately.  Otherwise, store it in the context and
            // retrieve when the host view is rendering similar resource reference on the page.  Warning: This can possibly
            // generate "invalid" HTML if it's a CSS resource and we end up dumping a CSS tag in the middle of the body.
            if (HtmlHelper.ViewContext.HttpContext.Request.IsAjaxRequest() && Template != null)
            {
                HtmlHelper.ViewContext.Writer.Write(Template(null));
            }
            else
            {
                OrderedDictionary<int, Func<object, HelperResult>> allItems = HtmlHelper.ViewContext.HttpContext.Items[resourceType] as OrderedDictionary<int, Func<object, HelperResult>>;
                if (allItems == null)
                {
                    allItems = new OrderedDictionary<int, Func<object, HelperResult>>();
                    HtmlHelper.ViewContext.HttpContext.Items[resourceType] = allItems;
                }

                int hash = -1;
                using (var writer = new StringWriter(CultureInfo.InvariantCulture))
                {
                    Template(null).WriteTo(writer, Microsoft.Extensions.WebEncoders.HtmlEncoder.Default);
                    hash = writer.ToString().GetHashCode();
                }

                if (!allItems.ContainsKey(hash))
                {
                    allItems.Add(hash, Template);
                }                
            }

            return new HtmlString(String.Empty);
        }

 public static HtmlString RenderResources(this IHtmlHelper HtmlHelper, PageResourceType resourceType)
        {
            if (HtmlHelper.ViewContext.HttpContext.Items[resourceType] != null)
            {
                OrderedDictionary<int, Func<object, HelperResult>> Resources = (OrderedDictionary<int, Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[resourceType];

                for(int i = 0; i < Resources.Count; i++)
                {
                    var Resource = Resources[i];
                    if (Resource != null)
                    {
                        HtmlHelper.ViewContext.Writer.Write(Resource(null));
                    }
                }
            }

            return new HtmlString(String.Empty);
        }

Then, in _Layout.cshtml:

<!--Pipe in view component and partials script references which don't support "sections"-->
    @Html.RenderResources(PageResourceType.JavaScript)
    @Html.RenderResources(PageResourceType.CSS)

@rynowak can you review this approach?

@SimonOrdo I'm trying to use your method for this in ViewComponents. If I try to invoke some c# code in the JS code I get errors. For example:

@Html.AddResource( PageResourceType.JavaScript,
    @<script>
        var foo = '@(Model.Bar)';
    </script>)

Cannot implicitly convert type 'Microsoft.AspNet.Mvc.Razor.HelperResult' to 'Microsoft.AspNet.Diagnostics.Views.HelperResult'

and

Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type

Do you have a workaround for this? I am not familiar with the @<script> syntax used here for the Template delegate.

I was successfully using this technique for a while in RC1, but as of RC2 i'm getting an issue, which I raised here: https://github.com/aspnet/Razor/issues/795

Greatful if anyone knows why this might have broken in RC2

Can you test this with RTM bits?

Can do. To be honest, I'd rather be able to eliminate this code all together and for the framework to provide a mechanism so that ViewComponent can include html and javascript on the page. Without this, it seems like they are a bit of a half baked feature imho. Any ideas if this ability is going to be coming in the future as if so, I'd prefer not to waste any further time on this issue,as I'll just wait for that release.

framework to provide a mechanism so that ViewComponentcan include html and javascript on the page.

I'll see if I can get an issue for us to do that.

+1. Without SOME method of including JS & CSS the entire purpose of ViewComponents being an encapsulated set of functionality is defeated.

This is a year old issue but I seem to have run into it and can't find a solution? May be I missed it?

This is what I've done to get JS working though I'd love to be able to include a script tag directly

<style onload='if (typeof init_component == "undefined") $.loadScript("/js/Components/top_bar_selection_popup.js", function () { });'> </style>

The style tag loads and inline JS works so this seems effective to a point

Although the inline JS allowed for a somewhat more pure solution I've opted to just use this JS func to include the scripts:

var ajaxComponent = function (controller, component_view, model, success) { post("/" + controller + "/" + component_view, JSON.stringify(model), function (data) { var component = data; var newdiv = document.createElement("div"); var scripts = document.createElement("script"); scripts.setAttribute('src', "/js/Components/" + component_view + ".js"); newdiv.innerHTML = component; newdiv.appendChild(scripts); return success && success(newdiv); }, event); };

@rynowak can you review showing how to ref JavaScript/CSS inside a View Components

Yes please. There has been a lot of demand for this - for example see this thread: https://github.com/aspnet/Mvc/issues/2910 - the solution up until today has been roll your own solution or use orchard core framework.

@rynowak can you review showing how to ref JavaScript/CSS inside a View Components

@danroth27 do you have any suggestions?

I would like to see example that demonstrates having multiple of the same view components on a page/view and how or if the javascript need to be set up so that the page doesn't have functions declared multiple times. Should the javascript functions have an view component Id appended to them that would come from the viewmodel? Or should the javascript just be external to the view component and referenced on the parent page/view.

The experience with View Components and working with static assets is suboptimal; we don't really have a recommended solution for this scenario using the View Component model. We understand that having a solution for composable UI is an important scenario and it's something something we want to address with the Razor Components work in ASP.NET Core 3.0. I think we should hold off on providing any official guidance for how to accomplish this scenario with View Components and instead focus our efforts getting good support with Razor Components. If folks in the community would like to document and share their View Component based solutions we would welcome that.

Erm.. what kind of answer is that? Are you saying there is no solution from
the framework (the framework that gives you view components as a paradigm) so lets see what
others can suggest? Its your paradigm! Show some vision please.

(Sorry I do respect you guys a lot, don't mean to come across negatively, but so far view components are completely lacking here and this needs answering)

On Fri, 19 Oct 2018, 00:14 Daniel Roth, notifications@github.com wrote:

Closed #687 https://github.com/aspnet/Docs/issues/687.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/aspnet/Docs/issues/687#event-1913566204, or mute the
thread
https://github.com/notifications/unsubscribe-auth/ADB4uIUUN6UGX8aHo2ZD4_pt-kiXP4o5ks5umQs7gaJpZM4Gmmrx
.

View Components are lacking here because this wasn't a scenario they were originally designed for. Could you get creative enough to extend things to get something working? Probably and I'm sure some folks already have. But it's not something that View Components have first class support for. The long term vision is to provide a component model that actually has a designed way to interact with the layout and enable composition. I'm sorry to disappoint on this one but I'm trying to be realistic here.

I ended up using this approach to execute JavaScript code in View Component after page content has been loaded:

https://stackoverflow.com/a/45292644/2804621

@danroth27 thanks for injecting the reality of the situation into my brain. As much as it is disappointing, atleast there is hope yet RE: Razor Components so I'll look forward to those.

@danroth27 Razor Components sound promising. Can they be considered an analog to View Components but includes support for @section blocks?

Does this mean that instead of being able to include simple scripts inside a view component, the proposed solution is to use server-side Blazor (aka Razor Components), which would imply the inclusion in the app of SignalR? Isn't that overkill for several use cases?

Was this page helpful?
0 / 5 - 0 ratings