Previewing Styling Options in Properties Dialogs

The properties dialog is where content editors can fine tune the appearance of a widget or page, from alignment and font sizes to colors and borders, etc.

Typically, making options available in a properties dialog comes down to adding the corresponding attribute to the editing configuration of the object or widget class. This is pretty straightforward as Scrivito does the rest: it renders the required editing controls for that attribute in the properties view.

However, editing object and widget properties can become cumbersome when there are many options to choose from. Also, for some options it can be tough to determine their effect without switching between the properties dialog and the webpage.

The good news is that the properties view is customizable. You can replace the built-in editing controls with your own for a more intuitive or visually appealing workflow. This can be achieved by providing a Scrivito extension, a component to be used for displaying the contents of a tab on a properties dialog.

In this tutorial, we are going to show you how to use a Scrivito extension to render the values of enum and multienum attributes in a more sophisticated way. For this, we’ll use dcsaszar’s scrivito-picks, an open-source Scrivito extension released as NPM package. To illustrate the possibilities available, we will add clipping options to the Example App’s image widget. Clipping will be done by means of the clip-path CSS property, and a preview of each of the shapes offered will be visible on the properties tab we are going to provide.

So this is sort of a two-in-one tutorial. :)

Getting started

As our extension to the Example App’s image widget is based on the scrivito-picks package, let’s first install it to tick off this item. In a terminal, switch to your project directory and execute:

Copy
$ npm install --save scrivito-picks

Later on, the “ScrivitoPicks” component in this package will allow us to add a callback to the widget’s editing configuration for rendering the values of the clipping shape attribute.

While we are on it, this enum attribute is the only item required to be added to the widget’s class definition. We named it clipShape and set its values to what we thought would make their meaning clear. Here’s our addition to the attributes in the image widget’s class definition:

