Blog
Sitecore SPEAK 3 - Creating an application

Sitecore SPEAK 3 - Creating an application

At the end of last year I wrote a post on A first look at Sitecore SPEAK 3 which gave an overview of what Speak is, and the large architecture change that has happened between Speak 1/2 to 3.

In this post I'm going to share my experience on how to set up a Speak 3 application with Angular.

Step 1 - Creating the Angular project

To start your going to need a few things installed:

  • An IDE - I'm using VS Code
  • NodeJs - This is to get access to node package manager and to run your application in debug mode
  • Angular

If you don't already have Node and Angular installed, I suggest going through Angular's quick start guide. If your also new to Angular I suggest going through their Tour of Heroes tutorial first. This will give you a good understanding of how Angular applications are built and some knowledge around a few key files.

One you've got everything installed, create a new angular project from the command line.

1ng new app-name

At this point you could try manually installing the various modules Sitecore provide, covering things like common components, logout functionality etc. However I personally found this a bit awkward. Unless you know what your doing your probably going to run into issues such as compatibility between the latest version of Angular and the Sitecore components (at time of writing Angular is on version 5 but Speak 3 only supports Angular 4).

Instead I would recommend downloading the sample application from https://dev.sitecore.net/Downloads/Sitecore_SPEAK/3/Sitecore_SPEAK_3.aspx and then copy over the .npmrc and package.json file to your solution.

By including these files, the .npmrc file will add a reference to Sitecores package repository and the package.json file will make sure the right packages and versions will be installed. Use npm to install the packages.

1npm install

Next we need to update a couple of files in the application to reference some Sitecore specific bits. This is explained in Sitecores documentation, in my examples though I've also included referencing some modules that you are likely to use.

app.module.ts

The app module file defines the modules that are going to be used in the application. Here we need to add the references to the Sitecore modules.

