Using Custom Dialogs and Menu Items

Typically, websites as a whole require some configuration. The Scrivito Example App, for instance, lets editors specify the logo to display, set IDs for authenticating against third-party services like Google Analytics, and a couple more. To do this, an editor navigates to the homepage, clicks “Edit page properties” from the main menu, and selects the “Site settings” tab where the desired changes can be applied.

Now, attaching site settings to the homepage is neither complicated nor special. But it’s also not always the most practical approach because

  • You have to navigate to the homepage to access the settings.
  • Most if not all of those settings aren’t related to the homepage but to the site.
  • Sooner or later, with lots of settings, the properties dialog will be cluttered.

So let’s take a look at a more flexible solution to the settings matter. As Scrivito lets us extend the main menu and open custom dialogs, we are going to take advantage of these features and develop a dialog for configuring the properties of a coupon. This coupon is to be displayed on any number of pages, so we need a global configuration for it. In your case, the properties could be API keys, a page reference and an image – anything with a global scope.

Persisting global settings

In the Example App, the “Homepage” object class includes the attributes relevant to the site as such, e.g. a googleMapsApiKey attribute:

src/Objs/Homepage/HomepageObjClass.js
Copy
const Homepage = Scrivito.provideObjClass("Homepage", {
  attributes: {
    ...defaultPageAttributes,
    showAsLandingPage: ["enum", { values: ["yes", "no"] }],
    cookieConsentLink: "link",
    childOrder: "referencelist",
    footer: ["widgetlist", { only: "SectionWidget" }],
    logoDark: "reference",
    logoWhite: "reference",
    dividerLogo: "reference",
    facebookAppId: "string",
    twitterSite: "string",
    googleMapsApiKey: "string",
    googleAnalyticsTrackingId: "string",
    intercomAppId: "string",
    ...metaDataAttributes,
  },
});

To persist settings and make them accessible independently of a page, we can simply use a configuration object, an ordinary CMS object equipped with attributes suitable for the data to be stored.

Defining a configuration object class and editing configuration

Our use case here is a website that displays a coupon on several pages using a widget. The coupon needs to be changed on a daily basis, which would be error prone and too time consuming if this had to be done manually for each widget. 

So let’s first define an object class, “CouponConfig”, for maintaining the couponCode as well as a message attribute:

src/Objs/CouponConfig/CouponConfigObjClass.js
Copy
import * as Scrivito from "scrivito";

const CouponConfig = Scrivito.provideObjClass("CouponConfig", {
  attributes: {
    couponCode: "string",
    message: "html",
  },
});

export default CouponConfig;

Next, we are going to provide an editing configuration, even though “CouponConfig” objects aren’t pages and thus don’t have a means for editing attribute values. However, for testing purposes, we want couponCode and message to have initial values. And, even more important, editors should be prevented from creating additional “CouponConfig” objects.

src/Objs/CouponConfig/CouponConfigEditingConfig.js
Copy
import * as Scrivito from "scrivito";

Scrivito.provideEditingConfig('CouponConfig', {
  hideInSelectionDialogs: true,
  initialContent: {
    couponCode: 'BOOKS15OFF',
    message: 'Get 15 % off on all books today!',
  },
});

For the reason stated we don’t require a component for rendering “CouponConfig” objects. Instead, we are going to provide a component we can pass to Scrivito’s openDialog function for making the attributes editable.

Providing the configuration object and dialog

Now that we have a “CouponConfig” object class as well as an editing configuration for it, let’s create an instance of it. First,

  • npm start your app,
  • activate the editing mode, 
  • open the browser console,
  • set the context to “scrivito_application”.

Then execute:

Copy
Scrivito.getClass('CouponConfig').create({_permalink: 'couponconfig'})

This creates the coupon configuration object and assigns to it the couponconfig permalink.

Next, we require the dialog component for editing this object’s attribute values. Note that we are using the permalink given to the object on creation to retrieve it in the dialog:

src/Components/ScrivitoExtensions/CouponConfigDialog.js
Copy
import * as React from "react";
import * as Scrivito from "scrivito";

