将JavaScript放在部分视图中是否可行?

80

我正在开发一个web应用程序,主页面由两部分组成:常驻区块(一直可见)和信息区块(由三个局部视图中的一个组成)。每个局部视图都是通过 AJAX 请求加载的,并且仅加载一次(之后使用 jQuery 提供了切换窗口的功能)。这个方案运行得很好,但我遇到了一个问题。

局部视图的 HTML 代码包含了在常驻区块和信息区块中都使用到的 JavaScript 函数。当页面加载时,这些函数可以“看到”彼此并正常运行,但 ReSharper 找不到函数声明并对此发出警告。我无法将它们转移到外部 JS 文件中以解决此问题,因为这些函数中包含了 Razor 语法。

我该怎么办呢?

谢谢。

更新:

最终,我决定将自己的 JS 代码与视图分离以解决这个问题。所以新问题是:如何在 JS 文件中包含 Razor 语法,或者有什么可接受的替代方案。我找到的流行解决方案有使用全局变量、数据属性以及我更喜欢的 RazorJS 库(作者是 John Katsiotis)。

http://djsolid.net/blog/razorjs---write-razor-inside-your-javascript-files

我希望它能稳定运行并让 Resharper 满意。

干杯!

更新:

三年后,我回忆起这个问题并决定根据我的经验对其进行更新。实际上,现在我更不推荐使用额外的库来解决此问题,尤其是如果您不是项目团队中唯一的成员...最好的方式是确保所有库都得到了创建者和社区的支持,并且可以轻松地集成到您的 IDE 中(例如,使用特殊语法)。同时,您的团队中的所有人都应该知道它的工作原理。因此,现在我建议执行以下操作:

  1. 将所有 JS 文件保存在单独的文件中。尽可能隔离它。 提供外部 API。
  2. 从视图中调用 API 函数。
  3. 将所有 Razor 生成的 URL、文本消息和常量作为资源参数传递。

例如:

JS 文件:

$.api.someInitFunction = function(resources){ ... }

查看:

<script>
    $.api.someInitFunction({
        urls: { myAction: '@Url.Action("MyAction", "MyController")' },
        messages: { error: '@errorMessage' },
        consts: { myConst: @myIntConst }
    });
</script>

2
我无法决定哪个答案对我更有用 :) - Tomy
很高兴能够以如此友好的接待开始,非常感谢! - Tomy
看一下这个链接,它提供了一个不影响效率的很好的解决方案,可以将脚本放在局部视图中。 - Mojtaba
@Tomy,这仍然是您处理此问题的推荐方式吗?(又两年过去了) - Worthy7
1
@Worthy7 如果使用相同的技术,实际上是可以的。 - Tomy
5个回答

43

如果 Resharper 给出警告,这不是什么大问题 ^_^

但如果是我,我会完全不在 partial view 中放置 JavaScript。

因为 partial view 可能会被插入到一个页面多次,那么你的 JavaScript 就会遇到问题。

对于你的情况,如果无法将 JavaScript 分离到 JS 文件中,那么只需创建另一个 PartialView,并将这些脚本放入其中,然后在主页中呈现它即可。


谢谢,Wahid!但有时候Resharper编写的代码比我自己写的好:) 事实上,我知道把js放进partial views不是一个好主意。但是正如我所说,在我的页面上它们存在一份副本,所以嵌套例如就变得“不可能”。根据您有关带脚本的partial view的建议,我将使主视图更加出色。但很遗憾RenderPartial不能解决Resharper的问题。我会错吗? - Tomy
3
如果这是一个小型应用程序,而且您不想在上面花费更多时间,那么就保持现状。但是如果您谈论的是“编程习惯”,那么您应该将JavaScript与视图分离。 - Wahid Bitar
对于 @Html.ActionLink(...),有很多解决方案,但没有一个是将 JavaScript 直接放在 PartialView 中的。您可以查看此答案 https://dev59.com/F1DTa4cB1Zd3GeqPLbV6#3789959 - Wahid Bitar

