Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View Engine
Asked Answered
J

23

384

I have this section defined in my _Layout.cshtml

@RenderSection("Scripts", false)

I can easily use it from a view:

@section Scripts { 
    @*Stuff comes here*@
}

What I'm struggling with is how to get some content injected inside this section from a partial view.

Let's assume this is my view page:

@section Scripts { 

    <script>
        //code comes here
    </script>
}

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>

I need to inject some content inside the Scripts section from _myPartial partial view.

How can I do this?

Janycejanyte answered 26/9, 2011 at 14:12 Comment(12)
for anyone coming to this later - there is a nuget package for handling this: nuget.org/packages/Forloop.HtmlHelpersGambit
@RussCam you should answer this question. +1 the nuget package solves the exact problem OP is having.Traffic
@RussCam NuGet package is not a solution, code of the package might be.Multiply
@MaksimVi. well, I wrote the nuget package and have no intentions of taking it down, so rather than repeat the code (bitbucket.org/forloop/forloop-htmlhelpers/src) or the wiki (bitbucket.org/forloop/forloop-htmlhelpers/wiki/Home) here, a link to it as a comment is keeps within the spirit of stackoverflow, IMO.Gambit
Here's another solution that seems very nice: #5355927Finochio
I'm in razor 5 & my related issue is to use my partial page as a type of template for a given section of code, and am using knockout and knockout mapper to map the model to a viewmodel created by mapper that is used exclusively by that partial page but have access to other libraries. I want that code separated, thus this is I'm attempting to using the scrip tag itself... of course when rendered its like a script tag in midst of the code... I get that, but for code management's sake am going to give it a shot because the model I need is in the partial page itself... it cannot be in the view.Zemstvo
A possible solution for .NET Core is to use the new TagHelper component in ASP.NET Core 2.0, see discussion.Apothecary
This is not what you exactly want but solves the main problem properly: https://mcmap.net/q/88128/-using-sections-in-editor-display-templatesDannadannel
My solution: gist.github.com/brynner/7de01fb7446bddd9116847323d18b10dSubtle
Does this answer your question? Using sections in Editor/Display templatesTemplate
Technically for this site this should be closed as a duplicate of https://mcmap.net/q/88129/-add-css-references-to-page-39-s-lt-head-gt-from-a-partial-view/125981 but this has more answers (and perhaps better ones)Afrikaner
@RussCam the Forloop.HtmlHelpers does not work for mvc net core, :(. Do you have any update by chance? ThanksAlgicide
P
283

Sections don't work in partial views and that's by design. You may use some custom helpers to achieve similar behavior, but honestly it's the view's responsibility to include the necessary scripts, not the partial's responsibility. I would recommend using the @scripts section of the main view to do that and not have the partials worry about scripts.

Polynices answered 26/9, 2011 at 14:23 Comment(27)
But what if the script is very specific to the partial? Doesn't it make logical sense for it to be defined in the partial, and not the view?Carolann
@Shimmy, because IMHO it is an unnecessary feature. Scripts have nothing to do in partial views anyway. They belong to separate javascript files. It is the responsibility of the parent view hosting those partials to register all the necessary scripts.Polynices
@Darin: I disagree. What about the DRY principle? I don't like to repeat myself, even if it's only script references.Gujarat
@fretje, everybody has the right to express his opinion on the topic. I respect yours. In my answer I've expressed mine and linked to an answer which would allow you to achieve this task. But I've also highlighted on what I would recommend and do for this situation.Polynices
@Darin: Sure thing! I was merely pointing out (to you and to future readers) a violation against the DRY principle with your approach... Doesn't mean I don't respect you ;-)Gujarat
@DarinDimitrov, tried to follow the scripts to placed at the bottom of the page for performance. So i decided to place and run accordingly using sections. But no use, I described my problem here https://mcmap.net/q/86917/-injecting-content-into-specific-sections-from-a-partial-view-asp-net-mvc-3-with-razor-view-enginePriestridden
@Murali, no you cannot use sections inside partials. This is not supported. Didn't you read my answer? Let me rephrase in case you haven't: Sections don't work in partial views and that's by design..Polynices
doesn't work on DisplayTemplates either and it's not good to check for the type of model when using DisplayForModel(). I think sections should be able to be inside the template. Also, it fails silently...Chara
@fretje, DRY is a marvelous principle, but it is often abused. DRY allows for maintainability of code but can actually curtail it if overused. Having script references in one central place seems good but it means that those scripts are downloaded even if they are not needed. Darin, I know it is your humble opinion, but I never like to force people to code in a specific way. Saying that it is completely unnecessary is a broad statement, and I am sure there are exceptions. And I respect all y'all!Allisan
@Allisan it is not solely about script references. It is about a piece of view-dependent script like @section ScriptSection { $("#@Html.IdFor(m => m.SomeProperty)").doJavaScript(); }. I want those sections injected in my "master" view's ScriptSection.Gourley
@CodeCaster, I know. I want the same thing. I'm agreeing with you. Partials should allow section injection.Allisan
This makes absolutely no sense to me. Of course partial views would and should have associated logic, and therefore javascript files, that are specific to them, but not their parent views.Kasiekask
seconding @JoshNoe and the rest -- a "widget" (display + rich interaction) is a perfect example of a partial view tightly coupled to associated javascript. By design I shouldn't have to write two include statements in different places to get the full functionality, because the display will never be without the attendant interaction, and the interaction will never appear elsewhere.Earleanearleen
Take a look at nuget.org/packages/Forloop.HtmlHelpers and the associated docs bitbucket.org/forloop/forloop-htmlhelpers. Sounds like a good fir for what you're afterGambit
Generally, you should not put scripts in partials. If the partial represents a web control that could be rendered within a single document many times, you'll have duplicated scripts. More importantly, scripts in the middle of your document will block parsing until the script finishes, which will ultimately delay execution of the document ready event. It's best to have a script bundle (just one) defined for each page, which will bundle all external scripts into a single compact file.Semiotics
The only exception is as follows. You cannot move your JavaScript script out of a cshtml file if it contains Razor code, since it must be associated with a view file (cshtml,vbhtml). Meanwhile, you don't want to repeat yourself (DRY principal), so you'd rather include it like a reference. I would suggest putting the script and ONLY THE SCRIPT in it's own cshtml file, and then having it render within the view's @scripts section just like you'd reference a script bundle. That way, you can inject the script on any view, have it rendered in the layout's script section, and keep Razor features.Semiotics
So in your view, you'd have something like @section Scripts { @Scripts.Render("~/somebundle"); @Html.Partial("_MyPageWithScriptTagAndCodeOnly") }Semiotics
Scripts aren't the only possible usage. What about forms, which break in certain browsers if nested inside of other forms?Calderon
I guess the big issue here is really the duplicate script scenario. Ideally, partials should be able to "return" a set of dependencies (maybe through Nuget or something) and a "render scripts" section in _Layout would scout and manage those dependencies. How hard could that be? Maybe it's possible to write a helper for that (lots of 'ifs' haha). Does anyone know of an Asp.Net class to handle this behavior ?? Bundle is quite similar right?Augmentative
Why can't you apply a class and meta-data to the html elements you want to apply the script to and make your script more dynamic? That would be an ideal solution as it would reduce the need to pepper in specific code injections for each partial. It would also reduce the footprint of your script if you were doing a mass operation versus operating on each individual component. If you need lots of code for each partial, perhaps you need to rethink your design?Carhop
can someone help with similar question? #42942356Clarkin
Partials are used to isolate common view code, which may include scripts specific for the partial. This argument doesn't make a sense, so I need to know any scripts reference that a Partial might need by the time I use it? Or are you are trying to say that using scripts in the partial is wrong? This is the kind of reply that doesn't contribute to the problem being presented, but just exposes arbitrary rules that doesn't fit a real world scenario.Hangeron
This is not by design. If this was designed to not work, it would throw an exception (possibly seen while rendering the webpage). An intentional result should always come from an intentional / "by design" decision. It may be acceptable to you as a "Minimal Viable Product" lacking a feature. But it is not by-design. When requested as a feature, it should be implemented to either succeed or fail. It should not be ignored and considered intended. IMHOHalbert
You didn't understand the use case.Justification
A single partial page could be included multiple times while generating the result of a request, and thus could lead to including the same script section multiple times. I guess this is why the render section is not available, but it would be more then welcome if they spent some time to solve this problem. Partial pages can very well have a dependency on a script only for their using.Eustasius
Please do not just link to the answer, but add it to the answer.Yourself
I think there is a scenario where you just return the partial view (like returning after an ajax call, where you wouldn't want to return a full view, kinda defeats the purpose of ajax sometimes if you do that)Pungy
A
97

This is quite a popular question, so I'll post my solution up.

I had the same problem and although it isn't ideal, I think it actually works quite well and doesn't make the partial dependant on the view.

My scenario was that an action was accessible by itself but also could be embedded into a a view - a google map.

In my _layout I have:

@RenderSection("body_scripts", false)

In my index view I have:

@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

In my clients view I have (all the map and assoc. html):

@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

My Clients_Scripts view contains the javascript to be rendered onto the page.

This way my script is isolated and can be rendered into the page where required, with the body_scripts tag only being rendered on the first occurrence that the razor view engine finds it.

That lets me have everything separated - it's a solution that works quite well for me, others may have issues with it, but it does patch the "by design" hole.

Adherent answered 12/4, 2013 at 12:31 Comment(8)
I wasn't the one to down vote you, but I will say that I don't really like this solution because it still separates the view specific scripts from the view itself.Hazing
20 other people and I have a different opinion. You can still have scripts directly related to a view that are in a separate file, it's a programming error if you don't include your script along with your view. Having it in a separate file separates interaction from presentation and allows an abundance of other benefits from it being in a separate file.Adherent
You're completely right. I actually completely agree and prefer this method personally. The real problem for me is that my colleagues struggle with this much separation. That's a domain problem, though. I think this method is ideal, especially once you factor in a JavaScript build process. I'll continue to work on educating my colleagues into using this method, and wholly support it. I do think your answer could be improved, though. You didn't need to mention the "20 people agree" though. Just because an answer is popular, doesn't always mean it's right. In this case it is right.Hazing
Very true, and I'm always happy to accept constructive feedback and alter my own code and answer if there is an improvement to be had :)Adherent
This solution has the added benefit of still being able to do all of the MVC-ish stuff that you would expect to be able to do in a typical View, such as being able to JSON encode a passed in Model and generate URLs using Url.Action. This approach then is an elegant way to set up your AngularJS controllers - each partial view can represent a separate controller in the Angular module. So clean!Dunlop
But.... "Sections don't work in partial views and that's by design." Your "clients" partial view has the "@section body_scripts { @Html.Partial("Clients_Scripts") }" which wouldn't be loaded. This doesn't address the issue of getting @sections to work inside of partial views at all? Or am I completely missing something?Dominion
@Dominion I think you are.... The partial view will not render out the section block, but because the partial view is used on a normal page (index) and that contains the section which renders the partial view containing the reusable part, it renders it. You can then use the partial view in isolation as a view if required and still include the same re-usable scriptAdherent
Still partial view's user is depended to other things. For every partial view there will be two more views with it one for css and other for js.Prig
E
43

From the solutions in this thread, I came up with the following probably overcomplicated solution that lets you delay rendering any html (scripts too) within a using block.

USAGE

Create the "section"

  1. Typical scenario: In a partial view, only include the block one time no matter how many times the partial view is repeated in the page:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
    
  2. In a partial view, include the block for every time the partial is used:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
    
  3. In a partial view, only include the block once no matter how many times the partial is repeated, but later render it specifically by name when-i-call-you:

    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        <b>show me once by name</b>
        <span>@Model.First().Value</span>
    }
    

