Czy Sitecore oferuje coś ekstra, w porównaniu do standardowej aplikacji ASP.Net, żeby obsłużyć zawołania AJAX? Najszybsza odpowiedź to tak i nie 🙂 . Strona zbudowana w Sitecore to pod spodem aplikacja ASP.Net MVC, więc żeby zawołać metodę przez AJAX możemy użyć typowego podejścia z ASP.Net. Aczkolwiek, Sitecore oferuje kilka pomocniczych metod, które powodują że implementacja jest trochę prostsza.
Tworzenie modelu, widoku i kontrolera
Możemy zacząć od stworzenia prostego View Rendering:
W pliku NewsFeed.cshtml dodaliśmy kontener, dla treści ładowanych dynamicznie i kilku pól, które będą użyte później w metodzie POST. Warto wspomnieć o atrybucie data-context, w którym przechowujemy ID bieżącego Itemu. To ważne, ponieważ musimy przekazać jakoś bieżący Item do metody AJAX, tak żeby nie zgubić kontekstu w Sitecore.
1 2 3 4 5 6 7 8 9 10 11 |
@model Sitecore.Mvc.Presentation.RenderingModel <div data-context="@Model.PageItem.ID.ToString()"> <div id="news-container"> @*lazy-loaded news container*@ </div> <input type="text" id="news-title" /> <input type="text" id="news-content" /> <input type="button" id="news-add" value="Add News" /> </div> |
Tworzymy też prosty Model. W całym projekcie używamy Glass.Mapper, który dodaje obsługę strongly typed objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using Glass.Mapper.Sc.Configuration.Attributes; namespace Sitecore.Feature.News.Models { [SitecoreType(TemplateId = Templates.NewsfeedArticle.ID)] public class NewsItem : IGlassBase { [SitecoreField(Templates.NewsfeedArticle.Fields.Title_FieldName)] public string Title { get; set; } [SitecoreField(Templates.NewsfeedArticle.Fields.Content_FieldName)] public string Content { get; set; } } } |
Musimy napisać także dodatkowy widok NewsFeedList.cshtml, który wyrenderuje model stworzony w poprzednim kroku. Żeby ułatwić przykład, tylko wyświetlamy wartości pól, bez wsparcia Experience Editor.
1 2 3 4 5 6 7 8 9 10 11 |
@model IEnumerable<Sitecore.Feature.News.Models.NewsItem> @foreach (var item in Model) { <div class="news-item"> <h2>@item.Title</h2> <p> @item.Content </p> </div> } |
Następnie dodajemy kontroler, który powiąże wszystkie części. Tworzymy dwie motody: pierwsza wyrenderuje artykuły, bazując na bieżącym Itemie, używając widoku stworzonego wyżej. Druga jest odpowiedzialna za dodanie nowego artykułu i zwrócenie statusu tej operacji. Ważne w tym kroku, to dziedziczenie z klasy bazowej GlassController, która dodaje łatwy dostęp do item’u z kontekstu. poprzez właściwość ContextItem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using Glass.Mapper.Sc.Web.Mvc; using Sitecore.Feature.News.Repositories; using System.Web.Mvc; using Sitecore.Feature.News.Models; namespace Sitecore.Feature.News.Controllers { public class NewsController : GlassController { private readonly INewsRepository _newsRepository; public NewsController() : this(new NewsRepository()) { } public NewsController(INewsRepository newsRepository) { _newsRepository = newsRepository; } [HttpGet] public ActionResult NewsList() { var items = _newsRepository.LoadNews(ContextItem); return View("NewsFeedList", items); } [HttpPost] public ActionResult InsertNews(NewsItem item) { var result = _newsRepository.AddNews(item, ContextItem); return Json(result); } } } |
Dodanie zawołań AJAX
Teraz opcjonalnie, rejestrujemy routing dla naszego kontolera, za pomocą standardowej metody .Net MVC. Używamy do tego pipeline’a Sitecore. Krok ten jest opcjonalny, ponieważ Sitecore w standardzie tworzy routingi dla każdej akcji we wszystkich kontrolerach. Są one dostępne pod adresem: /api/sitecore/{controller}/{action}.
Dlaczego więc powinniśmy stworzyć nasz własny routing? Mechanizm tworzący routing /api/sitecore/{controller}/{action} jest używany głównie przez framework SPEAK i nie jest oficjalną, udokumentowaną metodą na dostęp do akcji z kontrolera w Sitecore. Ta metoda wymaga żeby plik /App_Config/Include/Sitecore.Speak.Mvc.config był dodany na wszystkich serwerach CD i CM (wg wytycznych Sitecore’a ten plik powinien być wyłączony na serwerach CD w wersji 8.1 i wcześniejszych, w Sitecore 8.2 plik może być włączony na CD i CM). Jeśli to nie przeszkadza, następne dwa kroki opisane w poradniku są opcjonalne, trzeba jedynie pamiętać żeby użyć prefiksu /api/sitecore/ w plikach .js podczas dostępu do metod serwera.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace Sitecore.Feature.News.Pipelines { using System.Web.Mvc; using System.Web.Routing; using Sitecore.Pipelines; public class RegisterWebApiRoutes { public void Process(PipelineArgs args) { RouteTable.Routes.MapRoute("Feature.News.Api", "api/news/{action}", new { controller = "News" }); } } } |
Jeśli w poprzednim kroku stworzyliśmy pipeline, musimy go zarejestrować dodając patch do konfiguracji Sitecore, tak żeby kod rejestrujący nasz routing odpalił się przed standardowym kodem Sitecore’a.
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <initialize> <processor type="Sitecore.Feature.News.Pipelines.RegisterWebApiRoutes, Sitecore.Feature.News" patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" /> </initialize> </pipelines> </sitecore> </configuration> |
Wreszcie jesteśmy gotowi żeby napisać prosty kod javascript, który zawoła nasze akcje za pomocą jQuery. W pierwszym kroku do jednego z elementów widoku dodaliśmy atrybut data-context, teraz pora przekazać go do naszego requesta. Jeśli dodamy go jako parametr ?sc_itemid=, Sitecore automatycznie ustawi ContextItem w naszej klasie kontrolera. w funkcji “loadNews” dodajemy html zwracany przez serwer do kontenera. W funkcji “insertNewsItem” odczytujemy wartości z pól formularza, przekazujemy je na serwer, jako obiekt JSON, a w odpowiedzi dostajemy obiekt ze statusem operacji.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
(function ($) { //load news list function loadNews() { var container = $('#news-container'); var parameters = { sc_itemid: container.parent().data('context') }; $.get({ url: "/api/News/NewsList", data: $.param(parameters) }).done(function (html) { container.append(html); }) } //insert news article function insertNewsItem(component) { var parentContainer = $(component).parent(); var contextId = parentContainer.data('context'); var parameters = { title: parentContainer.find('#news-title').val(), content: parentContainer.find('#news-content').val() }; $.post( { url: "/api/News/InsertNews?sc_itemid=" + contextId, data: parameters }).done(function (result) { parentContainer.append(result); }); } //on page load $(function () { $('#news-add').on('click', function () { insertNewsItem(this); }); loadNews(); }); })(jQuery); |
Podczas debugowania naszego skryptu, widzimy że wszystko działa tak jak założyliśmy. Przekazujemy kontekst naszej strony (ID Itemu) dostajemy dynamiczny Html, lub obiekt JSON w odpowiedzi z serwera.