15

如果您想编写部分视图,将其封装为“小部件”,一旦在页面中包含它们,它们就可以正常工作,则在部分视图中嵌入脚本块是一种清晰的方法,可以将标记和初始化脚本捆绑在一起。例如:我可能有一个名为“_EventList”的部分视图,我可以在整个站点中使用它。如果我在主页的两个位置放置它,它应该可以正常工作,而我不想在我的主页中编写逻辑来初始化该小部件。

如果您只会在页面上使用一次,那很简单。但是,如果您可能会多次使用,则需要将脚本包装起来,以避免重复执行。请参见下面的示例。为了演示,我在代码片段中重复了两次部分视图,代表在主页中两次包含部分视图。

我的主页可能如下所示:

<div id="left-nav">
   @Html.Partial("_EventList")           
</div>

<div id="body">
</div>

<div id="right-nav">
   @Html.Partial("_EventList")
</div>

示例:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<!-- Self-contained partial view containing widget -->

<div id="widgetDiv" class="panel panel-default">

    <div class="panel-heading">Event Dates</div>
    <div class="panel panel-group">
       <ul class="widget">
         <!-- These will load dynamically -->
       </ul>
    </div>

    <script>
        $(document).ready(function() {

            // Run this once only in case widget is on page more than once
            if(typeof $widget == 'undefined') {
                $widget = $("ul.widget"); // could be more than one
                // mock data to simulate an ajax call
                var data = [
                   {Description: "March", StartDate: "03/01/2015"},
                   {Description: "April", StartDate: "04/01/2015"},
                   {Description: "May", StartDate: "05/01/2015"}
                ];

                $.each($widget, function(w, widget) {
                    // might be an $.ajax call
                    $.each(data, function(i, row) {
                        $(widget).append("<li><a href='/Widget/Search?startDate=" + row.StartDate + "'>" + row.Description + "</a></li>");
                    });
                });
            }
        });
    </script>
</div>
<!-- End of widget / partial view -->



<!-- Second copy of above for sake of example snippet -->
<!-- No need to read further -->









<!-- Self-contained partial view containing widget -->

<div id="widgetDiv" class="panel panel-default">

    <div class="panel-heading">Event Dates</div>
    <div class="panel panel-group">
       <ul class="tinylist nav nav-sidebar widget">
         <!-- These will load dynamically -->
       </ul>
    </div>

    <script>
        $(document).ready(function() {

            // Run this once only in case widget is on page more than once
            if(typeof $widget == 'undefined') {
                $widget = $("ul.widget"); // could be more than one
                // mock data to simulate an ajax call
                var data = [
                   {Description: "March", StartDate: "03/01/2015"},
                   {Description: "April", StartDate: "04/01/2015"},
                   {Description: "May", StartDate: "05/01/2015"}
                ];

                $.each($widget, function(w, widget) {
                    // might be an $.ajax call
                    $.each(data, function(i, row) {
                        $(widget).append("<li><a href='/Widget/Search?startDate=" + row.StartDate + "'>" + row.Description + "</a></li>");
                    });
                });
            }
        });
    </script>
</div>
<!-- End of widget / partial view -->


1
尽管有更多的点赞答案,但我绝对推荐这种方法,而不是仅为了按扩展名分组而分裂高度内聚的依赖关系。所以是的,我给出大加号。 - Cristian E.
我非常赞同:不要分离HTML和JS。毫无疑问...但是你的示例部分视图并不适用于ASP MVC的Visual Studios模板的所有用户:在_Layout.cshtml内,jQuery在@RenderBody()之后呈现,因此您不能使用它。您的解决方法并不是真正更好的选择:第一行<script src=".../jquery.min.js"></script>将会重复包含jQuery一次或多次(每个部分视图1次,在您的_Layout.cshtml中1次)。 - Marcel
@Marcel - 这个示例旨在在代码片段编辑器中运行,以展示这种技术。我在我的答案中实际上已经警告了这一点,包括多个jquery包含,并提出了改进建议,所以我不确定你是否读了我的“免责声明”。我的真正布局页面通常在头部部分有jquery和其他脚本。当时,额外的工作超出了我的答案范围/时间要求。只要它仍然在Stack Overflow内运行,您可以随意改进此答案。 - codenheim
如果您在部分视图中包含的脚本不依赖于其他脚本(如jquery),那么这种方法是很好的选择。此外,如果您在头部使用“defer”调用脚本,则会在部分视图上出现错误,并且必须重新加载jquery... - Offir

