Test Enterprise-Class Web CMS Scrivito Free for 30 Days
Test Scrivito Free for 30 Days

Migrating Pages and Widgets Using Bulk Operations

Migrating Pages and Widgets Using Bulk Operations

Whenever it gets too time consuming to change a property of several individual 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 or widgets using bulk operations. We will be using the Example App as our basis, so you can test the code and adapt it to your liking.

Adding tags to pages

To begin, we 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. If you are using tags to achieve this, assigning the “Blogpost” tag to each of them is promising. You can do this using a single (admittedly quite long) statement in the JavaScript console:

  • Create or select a working copy,
  • open the console,
  • select the scrivito_application context,
  • and run this code:
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 fetch 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 example, we are iterating the objs 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 similar and even more challenging use cases.

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:

[...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:

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 desire to be able to perform bulk operations on widgets as well is usually motivated by the requirement to change the appearance or behavior of all components of a given 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. Using a script, however, this task can be accomplished in no time:

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 to the desired value.

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

... .forEach(headlineWidget => { headlineWidget.update({ _objClass: 'CatchyHeadingWidget', 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 DividerWidgets 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:

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 not a Scrivito.Widget but a Scrivito.Obj). The function takes care of the container widgets we want to look into (e.g. SectionWidgets) by calling itself if it finds such a widget in the widgetlist attribute for which it was called.

In the second part of the script, the pages to be processed are determined, analogously to the tags example. For each page in the resulting objs array, removeWidget is called for iterating its body widgetlist.

In a nutshell ...

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