Sitecore placeholders as a Droplist

When it comes to selecting a placeholder in Sitecore we are required to enter text into a Single-Line Text field:

placeholder select

Obviously this introduces scope for a typo and potentially isn’t an ideal user experience. So I thought we’d look at changing this to a Droplist. The good news is the first step is a very straightforward task and we only need to update the Standard Rendering Parameters template found at /sitecore/templates/System/Layout/Rendering Parameters/Standard Rendering Parameters

The Placeholder field can be updated to a Droplist with a source query that filters anything that’s not a ‘Placeholder’ template (/sitecore/templates/System/Layout/Placeholder). We can use a query such as:

query:/sitecore/layout/Placeholder Settings//*[@@templateid='{5C547D4E-7111-4995-95B0-6B561751BF2E}’]

Our Standard Rendering Parameters template now appears as follows:

sitecore rendering parameters

And we can now select our placeholders from a Droplist:

sitecore_rendering_params_dropdown

Bear in mind that we have directly updated a Sitecore template, so any platform update will replace this change. We could serialize the Standard Rendering Parameters template but adding a vanilla template to source control is never a good idea. Food for thought.

Sadly our work isn’t quite finished, while this works for the Control Properties Dialog we also need to think about the Select Rendering Dialog incase a user wants to add a rendering from the Device Editor:

sitecore rendering selection

This is a sheer UI control found at:

MYSITE/sitecore/shell/default.aspx?xmlcontrol=Sitecore.Shell.Applications.Dialogs.SelectRendering&hdl=0ECAC17C88F941F08B14CE06796BF366&ro=sitecore%3A%2F%2Fmaster%2F%7BEB2E4FFD-2761-4653-B052-26A64D385227%7D%3Flang%3Den%26ver%3D1&ic=SoftwareV2%2F16x16%2Fcomponent_blue.png&txt=Select%20the%20rendering%20that%20you%20want%20to%20use.%20Click%20Select%20to%20continue.&ti=Select%20a%20Rendering&bt=Select&rt=Id&sph=1&sop=1&str=1

So we need to find the XML Control that uses the class Sitecore.Shell.Applications.Dialogs.SelectRendering

This is found in \sitecore\shell\Applications\Dialogs\SelectRendering\SelectRendering.xml

Rather than manipulate the vanilla XML control, we can copy this file to the ‘override’ folder to ensure that our new control is used:

\sitecore\shell\override\Applications\Dialogs\SelectRendering\SelectRendering.xml

Edit the line:

<CodeBeside Type=”Sitecore.Shell.Applications.Dialogs.SelectRendering.SelectRenderingForm,Sitecore.Client”/>

And change this to the class in your project:

<CodeBeside Type=”Helixbase.Feature.Fun.SelectRenderingForm,Helixbase.Feature.Fun”/>

And for our code behind file we can copy the class ‘SelectRenderingForm’ which is found in Sitecore.Shell.Applications.Dialogs.SelectRendering of the Sitecore.Client.dll (or Sitecore.Shell.dll depending upon your version of Sitecore).

First we need to change the PlaceholderName property from an ‘Edit’ to a ‘Listbox’ type:

protected Listbox PlaceholderName { get; set; }

We then loop through our placeholder items and populate the Listbox (view below or on Gist)

 var database = Sitecore.Configuration.Factory.GetDatabase("master");

            var placeHolderItems= database.SelectItems("/sitecore/layout/Placeholder Settings//*[@@templateid='{5C547D4E-7111-4995-95B0-6B561751BF2E}']");

            foreach (var placeholder in placeHolderItems)
            {
                this.PlaceholderName.Controls.Add(new Sitecore.Web.UI.HtmlControls.ListItem
                {
                    Value = placeholder.Name,
                    Header = placeholder.Name
                });
            }

We can now see placeholders as a Droplist:

sitecore_placehodler_dropdown

Advertisements

The Little Book of Sitecore® Tips volume 2 now available

littlebookofsitecore_newsFollowing volume 1 I’m pleased to announce the launch of volume 2 of The Little Book of Sitecore® Tips.

Just like the first book, these tips were created from my daily workings with the Sitecore experience platform and we now have another instalment in the ongoing series.

