web-dev-qa-db-fra.com

Javascript dans un composant de vue

J'ai un composant de vue qui contient du jQuery dans le fichier Razor (.cshtml). Le script lui-même est assez spécifique à la vue (concerne certaines configurations de bibliothèques tierces), je souhaite donc conserver le script et le code HTML dans le même fichier pour des raisons d'organisation.

Le problème est que le script n'est pas rendu dans la section Scripts _Layout. Apparemment, c'est juste comment MVC gère scripts en ce qui concerne View Components.

Je peux le contourner simplement en ayant les scripts dans le fichier Razor, mais pas à l'intérieur de la section Scripts.

Mais je rencontre ensuite des problèmes de dépendance, car jQuery est utilisé avant la référence à la bibliothèque (la référence à la bibliothèque se trouve au bas du fichier _Layout).

Existe-t-il une solution intelligente à cette question, autre que celle d'inclure la référence à jQuery dans le code Razor (ce qui empêcherait le rendu HTML lorsque le composant est placé)?

Je ne suis actuellement pas en face du code, mais je peux certainement le fournir une fois que j'ai la chance si quelqu'un doit l'examiner pour mieux le comprendre.

8
Max Jacob

Ce que j'ai décidé de faire est d'écrire un ScriptTagHelper qui fournit un attribut de "OnContentLoaded". Si la valeur est true, le contenu interne de la balise de script est enveloppé d'une fonction Javascript à exécuter une fois le document prêt. Cela évite le problème avec la bibliothèque jQuery n'ayant pas encore été chargée lorsque le script du ViewComponent est lancé.

[HtmlTargetElement("script", Attributes = "on-content-loaded")]
public class ScriptTagHelper : TagHelper
{
  /// <summary>
  /// Execute script only once document is loaded.
  /// </summary>
  public bool OnContentLoaded { get; set; } = false;

  public override void Process(TagHelperContext context, TagHelperOutput output)
  {
     if (!OnContentLoaded)
     {
        base.Process(context, output);
     } 
     else
     {
        var content = output.GetChildContentAsync().Result;
        var javascript = content.GetContent();

        var sb = new StringBuilder();
        sb.Append("document.addEventListener('DOMContentLoaded',");
        sb.Append("function() {");
        sb.Append(javascript);
        sb.Append("});");

        output.Content.SetHtmlContent(sb.ToString()); 
     }
  }
}

exemple d'utilisation dans un ViewComponent:

<script type="text/javascript" on-content-loaded="true">
    $('.button-collapse').sideNav();
</script>

Il y a peut-être un meilleur moyen que cela, mais cela a fonctionné pour moi jusqu'à présent.

13
Jake Shakesworth

Les sections ne fonctionnent que dans une vue [] et ne fonctionnent pas dans les vues partielles ou Voir Composant (Default.cshtml s'exécute indépendamment de la ViewResult principale et sa valeur Layout est null par défaut.) Et cela est inhérent à la conception.

Vous pouvez l'utiliser pour render sections pour une Layout que la vue partial view ou view component's déclare. Toutefois, les sections définies dans les partiels et les composants de vue ne sont pas redirigées vers la vue de rendu ou sa mise en forme . Si vous souhaitez utiliser jQuery ou une autre référence de bibliothèque dans votre vue partielle ou composant View, vous pouvez extraire votre bibliothèque en head plutôt que body dans votre page Layout

Exemple: 

Composant de vue:

public class ContactViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        return View();
    }
}

Emplacement de ViewComponent:

/Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml
/Views/Shared/Components/[NameOfComponent]/Default.cshtml

Default.cshtml:

@model ViewComponentTest.ViewModels.Contact.ContactViewModel

<form method="post" asp-action="contact" asp-controller="home">
    <fieldset>
        <legend>Contact</legend>
        Email: <input asp-for="Email" /><br />
        Name: <input asp-for="Name" /><br />
        Message: <textarea asp-for="Message"></textarea><br />
        <input type="submit" value="Submit" class="btn btn-default" />
    </fieldset>
</form>

_Layout.cshtml:

    <!DOCTYPE html>
    <html>
    <head>
    ...
        @RenderSection("styles", required: false)
     <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    </head>
    <body>
        @RenderBody()
    ...
    //script move to head
      @RenderSection("scripts", required: false)
    </body>
    </html>

View.cshtml:

@{
    Layout =  "~/Views/Shared/_Layout.cshtml";     
}
..................
@{Html.RenderPartial("YourPartialView");}
..................
@await Component.InvokeAsync("Contact")
..................
@section Scripts {
    <script>
    //Do somthing
    </script>
}

Voir le composant par grahamehorner : (vous pouvez plus d’informations ici )

Les scripts @section ne sont pas rendus dans un composant ViewComponent, view devrait pouvoir inclure des scripts dans la @ section contrairement à les vues partielles en tant que composants peuvent être réutilisées dans de nombreuses vues et dans Le composant est responsable de sa propre fonctionnalité.

1
Ashiquzzaman

Voici la solution que j'utilise. Il prend en charge le chargement de script externe et le script en ligne personnalisé. Bien que du code d'installation doive être inséré dans votre fichier de mise en page (par exemple, _Layout.cshtml), tout est orchestré à partir du composant View.

