Wstrzykiwanie zawartości do określonych sekcji z częściowego widoku ASP.NET MVC 3 z Razor View Engine

324

Mam tę sekcję zdefiniowaną w moim _Layout.cshtml

@RenderSection("Scripts", false)

Mogę z łatwością korzystać z widoku:

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

Mam problem z tym, jak uzyskać częściową treść w tej sekcji z częściowego widoku.

Załóżmy, że to moja strona widoku:

@section Scripts { 

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

<div>
    poo bar poo
</div>

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

Muszę wstrzyknąć część treści Scriptsz _myPartialczęściowego widoku.

W jaki sposób mogę to zrobić?

tugberk
źródło
17
dla każdego, kto przyjdzie do tego później - istnieje pakiet nuget
Russ Cam
@RussCam powinieneś odpowiedzieć na to pytanie. +1 pakiet nuget rozwiązuje dokładny problem, jaki ma OP.
Carrie Kendall
1
@RussCam Pakiet NuGet nie jest rozwiązaniem, kod pakietu może być.
Maksim Vi.
8
@MaksimVi. napisałem pakiet nuget i nie mam zamiaru go usuwać, więc zamiast powtarzać kod ( usuwać bitbucket.org/forloop/forloop-htmlhelpers/src ) lub wiki ( bitbucket.org/forloop/forloop-htmlhelpers/wiki / Home ) tutaj, link do niego jako komentarz jest zgodny z duchem stackoverflow, IMO.
Russ Cam
Oto inne rozwiązanie, które wydaje się bardzo miłe: stackoverflow.com/questions/5355427/...
jkokorian

Odpowiedzi:

235

Sekcje nie działają w widokach częściowych i jest to zgodne z projektem. Możesz użyć niestandardowych pomocników, aby osiągnąć podobne zachowanie, ale szczerze mówiąc, obowiązkiem widoku jest uwzględnienie niezbędnych skryptów, a nie częściowej. Polecam skorzystanie z sekcji @scripts w głównym widoku, aby to zrobić i nie martw się stronami o skrypty.

Darin Dimitrov
źródło
445
Ale co, jeśli skrypt jest bardzo specyficzny dla częściowego? Czy logiczne jest definiowanie go w częściowym, a nie w widoku?
Jez
43
Dlaczego jest to zgodne z projektem?
Shimmy Weitzhandler
56
@Darin: Nie zgadzam się. Co z zasadą DRY? Nie lubię się powtarzać, nawet jeśli są to tylko odniesienia do skryptu.
fretje,
14
@fretje, każdy ma prawo wyrazić swoją opinię na ten temat. Szanuję twój. W mojej odpowiedzi wyraziłem moją i powiązałem z odpowiedzią, która pozwoli ci osiągnąć to zadanie. Ale podkreśliłem również, co poleciłbym i zrobić w tej sytuacji.
Darin Dimitrov,
33
oddelegowanie @JoshNoe i reszta - „widżet” (wyświetlanie + bogata interakcja) jest doskonałym przykładem częściowego widoku ściśle powiązanego z powiązanym javascript. Z założenia nie powinienem pisać dwóch instrukcji dołączania w różnych miejscach, aby uzyskać pełną funkcjonalność, ponieważ wyświetlacz nigdy nie będzie bez towarzyszącej interakcji, a interakcja nigdy nie pojawi się gdzie indziej.
drzaus
83

To dość popularne pytanie, więc opublikuję swoje rozwiązanie.
Miałem ten sam problem i chociaż nie jest idealny, myślę, że faktycznie działa całkiem dobrze i nie uzależnia częściowego od widoku.
Mój scenariusz polegał na tym, że akcja była dostępna sama w sobie, ale mogła być również osadzona w widoku - mapie google.

W moim _layoutmam:

@RenderSection("body_scripts", false)

Moim indexzdaniem mam:

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

Moim clientszdaniem mam (cała mapa i assoc. Html):

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

Mój Clients_Scriptswidok zawiera javascript do renderowania na stronie

W ten sposób mój skrypt jest izolowany i może być renderowany na stronie tam, gdzie jest to wymagane, przy czym body_scriptstag jest renderowany tylko przy pierwszym wystąpieniu, w którym znajdzie go silnik przeglądania razor.

To pozwala mi oddzielić wszystko - jest to rozwiązanie, które działa dla mnie całkiem dobrze, inni mogą mieć z tym problemy, ale naprawia dziurę „według projektu”.

Dan Richardson
źródło
2
Nie byłem tym, który cię głosował, ale powiem, że nie podoba mi się to rozwiązanie, ponieważ nadal oddziela on skrypty specyficzne dla widoku od samego widoku.
zmiażdżyć
3
20 innych osób i ja mam inne zdanie. Nadal możesz mieć skrypty bezpośrednio związane z widokiem, które znajdują się w osobnym pliku, to błąd programowy, jeśli nie dołączasz skryptu wraz z widokiem. Umieszczenie go w osobnym pliku oddziela interakcję od prezentacji i pozwala na wiele innych korzyści z bycia w osobnym pliku.
dan richardson
1
Masz całkowitą rację. Właściwie całkowicie się zgadzam i wolę tę metodę osobiście. Prawdziwym problemem jest dla mnie to, że moi koledzy zmagają się z tak wielką separacją. To jednak problem z domeną. Myślę, że ta metoda jest idealna, zwłaszcza po uwzględnieniu procesu kompilacji JavaScript. Będę nadal pracował nad edukowaniem moich kolegów w zakresie korzystania z tej metody i całkowicie ją popieram. Myślę jednak, że twoją odpowiedź można poprawić. Nie trzeba jednak wspominać o „20 osobach się zgadza”. To, że odpowiedź jest popularna, nie zawsze oznacza, że ​​jest słuszna. W tym przypadku ma rację.
zmiażdżyć
Bardzo prawda, i zawsze cieszę się z konstruktywnej informacji zwrotnej oraz zmieniam własny kod i odpowiadam, jeśli trzeba coś poprawić :)
dan richardson
1
To rozwiązanie ma tę dodatkową zaletę, że nadal jest w stanie wykonywać wszystkie czynności zgodne z MVC, których można oczekiwać w typowym widoku, na przykład możliwość kodowania przekazanego modelu w modelu JSON i generowania adresów URL przy użyciu adresu URL. Akcja. To podejście jest zatem eleganckim sposobem konfiguracji kontrolerów AngularJS - każdy widok częściowy może reprezentować osobny kontroler w module Angular. Tak czysty!
Dan
40