function CouponConfigDialog() {
  const couponConfig = Scrivito.Obj.getByPermalink("couponconfig");
  const attributes = { couponCode: "Coupon code", message: "Message" };

  return (
    <div className="container-fluid pt-4">
      {Object.keys(attributes).map(key => (
        <div key={key} className="card mb-2">
          <div className="card-title strong ml-2 mr-2 mt-1">
            <span>{attributes[key]}</span>
          </div>
          <hr className="mt-0 mb-1" />
          <div className="pb-2">
            <Scrivito.ContentTag
              className="card-text ml-2 mr-2 mt-1"
              content={couponConfig}
              attribute={key}
            />
          </div>
        </div>
      ))}
    </div>
  );
}

Scrivito.registerComponent("CouponConfigDialog", CouponConfigDialog);

After successfully fetching the /couponconfig object, its couponCode and message attribute values are made editable using Scrivito.ContentTag. That’s all; the rest is styling for which we limited ourselves to Bootstrap. Feel free to swipe the CSS used on the custom tabs in the properties dialog of the homepage.

Providing a menu item for the custom dialog

As the final step to be made, we’ll add an item to the main menu for opening a dialog and rendering the above component. Don’t forget to import the “scrivitoExtendMenu.js” file via “index.js” in the same directory.

src/config/scrivitoExtendMenu.js
Copy
import * as Scrivito from 'scrivito';
import couponConfigDialogMenuIcon from '../assets/images/arrow_next.svg';

Scrivito.extendMenu(menu => {
  menu.insert({
    id: "couponConfiguration",
    title: "Edit coupon configuration",
    icon: couponConfigDialogMenuIcon,
    onClick: () => Scrivito.openDialog('CouponConfigDialog'),
    position: { after: 'system.openPageDetails' },
    group: 'system.details',
  });
});

The Scrivito.extendMenu API is quite versatile as it not only lets you add menu items but also remove, reposition, group them, and, last but not least, define their onClick event handler. The latter can be seen above where Scrivito.openDialog is called.

Try it out!

Start your app, switch to editing mode, then open the main menu to the top right, and select “Edit coupon configuration”.

That was it – now you can create your own custom dialogs and open them via the main menu!

What’s next?

For the sake of completeness (and, of course, to illustrate how to utilize a configuration object), we’ll provide you with a “CouponWidget” that accesses our global coupon settings:

src/Widgets/CouponWidget/CouponWidgetClass.js
Copy
import * as Scrivito from "scrivito";

const CouponWidget = Scrivito.provideWidgetClass("CouponWidget", {});

export default CouponWidget;
src/Widgets/CouponWidget/CouponWidgetComponent.js
Copy
import * as React from "react";
import * as Scrivito from "scrivito";

Scrivito.provideComponent("CouponWidget", ({ widget }) => {
  const couponConfig = Scrivito.Obj.getByPermalink("couponconfig");

  return (
    <Scrivito.InPlaceEditingOff>
      <div className="card text-center bg-warning">
        <div className="card-body pb-0">
          <Scrivito.ContentTag
            content={couponConfig}
            attribute="couponCode"
            className="h4 mb-0 strong"
          />
          <Scrivito.ContentTag
            content={couponConfig}
            attribute="message"
            className="mt-0 strong"
          />
        </div>
      </div>
    </Scrivito.InPlaceEditingOff>
  );
});

Note that there’s no editing configuration for the widget class. This is because it doesn’t define any attributes for its instances, e.g. for styling purposes. In our case, all widget instances were meant to look the same, so if we had wanted to style them, we would have had to extend the configuration object accordingly.

Furthermore, to make it clear to editors that the contents of coupon widgets cannot be altered individually (contrary to what one might expect), Scrivito’s in-place editing capability built into Scrivito.ContentTag has been disabled by means of Scrivito.InPlaceEditingOff   New in 1.5.0. If changing all those widgets at once by touching a single one is desirable in your use case, simply remove this component.