The main purpose of The Little Book of Sitecore Tips series is a light-hearted look at some useful Sitecore tips. These tips are targeted at all levels of user but whether you’re recapping or learning, I hope you find the material either useful, or of some entertainment value.

Your can order the next volume from the likes of Amazon or Barnes & Noble – The booklittlebookofsitecore_unicorn is also available as an eBook on the iBooks Store.

Thanks is due to the technical reviewers Michael West and Matthew Dresser – and the usual thanks to Tamas Varga who was as helpful as ever. Thanks is also due to Mr Helix Thomas Eldblom, and Dominic Hurst who supplied some nice reviews:

littlebookofsitecore_man

“Einstein once said: ‘The definition of genius is taking the complex and making it simple.’ – and this book does just that. It makes the complex simple and it is simply genius!”

“A must read from an author that doesn’t stop in his quest for learning, sharing and giving back.”

amazon-appsstore-us-black-v2Get_it_on_iBooks_Badge_US_1114google-play-badge

Using EnclosingTag, DisableWebEdit and ShowTitleWhenBlank when rendering fields in Glass

If we’re doing views right we’ll be doing views dumb (or replacing the layout service with JSS). In an ideal world our views would be free from helpers and business logic, they should simply render data from a model. Anybody who has been working with Glass for a while will know that the best way to achieve this is to create an editable field with GlassHtml, we simply pass a HtmlString to our view:

var myField = new HtmlString(_glassHtml.Editable(someitem, f => f.SomeField));

However, when rendering fields using the Sitecore API there are properties available from RenderFieldArgs (such as ‘EnclosingTag’) that Glass doesn’t use when rendering a field.

Fortunately, Glass calls the renderField pipeline when creating an editable field so I created a pull request to ensure we can use the following properties:

EnclosingTag – this is available when using @Html.Sitecore().Field as it is present in the render field pipeline as a field arg (renderFieldArgs.EnclosingTag). This can now be used in Glass to wrap our field in the chosen markup if the field value is not empty. Usage: new { EnclosingTag = “h2”}

DisableWebEdit – previously Glass forced renderFieldArgs.DisableWebEdit to false, this is now made optional with a ‘false’ default. There are instances where we may still want to pass in our field values as HtmlString but not allow it to be an editable field (when using EnclosingTag for example). Usage: new { DisableWebEdit = true }

ShowTitleWhenBlank – while it is possible to force this option across the Sitecore instance, we may still want to achieve this on a per field basis. This option will show a field title in the experience editor if the field is empty.  Usage: new { ShowTitleWhenBlank= “true” }

You can see an example implementation of the Glass features below (or using Gist):

This is available from the following pull request so be sure to update your version of Glass and begin (or continue) the good practice of simple view logic 🙂

Resolving workflow permission problems when using Habitat

If like the rest of us you’re a fan of Habitat you may find yourself experimenting with the solution and I wanted to draw attention to potential problems with workflow permissions when modifying the predicates for role serialization. Having disabled the serialization of feature roles:

e.g.
<include domain=”modules” pattern=”^Feature Accounts .*$” />

I found that users cannot edit any data when workflow is enabled. A quick chat with Mr Eldblom revealed the following:

“This is an example of how to cleanly separate security into the modules. Each feature module has a feature role defined which sets rights on the feature fields. Thereby if a feature is added then users do not automatically get access to the fields in the feature.”

So if you remove serialization of the feature role, you lose the ability to edit content. This is because the permissions are assigned on the template fields:

habitat_permissions

In order for your editors to modify content you will need to edit these permissions and grant access to the template fields, in my case I removed the restrictions above. Once you update these permissions, the fields that were previously locked will now be editable again.

Hopefully this saves a few headaches.

Automating item unlocks when deleting Sitecore users

Working with a large team of content editors can present us with difficulties, and one of these problems presents itself when we delete a user. If a user has locked items, deleting the user still leaves their items locked.

Sitecore contains a rule /sitecore/system/Settings/Validation Rules/Item Rules/Security/Locked By Non Existing User and we can use this item rule to give us a visual indicator of which items are locked by none existing users. This can be applied globally by adding it to our Global Rules item found at /sitecore/system/Settings/Validation Rules/Global Rules item.