Z rozwiązań w tym wątku wymyśliłem następujące prawdopodobnie skomplikowane rozwiązanie, które pozwala opóźnić renderowanie html (także skryptów) w bloku używającym.

STOSOWANIE

Utwórz „sekcję”

  1. Typowy scenariusz: w widoku częściowym dołącz blok tylko jeden raz, bez względu na to, ile razy widok częściowy jest powtarzany na stronie:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. W widoku częściowym dołącz blok za każdym razem, gdy użyty zostanie fragment:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. W widoku częściowym dołącz blok tylko raz, bez względu na to, ile razy powtórzy się częściowy, ale później renderuj go konkretnie według nazwy 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>
    }

Renderuj „sekcje”

(tzn. wyświetl opóźnioną sekcję w widoku rodzica)

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

KOD

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://stackoverflow.com/a/14127332/1037948 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));
    }


}
drzaus
źródło
1
Wow, nawet zrozumienie kodu jest dla mnie skomplikowane, ale +1 za
wymyślenie
@RameezAhmedSayad masz rację - wracając tutaj, nawet jestem zdezorientowany tym, jak chciałem powiedzieć, jak go używać. Aktualizacja odpowiedzi ...
drzaus
I w celu dalszego wyjaśnienia - powodem są dwie „nazwy”, ponieważ jeśli chcesz, aby był renderowany tylko wtedy, gdy potrzebuje unikalnego klucza w parametrze isOnlyOne, ale tylko jeśli chcesz wyrenderować go w określonej lokalizacji według nazwy, czy podajesz identyfikator, w przeciwnym razie zostanie porzucony Html.RenderDelayed().
drzaus
Osobiście nie sądzę, aby istniała potrzeba kupowania kłopotów i korzystania z tego podejścia, sekcja w widokach częściowych po prostu nie jest potrzebna, ponieważ można ją wyeliminować, a skrypty można tam przejść bez definiowania sekcji. Jest tak, ponieważ jest to renderowane zewnętrznie i jeśli zobaczysz kod renderowanej strony, po prostu zauważysz, że kod widoku częściowego nie jest tam widoczny. Jeśli więc chodzi o lepszą organizację itp., Nie będzie to miało żadnego efektu.
Transcendent
@Transcendent „debata” już się rozpoczęła w komentarzach do przyjętej odpowiedzi stackoverflow.com/a/7556594/1037948
drzaus
16

