视图组件中的Javascript
我有一个View组件,在Razor(.cshtml)文件中包含一些jQuery。 脚本本身非常特定于视图(处理一些第三方库的配置),因此我想将脚本和HTML保留在同一个文件中,以便组织起见。
问题是脚本没有在_Layout Scripts部分中呈现。 显然,这就是MVC处理与View组件有关的脚本的方式。
我可以通过在Razor文件中使用脚本来解决它, 但不能在Scripts部分内部 。
但后来我遇到依赖问题 – 因为在引用库之前使用了jQuery(对库的引用位于_Layout文件的底部附近)。
除了将jQuery的引用作为Razor代码的一部分包含在内之外,还有什么聪明的解决方案(这会阻碍HTML呈现放置组件的位置)吗?
我目前不在代码的前面,但是如果有机会,如果有人需要查看它以更好地理解它,我当然可以提供它。
我决定做的是编写一个提供“OnContentLoaded”属性的ScriptTagHelper。 如果为true,那么我将使用Javascript函数包装脚本标记的内部内容,以便在文档准备好后执行。 这避免了当ViewComponent的脚本触发时jQuery库尚未加载的问题。
[HtmlTargetElement("script", Attributes = "on-content-loaded")] public class ScriptTagHelper : TagHelper { /// /// Execute script only once document is loaded. /// 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中的示例用法:
可能有一个比这更好的方法,但到目前为止这对我有用。
仅在视图中工作的节不适用于部分视图或视图组件 (
Default.cshtml
独立于主ViewResult
执行,默认情况下其Layout
值为null
。)这是设计的。
您可以使用它来render sections
partial view
或view component's
视图声明的Layout
的partial view
。 但是,在partials和view组件中定义的节不会回流到渲染视图或它的Layout。 如果要在局部视图或视图组件中使用jQuery或其他库引用,则可以在“ Layout
页面中将Layout
入head
而不是body
。
例:
查看组件:
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
_Layout.cshtml :
... @RenderSection("styles", required: false) @RenderBody() ... //script move to head @RenderSection("scripts", required: false)
View.cshtml:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; } .................. @{Html.RenderPartial("YourPartialView");} .................. @await Component.InvokeAsync("Contact") .................. @section Scripts { }
由grahamehorner查看组件:(您可以在此处获取更多信息)
@section脚本不在ViewComponent中呈现,视图组件应该能够在@section中包含不同于partital视图的脚本,因为组件可以在许多视图中重用,组件负责它自己的function。
这是我正在使用的解决方案。 它支持外部脚本加载和自定义内联脚本。 虽然一些设置代码需要放在您的布局文件中(例如_Layout.cshtml),但所有内容都是在View Component中编排的。
布局文件:
在页面顶部附近添加(在标签内部是一个好地方):
将其添加到页面底部附近(就在关闭标记之前):
查看组件:
现在,您的代码中的任何位置(包括View Component)都可以执行此操作,并在页面底部执行脚本:
使用外部脚本依赖项
但有时这还不够。 有时您需要加载外部JavaScript文件,并且只在加载外部文件后才执行自定义脚本。 您还希望在View Component中编排所有内容以进行完全封装。 为此,我在布局页面(或布局页面加载的外部JavaScript文件)中添加了一些设置代码,以延迟加载外部脚本文件。 看起来像这样:
布局文件:
延迟加载脚本
查看组件:
现在,您可以在View Component中(或其他任何地方)执行此操作:
为了清理一下,这里是View Component的标签帮助器(灵感来自@JakeShakesworth的回答):
[HtmlTargetElement("script", Attributes = "delay")] public class ScriptDelayTagHelper : TagHelper { /// /// Delay script execution until the end of the page /// public bool Delay { get; set; } /// /// Execute only after this external script file is loaded /// [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组件中,我可以这样做:
如果View Component没有外部文件依赖关系,您也可以省略after
属性。