77

I’m working on the web app where the main page contains two parts: the constant block which is always visible and the info-block made up by one of 3 partial views. Each of the partial views appears as a result of AJAX request and is loaded just once (after that switching windows is provided by jquery). It works well but I’ve faced with one problem.

The html-code of partial views contains js functions that are used in the constant block and in the info-block as well. When the page is loaded, these functions can “see” each other and it works but resharper can’t find function declarations and warn me about this. I can’t solve the problem by transferring them into external js file because of razor syntax which can be found in their code.

What can I do with this?

Thanks.

Update:

Finally I’ve decided to solve the problem separating my js code from views. So the new question was how to include razor syntax into js files or what is the acceptable alternative. The popular solutions I’ve found are using global variables, data attributes and the one I like more – RazorJS library by John Katsiotis.

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

I hope it’s going to work stable and make Resharper happy.

Cheers!

Update:

After 3 years I recalled this question and decided to update it according to my experience. In fact now I would rather not recommend using additional libraries for that. Especially if you are not the only member in the project team… It is much better if you are ensured in all of your libraries, they are supported by creator and community and can be easily integrated into your IDE (if use special syntax for example). Also all the guys from your team should be aware of how does it work. So now I would suggest doing the next things:

  1. Hold all the JS in separate files. Isolate it as much as you can. Provide the external API for it.
  2. Call the API functions from your Views.
  3. Pass all the Razor generated URLs, text messages, constants as resource parameter.

For example:

js file:

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

View:

<script>
    $.api.someInitFunction({
        urls: { myAction: '@Url.Action("MyAction", "MyController")' },
        messages: { error: '@errorMessage' },
        consts: { myConst: @myIntConst }
    });
</script>
Tomy
  • 823
  • 1
  • 7
  • 11
  • 2
    I couldn't decide what answer was more useful for me :) – Tomy Jun 20 '12 at 11:49
  • It's pleasantly to start with such a nice reception, many thanks! – Tomy Jun 20 '12 at 14:49
  • Have a look on [this](http://forloop.co.uk/blog/managing-scripts-for-razor-partial-views-and-templates-in-asp.net-mvc#sthash.k8ICWUPP.dpbs); it's nice solution to put scripts in partial view without compromising efficiency – Mojtaba Apr 22 '16 at 15:49
  • @Tomy is this still your recommended way of dealing with it? (another 2 years later) – Worthy7 Mar 23 '17 at 00:30
  • 1
    @Worthy7 actually yes if using the same technologies – Tomy Apr 20 '17 at 11:19

5 Answers5

42

If Resharper warns you it's not a big deal ^_^

But if I were you I wouldn't put JavaScript in the partial view at all.

Because the partial view could be inserted in one page many times then you'll get an issues with your JavaScripts.

And for your case if you couldn't separate the JavaScript to JS file then just create another PartialView and put these scripts in it and just render it in your main page.

Wahid Bitar
  • 13,776
  • 13
  • 78
  • 106
  • Thanks, Wahid! But sometimes Resharper codes really better than I do :) In fact I know that it isn’t a good idea to put js into partial views. But as I have said, on my page they exist in one copy so nesting for example is just “impossibru”. Following your advice about partial view with scripts I’m going to make main views pretty good. But that’s a pity that RenderPartial doesn’t solve the Resharper problem. Can I be wrong? – Tomy Jun 19 '12 at 12:15
  • 3
    If this's a small application and you don't want to spend more time on it so just keep your work as it. But if you're talking about the "Programming habit" then you should separate your JavaScript from Views. – Wahid Bitar Jun 19 '12 at 12:46
  • And for the `@Html.ActionLink(...)` there are many solutions but non of them is to put the JavaScript direct in the PartialView. You may take look at this answer http://stackoverflow.com/a/3789959/105445 – Wahid Bitar Jun 19 '12 at 14:12
13

If you want to write partial views that are encapsulated "widgets" that just work once you include them in a page, then embedding a script block inside the partial view is one clean way to wrap the markup and the initialization script together in a partial view. For example, I might have a partial view named "_EventList" that I use throughout my site. If I place in two places on my master page it should just work and I prefer not to have to write logic in my master page to initialize the widget.

