Does Sitecore offer anything extra to call your code using AJAX, comparing to standard ASP.Net website? The quick answer is yes and no. Under the Sitecore’s hood there is ASP.Net MVC app, so basically you can use typical ASP.Net approach to call AJAX method. There are some helpers though, which make the implementation a little easier.
Creating Model View Controller
Let’s start with simple View Rendering:
In the NewsFeed.cshtml file for this view let’s add container for lazy loaded content and some input fields which will be used later for POST request. One addition worth mentioning, is data-context attribute where we store ID of current item. This is important cause we need to somehow pass it to AJAX request to keep Sitecore context.
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> |
Model for this sample is pretty straight-forward. In whole project we use Glass.Mapper’s 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; } } } |
We also create additional NewsFeedList.cshtml view, which will render the model created in previous step. For simplicity we just display field content without Experience Editor support.
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> } |
Next, let’s create controller which tie all together. We create two methods, one will render news articles based on the current context item using the view created above. Second will create new article and return status of this operation. Important thing in this step: we inherit from GlassController which gives us access to ContextItem property.
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); } } } |
Adding AJAX calls
Now, there is an optional step: we register route for our controller using standard .Net MVC method. We do it using Sitecore pipeline. This step is optional cause Sitecore out-of-the-box creates routes available under url /api/sitecore/{controller}/{action} for all your controllers actions.
Why should we register our custom route anyway? Yeah, /api/sitecore/{controller}/{action} route is used mainly for SPEAK framework and it’s not official, documented way to access your controllers actions in Sitecore. It requires that you have /App_Config/Include/Sitecore.Speak.Mvc.config file enabled on both CM and CD servers (for Sitecore 8.1 and earlier this file should be disabled on CD servers according to Sitecore guidance, for Sitecore 8.2 it should be enabled on both CD and CM). If you can live with it, next two steps are not required, also you need to remember to use /api/sitecore/ prefix in your .js files when calling your server’s methods.
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" }); } } } |
If we created pipeline in previous step, we need to create Sitecore configuration patch, so our route registration code will be called before Sitecore standard routes registration.
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> |
Finally let’s write simple javascript and call our controller actions using jQuery. In first step we added data-context attribute to one of the view’s element, now we read it and add to our request. If we add it as ?sc_itemid= parameter Sitecore will automatically set ContextItem in our controller. In “loadNews” function we append html returned from server to the container. In “insertNewsItem” function we read data from input fields pass it to the server as JSON object and receive object in response.
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); |
If debug our script we see that everything works as expected. We pass current page context (Item ID) and receive dynamic Html or JSON object in our server’s response.