Performing Bulk Operations

Whenever it gets too time consuming to individually change a property of several pages using Scrivito’s in-place editing interface, running a script via the JavaScript console is the means of choice. In this article, we are going to give you some examples of how to update any number of pages using bulk operations. We are using the Example App as our basis, so you can test the code and play with it.

Adding tags to pages

To begin, assume that your Scrivito-based website features a blog, and that you would like to make blogposts identifiable as such in some navigation or on search result pages, for example. Since you are using tags to this end anyway, assigning the “blogpost” tag to each of them seems natural. To do this in one step via the JavaScript console:

  • Create or select a working copy,
  • open the console,
  • select the “scrivito_application” context,
  • and run this code:
Copy
Scrivito.load(() => {
  return [...Scrivito.getClass('BlogPost').all()];
}).then(objs => {
  objs.forEach(o => {
    console.log(`Touching ${o.id()}...`);
    o.update({ tags: [...o.get("tags"), "Blogpost"] });
  });
  console.log("Done tagging all blogposts.");
});

We are using Scrivito.load to query all CMS objects of the “BlogPost” class. Scrivito.load ensures that the Scrivito SDK has completed asynchronously fetching the queried objects before they get processed. As the returned Promise resolves, our code for changing the objects is executed. In this case, we are iterating the array to update each object’s tags attribute (a stringlist) by appending our “blogpost” tag to the respective existing value.

The above code is very well suited as a template for performing all kinds of bulk operations because its two main parts, the search for the relevant pages, and the code to be executed for each of them, can be easily adapted to solve any use case.

For checking whether your code worked as intended, either open the Content Browser and look, with respect to the example above, for the tag in question, or execute a search like this one in the JavaScript console:

Copy
[...Scrivito.Obj.where('tags', 'equals', 'Blogpost')]

Removing tags from pages

Now, if you accidentally assigned one or more tags to some pages and want to remove them, you can simply replace the value of the tags stringlist with a filtered version of this list, like so:

Copy
Scrivito.load(() => {
  return [...Scrivito.getClass("BlogPost").all()];
}).then(objs => {
  objs.forEach(o => {
    console.log(`Touching ${o.id()}...`);
    o.update({
      tags: [
        ...o.get("tags").filter(t => {
          return t !== "Blogpost";
        })
      ]
    });
  });
  console.log("Done untagging all blogposts.");
});

For updating attributes of types other than stringlist, see Updating attributes in the SDK cheat sheet.

Modifying, replacing, or deleting widgets

The demand for being able to perform bulk operations for widgets as well is mostly motivated by the desire to change the look or behavior of all components of a specific type. If you suddenly wanted all headlines to be centered instead of left aligned, you would have a hard time changing them manually on a larger scale. So here’s how this can be done using a script:

Copy
Scrivito.load(() => {
  return [...Scrivito.Obj.where("_objClass", "equals", ["Page", "Homepage"])];
}).then(objs => {
  objs.forEach(o => {
    console.log(`Touching "${o.get("title")}" (${o.id()})...`);
    o.widgets()
      .filter(w => {
        return w.objClass() == "HeadlineWidget";
      })
      .forEach(headlineWidget => {
        headlineWidget.update({
          alignment: 'center'
        });
      });
  });
  console.log("Done aligning HeadlineWidgets");
});

The script calls the widgets() method, which returns a flat list of all the widgets on a page. The list is then filtered by the widget type we are looking for, in this case “HeadlineWidget”. Finally, we are iterating the remaining widgets to update them accordingly, i.e. set their alignment attribute.

You could even change the type of the widgets you are working on. If, for example, you wanted to replace the old “HeadlineWidget”s with your new “FancyHeadingWidget”s, just update their model class name and provide them with whatever attributes they are supposed to have:

Copy
...
      .forEach(headlineWidget => {
        headlineWidget.update({
          _objClass: 'FancyHeadingWidget',
          backgroundImage: image
        });
      });
...

This way, migrating widgets comes down to changing their class name and transferring or setting attribute values as needed.

The widgets() approach also lets you delete widgets from a page (independently of what they are contained in) by calling their destroy() instance method.

Traversing the widget hierarchy

Sometimes, only widgets contained in a parent widget of a specific type are meant to receive special treatment. In the (more extensive) example below, we are removing “DividerWidget”s from the Example App content, but only if they are contained in a “SectionWidget” or a “ColumnWidget” (and not in a “BoxWidget”, for example). Also, we’re limiting ourselves to the “Homepage” instance and “Page” instances:

Copy
function removeWidget(source, attributeName, widgetClassName) {
  var attributeValue = {};

  if (source instanceof Scrivito.Widget) {
    attributeValue[attributeName] = [
      ...source.get(attributeName).filter(w => {
        return w.objClass() !== widgetClassName;
        })
    ];
    source.update(attributeValue);
  }

  source.get(attributeName).forEach(w => {
    switch (w.objClass()) {
      case 'SectionWidget':
      case 'ColumnWidget':
        removeWidget(w, 'content', widgetClassName);
        break;
      case 'ColumnContainerWidget':
        removeWidget(w, 'columns', widgetClassName);
        break;
    }
  });
}

Scrivito.load(() => {
  return [
    ...Scrivito.Obj.where("_objClass", "equals", ["Page", "Homepage"])
  ];
}).then(objs => {
  const widgetClassName = 'DividerWidget';
  objs.forEach(o => {
    console.log(`Inspecting "${o.get('title')}" (${o.id()})...`);
    removeWidget(o, 'body', widgetClassName);   
  });
  console.log(`Done removing ${widgetClassName}s.`);
});

The code above first defines a generic function, removeWidget, for recursively removing widgets of the passed-in type (widgetClassName) from a widgetlist attribute – unless this attribute belongs to the page itself (in this case, source is a Scrivito.Obj). The function takes care of the container widgets we want to look into (e.g. “SectionWidget”s) by calling itself if it finds such a widget in the widgetlist attribute for which it was called.

Then, analogously to the tags example, the pages to be processed are determined. For each page in the resulting array, removeWidget is called for iterating the body widgetlist of the page.

In a nutshell ...

Whenever you want to change more than just a few pages or widgets, performing a bulk operation is the way to go. All that needs to be done is to fetch the relevant items and iterate them to update their attributes, for example. Keep in mind to always use Scrivito.load to determine all the CMS objects to process since this ensures that their data is available.