web-dev-qa-db-ja.com

ビューコンポーネントのJavascript

Razor(.cshtml)ファイルにjQueryを含むビューコンポーネントがあります。スクリプト自体はビューに非常に固有であるため(サードパーティライブラリのいくつかの構成を扱う)、組織のためにスクリプトとHTMLを同じファイルに保持したいと思います。

問題は、スクリプトが_Layout Scriptsセクションに表示されないことです。どうやら、これは MVCの処理方法 Viewコンポーネントに関するスクリプトです。

Razorファイルにスクリプトを置くだけで回避できますが、butnotScriptsセクション内

しかし、ライブラリへの参照の前にjQueryが使用されるため、依存関係の問題が発生します(ライブラリへの参照は_Layoutファイルの下部近くにあります)。

Razorコードの一部としてjQueryへの参照を含めること以外に、これに対する賢明な解決策はありますか(コンポーネントが配置されているHTMLレンダリングを妨げるでしょうか)。

私は現在コードの前にいませんが、誰かがそれをよりよく理解するためにそれを見る必要がある場合、機会があれば一度提供できます。

15
Max Jacob

私がやろうと決めたのは、「OnContentLoaded」の属性を提供するScriptTagHelperを書くことです。 trueの場合、ドキュメントの準備ができたら実行するJavaScript関数でscriptタグの内部コンテンツをラップします。これにより、ViewComponentのスクリプトの起動時にjQueryライブラリがまだロードされていないという問題を回避できます。

[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()); 
     }
  }
}

viewComponent内での使用例:

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

これよりも良い方法があるかもしれませんが、これは今のところうまくいきました。

21

セクションはViewでのみ機能しますpartial viewsまたはコンポーネントを表示Default.cshtmlはメインViewResultから独立して実行され、そのLayout値はnull)、それは仕様によるものです。

render sectionsまたはpartial viewビューが宣言するLayoutview component'sに使用できます。ただし、パーシャルとビューコンポーネントで定義されたセクションは、レンダリングビューまたはレイアウトに戻りません。部分ビューまたはビューコンポーネントでjQueryまたは他のライブラリ参照を使用する場合は、headページのbodyではなくLayoutにライブラリをプルできます。

例:

コンポーネントの表示:

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

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>
}

grahamehornerでコンポーネントを表示(詳細はこちら here

@sectionスクリプトはViewComponentでレンダリングされません。コンポーネントは多くのビューで再利用される可能性があり、コンポーネントが独自の機能を担当するため、ビューコンポーネントは部分ビューとは異なり@section内にスクリプトを含めることができます。

6
Ashiquzzaman

これが私が使用しているソリューションです。外部スクリプトの読み込みとカスタムインラインスクリプトの両方をサポートしています。一部のセットアップコードはレイアウトファイル(_Layout.cshtmlなど)に入れる必要がありますが、すべてはViewコンポーネント内から調整されます。

レイアウトファイル:

これをページの上部近くに追加します(<head>タグは良い場所です):

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

これをページの下部近くに追加します(閉じる</body>タグは良い場所です):

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

コンポーネントの表示:

これで、Viewコンポーネントを含むコードの任意の場所で、これを行うことができ、ページの下部でスクリプトを実行できます。

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



外部スクリプト依存関係あり

しかし、時にはそれだけでは十分ではありません。外部JavaScriptファイルをロードし、外部ファイルがロードされたらカスタムスクリプトを実行する必要がある場合があります。また、完全にカプセル化するために、Viewコンポーネントでこれをすべて調整する必要があります。これを行うには、レイアウトページ(またはレイアウトページによって読み込まれた外部JavaScriptファイル)にもう少しセットアップコードを追加して、外部スクリプトファイルを遅延読み込みします。次のようになります。

レイアウトファイル:

遅延読み込みスクリプト

<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>

コンポーネントの表示:

これで、Viewコンポーネント(またはそれ以外の場所)でこれを実行できます。

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

物事を少し整理するために、Viewコンポーネントのタグヘルパーを次に示します(@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();
    }
}

今、私のViewコンポーネントでこれを行うことができます:

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

ビューコンポーネントに外部ファイルの依存関係がない場合は、after属性を省略することもできます。

0
Johnny Oshika

この回答は.netcore 2.1用です。短い回答、Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManagerのインスタンスを挿入します。このインスタンスは、body要素の最後のノードとして、コンポーネントUI機能に必要なJavaScriptを受け取ります。

どうやってここに来たのか、何がうまくいくのか、長い答え。

同じHTML構造、スタイル、UI機能とロジックを備えたアプリが使用する多くのウィジェットがありますが、異なるデータセットが与えられます。