5
我同意Wahid的观点,不应该在视图中(包括部分视图)放置JavaScript。我看过很多这样的代码,知道这只会导致问题。
我还要说的是,你应该能够将Razor语法封装的逻辑转移到JavaScript中,你只需要向你的JavaScript传递所需的信息来实现你的逻辑。
根据我的经验猜测,你应该像设计C#或VB.NET代码结构一样设计JavaScript。这样,你使用Razor的逻辑应该成为你的JavaScript的一部分。
这样,你的JavaScript将更易于维护,我认为Resharper也会更开心。

谢谢您的回答!但问题并不在于逻辑。我使用 Razor 构建链接(例如 Url.Action)之类的东西。将它们作为参数传递可能看起来相当奇怪。 - Tomy
对于链接,您仍然可以将它们放在href上,只需使用preventDefault。此外,在这些情况下,HTML5数据属性也非常有用。查看dojo或yui3,了解它们的库是如何构建的。 - eaglestorm
@eaglestorm,我对你的回答唯一的问题是,我还没有得到答案。创建@section调用的目的是为了将特定于视图的呈现输出放置在视图的这些区域中。如果我有一个企业级应用程序并且有数百个脚本,我不想将它们全部包含在我的布局中,因为那会使我的HTML呈现输出变得巨大。在视图和/或部分视图中允许包含@section对于性能来说是必须的。 - clockwiseq
@Keith 我建议你在这种情况下连接和压缩你的JavaScript。浏览器将加载它一次并缓存它。但这意味着你必须编写能够处理这个问题的JavaScript代码。 - eaglestorm
感谢 @eaglestorm 的回复。我会尝试一下。 - clockwiseq

3
对于未来的问题查看者,这是我的经验。我认为将仅由部分使用的脚本保留在部分中以进行组织是一个好主意。
我的问题是在调试期间,有时会遇到麻烦,无法使断点生效,并且除非将脚本移动到父视图中,否则无法解决问题。最可能是由于多次打印该函数。我希望部分能够更好地解决和组织这个问题,但是部分视图不支持部分(至少在MVC 4中不支持)。
因此,为了维护和调试,请不要将脚本放在部分视图中。

有一个解决办法是使用console.log()alert()进行调试,根据我的经验,这已经足够了。 - undefined
@Lukas,没问题,但是问题是(如果我没记错的话),当部分代码在一个for循环中时,你会重复多次复制脚本。所以最好将脚本与部分代码分开。 - undefined
是的,那是一个独立的问题,但我可以想到解决办法。知道将JavaScript放入部分视图的所有问题清单会很好。我猜想要注意的问题可能并不多。如果是这样的话,提高代码可读性可能是值得的。 - undefined

2
我做了这件事,发现它非常方便。使用ViewBag、HttpContext等动态加载部分JavaScript代码的方法效果很好。
这会让你觉得像使用T4模板一样。
如果你添加幻影脚本标签,甚至可以获得JavaScript验证、智能感知等功能。
@using System.Configuration
@if (false)
{
    @:<script type="text/javascript">
}    
        $scope.clickCartItem = function(cartItem) {
            console.log(cartItem);
            window.location.href =@Html.Raw("('" + ConfigurationManager.AppSettings["baseUrl"] + "/Checkout/' + cartItem.ItemId)");
        };
        dataAccess.getCart(
        function (response) {
            //...
        },
        function (response) {
            //...
        }
        );

@if (false)
{
    @:</script>
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接