If you will never use it more than once in a page, its simple. But if you might, then wrap the script so it doesn't execute twice. See below. For the sake of Stack Overflow snippets I simulate it by repeating the partial view twice in the code snippet to represent including a partial view twice in a master page.

My master page might look like:

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

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

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

Example:

<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 -->
codenheim
  • 20,467
  • 1
  • 59
  • 80
  • 1
    Despite the fact that there are more upvoted answers, I'd definetely recommend this aproach instead of spliting highy cohesive dependencies just for the sake of grouping them by extensions. So yes a big ++ from me. – Cristian E. Jan 18 '16 at 10:17
  • I strongly agree: Don't split cohesive HTML and JS. No doubt... But your example partial view won't work for all consumers of Visual Studios templates of ASP MVC: Inside `_Layout.cshtml` jQuery is rendered *after* `@RenderBody()` so you cannot use it. Your workaround is not really better: The first line `` will include jQuery twice or more often (1x per partial view and 1x in you `_Layout.cshtml`) – Marcel Jul 25 '17 at 10:04
  • @Marcel - the example is meant to function within the code snippet editor in order to show the technique. I actually warned of that, including the multiple jquery includes, in my answer and suggested improvements, so I'm not sure if you read my "disclaimer". My real layout pages usually have jquery and other scripts in the head section. The additional work was outside the scope/time requirement of my answer at the time. You are welcome to improve this answer as long as it still functions within Stack Overflow – codenheim Jul 26 '17 at 15:56
  • This approach is good if the script you include in your partial view has no dependency on other scripts like jquery, also, if you call your scripts in the head section using `defer` you will get an error on your partial view and will have to load jquery again... – Offir Dec 05 '19 at 18:39
4

I agree with Wahid that you shouldn't put JavaScript in views at all partial or otherwise. I've seen enough code that does this to know it only leads to no good.

I would also say that you should be able to transfer the logic encapsulated in the Razor syntax into the JavaScript, you just need to pass to your JavaScript the information needed by your logic.

I'm just guessing from experience with this next comment but you should design the JavaScript the same way you would design the structure of your C# or VB.NET code. That way the logic that you are using Razor for should be part of your JavaScript.

That way your JavaScript will be easier to maintain and I assume Resharper should also be happier.

eaglestorm
  • 1,192
  • 1
  • 13
  • 32
  • Thanks for your answer! But the problem is not with logic. I use Razor to build links (Url.Action for example) and things like that. Passing them as parameters looks quite strange I guess. – Tomy Jun 19 '12 at 12:23
  • For links you can still put them on the href just use preventDefault. Also html5 data attributes are useful in these situations as well. check out either dojo or yui3 and see how there libraries are built. – eaglestorm Jun 19 '12 at 12:38
  • @eaglestorm, the only problem I have with your answer is one that I have yet to get an answer from. The purpose in creating the `@section` calls is so that view-specific rendered output can be placed in those areas of a view. If I have an enterprise-level application and have hundreds of scripts, I don't want to include them all in my layout as that would make the rendered output of my html enormous. Allowing the inclusion of a `@section` in a view and/or partial view is a must in my opinion with regards to performance. – clockwiseq Aug 21 '15 at 05:49
  • @Keith I would recommend that you concat and minify your javascript in that case. The browser will then load it once and cache it. It does mean that you have to write your javascript so that it can handle this though. – eaglestorm Aug 21 '15 at 07:34
  • Thanks for the reply @eaglestorm. I'll give that a shot. – clockwiseq Aug 21 '15 at 13:18
3

For future viewers of this question, here is my experience. I thought it would be a good idea to keep the scripts that were only used by the partial in the partial for organization.

The issue I had was during debugging, I sometimes would have trouble getting my breakpoints to hit and wouldn't be able to troubleshoot an issue unless I moved the script to the parent view. Most likely due to printing the function more than once. I was hoping sections would solve and organize this better, but sections are not supported in partial views (at least not in MVC 4).

So my answer would be for the sake of maintenance and debugging, no scripts should not be put inside partial views.

eaglei22
  • 2,589
  • 1
  • 38
  • 53
2

I do this and find it highly convenient. It works well to dynamically load partial javascript snippets with the availability of ViewBag, HttpContext, etc.

It results in something that feels like using T4 templates.

You can even get javascript validation, intellisense, etc if you add phantom script tags like this.

@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>
}
Bon
  • 1,083
  • 12
  • 23