Miałem ten problem i użyłem tej techniki.

To najlepsze rozwiązanie, jakie znalazłem, które jest bardzo elastyczne.

Również proszę głosować tutaj , aby dodać wsparcie dla zbiorczej deklaracji sekcji

ja chłopak
źródło
9

Jeśli masz uzasadnioną potrzebę uruchomienia niektórych jsz partial, oto jak możesz to zrobić, jQueryjest wymagane:

<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>
Serj Sagan
źródło
Próbowałem @drzaus, potrzebuje „SeeIfReady” lub nie działa.
Cacho Santa
8

Zgodnie z dyskretną zasadą nie jest wymagane, aby „_myPartial” wstrzykiwał zawartość bezpośrednio do sekcji skryptów. Możesz dodać te skrypty widoku częściowego do osobnego .jspliku i odwoływać się do nich w sekcji @scripts z widoku rodzica.

archil
źródło
10
Co by się stało, gdyby częściowy widok w ogóle nie był renderowany na stronie? Czy nadal odwołujemy się do tych plików .js nadrzędnych i powodują przeciążenie?
Murali Murugesan
5

W sposobie myślenia o sieci istnieje zasadnicza wada, szczególnie w przypadku korzystania z MVC. Wada polega na tym, że JavaScript jest poniekąd odpowiedzialnością widoku. Widok jest widokiem, JavaScript (behawioralny lub inny) to JavaScript. We wzorcu MVVM firmy Silverlight i WPF mamy do czynienia z „najpierw zobacz” lub „najpierw model”. W MVC zawsze powinniśmy starać się rozumować z punktu widzenia modelu, a JavaScript jest częścią tego modelu na wiele sposobów.

Sugerowałbym użycie wzorca AMD (ja sam lubię RequireJS ). Rozdziel swój JavaScript na moduły, zdefiniuj swoją funkcjonalność i podpisz się do kodu HTML od JavaScript zamiast polegać na widoku, aby załadować JavaScript. To wyczyści Twój kod, odseparuje Twoje obawy i ułatwi Ci życie za jednym zamachem.

Pan Baudin
źródło
Od około dwóch lub trzech miesięcy używam RequireJS i nie sądzę, żebym kiedykolwiek opracował inną aplikację internetową bez RequireJS.
tugberk
6
JavaScript może być również odpowiedzialny za wyświetlanie.
Kelmen
1
Użycie wzorca AMD jest dobrym pomysłem, ale nie zgadzam się z twoim twierdzeniem, że JavaScript jest częścią modelu. Często definiuje zachowanie widoku, szczególnie w połączeniu z czymś takim jak nokaut. Zrzuć reprezentację JSON swojego modelu do widoku JavaScript. Osobiście używam po prostu zamknięć, niestandardowej „przestrzeni nazw” na windowobiekcie i dołączam skrypty biblioteczne przed częściami.
zmiażdżyć
Myślę, że tutaj jest nieporozumienie. Tworząc większość aplikacji internetowych, tworzymy dwie aplikacje: jedną działającą na serwerze, a drugą na kliencie. Z punktu widzenia serwera wszystko, co wysyłasz do przeglądarki, to „widok”. W tym sensie JavaScript jest częścią widoku. Z punktu widzenia aplikacji klienckiej czysty HTML to widok, a JS to kod, który przypomina M i C w terminach MVC serwera. Myślę, że właśnie dlatego ludzie się tutaj nie zgadzają.
TheAgent
3

Celem PO jest to, że chce zdefiniować wbudowane skrypty w swoim widoku częściowym, który zakładam, że ten skrypt jest specyficzny tylko dla tego widoku częściowego i aby ten blok był zawarty w jego sekcji skryptu.