Fichier de mise en page:

Ajoutez ceci près du haut de votre page (dans la balise <head> est un bon endroit):

<script>
    MYCOMPANY = window.MYCOMPANY || {};
    MYCOMPANY.delayed = null;
    MYCOMPANY.delay = function (f) { var e = MYCOMPANY.delayed; MYCOMPANY.delayed = e ? function () { e(); f(); } : f; };
</script>

Ajoutez ceci au bas de votre page (juste avant que la balise </body> de fermeture soit un bon endroit):

<script>MYCOMPANY.delay = function (f) { setTimeout(f, 1) }; if (MYCOMPANY.delayed) { MYCOMPANY.delay(MYCOMPANY.delayed); MYCOMPANY.delayed = null; }</script>

Composant de vue:

Désormais, n'importe où dans votre code, y compris votre composant View, vous pouvez le faire et faire exécuter le script au bas de votre page:

<script>
    MYCOMPANY.delay(function() {
        console.log('Hello from the bottom of the page');
    });
</script>



Avec dépendance de script externe

Mais parfois, cela ne suffit pas. Parfois, vous devez charger un fichier JavaScript externe et exécuter votre script personnalisé une fois que le fichier externe est chargé. Vous souhaitez également orchestrer tout cela dans votre composant View pour une encapsulation complète. Pour ce faire, j'ajoute un peu plus de code d'installation dans ma page de présentation (ou dans un fichier JavaScript externe chargé par la page de présentation) afin de charger paresseux le fichier de script externe. Cela ressemble à ceci:

Fichier de mise en page:

Script de chargement paresseux

<script>
    MYCOMPANY.loadScript = (function () {
        var ie = null;
        if (/MSIE ([^;]+)/.test(navigator.userAgent)) {
            var version = parseInt(RegExp['$1'], 10);
            if (version)
                ie = {
                    version: parseInt(version, 10)
                };
        }

        var assets = {};

        return function (url, callback, attributes) {

            attributes || (attributes = {});

            var onload = function (url) {
                assets[url].loaded = true;
                while (assets[url].callbacks.length > 0)
                    assets[url].callbacks.shift()();
            };

            if (assets[url]) {

                if (assets[url].loaded)
                    callback();

                assets[url].callbacks.Push(callback);

            } else {

                assets[url] = {
                    loaded: false,
                    callbacks: [callback]
                };

                var script = document.createElement('script');
                script.type = 'text/javascript';
                script.async = true;
                script.src = url;

                for (var attribute in attributes)
                    if (attributes.hasOwnProperty(attribute))
                        script.setAttribute(attribute, attributes[attribute]);

                // can't use feature detection, as script.readyState still exists in IE9
                if (ie && ie.version < 9)
                    script.onreadystatechange = function () {
                        if (/loaded|complete/.test(script.readyState))
                            onload(url);
                    };
                else
                    script.onload = function () {
                        onload(url);
                    };

                document.getElementsByTagName('head')[0].appendChild(script);
            }
        };
    }());
</script>

Composant de vue:

Maintenant, vous pouvez le faire dans votre composant View (ou n'importe où ailleurs):

<script>
    MYCOMPANY.delay(function () {
        MYCOMPANY.loadScript('/my_external_script.js', function () {
            console.log('This executes after my_external_script.js is loaded.');
        });
    });
</script>

Pour nettoyer un peu les choses, voici un assistant de balise pour le composant View (inspiré de la réponse de @ JakeShakesworth):

[HtmlTargetElement("script", Attributes = "delay")]
public class ScriptDelayTagHelper : TagHelper
{
    /// <summary>
    /// Delay script execution until the end of the page
    /// </summary>
    public bool Delay { get; set; }

    /// <summary>
    /// Execute only after this external script file is loaded
    /// </summary>
    [HtmlAttributeName("after")]
    public string After { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (!Delay) base.Process(context, output);

        var content = output.GetChildContentAsync().Result;
        var javascript = content.GetContent();

        var sb = new StringBuilder();
        sb.Append("if (!MYCOMPANY || !MYCOMPANY.delay) throw 'MYCOMPANY.delay missing.';");
        sb.Append("MYCOMPANY.delay(function() {");
        sb.Append(LoadFile(javascript));
        sb.Append("});");

        output.Content.SetHtmlContent(sb.ToString());
    }

    string LoadFile(string javascript)
    {
        if (After == NullOrWhiteSpace)
            return javascript;

        var sb = new StringBuilder();
        sb.Append("if (!MYCOMPANY || !MYCOMPANY.loadScript) throw 'MYCOMPANY.loadScript missing.';");
        sb.Append("MYCOMPANY.loadScript(");
        sb.Append($"'{After}',");
        sb.Append("function() {");
        sb.Append(javascript);
        sb.Append("});");

        return sb.ToString();
    }
}

Maintenant, dans mon composant de vue, je peux le faire:

<script delay="true" after="/my_external_script.js"]">
  console.log('This executes after my_external_script.js is loaded.');
</script>

Vous pouvez également omettre l'attribut after si le composant View n'a pas de dépendance de fichier externe.

0
Johnny Oshika