Where should I include a script for a view component?
Asked Answered
O

7

25

I have tried adding a section script inside a view component's view.

@section scripts {
    <script src="~/somepath" asp-append-version="true"></script>
}

I also have the Render Section in the shared layout

@RenderSection("scripts", required: false)

When used in partial views and elsewhere in the project the script loads fine. However when in a View Component the script does not load.

I suppose I could include the script in the section tag of every view that calls the component. I feel this does not fit with the self contained nature of a view component.

Is there another way I can do this?

Ogburn answered 30/3, 2017 at 9:47 Comment(1)
It's shocking to me that Microsoft doesn't have a built-in solution to render sections into the layout page's @RenderSection position. github.com/aspnet/Mvc/issues/2910Luxe
T
13

I also had problems with sections tag in viewcomponents. Turns out, to the best of my knowledge, there is no support for it in viewcomponents. See https://github.com/aspnet/Home/issues/2037

Jake Shakesworth has implemented a tag helper as shown in: Javascript in a View Component

On the other hand you could just include it in your viewcomponent as an

<script defer src"...">
  </script>

My requirement was to show a google map from a viewcomponent. Problem was that the script was getting called before the jquery, jquery.ui stuff.
By using defer you are telling the parser not to execute it until the document had loaded thus avoiding the problem of the having to put it in the layout for proper execution. Defer is supported by chrome, safari, and ie(10+), ff(3.6+), o(15+)

Hope this helps

This is an example of my code:

@using MobileVet.WebApp.Services;
@inject ISettingsService SettingsService
@{
     var Options = SettingsService.Value();

    <!--Service Area-->
    <div class="container-fluid">
         <div class="row p-3">
            <!--First column-->
            <div class="col-md-3">
                <h5 class="title">Site Navigation</h5>
                <ul>
                    <li><a href="#!">Home</a></li>
                    <li><a href="#!">Services</a></li>
                    <li><a href="#!">Link 3</a></li>
                    <li><a href="#!">Link 4</a></li>
                </ul> 

            </div>
            <!--/.First column-->
            <hr class="w-100 clearfix d-md-none">

            <!--Second column-->
            <div class="col-md-9">

                <div id="map-canvas" style="min-height: 300px; min-width: 200px;"> 
                </div>
            </div>
            <!--/.Second column-->

        </div>
    </div>
    <!--Service Area-->


<script src="http://maps.google.com/maps/api/js?key=XXXXXXXXXXXXXXXXXXXXXXX&sensor=false"></script>
<script type="text/javascript" src="~/js/components/servicearea.js" defer ></script>

}

Note that you would probably need to write some logic to prevent the script to be included multiple times if the view component is present more than once on a page, which was not my case

Tiu answered 21/9, 2017 at 15:19 Comment(4)
my use case require me having multiple view components in 1 page. using your answer as an example, would that require ensuring 1 addition for each of maps and servicearea scripts?Promptitude
You can have as many viewcomponents or instances of the same viewcomponent you need in a view. If your view component has scripts then, yes you should write some logic that checks if the script is already included in the page and, if it is it skips adding it. I haven't had the need so I haven't played around a lot with it, but a quick fix could potentially be having an empty viewcomponent that only has the scripts in it and gets added after all the needed instances of the actual viewcomponent are.Tiu
Actually I just tried and removing the scripts and placing them in a separate viewcomponent works. The caveat is that now your scripts have to be smart enough to properly identify the objects they are supposed to interact with, and your viewcomponent should give the dom objects unique ids (eg map-canvas-001, map-canvas-002). The initialization function can then loop through the dom to find the objects to initialize. Hope this helps.Tiu
This doesn't help if you want to collect and place items from multiple viewcomponents. for example scheme markups and et.cLarson
H
4

I'm registering the ViewComponents scripts in a scoped service, the registered scripts are then rendered after the scripts section in layout.

ViewComponentsService.cs

using System;
using System.Collections.Generic;

namespace YourProject.Services
{
    public class ViewComponentsService
    {
        
        private readonly List<Func<object>> _scripts = new List<Func<object>>();

        public IEnumerable<Func<object>> Scripts {
            get { 
                foreach (var script in _scripts)
                {
                    yield return script;
                }
            }
        }
        
        // A dictionary could be used as type for the _scripts collection.
        // Doing so a script Id could be passed to RegisterScript.
        // Usefull if only one script per ViewComponent type needs to be rendered.
        public void RegisterScript(Func<object> script) {
            _scripts.Add(script);
        }

    }
}

Don't forget to register the service in startup.

services.AddScoped<ViewComponentsService>();

Example ViewComponent

Here we have the ViewComponent and its scripts in the same file!

