In Memri, renderers allow you to display data in a certain way. Renderers are configurable using CVU and in the previous tutorials you have already become familiar with the list renderer that displays items in a list, as well as the thumbnail renderer that displays items as thumbnails in a grid. Memri comes with several other renderers and more are on the way. These renderers give you access to powerful functionality from your CVU.

Map renderer

The map renderer can display items (or their properties or edges) on a map. It supports displaying Address and Location items and you can configure a label to display. In the screenshots below on the left addresses are displayed in the list renderer and on the right we see the same addresses displayed in the map renderer.

The CVU for this (almost trivial) example is very simple indeed. The code below specifies that the address is the item in the list using {{.}} and to display its computedTitle as the label.

[renderer = map] {
    address: {{.}}
    label: {{.computedTitle()}}
}

However you can make this more interesting. In the next example we'll plot Person items on a map based on their addresses, and we'll show their profile picture on the map. Since persons can have multiple addresses, we'll select all address items of a person using .address[]. For each address the VStack with an image and text element is rendered on the map showing the profile picture of the person and the computedTitle of the address.

[renderer = map] {
    address: {{.address[]}}
    
    VStack {
        Image {
            image: {{.profilePicture}}
        }
        Text { text: {{address.computedTitle()}} }
    }
    
    mapStyle: satellite
}

Lastly we configure the map to show satellite instead of the default map view.

Charts renderer

The chart renderer, as you guessed it, displays content on a chart. Memri currently supports bar charts and line charts (more are on the roadmap). In following example, we'll plot Person items on a bar chart based on their height. This is easily achieved using the following CVU code.

[renderer = chart] {
    chartTitle: "People by height"
    
    label: "{.firstName}"
    yAxis: {{.height}}
    
    hideGridlines: true
    yAxisStartAtZero: true
    
    sortProperty: height
    sortAscending: true
}

Currently all these settings can only be set via CVU. In the near future users can tweak these settings in the filter panel, to give even more control as a users views information.

Similar to the bar graph, we can create a line chart (or scatter chart, by not setting lineWidth) that displays the height vs age of a person using the following CVU.

[renderer = chart.line] {
    chartTitle: "Height vs. Age"
    chartSubtitle: "A demo of dynamically graphing your data."

    lineWidth: 1
    
    xAxis: "{.age()}" /* Age is calculated based on the birth date */
    yAxis: "{.height}"
    label: "{.firstName}"
}

General editor

The general editor is the editor that can display any item and allow a user to edit its fields and edges. In its simplest form the general editor displays one row for each property and allows a user to edit it using the most appropriate editor (e.g. a textfield for a string, or a checkbox for a boolean field).

The screenshots below show an Address (left) and an IndexerRun (right) rendered in the general editor. They both have a CVU that customizes how each is rendered. The general editor is broken into sections that group one or more fields or edges together. Sections can also be added that represent no fields or edges and draw something custom such as the map in the top of the address editor.

In our CVU we define a layout property that is an ordered collection of sections. Each section has a name that is used by default as the title of the section. The fields property determines which properties are included in the group, and * is used to render all the remaining fields. The exclude property tells the general editor to not render the field at all.

This is the CVU that produces the general editor for Address.

[renderer = generalEditor] {
    layout: [
        { section: location, exclude: locationWasAutomaticLookupWithHash }
        { section: labels }
        { section: address, fields: street city state postalCode country }
        { section: other, fields: * }
        { section: dates }
    ]
    
    location: {
        showTitle: false
        
        Map {
            address: {{.}}
            minHeight: 150
            maxHeight: 150
        }
    }
}

Note that the labels and dates section are cascaded from the base general editor CVU which you can find here.

More customization

Let's dive a bit deeper and explore how to customize the general editor even more. We'll look at the Person item and how it is defined to render in the general editor.

In addition to fields, the person editor heavily uses edges to display information about the person. The code example below has all the sections and many of them define one or more edges that are displayed. The type property tells the editor which type to add when the user wishes to add one or more items to the section while in edit mode. Edit mode is toggled by tapping the pencil icon in the top.

[renderer = generalEditor] {
    layout: [
        { section: profilePicture }
        { section: labels }
        { section: names, fields: firstName lastName }
        { section: picturesOfPerson }
        { section: phoneNumbers, edges: hasPhoneNumber, type: PhoneNumber }
        { section: relationships, edges: relationship, type: Person }
        { section: addresses, edges: address, type: Address }
        { section: websites, edges: website, type: Website }
        { section: companies, edges: company, type: Company }
        { section: diets, edges: diet, type: Diet }
        { section: medicalConditions, edges: medicalCondition, type: MedicalCondition }
        { section: publicKeys, edges: publicKey, type: PublicKey }
        { section: onlineProfiles, edges: onlineProfile, type: OnlineProfile }
        { section: other, fields: * }
        { section: dates }
    ]

    ...
}

By default edges are displayed as bubble buttons showing their type or edgeType and the computedTitle(). However the default rendering of these can be overwritten, as can the default rendering of fields. An easy example is how the starred field is rendered not as a checkbox (which is the default for boolean fields), but with a button with a star on it instead. The following CVU code is responsible for that:

starred {
    Action {
        press: star
    }
}

The name "starred" refers either to the field name (in this case) or to the name of the section (the example below). Below we render the OnlineProfile elements like regular editor rows. It's now very easy to customize it even further, for instance with a button that opens the website of that online profile.

onlineProfiles {
    EditorRow {
        title: "{.type}"
    
        HStack {
            Text {
                text: "{.handle}"
            }
            
            /* Example! URL opening is not yet implemented */
            Button {
            	openView {
                    url: {{.url}}
                }
            }
        }
    }
}

In addition to overwriting how these edges are rendered right in the Person definition there's another way to do so: By cascading the definitions for the specific item type. In fact, the bubble buttons (or memri buttons as we call them) are not hardcoded, but defined in CVU for any item that does not have its own definition. You can find that fallback in any.cvu.

As you can see in the screenshots above the address edges are rendered very differently as well. In fact, any item that is rendered in a general editor and has edges to Address items will render in that way. These definitions are not part of the Person definition, instead they are part of the Address[] definition.

SubView

The picturesOfPerson section is the last special section we cover. It's special because it does not render any fields or specific edges. Instead it is rendered as a SubView that queries to load all the photos that contain this person. This is what its definition looks like:

picturesOfPerson {
    title: "Photos of {.computedTitle()}"

    SubView {
        minHeight: 165
        
        view {
            defaultRenderer: thumbnail.horizontalgrid
            
            [datasource = pod] {
                query: "Photo AND ANY allEdges.targetItemID = {.uid}"
            }
            
            [renderer = thumbnail.horizontalgrid] {
                columns: 2
                edgeInset: 0
                allowBounce: false
            }
        }
        arguments: {
            toolbar: false
            searchbar: false
            readOnly: true
        }
    }
}

And with that we have a rich and dynamic viewer / editor for Persons in memri. You can apply these methods to CVUs you may make for a specific data type or application.

Roadmap

More renderers are in the pipeline. Work is underway on the following renderers:

  • Calendar renderer
  • Timeline renderer
  • Messages renderer
  • Various new chart renderers
  • Photo editor / viewer
  • Audio recorder / player
  • Video viewer
  • File viewers

If you have the skills we welcome any help to add more renderers!


Check out the new Memri forum where all Memri engineers are active to answer questions and participate in discussions. You can find all the Memri source code on the Memri gitlab.