Rozumiem, że chce, aby ten widok częściowy był samodzielny. Pomysł jest podobny do komponentów podczas korzystania z Angulara.

Moim sposobem byłoby po prostu zachować skrypty w częściowym widoku takim, jaki jest. Problem polega na tym, że podczas wywoływania widoku częściowego może on tam wykonać skrypt przed wszystkimi innymi skryptami (zwykle dodawanymi na dole strony układu). W takim przypadku po prostu skrypt Częściowego widoku czeka na pozostałe skrypty. Można to zrobić na kilka sposobów. Najprostszym, z którego korzystałem wcześniej, jest użycie zdarzenia body.

W moim układzie miałbym na dole coś takiego:

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

Następnie w częściowym widoku (na dole):

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

      // .. do your thing here

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

Innym rozwiązaniem jest użycie stosu do wypchnięcia wszystkich skryptów i wywołania każdego z nich na końcu. Innym rozwiązaniem, jak już wspomniano, jest wzorzec RequireJS / AMD, który również działa naprawdę dobrze.

alans
źródło
2

Pierwszym rozwiązaniem, jakie mogę wymyślić, jest użycie narzędzia ViewBag do przechowywania wartości, które należy renderować.

Po pierwsze, nigdy nie próbowałem, czy to działa z częściowego widoku, ale powinno imo.

Iridio
źródło
Po prostu próbowałem; niestety to nie działa (utworzyłem ViewBag.RenderScripts = new List<string>();u góry strony głównej, potem zadzwoniłem @Html.Partial("_CreateUpdatePartial",Model,ViewData), a potem umieściłem @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. W widoku częściowym umieściłem @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan
2

Nie trzeba używać sekcji w widoku częściowym.

Uwzględnij w częściowym widoku. Wykonuje funkcję po załadowaniu jQuery. Możesz zmienić klauzulę de condition dla swojego kodu.

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

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

Julio Spader

Julio Spader
źródło
2

Możesz użyć tych metod rozszerzenia : (Zapisz jako 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);
                }
            }
        }
    }
}

Użyj w ten sposób:

Przykład częściowy: (_MyPartial.cshtml) Umieść HTML w if, a JS w else.

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

W pliku _Layout.cshtml lub w dowolnym miejscu, w którym mają być renderowane skrypty z części, umieść następujące (raz): Wyrenderuje tylko javascript wszystkich części na bieżącej stronie w tej lokalizacji.

@{ Html.RenderPartialScripts(); }

Następnie, aby użyć częściowego, po prostu zrób to: Wyrenderuje tylko HTML w tej lokalizacji.

@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}
Łomak
źródło
1

Istnieje sposób wstawiania sekcji w widokach częściowych, choć nie jest to ładne. Musisz mieć dostęp do dwóch zmiennych z widoku nadrzędnego. Ponieważ częścią twojego częściowego widoku jest utworzenie tej sekcji, sensowne jest wymaganie tych zmiennych.

Oto jak to wygląda wstawić sekcję w widoku częściowym:

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

A na stronie wstawianie widoku częściowego ...

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

Tej techniki można także użyć do programowego zdefiniowania zawartości sekcji w dowolnej klasie.

Cieszyć się!

Pluton
źródło
1
Czy możesz i link do w pełni działającego projektu?
Ehsan Zargar Ershadi
1

Pomysł Plutona w ładniejszy sposób:

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

Widoki \ web.config:

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

Widok:

@PartialWithScripts("_BackendSearchForm")

Częściowe (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

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

Strona układu:

@RenderSection("scripts", required: false)
PaulSanS
źródło
1

To działało dla mnie, umożliwiając mi wspólną lokalizację javascript i HTML dla częściowego widoku w tym samym pliku. Pomaga procesowi myślenia zobaczyć HTML i powiązaną część w tym samym pliku widoku częściowego.


W widoku używającym widoku częściowego o nazwie „_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" } })

 }

W pliku częściowego widoku

@model SomeType

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

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}
purvin
źródło
0

Rozwiązałem tę zupełnie inną trasę (ponieważ spieszyłem się i nie chciałem wdrożyć nowego HtmlHelper):

Owinęłam mój widok częściowy w dużą instrukcję if-else:

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

