Platform CLI
Learn
Developer docs User guide Quickstart CLI and AI agents Blog
Company
Services About Contact Links Get started

Detail views vs list views

Updated

A public route in Raytha shows either one content item (a detail view) or a published list of items of one content type (a list view). The two use different templates and a different Target. This page shows both, with paging and search.

The two kinds of route

Detail viewList view
What it showsOne content itemItems of one content type, filtered, sorted and paged by the view's settings
URLThe item's route path, for example /blog/helloThe view's route path, for example /blog
TargetOne itemA list result: Target.Items, Target.TotalCount and the paging variables
ContentTypeThe item's content typeThe view's content type
Template chosen byThe template bound to that item in the active theme, else raytha_html_content_item_detailThe template bound to that view in the active theme, else raytha_html_content_item_list
Not published404, unless you preview a draft404

Either one can be the home page. Pick it in the admin: a content item, a list view or a site page.

Detail view

Target is one content item:

MemberTypeNotes
Target.IdstringThe item id.
Target.PrimaryFieldstringThe value of the content type's primary field. Usually the title.
Target.RoutePathstringPath without a leading slash, for example blog/hello. Prefix with {{ PathBase }}/.
Target.PublishedContentobjectYour custom fields, keyed by developer name. Each is a field value object (see below).
Target.CreationTimedate (UTC)Format with organization_time.
Target.LastModificationTimedate (UTC) or nothingNothing when the item was never modified.
Target.CreatorUser, Target.LastModifierUserobject or nothingId, FirstName, LastName, FullName, EmailAddress.
Target.TemplatestringDeveloper name of the template that rendered the item.
Target.ContentTypeobjectSame as the top-level ContentType.

Read a custom field as Target.PublishedContent.<developer_name>, then pick the part you need. For a field named summary:

{% if Target.PublishedContent.summary.HasValue %}
  <p class="lead">{{ Target.PublishedContent.summary.Value | escape }}</p>
{% endif %}

The field object itself is always truthy once the field exists, even when it is empty. Test .HasValue. The full list of .Value types is in Template variables.

Previewing a draft

Open the item's route with ?previewDraft=true. If you are signed in and may edit that content type, Raytha renders the draft: Target.PublishedContent then holds the draft fields, despite its name. Anyone else gets the 404 template.

List view

The view decides what is listed. Its settings (in the admin, open the content type, then Views) are the route path, whether it is published, the stored filter and sort, the columns that search looks at, the default and maximum page size, and the template. A list view only ever returns published items.

MemberTypeNotes
Target.ItemsarrayItems for the current page. Each has the members listed under Detail view.
Target.TotalCountnumberItems matching across all pages.
Target.PageNumbernumber1-based. Values below 1 become 1.
Target.PageSizenumberThe size in effect after the view's limits.
Target.TotalPagesnumberceil(TotalCount / PageSize).
Target.PreviousDisabledCss, Target.NextDisabledCssbooleanPrevious is true on the first page, next is true on the last page, and both are true when there are no pages. The names say CSS, but the values are plain booleans.
Target.FirstVisiblePageNumber, Target.LastVisiblePageNumbernumberA window of at most four page numbers for a pager.
Target.Search, Target.Filter, Target.OrderBystringThe query parameters the visitor sent, or empty.
Target.RoutePathstringThe view's route path.
Target.Label, Target.DeveloperName, Target.DescriptionstringProperties of the view, not of the content type.

Query parameters

A list view reads these from the URL, and QueryParams also exposes them to the template:

ParameterEffect
pageNumberPage to show. Default 1.
pageSizeItems per page. When missing or 0, the view's default (25 unless you changed it). Capped at the view's maximum (1000 by default).
searchCase-insensitive "contains" match on the view's search columns. With no columns set, only the primary field is searched. If the search text is a number, a number column is compared for equality instead; the same applies to a checkbox column and true/false.
filterA filter expression that is combined with the view's own filter using AND. See Filtering and sorting.
orderByFor example PrimaryField asc. Replaces the view's sort.

If you tick Ignore client filter and sort query params in the view settings, filter and orderBy from the URL are dropped and only the view's own filter and sort apply. search, pageNumber and pageSize still work. Turn it on for any view where visitors should not be able to slice the data themselves.

A complete list template

<div class="container py-5">
  <h1>{{ Target.Label | escape }}</h1>

  <form method="get" action="{{ PathBase }}/{{ Target.RoutePath }}" class="mb-4">
    <input type="search" name="search" value="{{ Target.Search | escape }}" placeholder="Search">
    <button type="submit">Search</button>
  </form>

  {% if Target.Items.size == 0 %}
    <p>Nothing found.</p>
  {% else %}
    <p>{{ Target.TotalCount }} result{% if Target.TotalCount != 1 %}s{% endif %}</p>
    {% for item in Target.Items %}
      <article class="mb-4">
        <h2 class="h4"><a href="{{ PathBase }}/{{ item.RoutePath }}">{{ item.PrimaryField | escape }}</a></h2>
        <small>{{ item.CreationTime | organization_time: "%b %e, %Y" }}</small>
        {% if item.PublishedContent.content.HasValue %}
          <p>{{ item.PublishedContent.content.Text | strip_html | truncate: 200 }}</p>
        {% endif %}
      </article>
    {% endfor %}
  {% endif %}

  {% if Target.TotalPages > 1 %}
    <nav aria-label="Pages">
      {% unless Target.PreviousDisabledCss %}
        <a href="{{ PathBase }}/{{ Target.RoutePath }}?pageNumber={{ Target.PageNumber | minus: 1 }}&search={{ Target.Search | url_encode }}">Previous</a>
      {% endunless %}
      {% for i in (Target.FirstVisiblePageNumber..Target.LastVisiblePageNumber) %}
        {% if i == Target.PageNumber %}
          <strong>{{ i }}</strong>
        {% else %}
          <a href="{{ PathBase }}/{{ Target.RoutePath }}?pageNumber={{ i }}&search={{ Target.Search | url_encode }}">{{ i }}</a>
        {% endif %}
      {% endfor %}
      {% unless Target.NextDisabledCss %}
        <a href="{{ PathBase }}/{{ Target.RoutePath }}?pageNumber={{ Target.PageNumber | plus: 1 }}&search={{ Target.Search | url_encode }}">Next</a>
      {% endunless %}
    </nav>
  {% endif %}
</div>

The built-in raytha_html_content_item_list template is a Bootstrap version of the same thing. Pull the default theme and read it as a starting point (see Themes and the command line tool).

Choosing which template a view or item uses

Bindings are per theme. A theme holds its own set of templates, and each view and item is bound to one template of each theme. A web template only appears in the choices for a content type if that content type has access to it; new templates can be set to apply to all new content types. If an item or view has no binding in the active theme, Raytha uses the built-in detail or list template. Duplicating a theme copies the bindings, which is the safe way to start a redesign.

Gotchas

  • List views and get_content_items are separate. The view uses its stored filter, sort and search columns. get_content_items takes its own Filter and OrderBy and cannot search.
  • Target.Items can be empty on a page above the last one. ?pageNumber=99 renders the template with no items, not a 404.
  • The same name means different things: Target.Label in a list view is the view's label. Use ContentType.LabelPlural for the content type's name.
  • An item with no template in the active theme is rendered with the built-in detail template. After switching themes, check that bindings carried over.
  • An unpublished related item is still returned through a relationship field. Check IsPublished before you link to it.

Next steps