Sitecore Headless and JSS (Part 1)

November 2, 2017

Blog | Development | Sitecore Headless and JSS (Part 1)
sitecore embraces headless

The enterprise content management system we have all come to know and love has lost its head, and that’s actually a good thing!  Headless within Sitecore isn’t something totally new to us, having done a few projects completely headless or semi headless (de-coupled components with the majority of the layout driven from Sitecore).

Having written about some of the pros and cons of headless in the past, the introduction of Sitecore JSS (JavaScript Services) may have been the last big push needed to truly embrace the headless architecture.  Until now, the easiest ways to implement headless were to either use:

  • Sitecore ItemApi
    • As it sounds, it’s simply a REST based CRUD layer, lacking personalization, analytics etc.
  • Create your own API using something like WebAPI
    • This could achieve an element of personalization and tracking but you needed to write for, and account for, any of the out-of-box enterprise features Sitecore has already baked in.

Proof of Concept

So, with all new concepts that come out of the Sitecore kiln, we, of course, wanted to kick the tires and JSS was no different.  What I wanted to do for starters is see how painless the initial install is to get JSS going on a Sitecore 9 server.  Given the available downloads, installed thru the Installation Wizard, it took minutes.  My next step was to find some documentation and start to figure out how to consume this.  After reading through it a ways, I came to realize it has very strong support for React.js, something I am pretty novice in so I opted to go down the path less traveled and mock out a proof of concept with Angular.js (knowing full well there are a handful of React samples readily available).  Below is a few of files in my very basic proof-of-concept in Angular (also available on github).

The Code

Main Index.html code:

<html ng-app="app">
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-sanitize.min.js"></script>
    <script src="/app.js"></script>
    <style>
      .fullwidth{ width: 1200px;  margin: auto; background: #eef; }
      .small, label{ font-size: 12px; color: #ccc; }
      a.small{ color: #777; }
      img{ max-width: 100%; }
      .page{ padding: 5px; border: 1px solid grey; }
    </style>
  </head>
  <body>
    <div class="fullwidth">
      <div class="nav"><a href="#">Home</a>|<a href="#about">About</a></div>
      <!-- Grabs Home Page thru JSS call -->
      <div ng-controller="HomeCtrl as homeCtrl" class="page">
        <ul class="small">
          <li> - Debug - </li>
          <li>PageEditing:<span ng-bind="homeCtrl.model.context.pageEditing"></span></li>
          <li>Domain:<span ng-bind="homeCtrl.model.context.user.domain"></span></li>
          <li>User:<span ng-bind="homeCtrl.model.context.user.name"></span></li>
          <li>Browser:<span ng-bind="homeCtrl.model.context.sessionInfo.deviceInfo.browser"></span></li>
        </ul>
        <div ng-repeat="(key, field) in homeCtrl.model.fields track by $index">
          <label ng-bind="key" for="field_{{$index}}"></label>
          <a class="small" name="field_{{$index}}" ng-hide="!homeCtrl.model.context.pageEditing" ng-click="field.editing = !field.editing">Edit/Show</a>
          <span ng-bind-html="field.value" ng-hide="!!field.editing"></span>
          <span ng-bind="field.editable" ng-hide="!field.editing"></span>
        </div>
      </div>
      <!-- Grabs About Us page thru JSS call -->
      <div ng-controller="AboutCtrl as aboutCtrl" class="page">
        <div ng-repeat="(key, field) in aboutCtrl.model.fields track by $index">
          <label ng-bind="key" for="field_{{$index}}"></label>
          <a class="small" name="field_{{$index}}" ng-hide="!aboutCtrl.model.context.pageEditing" ng-click="field.editing = !field.editing">Edit/Show</a>
          <img ng-if="key == 'Hero'" ng-src="{{field.value.src}}" ng-hide="!!field.editing"></img>
          <span ng-if="key != 'Hero'" ng-bind-html="field.value" ng-hide="!!field.editing"></span>
          <span ng-if="key != 'Hero'" ng-bind="field.editable" ng-hide="!field.editing"></span>
        </div>
      </div>
    </div>
  </body>
</html>

Main App.js code (Angular):

(function () {
  'use strict';
  var app = angular.module("app", ['ngSanitize']); //startup

  //Home Controller
  function HomeCtrl($scope, $log, $http)
  {
    $log.info("HomeCtrl loading...")
    var me = this;
    $http.get('http://yourSC9install/sitecore/layoutsvc/render/jss?item=/')
    .then(function(response) {
        me.model = response.data;
    });
  }

  //About Controller
  function AboutCtrl($scope, $log, $http)
  {
    $log.info("AboutCtrl loading...")
    var me = this;
    $http.get('http://yourSC9install/sitecore/layoutsvc/render/jss?item=/About%20Us')
    .then(function(response) {
        me.model = response.data;
    });
  }

  HomeCtrl.$inject = ["$scope", "$log", "$http"];
  AboutCtrl.$inject = ["$scope", "$log", "$http"];
  app.controller("HomeCtrl", HomeCtrl);
  app.controller("AboutCtrl", AboutCtrl);
})();

A pipeline to augment some of the returned context information:

using System.Web;
using Sitecore.JavaScriptServices.Configuration;
using Sitecore.JavaScriptServices.ViewEngine.LayoutService.Pipelines.GetLayoutServiceContext;

namespace JSS.Pipelines
{
    public class VisitorInfoPipeline : JssGetLayoutServiceContextProcessor
    {
        public VisitorInfoPipeline(IConfigurationResolver configurationResolver) : base(configurationResolver)
        {
        }

        protected override void DoProcess(Sitecore.LayoutService.ItemRendering.Pipelines.GetLayoutServiceContext.GetLayoutServiceContextArgs args, 
		    AppConfiguration application)
        {
            args.ContextData.Add("sessionInfo", new
            {
                isAnonymous = !Sitecore.Context.User.IsAuthenticated,
                deviceInfo = HttpContext.Current.Request.Browser
            });
            

        }
    }
}

And the patch needed in order to invoke the pipeline above:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <group groupName="layoutService">
        <pipelines>
          <getLayoutServiceContext>
            <processor type="JSS.Pipelines.VisitorInfoPipeline, JSS" resolve="true">
              <AllowedConfigurations hint="list">
                <!-- Unless you change the Layout Service config used by JSS, this should always be present. -->
                <jss>jss</jss>
              </AllowedConfigurations>
              <Applications hint="list">
                <!-- Restrict the JSS apps for which this processor will execute. -->
              </Applications>
              <Routes hint="list">
                <!-- Restrict the route items for which this processor will execute. IDs, item paths, and wildcard item paths are allowed. -->
              </Routes>
            </processor>
          </getLayoutServiceContext>
        </pipelines>
      </group>
    </pipelines>
  </sitecore>
</configuration>

The final product (nothing too fancy, I know):

Summary

With all the ease and beauty of pipelines, personalization, and most if not all the native bells and whistles that make Sitecore so great, the future of JSS is very promising.  It may or may not be the silver bullet needed to execute every aspect of a Headless build, but it is a leap in the right direction, not to mention, already being well embraced by the Sitecore Community.  As I continue down this beautiful rabbit-hole called JSS, I will continue to blog about it, so stay-tuned for Part 2!

Steve VandenBush

Technical Lead
Tags
  • Angular.js
  • JSS
  • Sitecore
subscribe to GeekHive's newsletter

Never miss out on a Sitecore update again!

Our newsletter features a blog roundup of our top posts so you can stay up to date with industry trends, tutorials, and best practices.

Recent Work

Check out what else we've been working on