Instantiate page button

Create items via Sitecore ItemService, Experience Editor button and JavaScript.

So what is going on here? That is a complicated title! Well yes it is, that’s because this was the easiest way to achieve what i wanted and it resulted in what could have been three separate blog posts. I’ll try to keep it together by starting with an explanation.

In a solution I work with we had gone for the *-item solution for displaying very many pages that all are fed with data from 3:rd party systems. A *-item is an item that is named “*”. An item that is named “*” acts as a wildcard and will pick up all requests that do not fit an actual item in the same path. For example the item at
/sitecore/content/home/pagecontainer/*
will be hit when the url http://thesite.com/pagerepo/apage is resolved. Or at least as long as the item
/sitecore/content/home/pagecontainer/apage
does not exist it will. This is all pretty basic and has been a part of Sitecore for ever.

The editors then wanted the opportunity to insert custom data via the experience editor on some of the pages. So the challenge to easily make some of the pages available for edit arose.

The solution was to create a button in experience editor that takes the url that the editor is on and create an item with the name that reflects the url (only if the editor is currently on a *-item). The button should also copy the renderings from the *-item to the new item and navigate the editor to the newly created item.

The custom experience editor button

So let us start by adding a button to Sitecore. I want the button to appear in the “new” ribbon in the “home” menu of the experience editor.

Instantiate page button

So first of all switch to the core database of Sitecore content editor and navigate to
/sitecore/content/Applications/WebEdit/Ribbons/WebEdit/Page Editor/New
and insert a new item of the type
/sitecore/templates/System/Ribbon/Large Button.

Add button in core database

I chose to call the new button InstantiatePage, I gave it the header (that is the text that will appear under the button in the experience editor) “Instantiate Page”. I also gave it the ID NewInstantiatePage.

Now we need to tell the button what it should do. In order to achieve this we have to, yes as I understand it this is the only way to do it (please comment if you know of another way), open Sitecore Rocks and first of all add the large button rendering to the buttons layouts. you can do this by finding the button item, right clicking it and choosing Tasks->Design Layout.

Click design layouts

Then you have to find the large button layout located here:
/sitecore/client/Applications/ExperienceEditor/Common/Layouts/Renderings/Ribbon/LargeButton.
Place it on the InstantiatePage buttons layouts by dragging it onto there. When this is all done the properties of the layout needs to be changed.

Large button properties

The click property should be filled in with “trigger:button:click”.
The Command property should be filled in with “InstantiatePage”. This nees to match the command that we will create in a while.
The PageCodeScriptFileName should be
/sitecore/shell/client/Sitecore/ExperienceEditor/Commands/InstantiatePage.js.
When you read up on how to this Sitecore will tell you to use a different path here. Don’t. It does not work. This actually reflects the path on disk where the js-file needs to be placed.

The JavaScript and the Sitecore ItemService

So I mentioned the js-file before. Let us go ahead and create the file InstantiatePage.js. It should be placed in “yourwebsiteroot/sitecore/shell/client/Sitecore/ExperienceEditor/Commands/” (or since you are developing a Helix site, at the corresponding path in the feature that your are creating. It will then get published to the correct path in the website).

JavaScript is not my first language (or second for that matter) so bare with me here. There might be some errors in here (again, please comment if there is). But this is what I ended up with.

define(["sitecore"], function (Sitecore) {

  Sitecore.Commands.InstantiatePage =
    {
      canExecute: function (context) {
        if (context.currentContext.ribbonUrl.toLowerCase().indexOf('pagecontainer') > -1)
          return true;
        else
          return false;
      },

      execute: function (context) {
        var itemId = context.currentContext.itemId;
        var xhr = new XMLHttpRequest();
        // Get the *-item from Sitecore
        xhr.open("GET", origin + "/sitecore/api/ssc/item/" + itemId + "?includeStandardTemplateFields=true");
        xhr.onreadystatechange = function () {
          if (this.readyState == 4) {
            var item = JSON.parse(this.responseText);
            var finalRenderings = item['__Final Renderings'];
            if (item["ItemName"] != "*") {
              alert('Item already is an instance');
              return;
            }
            // Get the item name from the url
            var itemName = context.currentContext.ribbonUrl.substring(context.currentContext.ribbonUrl.lastIndexOf("/") + 1);
            createItem(finalRenderings, itemName);
          }
        };
        xhr.send(null);
      }
    };

  function createItem(finalRenderings, itemName) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", origin + "/sitecore/api/ssc/item/sitecore%2Fcontent%2FHome%2Fpagecontainer");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {
      if (this.readyState == 4) {
        if (this.status == "201") {
          response = JSON.stringify(this.getAllResponseHeaders());
          // Find the guid of the newly created item
          var match = response.match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g);
          if (match.length == 0) {
            alert('Something went wrong, item id not found');
          } else {
            alert('New item created successfully. Redirecting.');
            window.top.location.href = origin + "/?sc_mode=edit&sc_itemid=" + match[0];
          }
        } else {
          alert('Something went wrong, item not created');
        }
      }
    };
    var obj = {};
    obj['ItemName'] = itemName;
    obj['TemplateID'] = "413B8116-3CC4-400B-ACB5-5A1A4394799E";
    obj['__Final Renderings'] = finalRenderings;
    xhr.send(JSON.stringify(obj, null, 4));
  }
});

So lets go through this piece by piece. The first part

      canExecute: function (context) {
        if (context.currentContext.ribbonUrl.toLowerCase().indexOf('pagecontainer') > -1)
          return true;
        else
          return false;
      }

This is the code that tells if the button should be active or inactive. I want the button to be inactive if the editor is not in the correct context. For this project that means it should only be active if the editor is under the item “pagecontainer”.

The second part of the script

      execute: function (context) {
        var itemId = context.currentContext.itemId;
        var xhr = new XMLHttpRequest();
        // Get the *-item from Sitecore
        xhr.open("GET", origin + "/sitecore/api/ssc/item/" + itemId + "?includeStandardTemplateFields=true");
        xhr.onreadystatechange = function () {
          if (this.readyState == 4) {
            var item = JSON.parse(this.responseText);
            var finalRenderings = item['__Final Renderings'];
            if (item["ItemName"] != "*") {
              alert('Item already is an instance');
              return;
            }
            // Get the item name from the url
            var itemName = context.currentContext.ribbonUrl.substring(context.currentContext.ribbonUrl.lastIndexOf("/") + 1);
            createItem(finalRenderings, itemName);
          }
        };
        xhr.send(null);
      }
    };

This tells what the button should do. The idea here is to only use JavaScript to create everything that we need. First of all we need to find the renderings of the context item (that is the *-item, remember). We will use the Sitecore ItemService to achieve this. So the way to do it is by opening an XMLHttpRequest and get the information via “yoursite/sitecore/api/ssc/item/itemid”. In this context the variable origin gives the domain of your site (pretty handy). We also include the querystring “includeStandardTemplateFields” that makes sure that the field we are interested in is returned.

Next the response is parsed into a JSON object and we extract the contents of the “__Final Renderings” field. We then try to find the name of the item that we need to create by using all text after the last “/” in the url.

Also we do some error checking here. If all is well we pass the finalrenderings and the item name to last part of the scripot “createItem”.

  function createItem(finalRenderings, itemName) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", origin + "/sitecore/api/ssc/item/sitecore%2Fcontent%2FHome%2Fpagecontainer");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {
      if (this.readyState == 4) {
        if (this.status == "201") {
          response = JSON.stringify(this.getAllResponseHeaders());
          // Find the guid of the newly created item
          var match = response.match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g);
          if (match.length == 0) {
            alert('Something went wrong, item id not found');
          } else {
            alert('New item created successfully. Redirecting.');
            window.top.location.href = origin + "/?sc_mode=edit&sc_itemid=" + match[0];
          }
        } else {
          alert('Something went wrong, item not created');
        }
      }
    };
    var obj = {};
    obj['ItemName'] = itemName;
    obj['TemplateID'] = "413B8116-3CC4-400B-ACB5-5A1A4394799E";
    obj['__Final Renderings'] = finalRenderings;
    xhr.send(JSON.stringify(obj, null, 4));
  }

This will again create an XMLHttpRequest and use it to create an item via the Sitecore ItemService. Again we use the origin variable and add the following url
/sitecore/api/ssc/item/sitecore%2Fcontent%2FHome%2Fpagecontainer
since we know where the item should be created.

I used this pageĀ The RESTful API for the ItemServiceĀ  to read up on the ItemService. It did however suggest to compose the JSON object by string concatenation. I find this solution a bit more elegant where the object properties “ItemName”, “TemplateID” (be sure not to write Id with a small d, it wont work, it will also take a good while to find) and “__Final Renderings” are set in the obj and the stringified with JSON.stringify.

If all goes well we should end up in readystatechange function and the status should be 201. If all goes well that is. I use some regex to find the guid of the newly created item and then simply move the editor to the correct url using sc_mode=edit and sc_itemid.

Leave a Reply

Your email address will not be published. Required fields are marked *