Render the "sections"

(i.e. display the delayed section in a parent view)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`

CODE

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://mcmap.net/q/88128/-using-sections-in-editor-display-templates and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
    /// </summary>
    private class DelayedInjectionBlock : IDisposable {
        /// <summary>
        /// Unique internal storage key
        /// </summary>
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// <summary>
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// </summary>
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// <summary>
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// </summary>
        private const string EMPTY_IDENTIFIER = "";

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// <summary>
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// </summary>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// <summary>
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
        /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// <summary>
        /// Append the internal content to the context's cached list of output delegates
        /// </summary>
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// <summary>
    /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
    /// <para>
    /// <example>
    /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show at later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
    /// <code>
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     <b>show me once</b>
    ///     <span>@Model.First().Value</span>
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
    /// <returns>using block to wrap delayed output</returns>
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// <summary>
    /// Render all queued output blocks injected via <see cref="Delayed"/>.
    /// <para>
    /// <example>
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show me later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>more for later</b>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
    /// <code>
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// </code>
    /// </example>
    /// </para>

    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="removeAfterRendering">only render this once</param>
    /// <returns>rendered output content</returns>
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}
Earleanearleen answered 13/9, 2013 at 15:36 Comment(5)
Wow it even is complicated for me to understand the code ,but +1 for coming up with a solutionBeginner
@RameezAhmedSayad you're right -- coming back here even I'm confused by how I meant to say how to use it. Updating the answer...Earleanearleen
And to clarify further - the reason there are two "names" is that if you only want it rendered once it needs the unique key in parameter isOnlyOne, but only if you want to render it at a specific location by name do you provide the identifier, otherwise it gets dumped at Html.RenderDelayed().Earleanearleen
I personally don't think there'd be any need for buying the trouble and using this approach, the section in partial views is simply not needed as it can be eliminated, and the scripts can go there without defining a section. It is because that's externally rendered and if you see the code for the rendered page you simply notice the code for the partial view is not visible in there. So if that's the matter of better organization, etc, that will not have any effect at all.Adjudicate
@Adjudicate the "debate" has already been started in the comments on the accepted answer https://mcmap.net/q/86917/-injecting-content-into-specific-sections-from-a-partial-view-asp-net-mvc-3-with-razor-view-engineEarleanearleen
N
12

If you do have a legitimate need to run some js from a partial, here's how you could do it, jQuery is required:

<script type="text/javascript">        
    function scriptToExecute()
    {
        //The script you want to execute when page is ready.           
    }

    function runWhenReady()
    {
        if (window.$)
            scriptToExecute();                                   
        else
            setTimeout(runWhenReady, 100);
    }
    runWhenReady();
</script>
Nafis answered 18/10, 2013 at 20:57 Comment(1)
I tried @drzaus, it needs the 'SeeIfReady' or it doesn't work.Chordophone
F
12

The goal of the OP is that he wants to define inline scripts into his Partial View, which I assume that this script is specific only to that Partial View, and have that block included into his script section.

I get that he wants to have that Partial View to be self contained. The idea is similar to components when using Angular.

My way would be to just keep the scripts inside the Partial View as is. Now the problem with that is when calling Partial View, it may execute the script in there before all other scripts (which is typically added to the bottom of the layout page). In that case, you just have the Partial View script wait for the other scripts. There are several ways to do this. The simplest one, which I've used before, is using an event on body.

On my layout, I would have something on the bottom like this:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
  (function(){
    document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
  })();
</script>

Then on my Partial View (at the bottom):

<script>
  (function(){
    document.querySelector('body').addEventListener('scriptsLoaded', function() {

      // .. do your thing here

    });
  })();
</script>

Another solution is using a stack to push all your scripts, and call each one at the end. Other solution, as mentioned already, is RequireJS/AMD pattern, which works really well also.

Friederike answered 17/8, 2018 at 16:30 Comment(1)
Nice, this is clever.Caryl
R
9

Following the unobtrusive principle, it's not quite required for "_myPartial" to inject content directly into scripts section. You could add those partial view scripts into separate .js file and reference them into @scripts section from parent view.

Ramiroramjet answered 26/9, 2011 at 14:21 Comment(2)
What would happen if partial view is not at all rendered in the page? Do we still reference those .js files in parent and make it overload?Priestridden
Actually, sections do now work from partial views.History
S
6

There is a fundamental flaw in the way we think about web, especially when using MVC. The flaw is that JavaScript is somehow the view's responsibility. A view is a view, JavaScript (behavioral or otherwise) is JavaScript. In Silverlight and WPF's MVVM pattern we we're faced with "view first" or "model first". In MVC we should always try to reason from the model's standpoint and JavaScript is a part of this model in many ways.

I would suggest using the AMD pattern (I myself like RequireJS). Seperate your JavaScript in modules, define your functionality and hook into your html from JavaScript instead of relying on a view to load the JavaScript. This will clean up your code, seperate your concerns and make life easier all in one fell swoop.

Sebaceous answered 18/6, 2013 at 9:0 Comment(7)
For like two or three months or so, I'm using RequireJS and I don't think I'll ever develop another web application without RequireJS.Janycejanyte
JavaScript can be the View responsibility as well.Swedish
Using the AMD pattern is a good idea, but I don't agree with your assertion that JavaScript is part of the model. It's often to define View behavior, especially when coupled with something like Knockout. You dump a JSON representation of your model into your JavaScript View. Personally, I just use closures, a custom "namespace" on the window object, and include library scripts before any partials.Hazing
I think there is a misunderstanding here. When developing most web apps, we are actually developing two applications: one that runs on the server and one that runs on the client. From the server's perspective, anything you send down to the browser is the "view". In that sense, JavaScript is part of the view. From the perspective of the client app, pure HTML is view and JS is code that parallels the M and C in server's MVC terms. I think this is why people are disagreeing here.Microanalysis
There are often small pieces of javascript that are tightly coupled to a view and it makes perfect sense both organizationally and for maintenance to put them together. This would be common sense, but the reason it's not is because performance considerations dictate we include javascript at the end of our web pages and not the beginning (this is the source of the problem... we wouldn't even need a special "section" for scripts otherwise). So the issue has nothing to do with a view having javascript in it as you suggest. Under the right circumstances, there's absolutely nothing wrong with that.Opportina
@BVernon, let me start by saying that you're absolutely right, I'd also like to state that this original answer is 7 years old, the world has changed. We used to have a hard time getting the little bits of JavaScript which should be tightly coupled to your view into the view. Now we have things like Svelte which revolutionize the way we work with Markup/JavaScript. The gist of the pattern remains the same though, decoupling is always the right way to go; invert the dependency through an interface (or function composition) and keep your "state" and the "state changes" away from your view logic.Sebaceous
@Mr.Baudin I'm probably 7 years behind trying to play catchup right now, which is probably why I ran across this, lol. But I think there's a time and a place for everything. Wouldn't React, for example seem to contradict your supposition (at least in certain contexts, even if not generally speaking). In other words, I think idealism is often the enemy of practicality. But as always, there has to be a balance. What do you think?Opportina
C
3

You can't need using sections in partial view.

Include in your Partial View. It execute the function after jQuery loaded. You can alter de condition clause for your code.

<script type="text/javascript">    
var time = setInterval(function () {
    if (window.jQuery != undefined) {
        window.clearInterval(time);

        //Begin
        $(document).ready(function () {
           //....
        });
        //End
    };
}, 10); </script>

Julio Spader

Cycloplegia answered 30/5, 2014 at 17:29 Comment(1)
I was trying to run it as a section after some javascripts were loaded in the layout, but I was looking for a solution because it didn't work. This worked for me.Modernity
G
3

This worked for me allowing me to co-locate javascript and html for partial view in same file. Helps with thought process to see html and related part in same partial view file.


In View which uses Partial View called "_MyPartialView.cshtml"

<div>
    @Html.Partial("_MyPartialView",< model for partial view>,
            new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>

@section scripts{

    @Html.Partial("_MyPartialView",<model for partial view>, 
                  new ViewDataDictionary { { "Region", "ScriptSection" } })

 }

In Partial View file

@model SomeType

@{
    var region = ViewData["Region"] as string;
}

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}
Gerbil answered 12/1, 2017 at 13:18 Comment(0)
A
2

The first solution I can think of, is to use ViewBag to store the values that must be rendered.

Onestly I never tried if this work from a partial view, but it should imo.

Alteration answered 26/9, 2011 at 14:18 Comment(1)
Just tried; sadly that doesn't work (created a ViewBag.RenderScripts = new List<string>(); at the top of the main page, then called @Html.Partial("_CreateUpdatePartial",Model,ViewData), then put @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. In Partial view I put @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.Galling
S
2

You can use these Extension Methods: (Save as PartialWithScript.cs)

namespace System.Web.Mvc.Html
{
    public static class PartialWithScript
    {
        public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
        {
            if (htmlHelper.ViewBag.ScriptPartials == null)
            {
                htmlHelper.ViewBag.ScriptPartials = new List<string>();
            }

            if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
            {
                htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
            }

            htmlHelper.ViewBag.ScriptPartialHtml = true;
            htmlHelper.RenderPartial(partialViewName);
        }

        public static void RenderPartialScripts(this HtmlHelper htmlHelper)
        {
            if (htmlHelper.ViewBag.ScriptPartials != null)
            {
                htmlHelper.ViewBag.ScriptPartialHtml = false;
                foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
                {
                    htmlHelper.RenderPartial(partial);
                }
            }
        }
    }
}

Use like this:

Example partial: (_MyPartial.cshtml) Put the html in the if, and the js in the else.

@if (ViewBag.ScriptPartialHtml ?? true)
    <p>I has htmls</p>
}
else {
    <script type="text/javascript">
        alert('I has javascripts');
    </script>
}

In your _Layout.cshtml, or wherever you want the scripts from the partials on to be rendered, put the following (once): It will render only the javascript of all partials on the current page at this location.

@{ Html.RenderPartialScripts(); }

Then to use your partial, simply do this: It will render only the html at this location.

@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}
Stultz answered 25/4, 2016 at 11:54 Comment(0)
P
2

Pluto's idea in a nicer way:

CustomWebViewPage.cs:

    public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {

    public IHtmlString PartialWithScripts(string partialViewName, object model) {
        return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
    }

    public void RenderScriptsInBasePage(HelperResult scripts) {
        var parentView = ViewBag.view as WebPageBase;
        var parentHtml = ViewBag.html as HtmlHelper;
        parentView.DefineSection("scripts", () => {
            parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
        });
    }
}

Views\web.config:

<pages pageBaseType="Web.Helpers.CustomWebViewPage">

View:

@PartialWithScripts("_BackendSearchForm")

Partial (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

@helper scripts() {
<script>
    //code will be rendered in a "scripts" section of the Layout page
</script>
}

Layout page:

@RenderSection("scripts", required: false)
Prism answered 23/12, 2016 at 7:32 Comment(0)
G
1

There is a way to insert sections in partial views, though it's not pretty. You need to have access to two variables from the parent View. Since part of your partial view's very purpose is to create that section, it makes sense to require these variables.

Here's what it looks like to insert a section in the partial view:

@model KeyValuePair<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}

And in the page inserting the partial view...

@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))

You can also use this technique to define the contents of a section programmatically in any class.

Enjoy!

Gym answered 14/8, 2014 at 5:1 Comment(1)
Can you please and a link to a fully working project?Thirion
F
1

Using Mvc Core you can create a tidy TagHelper scripts as seen below. This could easily be morphed into a section tag where you give it a name as well (or the name is taken from the derived type). Note that dependency injection needs to be setup for IHttpContextAccessor.

When adding scripts (e.g. in a partial)

<scripts>
    <script type="text/javascript">
        //anything here
    </script>
</scripts>

When outputting the scripts (e.g. in a layout file)

<scripts render="true"></scripts>

Code

public class ScriptsTagHelper : TagHelper
    {
        private static readonly object ITEMSKEY = new Object();

        private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;

        private IHttpContextAccessor _httpContextAccessor;

        public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var attribute = (TagHelperAttribute)null;
            context.AllAttributes.TryGetAttribute("render",out attribute);

            var render = false;

            if(attribute != null)
            {
                render = Convert.ToBoolean(attribute.Value.ToString());
            }

            if (render)
            {
                if (_items.ContainsKey(ITEMSKEY))
                {
                    var scripts = _items[ITEMSKEY] as List<HtmlString>;

                    var content = String.Concat(scripts);

                    output.Content.SetHtmlContent(content);
                }
            }
            else
            {
                List<HtmlString> list = null;

                if (!_items.ContainsKey(ITEMSKEY))
                {
                    list = new List<HtmlString>();
                    _items[ITEMSKEY] = list;
                }

                list = _items[ITEMSKEY] as List<HtmlString>;

                var content = await output.GetChildContentAsync();

                list.Add(new HtmlString(content.GetContent()));
            }
        }
    }
Fellner answered 14/12, 2017 at 17:14 Comment(0)
P
1

I had this issue today. I'll add a workaround that uses <script defer> as I didn't see the other answers mention it.

//on a JS file somewhere (i.e partial-view-caller.js)
(() => <your partial view script>)();

//in your Partial View
<script src="~/partial-view-caller.js" defer></script>

//you can actually just straight call your partial view script living in an external file - I just prefer having an initialization method :)

Code above is an excerpt from a quick post I made about this question.

Pungy answered 10/11, 2020 at 10:52 Comment(0)
J
0

I solved this a completely different route (because I was in a hurry and didn't want to implement a new HtmlHelper):

I wrapped my Partial View in a big if-else statement:

@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}

Then, I called the Partial twice with a custom ViewData:

@Html.Partial("MyPartialView", Model, 
    new ViewDataDictionary { { "ShouldRenderScripts", false } })

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}
Jordain answered 28/1, 2015 at 20:16 Comment(2)
Surely the whole idea is that the consumer of the partial view shouldn't need to know that it has to include scripts, that's kinda of the issue? Otherwise you may as-well just say @Html.Partial("MyPartialViewScripts")Adherent
No, the idea is to allow the scripts to be defined in the same document as the html, but I agree this is not ideal.Jordain
K
0

I had a similar problem, where I had a master page as follows:

@section Scripts {
<script>
    $(document).ready(function () {
        ...
    });
</script>
}

...

@Html.Partial("_Charts", Model)

but the partial view depended on some JavaScript in the Scripts section. I solved it by encoding the partial view as JSON, loading it into a JavaScript variable and then using this to populate a div, so:

@{
    var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}

@section Scripts {
<script>
    $(document).ready(function () {
        ...
        var partial = @partial;
        $('#partial').html(partial.html);
    });
</script>
}

<div id="partial"></div>
Known answered 1/6, 2016 at 8:40 Comment(1)
IMO you should have solved this by moving your JS into a separate file.Initiation
N
0

choicely, you could use a your Folder/index.cshtml as a masterpage then add section scripts. Then, in your layout you have:

@RenderSection("scripts", required: false) 

and your index.cshtml:

@section scripts{
     @Scripts.Render("~/Scripts/file.js")
}

and it will working over all your partialviews. It work for me

Nordine answered 6/12, 2016 at 14:18 Comment(0)
J
0

My solution was to load the script from the layout page. Then in the javacript, check for the presence of one of the elements in the parial view. If the element was present, the javascript knew that the partial had been included.

$(document).ready(function () {
    var joinButton = $("#join");
    if (joinButton.length != 0) {
        // the partial is present
        // execute the relevant code
    }
});
Joshia answered 20/5, 2021 at 11:41 Comment(0)
B
-1

Well, I guess the other posters have provided you with a means to directly include an @section within your partial (by using 3rd party html helpers).

But, I reckon that, if your script is tightly coupled to your partial, just put your javascript directly inside an inline <script> tag within your partial and be done with it (just be careful of script duplication if you intend on using the partial more than once in a single view);

Boor answered 27/7, 2016 at 14:55 Comment(1)
This isn't usually ideal because the loading of jQuery etc would happen after the inline scripts... but for native code I guess it is fine.Initiation
H
-3

assume you have a partial view called _contact.cshtml, your contact can be a legal (name) or a physical subject (first name, lastname). your view should take care about what's rendered and that can be achived with javascript. so delayed rendering and JS inside view may be needed.

the only way i think, how it can be ommitted, is when we create an unobtrusive way of handling such UI concerns.

also note that MVC 6 will have a so called View Component, even MVC futures had some similar stuff and Telerik also supports such a thing...

Hadlee answered 27/11, 2014 at 6:57 Comment(1)
3 years late, and I don't think this even answers the question at all? What are you trying to say here? Answering a question 3 years later with speculative features of future technologies is not really an answer or particularly helpfulAdherent
D
-4

I have just added this code on my partial view and solved the problem, though not very clean, it works. You have to make sure the the Ids of the objects you are rendering.

<script>
    $(document).ready(function () {
        $("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
    });
</script>
Designed answered 5/6, 2018 at 2:36 Comment(0)
C
-5

I had the similar problem solved it with this:

@section ***{
@RenderSection("****", required: false)
}

Thats a pretty way to inject i guesse.

Cover answered 20/8, 2015 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.