codeflood logo

Reusable SPEAK components

With the recent(ish) release of Sitecore 7.2 the greater Sitecore development community is finally getting their hands all over the shiny new SPEAK UI framework. SPEAK is a successor to Sheer UI and is intended to be used by all developers wanting to add functionality to a Sitecore instance. I've written this post against SPEAK 1.1 which is included in Sitecore 7.2. One of the strong design principals of SPEAK is reusability. Components should be developed in such a way as to be reusable and not developed bespoke for a single purpose. The Business Component Library (BCL) components exhibit this principal in their separation of concerns. For example, a list component doesn't access the Sitecore content tree directly to retrieve items to display. Instead it uses a data source component which it reads objects from. Both the list component and data source component can be swapped out to either display the items in a different fashion or to display different data respectively. Even though the BCL contains a lot of components one would need for their own SPEAK applications there will come times when one must develop custom components to meet their goals. So with all the reusability of the components themselves, why not reuse the constituent pieces of the components themselves? But first, a special disclaimer. In case you missed the news (or have only just stumbled upon my blog), I now work at Sitecore as a developer in the product team. On my current project I'm using SPEAK and creating new SPEAK components every day. Although I'm using SPEAK, I'm not part of the SPEAK team, so any insights I share here are of my own personal experiences and are not to be taken as official Sitecore advice. A SPEAK component is made up of 5 pieces. These are:

  • A view rendering definition item
  • A parameters template
  • A razor view (CSHTML)
  • A JavaScript file (JS)
  • A class definition (CS or your other favourite .net language)

The class definition may be omitted for simple components, though I find it's always a good idea to use one so you don't end up with a bunch of functional code in your markup file. Classic ASP anyone? For most of the pieces listed above we already have well defined reusability principals to leverage and made reuse of existing pieces. For Sitecore items we can use template inheritance, though the view rendering definition item shouldn't require any reuse as it's just registering the razor view with Sitecore. The CSHTML file has limited reusability but does offer some through the use of partial views. And of course for .net code we have class inheritance. Of the pieces listed above, the piece with the most obscure reusability for normal Sitecore developers (whom are normally used to static languages) is the JavaScript file. JavaScript (currently) does not have classes, so there's no class inheritance. But JavaScript has plenty of other inheritance techniques available. In fact, when you create a new SPEAK component through Sitecore Rocks, you'll be able to see a particular inheritance technique employed by Backbone.js which SPEAK uses. Let's take a look at a component I've just created:

define(["sitecore"], function (Sitecore) {
  var model = Sitecore.Definitions.Models.ControlModel.extend({
    initialize: function (options) {
      this._super();
    }
  });

  var view = Sitecore.Definitions.Views.ControlView.extend({
    initialize: function (options) {
      this._super();
    }
  });

  Sitecore.Factories.createComponent("SimpleComponent", model, view,
    ".sc-SimpleComponent");
});

Notice on the 2nd line how the model is created through the call to Sitecore.Definitions.Models.ControlModel.extend(). The model we define is actually an extension to the ControlModel model which SPEAK defines. If you've ventured into the JS code of any components or if you've created your own you may have seen the calls in the model to this.get() and this.set() to get and set data within the model. Where do these functions come from? ControlModel of course. Well, actually, from backbone, but Sitecore has already extended the base backbone model and that's what we're extending. Same goes for the view we're defining here; it's an extension to the ControlView view which SPEAK has defined. Ok, so we're already reusing pieces the very moment we define a new component. But how do we reuse our own models and views? Well we can extend them in the same fashion as we do for a new component. When we register a SPEAK component with the call to Sitecore.Factories.createComponent() at the bottom of the code above, Sitecore will store the model we defined in the Sitecore.Definitions.Models object with the same name as the component. So if we now wanted to create a new component and extend the model above, we could do so by:

var model = Sitecore.Definitions.Models.SimpleComponent.extend({
  initialize: function (options) {
    this._super();
  }

  // extend the model
});

But that's not the only way. Say hello to the Sitecore.Factories.createBaseComponent() function. This function name is a little misleading. It doesn't create a base component, it creates a component using another as a base. Unlike the structure of the simple component JS above where we define the model and view and then register them for the component, when using the Sitecore.Factories.createBaseComponent() function you're just defining the view of the component. However createBaseComponent() allows passing an extendModel object to extend the model of the component.

define(["sitecore"], function (Sitecore) {
  Sitecore.Factories.createBaseComponent({
    name: "AnotherExtendedComponent",
    base: "SimpleComponent",
    selector: ".sc-AnotherExtendedComponent",
    attributes: [
      { name: "myProperty", value: "$el.text" }
    ],
    extendModel: {
      setData: function(data) {
        this.set("items", data.Items);
      }
    }
  });
});

The attributes property above contains an array of objects to expose as properties of the model of the component. In the above case the model for the AnotherExtendedComponent component would contain a property myProperty which is accessible through the get and set calls on the model. Many of the BCL components make use of createBaseComponent() as we've done above. For example, go and check out the ToggleButton component found in the \sitecore\shell\client\Business Component Library\Layouts\Renderings\Common\Buttons folder. Is uses createBaseComponent() and selects the ButtonBase component as the component to extend. That's about it. I've shown you a few examples of how to reuse pieces within the JavaScript of your component which will allow you when creating your own components to create base components and extend them to reduce the amount of code you need to write. But before I sign off, just a quick note about require.js. SPEAK uses require.js to allow defining modules and manage the dependencies between them. In the above examples when I've been accessing Sitecore objects within the JS I've accessed them through the identifier Sitecore. But this is only because the code generated by Rocks by default will include a dependency on the sitecore module (note the lower case 's') which is exposed within the components scope under the name Sitecore (note the capitol 'S'). I've also seen components defined where they have used a different name for the sitecore module:

define(["sitecore"], function (_sc) {
});

So when working with SPEAK, just keep in mind that module identifiers can be different and you need to refer to the call to define at the top to make sure you're using the appropriate identifier for the module.

Comments

Clem

It would be helpful to show how this code works with the Razor view. Razor views use @model RenderingModel. Knowing how to tie the 2 together would be very helpful. Thanks,

Alistair Deneys

Hi Clem, SPEAK uses a different approach to normal Sitecore development. Keep in mind that SPEAK is a JavaScript application, with everything running on the page. Unlike normal Sitecore dev where you make requests and render the page as response, in a SPEAK application you render the definitions of all the components on your page, then make requests through ajax to load the data required and populate those components. You're not really taking a model on the server and rendering it.

Leave a comment

All fields are required.