Simple View

Context

You have found some useful domain information to expose and have decided to implement a Custom View.

Problem:

How should you design a custom view to effectively communicate the information of interest?

Forces

— There are many different ways to design a view.

— It is hard to anticipate what information another Stakeholder would be interested in seeing.

— You can spend an arbitrary amount of time and effort implementing a rich, interactive visualization as a view.

Solution

Always start with the simplest kind of view that you can implement quickly to convey just the information you are interested in for your current task.

Steps

Enhance the simple view, or replace it by a richer view only when there is a clear need to do so.

This pattern follows exactly the same reasoning as the Goal/Question/Metric (GQM) approach for designing software metrics. A view, like a metric, only exists because it helps you to answer a question that supports you in achieving some goal. Views or metrics that do not support a specific goal or answer a concrete question have no reason to exist. It follows, then, that a view should be designed to answer a specific question you have in a development task. The simplest design that achieves this is the one you want. If new goals and questions arise, you will then either design new views, or extend or replace the existing views by richer ones.

Examples

GT supports a wide range of different kinds of views, ranging from simple textual lists and trees, to arbitrary visualizations. However, the most widely used view in the default environment is the forward view, which allows an object to reuse a view of a component or a collaborator. We can see this below, where a webpage object leverages the existing gtContentsFor: view of the underlying file to display the file’s contents. This is also the simplest view to implement, since it only requires, at a minimum, the specification of the target object (self webPageFile) and the view method to reuse (#gtContentsFor:).

The next most popular view is a columnedList, which presents a collection of data in a format resembling a spreadsheet with column headers. The Moves view of a Ludo game is an example. A common flow is to start with a forward view to an existing columnedList view of another object, then replace it by a custom columnedList when a need is identified to rename, replace or add columns.

NB: A raw view (to the right of the Moves view) is not a simple view. Although it is “simple” in the sense that it requires no effort to implement, it does not succeed in focusing your attention to the information of interest. With a raw view, it is almost always necessary to navigate through the view to find what you want. Although the Raw view and the Moves view are showing us the same information, only the the (simple) Moves view highlights this information in a clear and accessible way.

Similarly, the Board view of the Ludo game (first view above) is not a simple view, as it requires some effort to build up the interactive GUI view of the board. On the other hand, once we have such a view, it can be reused and repurposed, as we can see in the Move view of the Ludo Move object.

Other kinds of simple views exist to display plain text, simple lists without columns, and trees of hierarchical data. (See GT implementation notes below .)

Consequences

A simple view requires little effort to implement and can be quickly deployed and tested.

Simple views are easy to understand and extend or replace.

Related patterns

When you introduce a Custom View, you want to start with a Simple View.

GT implementation notes

GT supports a wide range of different kinds of views, ranging from simple textual lists and trees, to arbitrary visualizations.

forward

There are numerous kinds of Inspector views available. One of the simplest is the forward view, which defines a view to be forwarded to an existing view of another object. An example is the Lepiter page view of the file storing the contents of a Lepiter page, such as this one:

thisSnippet page database monitor pageFileReference: thisSnippet page
  

The code for this view is defined in AbstractFileReference>>#gtLepiterPageFor: gtLepiterPageFor: aView <gtView> (self isFile and: [ self extension = #lepiter ]) ifFalse: [ ^ aView empty ]. ^ aView forward title: 'Lepiter page'; priority: 10; object: [ LeLocalStoreLoad current loadPageFrom: self ]; view: #gtLiveFor: and just forwards the view to the existing Live view of the actual lepiter page (i.e., this page).

textEditor

Another trivial view is the textEditor view, such as is used in the default Print view of any Object. It's defined in Object>>#gtPrintFor: gtPrintFor: aView <gtView> ^ aView textEditor title: 'Print'; priority: 110; aptitude: BrGlamorousCodeEditorAptitude; text: [ self printString asRopedText ]; actionUpdateButton .

lists and trees

The most common non-trivial views are variants of the list and tree views. The second view of the address book example is a list view.

explicit and mondrian

An explicit view allows you to plug in any graphical element. This can be a simple view if the graphical element exists already, for example the Live view of the Memory game: GtMemoryGame>>#gtLiveFor: gtLiveFor: aView <gtView> ^ aView explicit title: 'Live'; priority: 10; stencil: [ self asElement ] . If you have to build the element, then it can be arbitrarily complex.

Similarly, a Mondrian view, such as the Circular view of the address book example above, can be quite simple, but in general visualizations can quickly become complicated.

It is common to start with a forward view, and then later replace it by a custom view, or to start with a very simple columnedList view, and gradually add more columns.

Consider a columnedTree if the list items can be group and organized into a tree.

To find examples of a given view type, you can use a query like this one.

#gtView gtPragmas & #columnedTree gtSenders
  

Don’t forget the empty view. This can be used as a placeholder for a new view you are creating, or as an option if the view is not needed in some cases. For example, the Items (files) view is empty if the file being inspected is not a directory: AbstractFileReference>>#gtItemsFor: gtItemsFor: aView <gtView> | eventLoop | self isDirectory ifFalse: [ ^ aView empty ]. eventLoop := self watcher startLoop. ^ aView columnedList title: 'Items' translated; priority: 10; updateWhen: GtFileWatcherChangeAnnouncement in: eventLoop announcer; items: [ self gtChildrenWithParent ]; column: 'Icon' translated icon: [ :each | each isDirectory ifTrue: [ BrGlamorousIcons folder ] ifFalse: [ BrGlamorousIcons file ] ]; column: 'Name' translated text: [ :each | (self isChildOf: each) ifTrue: [ '..' ] ifFalse: [ each basename asString ] ]; column: 'Size' translated text: [ :each | [ each isDirectory ifTrue: [ '--' ] ifFalse: [ each humanReadableSize asString ] ] on: FileException, FileSystemError do: [ :anException | anException return: '' ] ] width: 100; column: 'Creation' translated text: [ :each | [ String streamContents: [ :s | each creationTime printYMDOn: s. s nextPut: Character space. each creationTime printHMSOn: s ] ] on: FileException, FileSystemError do: [ :anException | anException return: '' ] ] width: 150; actionUpdateButton

A list of heterogeneous attributes can also be displayed as a columnedList by dynamically creating a dictionary of keys and values, and using those as the column headers. For an example, inspect the result of the query above, click on the (i) inspect button, and go to the Metrics view.