Scheduling and Expiring Page Content

When creating content for the web and other media, it’s a common practice to prepare pieces in advance and publish them on a specific date: news articles, business reports, etc., may include information not to be disclosed before that date. Sometimes, such scheduled content additionally needs to be deactivated after some time, e.g. time-limited offers.

With Scrivito, scheduling and expiring pages can be achieved with the help of date attributes for capturing a validity period. In this tutorial, we are going to add two such attributes, validFrom and validUntil, to the page types of the Example App and illustrate how to take them into account when rendering pages.

Add date attributes and make them editable

This guide is based on the assumption that all pages independently of their type should be restrictable with respect to their temporal validity.

To be able to optionally assign a validity start and end date to any page, we’ll first add the two attributes mentioned above, validFrom and validUntil, to all page model classes. Luckily, the Example App lets us define a defaultPageAttributes object, which is imported into every page class. Thus, we don’t need to touch all the page classes individually but can simply add the attributes to the defaultPageAttributes:

Validity start and end dates in the page properties

src/Objs/_defaultPageAttributes.js
Copy
const defaultPageAttributes = {
  body: ['widgetlist', { only: 'SectionWidget' }],

// ...

  validFrom: 'date',
  validUntil: 'date',
};

export default defaultPageAttributes;

The same applies to the editing configuration of pages, i.e. the attributes to be made available in the page properties. The default ones can be defined using the defaultPageEditingConfigAttributes object to which we are adding our validity attributes:

src/Objs/_defaultPageEditingConfig.js
Copy
const defaultPageEditingConfigAttributes = {

// ...

  validFrom: {
    title: 'Valid from',
  },
  validUntil: {
    title: 'Valid until',
  },
};

/ ...

const defaultPageProperties = [
  'title',
  'navigationHeight',
  'navigationBackgroundImage',
  'navigationBackgroundImageGradient',
  'validFrom',
  'validUntil',
];

// ...

Now every page features a validity period, specifiable using the validFrom and validUntil attributes.

Check the dates …

Where should the code go that checks the validity of a page and reacts accordingly? To be able to maintain the code in one place, we could extend a higher-level component, e.g. App, which renders the current page.

In the end, to keep it simple and provide a guiding line, we’ve placed the validity check into an individual component of a CMS object class, Page, knowing that we can still move the parts common to other page types into a component or a function later on.

So here is our extended Page component, which now displays some text if the current date doesn’t lie within validFrom and validUntil – unless we are in editing mode!

src/Objs/PageObjClass.js
Copy
import * as React from 'react';
import * as Scrivito from 'scrivito';

Scrivito.provideComponent('Page', ({ page }) => {
  const validFrom = page.get('validFrom');
  const validUntil = page.get('validUntil');
  const editing = Scrivito.isInPlaceEditingActive();
  const now = new Date();
  const isValid = (
    (now >= validFrom) && 
    (!validUntil || now <= validUntil)
  );
  const validityInfo = (editing && !isValid) ? (`
    Not in validity period from
    ${validFrom ? validFrom.toLocaleDateString('en') : '…'}
    until
    ${validUntil ? validUntil.toLocaleDateString('en') : '…'}
  `) : '';

  if (isValid || editing) return (
    <div>
    <div className='text-center strong text-danger'>{ validityInfo }</div>
  	<Scrivito.ContentTag tag="div" content={ page } attribute="body" />
  	</div>
  	);

  return (
	<div className='text-center text-primary'>
     <h1>Sorry!</h1>
     <h2>This offer is currently not available.</h2>
    </div>
  );
});

Pages remain editable even if they aren’t valid

In preview mode, some text is displayed

If the page is not temporally valid and in-place editing is active (Scrivito.isInPlaceEditingActive()), the page is still displayed normally, but a corresponding message is displayed at the top to indicate this to the editor.

In preview mode, some hard-coded text is displayed instead of the real page content if the page is invalid. Of course, you could also render a component here.

Note that both dates can be null (no date specified), and that this case needs to be handled only for validUntil to make this value mean “forever”.

That was it - almost

Keep in mind that invalid pages are still accessible via the browser console. So, if you need to conditionally deliver sensitive content, make sure to secure it, e.g. by placing it on a separate system and fetching it on demand.

If you want to exempt invalid pages from navigations, the Scrivito.ChildListTag component lets you define a function (renderChild) for rendering the links to the individual child pages. You can apply your validity checks in this function.

You might want to add one day when checking validUntil because the specified date refers to 00:00 am.

For excluding invalid pages from search results, the query needs to be extended so that it checks the date attributes. In the Example App this could be done like this:

src/Objs/SearchResults/SearchResultsComponent.js
Copy
// Imports ...

class SearchResultsComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { maxItems: 10, now: new Date() };

    this.incrementMaxItems = this.incrementMaxItems.bind(this);
    this.calculateResults = this.calculateResults.bind(this);
  }

  calculateResults() {

// ...

    let search = Scrivito.Obj
      .where('*', 'containsPrefix', this.props.params.q)
      .andNot('validFrom', 'isGreaterThan', this.state.now)
      .andNot('validUntil', 'isLessThan', this.state.now)
      .andNot('_objClass', 'equals', blacklistObjClasses);

// ...
  }

// ...

We’ve added two andNot subqueries to the search query for dropping invalid hits based on our validFrom and validUntil attributes.

The current date is stored to a state variable, now, in the constructor, so that the query remains the same throughout the lifetime of the component, causing subsequent hits to be displayed faster.

Last but not least, if you want to apply the same validity checks to instances of more than one page type, consider using a higher-order component (HOC) that wraps the validity logic around whichever page type you choose.

In a nutshell ...

Restricting the display of page content to a definable time slot, or even scheduling alternative versions, can be broken down to

  • defining the required number of meaningful date attributes in the page model classes,
  • adding the attributes to the editing configuration of those classes,
  • and implementing the conditional display in the page components where needed.

You might have already noticed that all conditional rendering follows this pattern. So, for example, letting editors show or hide a header or a footer requires nothing more than an editable criterion (an attribute) and some lines of code acting on it.