私のアプリは、IDクレーム(ロール)を使用してナビゲーションを動的に制御します。これらの役割は、単一のページでそのデータの異なるビューを提供するために組み合わせることができるウィジェットに供給するデータセットも決定します。

Areasを使用して、そのコードを分離します。懸念事項のさらなる分離を促進するために、アプリはウィジェットコード(データ取得、マークアップ、スタイル、機能)を個別に保持する必要があるため、ViewComponentsを使用することにしました。

Shared/_layoutファイル内のアプリ全体で使用できるようにするJavaScriptライブラリに機能が依存するという点で、同じ問題がありました。

スクリプトのレンダリングは、_layoutファイルのbodyタグの前の最後の要素です。私の体のレンダリングは、構造上のいくつかのノードであるため、私は、body要素の子ノードのコレクションに追加する必要があるjavascriptを何らかの方法で取得する必要がありました。そうしないと、コンポーネントは必要な機能を提供するために必要なjavascriptオブジェクトを作成できません。

このソリューションのルートにあるのは TagHelperComponentTagHelperComponentManager です。このファイルのITagHelperComponentの抽象基本クラス実装から派生するクラスを作成し、。/ Pages/Shared /に配置しました

これが私のScriptTagHelperです

public class ScriptTagHelper : TagHelperComponent
{
    private readonly string _javascript;
    public ScriptTagHelper(string javascript = "") {
        _javascript = javascript;
    }
    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        if (string.Equals(context.TagName, "body", StringComparison.Ordinal))
        {
            output.PostContent.AppendHtml($"<script type='text/javascript'>{_javascript}</script>");
        }
        return Task.CompletedTask;
    }
}

このコードは、タグヘルパーへのコンストラクター引数としてjavascript文字列を受け取ります。 ProcessAsyncタスクは、bodyタグを見つけてjavascriptを追加するためにオーバーライドされます。

Javascript文字列は、Htmlスクリプトタグでラップされ、その完全なHtml文字列は、既に本文にあるコンテンツの最後に追加されます。 (つまり、レンダリングスクリプトによってページに挿入されたスクリプトセクション、およびレイアウトページのスクリプト)。

このタグヘルパーの使用方法は、ViewComponent内への依存性注入です。 ScriptTagHelperのインスタンスを作成するために配線されたウィジェットの例を次に示します。

これは私のデータ使用量分布ウィジェットです。最終的にデータ使用量の分布を円グラフで表示します。グラフの特定のウェッジにフロートすると、その特定のデータセグメントのより多くのデータハイライトも表示されます。現時点では、すべてのウィジェットがテンプレートを使用して、タスクをチームに割り当ててリソースの構築を開始するためのセットアップを支援します。

このファイルは./Pages/Shared/Components/DataDistribution/Default.cshtmlです。

@model dynamic;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager tagManager;

<div class="col-md-3 grid-margin stretch-card">
    <div class="card">
        <div class="card-body">
            <p class="card-title text-md-center text-xl-left">Data Usage Distribution</p>
            <div class="d-flex flex-wrap justify-content-between justify-content-md-center justify-content-xl-between align-items-center">
                <h3 class="mb-0 mb-md-2 mb-xl-0 order-md-1 order-xl-0">40016</h3>
                <i class="ti-agenda icon-md text-muted mb-0 mb-md-3 mb-xl-0"></i>
            </div>
            <p class="mb-0 mt-2 text-success">10.00%<span class="text-body ml-1"><small>(30 days)</small></span></p>
        </div>
    </div>
</div>


@{
    Layout = null;

    string scriptOptions = Newtonsoft.Json.JsonConvert.SerializeObject(Model);

    string script = @"";

    script += $"console.log({scriptOptions});";

    tagManager.Components.Add(new ScriptTagHelper(javascript: script));
}

ITagHelperComponentManagerのデフォルト実装を注入することにより、タグヘルパーコンポーネントのコレクションにアクセスし、独自のタグヘルパーの実装を追加できます。このタグヘルパージョブは、このビューコンポーネントのJavaScriptを含むスクリプトタグをbodyタグの下部に配置することです。

この単純なコードスタブでは、これらのライブラリを使用してUIを変更するスクリプトのレンダリングを開始する前に、サポートするすべてのUIライブラリが安全かつ確実にロードされることを期待できます。ここでは、ビューコンポーネントから返されたシリアル化されたモデルをコンソールログに記録しています。したがって、このコードを実行しても実際に機能することは証明されませんが、jqueryがレイアウトファイルにロードされていることを確認する場合は、誰でもスクリプト変数を変更してjqueryバージョンをコンソールログに記録できます。

これが誰かを助けることを願っています。変更点は.netcore 3のセクションをサポートするようになったと聞きましたが、ここで示したように、人々が抱えている問題は2.1でネイティブかつかなりエレガントに既に解決されています。

0