src/Widgets/ImageWidget/ImageWidgetClass.js
Copy
const ImageWidget = Scrivito.provideWidgetClass("ImageWidget", {
  attributes: {
// …
    clipShape: [
      "enum",
      {
        values: [
          "none",
          "smallInset",
          "largeInset",
          "smallCircle",
          "largeCircle",
          "star",
          "triangle",
          "octagon",
          "butterfly"
        ],
      },
    ],

Extending the widget’s editing configuration

The editing configuration of a widget (or object) class allows you to define propertiesGroups. A property group definition includes a title and, usually, a selection of properties (attributes) to be made editable on a dedicated tab in the properties dialog. As an alternative to the properties, a custom component for rendering the tab contents can be specified.

The component option is where “ScrivitoPicks” comes into play. Being a wrapper around the rendering component, “ScrivitoPicks” lets us define for the values of enum and multienum attributes either a rendering callback, or a bunch of callbacks for delivering a value’s CSS class name, style, or text. For details, see the scrivito-picks package description.

Now, let’s add the crucial part, the propertiesGroups key, to the image widget’s editing configuration. We’ve inserted it directly underneath the properties (which are rendered, as usual, on the default ”General” tab). Our propertiesGroups array consists of exactly one element declaring a tab titled ”Shape”:

src/Widgets/ImageWidget/ImageWidgetEditingConfig.js
Copy
import * as React from 'react';
import * as ScrivitoPicks from 'scrivito-picks';
import imageStyle from "./imageStyle";
// …
Scrivito.provideEditingConfig("ImageWidget", {
  title: "Image",
  thumbnail: imageWidgetIcon,
  attributes: {
    // …
  },
  properties: ["animation", "alignment", "alternativeText", "link"],
  propertiesGroups: [
    {
      title: "Shape",
      component: ScrivitoPicks.createComponent([
        {
          attribute: "clipShape",
          values: [
            { value: "none", title: "None" },
            { value: "smallCircle", title: "Small circle" },
            { value: "largeCircle", title: "Large circle" },
            { value: "smallInset", title: "Small inset" },
            { value: "largeInset", title: "Large inset" },
            { value: "star", title: "Star" },
            { value: "triangle", title: "Triangle" },
            { value: "octagon", title: "Octagon" },
            { value: "butterfly", title: "Butterfly" },
          ],
          renderPreview: ({ widget, value }) => (
            <div className="h-100">
              <img
                src={
                  widget.get("image")
                    ? Scrivito.urlFor(widget.get("image"))
                    : imageWidgetIcon
                }
                style={imageStyle(value)}
                className={"h-100"}
              />
            </div>
          ),
        }
      ])
    }
  ],
  initialContent: {
    // …
  },
});

The component to use for rendering the tab contents is created using a call to ScrivitoPicks.createComponent. This method expects an array of objects to be passed in, each of which specifying how to render a particular attribute. In our case, it’s just the clipShape attribute. We will use its values as keys for looking up a CSS property, so we provided titles for each of them here, too, for better readability.

The most interesting part is the callback we specified via the renderPreview key. This callback is called for each attribute value whenever the Scrivito UI renders the component on the “Shape” tab. By the way, the widget argument we are passing to the callback originates from the UI that passes it to the component, whereas the value is provided by “ScrivitoPicks”.

How the rendering callback works

The callback we’ve specified for the renderPreview key renders an <img> tag using the URL of the widget’s image as the src, if present. If not, the thumbnail icon symbolizing the widget in the widget selection dialog is used.

The value of the style tag attribute is set by calling a function, imageStyle, to which the clipShape value concerned is passed. We’ve placed this function in a dedicated file:

src/Widgets/ImageWidget/imageStyle.js
Copy
function imageStyle(clipShape) {
  var style = {};
  const paths = {
    none: "",
    smallCircle: "circle(20%)",
    largeCircle: "circle(38%)",
    smallInset: "inset(2% 2%)",
    largeInset: "inset(8% 8%)",
    star:
      "polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)",
    triangle: "polygon(50% 0%, 0% 100%, 100% 100%)",
    octagon:
      "polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%)",
    butterfly:
      "polygon(7% 6%, 33% 5%, 50% 18%, 67% 5%, 93% 6%, 83% 35%, 63% 47%, 84% 58%, 66% 90%, 50% 77%, 34% 90%, 16% 57%, 39% 47%, 17% 35%)"
  };

  if (clipShape) {
    style.clipPath = paths[clipShape];
    style.WebkitClipPath = paths[clipShape];
  }
  return style;
}
export default imageStyle;

As you can see, the imageStyle function uses the passed-in clipShape attribute value to look up the corresponding clipping path which is then assigned to the clipPath style property. clipPath is React’s spelling of the clip-path CSS property (note that browser support is limited).

Voilà! You could start your app right now to see the clipping options come to life on an image widget’s properties view.

What about the actual image?

At this point, the image displayed on a page is ignorant towards the clipping we want to have applied. To remedy this, we need to pass the style with our computed clipPath property to the Scrivito.ImageTag component that renders the image:

src/Widgets/ImageWidget/ImageWidgetComponent.js
Copy
import imageStyle from "./imageStyle";

Scrivito.provideComponent("ImageWidget", ({ widget }) => {
  let image = (
    <Scrivito.ImageTag
      content={widget}
      attribute="image"
      style={imageStyle(widget.get("clipShape"))}
      alt={alternativeText(widget)}
    />
  );
// …

Once again the imageStyle function is used to set the value of the style prop which, in our case, is made up of nothing more than the clip-path associated with the current clipShape attribute value.

Final words

You’ve significantly improved the way in which an enum attribute is presented to editors on a widget’s properties view! This alone is already great, but on top you’ve applied a clip path to an image, extending your toolbox for creating impressive web pages. Congratulations!

There’s a fantastic “CSS clip-path maker” we highly recommend trying out, Clippy. If you would like to dig deeper, we found Clipping and Masking in CSS to be an excellent read.