1import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ScAccountInformationModule } from '@speak/ng-bcl/account-information'; import { ScActionBarModule } from '@speak/ng-bcl/action-bar'; import { ScApplicationHeaderModule } from '@speak/ng-bcl/application-header'; import { ScButtonModule } from '@speak/ng-bcl/button'; import { ScGlobalHeaderModule } from '@speak/ng-bcl/global-header'; import { ScGlobalLogoModule } from '@speak/ng-bcl/global-logo'; import { ScIconModule } from '@speak/ng-bcl/icon'; import { ScMenuCategory, ScMenuItem, ScMenuItemLink, ScMenuModule } from '@speak/ng-bcl/menu'; import { ScTableModule } from '@speak/ng-bcl/table'; import { ScPageModule } from '@speak/ng-bcl/page'; import { CONTEXT, DICTIONARY } from '@speak/ng-bcl'; import { NgScModule } from '@speak/ng-sc'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, ScAccountInformationModule, ScActionBarModule, ScApplicationHeaderModule, ScButtonModule, ScGlobalHeaderModule, ScGlobalLogoModule, ScIconModule, ScPageModule, ScMenuModule, ScTableModule, NgScModule.forRoot({ contextToken: CONTEXT, // Provide Sitecore context for SPEAK 3 Components (optional) dictionaryToken: DICTIONARY, // Provide translations for SPEAK 3 Components (optional) translateItemId: '0C979B7C-077E-4E99-9B15-B49592405891', // ItemId where your application stores translation items (optional) authItemId: '1BC79B7C-012E-4E9C-9B15-B4959B123653' // ItemId where your application stores user access authorization (optional) }) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

app.component.ts

The component file needs updating to call init on the ngScService.

1import { Component, OnInit } from '@angular/core'; import { NgScService } from '@speak/ng-sc'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { constructor( private ngScService: NgScService ) {} ngOnInit() { this.ngScService.init(); } }

.angular-cli.json

In the angular-cli.json file you will see a styles section which references the main css file in the solution. Here you will need to add an additional reference to Sitecores css file.

1../node_modules/@speak/styling/dist/styles/sitecore.css

Launch

You can now launch your application from the command line and see the default start screen.

1ng serve --open

Step 2 - Building your application

It's not time to start building your application. If you don't know Angular I suggest going through a couple of tutorials, and go from there. I'm not going to go into any details about how Angular apps are and should be written, but I am going to go through a few of the Sitecore controls needed to make an application that fit's the Sitecore admin.

Example Page

To make this page first I cleared out everything from app.component.html and started adding some Sitecore components. Normally you would start generating your own components to represent things like pages, but for the purposes of the example I placing everything in the one file.

To start I have a sc-page containing a header. This comes out of Sitecores demo application and will give you the standard bar that sites at the top of the Sitecore admin, informing users where they are.

1<div> <a href="#"></a> <!-- AccountInformation gets accountName and accountImageUrl automatically from Sitecore context which is configured in AppModule --> </div>

To create the menu I'm using an sc-menu. Notice how some items are marked as active.

1<aside> <a>Menu item 1</a> <a>Menu item 2</a> <a>Menu item 3</a> <a>Menu item 4</a> </aside>

Lastly to create the main content of the page I'm using a scPageAppHeader, scPageContent and an scTable for the table.

1<div> </div> <article> <table> <thead> <tr> <th>Name</th> <th>Status</th> <th>Created by</th> <th>Created data</th> </tr> </thead> <tbody> <tr> <td>Lorem</td> <td>Active</td> <td>sitecore\admin</td> <td>Jan 20, 2018</td> </tr> <tr> <td>Ipsum</td> <td>Active</td> <td>sitecore\admin</td> <td>Jan 20, 2018</td> </tr> <tr> <td>Foop</td> <td>Inactive</td> <td>sitecore\admin</td> <td>Jan 22, 2018</td> </tr> </tbody> </table> </article>

The complete code looks like this:

1<div> <a href="#"></a> <!-- AccountInformation gets accountName and accountImageUrl automatically from Sitecore context which is configured in AppModule --> </div> <aside> <a>Menu item 1</a> <a>Menu item 2</a> <a>Menu item 3</a> <a>Menu item 4</a> </aside> <div> </div> <article> <table> <thead> <tr> <th>Name</th> <th>Status</th> <th>Created by</th> <th>Created data</th> </tr> </thead> <tbody> <tr> <td>Lorem</td> <td>Active</td> <td>sitecore\admin</td> <td>Jan 20, 2018</td> </tr> <tr> <td>Ipsum</td> <td>Active</td> <td>sitecore\admin</td> <td>Jan 20, 2018</td> </tr> <tr> <td>Foop</td> <td>Inactive</td> <td>sitecore\admin</td> <td>Jan 22, 2018</td> </tr> </tbody> </table> </article>

To avoid some build errors later on we also need to update the app.components.ts file (think of this as a code behind file), to have an additional property and service.

1import { Component, OnInit } from '@angular/core'; import { NgScService } from '@speak/ng-sc'; import { SciLogoutService } from '@speak/ng-sc/logout'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { isNavigationShown = false; constructor( private ngScService: NgScService, public logoutService: SciLogoutService ) {} ngOnInit() { this.ngScService.init(); } }

How to find more components

Unfortunately the Sitecore documentation doesn't currently contain a list of what's available. However if you look in your node_modules folder there is a change log containing information on each component here \node_modules\@speak\ng-bcl\CHANGELOG.md.

Step 3 - Publishing the application

Once you've built the application you need to publish it and copy it into Sitecore.

There are some differences in the way a Speak 3 Angular application needs to work which differ from the normal way an Angular application runs. Among others these include having an index.apsx page rather than an index.html and the application not being located in the root of a site. You can read more about this in Sitecores documentation. The good news though is Sitecore have provided a post build step to sort this out for you.

If you copied the package.json file at the beginning this will already be set up, one thing you do need to do though is update the base location to be where your application is going to live.

Once this is done you can run a build.

1npm run-script build

Note this is using npm to run the build script from the packages.json file rather than doing a regular ng build from Angulars CLI.

If all succeeds your dist folder will now contain a compiled version of the application.

Copy these files into the destination folder in your Sitecore site. For me this is \sitecore\shell\client\Applications\Speak-Example. You should now be able to log in and view your application.

Notice the logout button now functions and the current user is displayed in the top right. The menu sections are also collapsible, but other than that our application doesn't actually do anything.

Moving on from this there's lot's more to cover on building out the functionality in the application and you may have also noticed in the app.module.ts file a reference for translations which I never created, but this should be enough to get anyone started with building an Angular Speak 3 project and then publishing it into Sitecore.

Related Links

Speak 3 Official documentation
Speak 3 Downloads

Bundling with Gulp in TeamCity

Bundling with Gulp in TeamCity

Like most, our front end developers write CSS in LESS or SASS and then use Gulp to compile the result. This is great but up until recently both the compiled and source style files would end up in our source control repository.

While this isn't a major issue it was more of an annoyance factor when doing a merge that the compiled file would need to be merged as well as the source files. Any conflicts in bundled/minified files also can become problematic to solve. As well as this it also just seems wrong to have both files in a repo, effectively duplicating the file. After all we wouldn't put compiled dll's into a repo with their source.

Our solution was to get the build server to start running the gulp tasks to produce the bundled files.

Step 1 - Install Node on the build server

To start we need NodeJS installed on the build server. This allows extensions to be installed via NPM (Node Package Manager), it's a similar thing to NuGet,

Step 2 - Install the TeamCity plugin for NodeJS

To add built steps for Node and Gulp we need to install a plugin to make them available. Lucking there is one that does such a thing here https://github.com/jonnyzzz/TeamCity.Node

The actual build of the plugin you can download from Jetbrains team city here https://teamcity.jetbrains.com/viewType.html?buildTypeId=bt434. Just login as guest and then download the latest zip from the artifacts of the last build.

To install the plug you need to copy the zip to Team City's plugin folder. For me this was C:\ProgramData\JetBrains\TeamCity\plugins, if your having trouble finding your's just go to Administration > Global Settings in Team City and it will tell you the data directory. The plugin folder will be in there.

Restart the TeamCity server and the plugin should now show under Administration > Plugins List

Step 3 - Add a NPM Setup build step

The NPM step with a command of install will pick up dependencies and get the files.

Step 4 - Add a Gulp build step

 

In your gulp step add the path to the gulp file and the tasks in your gulp file that need to be run. I'm using a gulp file that our front end devs had already created for the solution that contained as task for bundling css and another for bundling js.

Step 5 - Including bundled files in a MSBuild

As the bundled files are no longer included in our Visual Studio solution it also means that they arn't included in the set of files which will be included in a publish when MSBuild runs.

To overcome this update the .csproj file with a Target with BeforeTargets set to BeforeBuild and list your bundled files as content. In my example I'm included the whole Content\bundles folder

1<Target Name="BundlesBeforeBuild" BeforeTargets="BeforeBuild">
2<ItemGroup>
3<Content Include="Content\bundles\**" />
4</ItemGroup>
5</Target&gt;
Sitecore Alias as Redirect

Sitecore Alias as Redirect

One feature of Sitecore that I have always disliked is Alias's. On each page of a site, content editors have the ability to click an alias button on the presentation tab and add alternative urls for the page.

Once added these will appear in the Aliases folder under system.

However all this accomplishes is multiple URLs existing for one page which is a big SEO no no.

Content editors like to do this in order to create simple URLs for things like landing pages. e.g. himynameistim.com/Sitecore but search engines hate it as they see multiple pages with the exact same content. As a result the value of each page gets lowered and appears lower in search engine results. What Content editors really want is to set up a 301 redirect so that they can have the simple URL but redirect users to the actual page on the site.

Aliases as Redirects

One solution is to updated the aliases functionality to cause a redirect to it's linked item rather than resolve the page.

To do this we need to create a pipeline processor that inherits from AliasResolver.

1using Sitecore; using Sitecore.Configuration; using Sitecore.Diagnostics; using Sitecore.Pipelines.HttpRequest; using System.Net; using System.Web; using AliasResolver = Sitecore.Pipelines.HttpRequest.AliasResolver; namespace HiMyNameIsTim.Pipelines { public class AliasAsRedirectResolver : AliasResolver { public override void Process(HttpRequestArgs args) { if (!Settings.AliasesActive) { return; // if aliases aren't active, we really shouldn't confuse whoever turned them off } var database = Context.Database; if (database == null) { return; // similarly, if we don't have a database, we probably shouldn't try to do anything } if (!Context.Database.Aliases.Exists(args.LocalPath)) { return; // alias doesn't exist } var targetID = Context.Database.Aliases.GetTargetID(args.LocalPath); // sanity checks for the item if (targetID.IsNull) { Tracer.Error("An alias for \"" + args.LocalPath + "\" exists, but points to a non-existing item."); return; } var item = args.GetItem(targetID); if (database.Aliases.Exists(args.LocalPath) &amp;&amp; item != null) { if (Context.Item == null) { Context.Item = item; Tracer.Info(string.Concat("Using alias for \"", args.LocalPath, "\" which points to \"", item.ID, "\"")); } HttpContext.Current.Response.RedirectLocation = item.Paths.FullPath.ToLower() .Replace(Context.Site.StartPath.ToLower(), string.Empty); HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.MovedPermanently; HttpContext.Current.Response.StatusDescription = "301 Moved Permanently"; HttpContext.Current.Response.End(); } } } }

And patch in in place of the regular Alias Resolver.

1<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
2<sitecore> <pipelines>
3<httpRequestBegin> <processor type="HiMyNameIsTim.Core.Pipelines.AliasAsRedirectResolver, LabSitecore.Core" patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel']"/> </httpRequestBegin>
4</pipelines> </sitecore>
5</configuration>

The above code is adapted from a solution given by Jordan Robinson but with a bug fixed to stop every valid URL without an alias writing an error to the log file.

Using compile options for version compatibility

Using compile options for version compatibility

Here's the scenario; Your building a module and it needs to be compatible with different versions of a platform. e.g. Sitecore, and everything's great up until the day you need to call different methods in different versions of the platform. You'd rather not drop support for the old versions, and nor do you want to start maintaining two code bases. So what do you do?

C# Preprocessor Directives

Preprocessor directives provide a way to give the compiler instructions to follow while its compiling a project. By using this we can give the compiler conditions to compile different versions in different ways. Thereby allowing us to maintain one codebase, but produce compilations for different versions of the platform. e.g. One for Sitecore 8.0 and another for Sitecore 9.0.

#if, #else and #endif

When the compiler encounters an #if followed by an #endif, it will only compile the code between the two if the specified symbol had been defined.

1#if DEBUG
2 Console.WriteLine("Debug version");
3#else
4 Console.WriteLine("Non Debug version");
5#endif
6

Defining a preprocessor symbol

For the if statement to work, your going to need to define your symbol which is being evaluate.

This can be included in code as follows

1#define YOURSYMBOL

A more useful was of defining this however is to include it in your call to MSBuild (this is particularly useful when using a build server).

1-define:name[;name2]

If your compiling from Visual Studio an easier solution is to set up a new build configuration with a conditional compilation symbol.

  • Right click your solution item in Solution Explorer and select Properties
  • Click Configuration Properties on the left and then Configuration Manager on the right
  • In the pop up window click the Active solution configuration drop down and then click New
  • Enter the name of the build config. In my example above I have SC82 for Sitecore 8.2 and SC90 for Sitecore 9.0.
  • Click Ok and close all the windows you just opened
  • Right click the project that your going to build and select Properties
  • Select the Build tab
  • Select your build configuration from the configuration at the top
  • Enter the symbol your using for the #if directives

Reference different versions of an assembly

Adding conditions to our code is good, but for this to fully work we also need to reference different versions of the assemblies that are causing the issue in the first place.

There's no way of doing this through Visual Studio but by editing the .csproj file manually we can update the hint path on a reference to include the configuration name as a variable.

1..\libraries\$(Configuration)\Sitecore.Kernel.dll False

This example shows how different versions of the Sitecore Kernel can be referenced by keeping each version in a subfolder that corresponds with the build configuration name.

As well as different versions of assemblies, it may also be needed to target different versions of the .NET framework. This can be done in the .csproj file by including additional property groups that have a condition on the configuration name.

1bin\SC82\
2TRACE;SC82
3true
4v4.5.2
5
6bin\SC90\
7TRACE;SC90
8true
9v4.6.2

In this example I'm targeting .net 4.5.2 for my Sitecore 8.2 configuration and 4.6.2 for my Sitecore 9 configuration.

Useful Links

C# preprocessor directives
-define (C# Compiler Options)