Support and Documentation

Advanced view modeling

This section describes some advanced techniques for working with view models.

ModelWrapper

Some models wrap models which themselves wrap models. For example, an article can contain two types of image models, one a closeup and another a panorama. Each of those models wraps an image model that is comprised of the raw image and a caption. For the article to access the image—

  1. The article first accesses the intermediate model (CloseUp or Panorama).

  2. From the intermediate model, accesses the image itself.

Brightspot provides a convenience function unwrap that saves the second step. Placing unwrap in the intermediate model allows access to the image directly.

unwrap-diagram.svg

Referring to the previous illustration—

  • Article includes an abstract marker class ImageOption. This is a useful technique for easily including additional classes into Article: any concrete subclass of ImageOption automatically appears as an option in Article.

  • Each concrete subclass of ImageOption, CloseUp, and Panorama implements unwrap. This method returns the Image wrapped in either model.

The following example describes how to implement unwrap as envisioned in the previous illustration.

Step 1: Implement low-level model

import com.psddev.cms.db.Content;
import com.psddev.dari.util.StorageItem;

public class Image extends Content  {

    private StorageItem rawImage;

    private String caption;

    /* Getters and setters */

}

Step 2: Implement intermediate-level models

Example 30. Model for CloseUp
public class CloseUp extends ImageOption {

    @Embedded
    private Image image;

    public Image getImage() {
        return image;
    }

    @Override
    public Object unwrap() { 1
        return getImage();
    }
} 

1

Implements the unwrap method. When accessing CloseUp, a parent model can immediately access the lower-level Image.



Example 31. Model for Panorama
public class Panorama extends ImageOption {

    private Image image;

    public Image getImage() {
        return image;
    }

    @Override
    public Object unwrap() { 1
        return getImage();
    }

}

1

Implements the unwrap method. When accessing Panorama, a parent model can immediately access the lower-level Image.



Step 3: Implement abstract model

import com.psddev.cms.db.Content;
import com.psddev.cms.view.ModelWrapper;
import com.psddev.dari.db.Recordable;

@Recordable.Embedded
public abstract class ImageOption extends Content implements ModelWrapper, Directory.Item {

}

The previous snippet is an abstract marker class. Any of its concrete subclasses become available to its enclosing class.

Step 4: Implement top-level model

import com.psddev.cms.db.Content;

public class Article extends Content implements Page, ImageOption {

    private ImageOption imageOption; 1

    public ImageOption getImageOption() { 
        return imageOption;
    }

}

1

Declares the imageOption field. Because ImageOption is an abstract class, any concrete subclass becomes an option for Image Option in the content edit form.

selections-from-abstract-marker-class.png

Step 5: Implement low-level template

This file is saved as Image.hbs.

<div>
    <img src="{{imageSrc}}">
</div>
<div>
    {{caption}}
</div>

Step 6: Implement low-level data file

This file is saved as Image.json.

 {
     "_template": "Image.hbs", 1
     "imageSrc": "http://url/to/any/image.jpg",
     "caption": "Static caption"
 }

1

Provides the link between the image's view and the template.

Step 7: Implement low-level view model

The following snippet provides the data for the embedded image's view.

import com.psddev.cms.db.ImageTag;
import com.psddev.cms.view.ViewModel;
import styleguide.content.article.ImageView;

public class ImageViewModel extends ViewModel<Image> implements ImageView {

    public String getImageSrc() { 1
        return new ImageTag.Builder(model.getRawImage()).toUrl();
    }

    public String getCaption() { 2
        return model.getCaption();
    }

}

1

Retrieves the URL.

2

Retrieves the caption.

Because of the implementation of unwrap, there is no need to create a view model for the intermediate-level CloseUp or Panorama.

Rendering rich text with RichTextViewBuilder

By default, Brightspot's rich-text editors store escaped HTML tags. For example, rich-text editors store <b>bold text</b>&lt;/b&gt; as &lt;b&gt;bold text&lt;/b&gt;. To ensure unescaped HTML tags appear in your rendered pages, you need to use the RichTextViewBuilder.buildHtml method. The following example shows how to implement a rich-text editor and render its output.

Step 1: Render string field as rich-text editor

import com.psddev.cms.db.Content;
import com.psddev.cms.db.ToolUi;

public class Article extends Content implements Page {

    @ToolUi.RichText
    private String body;

}

In the previous snippet, the annotation @ToolUi.RichText renders the body field as a rich-text editor. For detailed information about customizing the appearance and functionality of the rich-text editor, see Rich text.

rte-annotation.png

For additional information about this annotation, see @ToolUi.RichText.

Step 2: Implement templates

The following file is saved as Article.hbs.

<div>
    {{headline}}
    {{body}}
</div>

The following file is saved as Article.json.

{
  "_template": "Article.hbs",
  "headline": "This is a placeholder for the headline",
  "body": "This is a placeholder for rich text"
}

Step 3: Implement view model

 import com.psddev.cms.rte.RichTextViewBuilder;
 import com.psddev.cms.view.ViewModel;
 import styleguide.content.article.ArticleView;

 public class ArticleViewModel extends ViewModel<Article> implements ArticleView {

     @Override
     public CharSequence getBody() { 1
         return RichTextViewBuilder.buildHtml(model.getBody(), 2
                 rte -> createView(RichTextElementView.class, rte));
     }

 }

1

Implements the getBody method declared in the view interface.

2

Takes the escaped HTML from the model's body and combines it with a standard rich-text view class to produce unescaped HTML.

rendered-rich-text.png
Conditional redirect

Because the onCreate event occurs before Brightspot generates any of the view's components, you can use it to perform conditional redirects.

import com.psddev.cms.view.ViewModel;
import com.psddev.cms.view.ViewResponse;

public class ArticleViewModel extends ViewModel<Article> implements ArticleView {

    protected void onCreate(ViewResponse response) { 1
        if (somestatement == true) {
            response.redirectTemporarily("http://domain/temporary-redirect-page.html");
            throw response; 2
        }

    }

}

1

The ViewResponse parameter passed to onCreate contains a variety of fields you can initialize and send back to the client. For details, see View Response.

2

Terminates the onCreate method, so Brightspot does not create the view.