Następnie dwukrotnie wywołałem Partial z niestandardowym ViewData:

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

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}
Rick Love
źródło
Z pewnością cała idea polega na tym, że konsument częściowego widoku nie powinien wiedzieć, że musi zawierać skrypty, to jest pewien problem? W przeciwnym razie możesz równie dobrze powiedzieć @Html.Partial("MyPartialViewScripts")
Dan Richardson
Nie, chodzi o to, aby umożliwić zdefiniowanie skryptów w tym samym dokumencie co HTML, ale zgadzam się, że to nie jest idealne.
Rick Love
0

Miałem podobny problem, gdy miałem stronę wzorcową w następujący sposób:

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

...

@Html.Partial("_Charts", Model)

ale widok częściowy zależał od niektórych skryptów JavaScript w sekcji Skrypty. Rozwiązałem go, kodując widok częściowy jako JSON, ładując go do zmiennej JavaScript, a następnie używając tego do wypełnienia div, więc:

@{
    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>
John M.
źródło
IMO powinieneś był rozwiązać ten problem, przenosząc JS do osobnego pliku.
Worthy7
0

wybieralnie, możesz użyć folderu / index.cshtml jako strony głównej, a następnie dodać skrypty sekcji. Następnie w swoim układzie masz:

@RenderSection("scripts", required: false) 

i twój index.cshtml:

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

i będzie działać na wszystkich twoich częściowych przeglądach. To działa dla mnie

RogerEdward
źródło
0

Za pomocą Mvc Core możesz stworzyć schludny TagHelper, scriptsjak pokazano poniżej. Można to łatwo przekształcić w sectiontag, w którym również nadajesz nazwę (lub nazwa pochodzi od typu pochodnego). Należy pamiętać, że należy skonfigurować wstrzykiwanie zależności IHttpContextAccessor.

Podczas dodawania skryptów (np. Częściowo)

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

Podczas wysyłania skryptów (np. W pliku układu)

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

Kod

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()));
            }
        }
    }
BlackjacketMack
źródło
-1

Cóż, myślę, że inne plakaty zapewniły ci sposób na bezpośrednie dołączenie @section w swojej częściowej (za pomocą zewnętrznych pomocników HTML).

Ale sądzę, że jeśli twój skrypt jest ściśle powiązany z częściowym, po prostu umieść javascript bezpośrednio w wewnętrznym <script>znaczniku wewnątrz częściowego i załatw go (bądź ostrożny z duplikacją skryptu, jeśli zamierzasz używać częściowego więcej niż raz w jednym widoku);

CShark
źródło
1
Nie jest to zwykle idealne, ponieważ ładowanie jQuery itp. Odbywa się po skryptach wbudowanych ... ale w przypadku kodu natywnego wydaje się, że jest w porządku.
Worthy7
-3

zakładamy, że masz częściowy widok o nazwie _contact.cshtml, twój kontakt może być osobą prawną (imię) lub fizyczną (imię, nazwisko). twój widok powinien zadbać o to, co jest renderowane i co można uzyskać za pomocą javascript. dlatego może być potrzebne opóźnione renderowanie i widok wewnętrzny JS.

jedyny sposób, w jaki myślę, jak można to pominąć, to stworzenie dyskretnego sposobu radzenia sobie z takimi obawami dotyczącymi interfejsu użytkownika.

zauważ także, że MVC 6 będzie miał tak zwany komponent widokowy, nawet futures MVC miały podobne rzeczy, a Telerik również obsługuje takie rzeczy ...

użytkownik4298890
źródło
1
3 lata spóźnienia i nie sądzę, żeby to w ogóle odpowiadało na pytanie? Co próbujesz tu powiedzieć? Odpowiedź 3 lata później na spekulacyjne cechy przyszłych technologii nie jest tak naprawdę odpowiedzią ani szczególnie pomocną
dan richardson
-3

Właśnie dodałem ten kod do mojego częściowego widoku i rozwiązałem problem, choć niezbyt czysty, działa. Musisz się upewnić, że identyfikatory obiektów, które renderujesz.

<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>
Luis
źródło
-5

Miałem podobny problem rozwiązałem to z tym:

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

To ładny sposób na zastrzyk.

Pouria Jafari
źródło