So we can see which items are locked, and as an individual a content editor can unlock their own files by selecting the ribbon option ‘My Items’ and then using ‘Unlock All Items’ which calls the a method in the Sitecore.Shell.Applications.WebEdit.Commands.UnlockAll class. However, this method executes in the context of the current user rather than our non existing user who has locked the items. So while this method isn’t suitable for what we’re looking for, we can use something similar.

Upon deleting users Sitecore calls the user:deleted event so we need to create a handler for this event. You can see this handler being patched in Helix Base, and the source used by the event but below is the source for the handler:

    public class RemovedUserEventHandler
    {
        public void OnUserDeletedUnlockFiles(object sender, EventArgs args)
        {
            if (EventDisabler.IsActive)
                return;

            var objList = new List<Item>();
            var userName = Event.ExtractParameter<string>(args, 0);

            Assert.IsNotNullOrEmpty(userName, "User name was null or empty");

            var lockedItems = Client.ContentDatabase.SelectItems($"search://*[@__lock='%{userName}%']");

            if (lockedItems == null || !lockedItems.Any())
                return;

            foreach (var lockedItem in lockedItems)
                objList.AddRange(lockedItem.Versions.GetVersions(true).Where(version =>
                    string.Compare(version.Locking.GetOwner(), userName, StringComparison.OrdinalIgnoreCase) == 0));

            ProgressBox.Execute(nameof(RemovedUserEventHandler), "Unlocking items", "Network/16x16/lock.png",
                UnlockAllItems, "lockeditems:refresh", Context.User, objList);

            SheerResponse.Alert($"Successfully unlocked {objList.Count} item(s) checked out by {userName}",
                Array.Empty<string>());
        }

        private static void UnlockAllItems(params object[] parameters)
        {
            Assert.ArgumentNotNull(parameters, nameof(parameters));

            if (!(parameters[0] is List<Item> parameter))
                return;

            var job = Context.Job;

            if (job != null)
                job.Status.Total = parameter.Count;

            foreach (var lockedItem in parameter)
            {
                job?.Status.Messages.Add(Translate.Text("Unlocking {0}", (object)lockedItem.Paths.ContentPath));
                lockedItem.Locking.Unlock();
                if (job != null)
                    ++job.Status.Processed;
            }
        }
    }

Which we then patch in to the event:

  <sitecore>
    <events>
      <event name="user:deleted">
        <handler type="Helixbase.Feature.ItemUnlock.Events.RemovedUserEventHandler, Helixbase.Feature.ItemUnlock" method="OnUserDeletedUnlockFiles" />
      </event>
    </events>
  </sitecore>

Upon deleting users, you should now see their files automatically unlocked.

You can also find this source on GitHub Gist.

Edit… since writing this blog post Michael West has created a useful script for SPE to transfer ownership from one user to another. This can be used in situations where you wish to maintain a record of a previous users locked items.

Using voice commands to add items to Sitecore. IFTTT: Complete walk through

Having purchased a few Google Home devices my mind turned to getting them to work with Sitecore. Wouldn’t it be cool to tell your Google Home or mobile device to interact with the Sitecore environment?! So I setup a POC to do just that, by using a custom route in an API controller we can create Sitecore items by asking Google Home to “Create Sitecore item [ItemName]“:

First visit the IFTTT Platform where we can add our Applet. By selecting ‘New Applet’ we’re first asked to create a Trigger, for this we select ‘Google Assistant’

Google assistant

This will present us with some options:

google trigger

We’ll be using a phrase with a text ingredient, this allows us to say something like “Hey Google, add [ingredient] to my request”. For the POC this ingredient will be an item name which will be used to add an item to Sitecore under /sitecore/content/Home/Google Home

Now we’ll tell Google that our phrase will be “Create Sitecore item [ItemName]:

google assitant sitecore

And we need a reply from the device we’re speaking to:

google assistant sitecore response

