ViewModels

In the Model-View-ViewModel pattern, the ViewModel receives raw data from the Model and transforms that data to produce a View. Brightspot’s ViewModels accomplish this transformation in two steps. In the first step, the ViewModel receives two categories of data:

  • Content, such as text, images, and videos available through methods in a received model object.
  • Web environment, such as HTTP header fields, query strings, and cookies available through annotations.

The ViewModel implements methods specified in an underlying Java interface such that there is one method for each item appearing in the View. For example, if a View has three elements (headline, body, and date published), the ViewModel includes three methods to return each element individually. If those elements require additional logic, such as instantiating a locale to properly format a date, the ViewModel implements the corresponding methods and constructors accordingly.

In the second step, the ViewModel transforms the extracted data into a form the View is expecting.

In the following example, the ViewModel uses the getHeadline and getBody methods to access those Model elements; it also uses the @HttpHeader annotation and getDatepublished method to access and format the publication date in a manner indicated by the client’s locale.

package content.article;

import com.psddev.cms.view.ViewModel;
import com.psddev.cms.view.servlet.HttpHeader;
import java.util.Locale;
import java.text.DateFormat;
import java.util.Date;

public class ArticleViewModel extends ViewModel<Article> {

   /* Return the article's headline. */
   @Override
   public String getHeadline() {
      return model.getHeadline();
   }

   /* Return the article's body. */
   @Override
   public String getBody() {
      return model.getBody();
   }

   /*
   Extract the Accept-Language field from the HTTP header.
   The first five characters of this field provide the locale,
   such as 'en-us.'
   */
   @HttpHeader("Accept-Language")
   public String acceptLanguage;

   /*
   The getDatePublished method returns a date in a format as expected
   by the client's locale prepended with the string 'Date published: '.
   */
   @Override
   public String getDatepublished() {

      /* Retrieve the date published from the Model. */
      Date datePublished = model.getDatePublished();

      /*
      Extract the first five characters from the Accept-Language field
      to get the client's language and country.
      */
      String httpLocale = this.acceptLanguage.substring(0,5);

      /*
      Declare a standard Locale variable; set it to en-us or en-au depending
      on the characters we extracted from the Accept-Language field.
      */
      Locale readerLocale = null;
      if (httpLocale.equals("en-US")) {
         readerLocale = new Locale("en","us");
      } else {
         readerLocale = new Locale("en","au");
      }

      /*
      Instantiate a DateFormat variable that formats the date based on the
      discovered locale.
      */
      DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, readerLocale);

      /* Return the formatted date prepended with the string 'Date published: '. */
      return "Date published: " + df.format(datePublished);

    }

}

The following image is an example of the previous snippet’s output.

../../../../_images/vm-output.png

You can modularize your code by implementing the interface PageEntryView—a marker interface you can use to determine the required ViewModel based on the Model’s class or other criteria. See the following example.

1
2
3
4
5
6
7
8
public class ArticleViewModel extends ViewModel<Article> implements PageEntryView {

   if ((model instanceof DailySection) || (model instanceof WeeklySection)) {
      return createView(SectionViewModel.class, model);
   } else {
      return createView(ArticleViewModel.class, model);
   }
}

For information about the createView method in lines 4 and 6, see createView.