@model UI.FailUserFeedback
@inject Services.ViewComponentsService _viewComponentsService

@{
    var modalId = UI.Utilities.RandomId();
    var labelId = UI.Utilities.RandomId();
}

<div class="modal fade" id="@modalId" tabindex="-1" aria-labelledby="@labelId" aria-hidden="true">
    @*omitted for brevity*@
</div>

@{
    // the script is written here
    Func<dynamic, object> RenderScript =
    @<script>
            (function () {
                var modal = new bootstrap.Modal(document.getElementById('@modalId'));
                modal.show();
            })();
    </script>;
    // and registered here
    _viewComponentsService.RegisterScript(() => RenderScript(this));
}

Layout

@inject Services.ViewComponentsService _viewComponentsService
...
@await RenderSectionAsync("Scripts", required: false)
@foreach(var script in _viewComponentsService.Scripts) {
    @script();
}
Hidrosis answered 13/7, 2021 at 12:16 Comment(1)
Tried the other solutions and had no luck, this worked like a charm. I think its because I needed to manipulate the scripts as they loaded (adding data to a chart).Devinne
F
3

From what I have seen, a "@section Scripts {}" in a ViewComponent is ignored and does not render in the relevant @RenderSection() of the ViewComponents _*layout.cshtml

Why that is I do not know.

Faradic answered 23/4, 2017 at 23:2 Comment(1)
its a feature not a bug.Stopped
G
3

@section scripts { } in viewcomponents is ignored and not rendered by Asp.Net rendering engine. So just use at the end of the view component. Also if your jquery scripts are specified at the end in your layout, then jquery will not be available in your viewcomponents. Of course moving the jquery script to the head section in layout will solve the problem but it is recommended to load the js files at the end.

So if you want to keep jquery scripts at the end of layout and still use jquery in viewcomponents, you could use javascript domcontentloaded and any jquery can be written inside domcontentloaded. Not a permanent good approach but works for me.

<script>
    document.addEventListener('DOMContentLoaded', function (event) {
        console.log($ === jQuery)
    });
</script>

Or as mentioned by @Alberto L. Bonfiglio you could also try to move your script to another JS file and defer load it in your viewcomponent:

<script src="viewComponentScript.js" defer></script>
Gotcher answered 27/3, 2020 at 8:57 Comment(0)
J
1

This is how I approached inserting scripts into a view component using Asp.net core 2.0.

First I created a partial view which I placed inside of the view components view folder.

Path: Views/Shared/Components/CalendarWidget/_CalendarScriptsPartial.cshtml

_CalendarScriptsPartial.cshtml

<environment include="Development">
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/moment/moment.js"></script>
    <script src="~/lib/fullcalendar/dist/fullcalendar.js"></script>
    <script src="~/js/calendarWidget.js"></script>
</environment>
<environment exclude="Development">
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/moment/min/moment.min.js"></script>
    <script src="~/lib/fullcalendar/dist/fullcalendar.min.js"></script>
    <script src="~/js/calendarWidget.js"></script>
</environment>

Then, I brought in the scripts via the Html partial async helper method inside of my view components view.

Path: Views/Shared/Components/CalendarWidget/Default.cshtml

Default.cshtml

<section id="calendar"></section>
@await Html.PartialAsync( "Components/CalendarWidget/_CalendarScriptsPartial" )

And for the sake of completeness here is my view components class.

Path: ViewComponents/CalendarWidgetViewComponent.cs

CalendarWidgetViewComponent.cs

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace LodgersChoice.ViewComponents
{
    public class CalendarWidgetViewComponent : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync( )
        {
            return View( );
        }
    }
}

Note: Async isn't currently required in my example but I intend to inject a repository into the ctor of the class which will be using async/await.

Note 2: Once I'm done developing this I plan on bundling and minifying everything down to one script.

Justiciable answered 16/10, 2017 at 22:46 Comment(0)
S
0

In case of ViewComponent called by Controller inside a modal or another element being rendered after page is fully loaded, defer won't work either. Instead you must put these scripts in a parent View or in _Layout. In my case it was an ajax form and

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script src="~/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.js"></script>

or even@{await Html.RenderPartialAsync("_ValidationScriptsPartial");} would not be loaded and caused problems posting the form correctly.

Spotty answered 14/7, 2021 at 7:15 Comment(0)
A
-2

View component in ASP.NET Core acts like independent view with separated controller, so you can insert bellow tag above of your view component

@{
Layout = null;
}

after that insert bellow tag to use related script,for example:

<environment include="Development">

<script src="~/js/chart.js"></script>

</environment>
 <environment exclude="Development">

<script src="~/js/chart.min.js"></script>

</environment>
Allinclusive answered 25/6, 2018 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.