Great, so we’ve got an “if this”, now it’s time for a “then that”. Our devices know what we’re going to say and know how to respond, so we need to create our API controller in Sitecore that’s going to create the item for us. The rough example I used is below:

    public class GoogleTestController: SitecoreController
    {
        public ActionResult IFTTT(string itemName)
        {
            using (new Sitecore.SecurityModel.SecurityDisabler())
            {
                var masterDb = Sitecore.Data.Database.GetDatabase("master");

                var template = masterDb.GetItem("/sitecore/templates/System/Templates/Standard template");

                var parentItem = masterDb.GetItem("/sitecore/content/Home/Google Home");

                parentItem.Add(itemName, new Sitecore.Data.Items.TemplateItem(template));

                return Content($"{itemName} added");
            }
        }
    }

And we’ll need our custom route

        public void Process(PipelineArgs args)
        {
            RouteTable.Routes.MapRoute("Feature.GoogleTest", "GoogleTest/{itemName}",
                new { controller = "GoogleTest", action = "IFTTT" });
        }

That’s our Sitecore code setup, now we need to add a webhook so that Google Assistant can talk to our Sitecore API. Under ‘then’ we select ‘Add action’ and select ‘Webhooks’:

ifttt then

This presents us with a dropdown and we can select ‘Make a web request’. These fields should be self explanatory, so the one I’ll focus on is the URL. In the below I have used a sample URL but obviously this needs to be a publicly accessible server with the Sitecore item Web API enabled:

Sitecore google api

Now we’re calling our Sitecore API controller, and sending in the name of the item that the user has asked to create. We could stop there and deploy but let’s select ‘Add action’ again and choose ‘notifications’. We want a phone notification when an item has been added to Sitecore:

ifttt notification sitecore

Give the Applet a title and description and save it. The final step is to enable this Applet on our IFTTT devices, simply select ‘Turn on’:

sitecore ifttt applet.png

Once it’s on we can talk to any of the Google devices on our account. I tested this by saying the following to my Google Home… “Ok Google, create Sitecore item test item” I hear the response “Ok, creating item test item on your Sitecore instance”, you can hear her response on this recording. We can then confirm that our device has added the item to Sitecore:

test item sitecore

We also get a nice phone notification:

sitecore-item-created-ifttt

The possibilities for this one are endless – Sitecore, it’s invading our homes!

Using the OData Item Service in Sitecore 9 – Step by step

Pre Sitecore 9 if we wanted to retrieve Item data we could make use of RESTful services easily with a simple GET HTTP Request. If for example we wanted to get children of an item we could call an endpoint such as:

http://YourInstance/sitecore/api/ssc/item/Some_Guid/children?fields=ItemId,ItemName,TemplateName

In Sitecore 9 we also have the introduction of the  OData Item Service so I thought I’d take us through a step by step.

First go to the following item in the Core database:

/sitecore/system/Settings/Services/API Key

Right click the API Key folder and insert an ‘OData Item API Key’. An explanation of the fields for this item can be found in the documentation but here’s my setup with an explanation:

ODataItemAPIKey

Database – fairly self explanatory, the Sitecore database you’re retrieving an item from.

Search Filter – we can apply search options using an OData filter format, I’ve left this as the default which will return the latest version of items.

CORS Origins – which origins are allowable? Add your required origins separated with a semicolon. As I’m testing this on Helix Base I have added the relevant origin.

AllowedControllers – you may wish to restrict this API Key to certain controllers and can list those here separated with a semicolon. I’m allowing all so have opted for the wildcard *

Impersonation User – you could leave this blank in which case Sitecore will use the default from your Sitecore.Services.AnonymousUser setting. The default is most likely sitecore\ServicesAPI but I’ve added this to the field anyway.

Once we’ve saved the item it’s time to copy the item Id which we can then use in our request headers, or on the endpoint. For example if my item Id is 1234 I could use a Key of sc_apikey  and value 1234 in my headers, or apply it to the endpoint as follows:

http://demo.helixbase/sitecore/api/ssc/aggregate/content/Items(‘/sitecore/content/Home’)?sc_apikey=1234

Using either approach we can then see the fruits of our labour…

SitecorePostman

To see a list of endpoints for the OData Item Service see the Sitecore documentation