Writing Attribute Editors

What are attribute editors?

Attribute editors are tools Scrivito presents to users to let them edit attribute content. Scrivito comes with string, html, enum, date and several other attribute types and includes a default, ready-to-use attribute editor for each of them. For example, strings can be edited using a text input field, enums (pick none or one of several) with a drop-down menu, dates with a date picker, and so on.

Whether you are happy or not with the built-in attribute editors, Scrivito gives you the freedom to write your own ones. Usually, custom attribute editors are specialized tools that you as a developer provide to your users for editing attribute values more intuitively and more comfortably. Custom attribute editors are meant to make it easier for users to understand the options to select from, to give them more context, to help them with making good content decisions.

How do custom editors integrate?

First things first. Let's take a look at how custom editors integrate and how users are supposed to interact with them.

Attribute editors make use of a slim yet powerful Javascript API which is part of the Scrivito SDK. The API consists of functions for fetching attribute values, saving modified values back to Scrivito, searching Scrivito content, creating and deleting content (pages and resources), etc. There are virtually no limits on the API functions you could use in your custom attribute editors.

In general, Scrivito lets users alter attribute values by means of text input areas or drop-down menus, depending on the individual attribute types. So, the main task of an attribute editor is to fetch the current attribute value from Scrivito, to properly show it to the user, to additionally make options, alternatives or help available, to wait for the user to provide input, and finally to save the value back to Scrivito.

An example walkthrough: Getting the big picture

Enum editor used for the headline widget

As an example, we are going to unravel the enum attribute editor built into the Scrivito SDK. We'll explain it step-by-step in order for you to grasp what makes an attribute editor.

The “Alignment” enum editor of the headline widget, as shown in the screenshot, is based on two things. First, an attribute declaration in the headline_widget.rb model file, e.g:

Copy
class HeadlineWidget < Widget
  attribute :alignment, :enum, values: %w(left center right)
  …
end
Second, to render the editor for the alignment attribute as a user opens the widget properties dialog of the headline widget, the following call to scrivito_tag is included in the headline widget's details.html.erb:
Copy
<%= scrivito_details_for 'Alignment' do %>
  <%= scrivito_tag(:div, widget, :alignment) %>
  …
<% end %>

The scrivito_tag helper renders a <div> element in which it renders the alignment attribute editor for the current widget. Scrivito determines from the attribute declaration in the model that the type of the attribute is enum and that the enum attribute editor is suitable for displaying and changing the value of such an attribute.

Scrivito then invokes the enum attribute editor to present the current value along with the other available values to the user, but it's ignorant as to how the editor does this. It just provides an API for fetching the current value and the available ones, and for permanently storing whichever value the user may choose. Besides this, Scrivito also sets up autosaving so that value changes are persisted automatically.

So, for the alignment attribute, the editor fetches the current value, "left", and the selectable values, "left", "center" and "right", via the Scrivito API, renders a <ul> element with an <li> for every selectable value, highlighting the current one by means of CSS, and installs a JavaScript click handler on the individual <li> elements. Whenever the user clicks one of the values, the click handler is triggered, causing the currently selected value to be stored in Scrivito. In our case, the internal editor state is part of the DOM.

Let's have a look at the actual enum editor code. It's written in CoffeeScript.

Copy
enum_editor =
  can_edit: (element) -> $(element).is '[data-scrivito-field-type=enum]'
  activate: (element) -> activate $(element)

scrivito.on 'load', -> scrivito.define_editor('enum', enum_editor)

activate = (cmsField) ->
  cmsField.html(renderTemplate(cmsField.scrivito('content'), cmsField.scrivito('valid_values')))
  cmsField.find('.scrivito_enum_editor li').on 'click', -> handleClick(cmsField, $(this))

renderTemplate = (value, validValues) ->
  ul = $('<ul class="scrivito_enum_editor"></ul>')
  for validValue in validValues
    li = $('<li></li>')
    li.text(validValue)
    if validValue is value
      li.addClass('scrivito_enum_active')
    ul.append(li)
  ul

handleClick = (cmsField, clickedItem) ->
  if clickedItem.hasClass('scrivito_enum_active')
    clickedItem.removeClass('scrivito_enum_active')
    save(cmsField, null)
  else
    cmsField.find('li').removeClass('scrivito_enum_active')
    clickedItem.addClass('scrivito_enum_active')
    save(cmsField, clickedItem.text())
  off

save = (cmsField, value) ->
  cmsField.scrivito('save', value)


Yes, that's all. Let's go through it line by line.

Declaring an editor

Copy
enum_editor =
  can_edit: (element) -> $(element).is '[data-scrivito-field-type=enum]'
  activate: (element) -> activate $(element)

scrivito.on 'load', -> scrivito.define_editor('enum', enum_editor)

These first lines declare a new editor, the enum_editor. Every editor must implement the can_edit and activate functions.

Scrivito calls can_edit to determine whether the editor is suitable for editing a specific attribute. Scrivito calls this function for every attribute in question and passes the corresponding attribute DOM element to it. Every attribute DOM element has a data attribute named data-scrivito-field-type. For enum attributes, the value of this data attribute is – you guessed it: enum. We can now see from the can_edit declaration above that the enum_editor handles attributes of the enum type.

The other function, activate, is the core of the editor implementation. It is responsible for interacting with the user by displaying values, wiring click handlers, and managing the internal editor state. In our code, it delegates to the activate function which is defined a few lines down.

Before we get to it, let's take a look at the scrivito.on 'load' line. It installs a handler which is invoked after Scrivito has been loaded for the current page, i.e. after the DOM has been loaded. This handler makes the editor declared above known to Scrivito. Without this defining call, Scrivito wouldn't take the newly declared editor into consideration for any attribute.

Editor activation

Copy
activate = (cmsField) ->
  cmsField.html(renderTemplate(cmsField.scrivito('content'), cmsField.scrivito('valid_values')))
  cmsField.find('.scrivito_enum_editor li').on 'click', -> handleClick(cmsField, $(this))



The activate function receives the Scrivito attribute DOM element, cmsField. This element acts as an anchor for the editor code to connect to the view and the position on the page where it should integrate itself. The element includes data attributes referring to the current state plus other internal Scrivito properties. Anyway, the editor's task is to replace the content of the DOM element with the DOM it wants to present to the user for editing the value. In our example, we set the HTML content to what the renderTemplate function returns. renderTemplate receives two arguments, the current attribute value (retrievable through cmsField.scrivito('content')), and the selectable values (through cmsField.scrivito('valid_values')). After setting up the DOM of the editor, we are going to install a click handler that is fired whenever a user clicks one of the <li> tags. We're going to take a look at how this handler is connected in a minute.

Rendering the items

Copy
renderTemplate = (value, validValues) ->
  ul = $('<ul class="scrivito_enum_editor"></ul>')
  for validValue in validValues
    li = $('<li></li>')
    li.text(validValue)
    if validValue is value
      li.addClass('scrivito_enum_active')
    ul.append(li)
  ul



renderTemplate builds the DOM representation of the editor. For an enum editor, this can be as simple as a <ul> list in which each enum value is represented as an <li> element. As a not as pretty but less space-consuming alternative, a <select> dropdown might have been used. When writing an editor, you're free to choose any representation you deem appropriate.

The CSS classes scrivito_enum_editor and scrivito_enum_active don't have any special meaning. They are used exclusively by the editor for connecting the click handler and for styling purposes.

Our editor uses the enum values as the contents of the <li> elements. This way, the internal state of the editor is kept by means of the <li> texts. One might, alternatively, use a special data attribute on the <li>s to keep track of the enum values represented by the individual <li> elements, should they be different (e.g. after localization).

Handling item selection

Copy
handleClick = (cmsField, clickedItem) ->
  if clickedItem.hasClass('scrivito_enum_active')
    clickedItem.removeClass('scrivito_enum_active')
    save(cmsField, null)
  else
    cmsField.find('li').removeClass('scrivito_enum_active')
    clickedItem.addClass('scrivito_enum_active')
    save(cmsField, clickedItem.text())
  off



Next, our enum editor implements handleClick. This event handler receives the Scrivito attribute DOM element and the <li> the user clicked. The task of the editor is to update the DOM to reflect the state change and to save the new value to Scrivito.

There are two cases we need to distinguish: The clicked <li> either represents the currently selected attribute value, or it doesn't. If the clicked <li> has the scrivito_enum_active CSS class (assigned to it by renderTemplate, see above), then it represents the current attribute value. In this case, the scrivito_enum_active class is removed to make it appear unselected in the browser. Immediately after that, save is called with a null value as the new attribute value.

Otherwise, if the clicked <li> represents one of the other enum values, the scrivito_enum_active class gets removed from the previously selected <li> and assigned to the one that was clicked. Then the clickedItem.text() is saved as the new attribute value. Remember, the available enum values are the same as the <li> text values.

Calling off, finally, prevents default browser click handlers from firing, if such handlers exist.

Saving the selected item

Copy
save = (cmsField, value) ->
  cmsField.scrivito('save', value)


As you can see, handling item selection includes a call to save to have Scrivito persist the currently selected enum value.

What's next?

The general procedure of writing an attribute editor is fairly straightforward: Declare and activate the editor, provide the code for rendering the attribute values, handle changes to them, save the new attribute value.

Some attribute types, particularly enum, multienum, or stringlist, are candidates for customizing their respective editor: you may, for example, want to install a mapping for translating attribute values to different display values depending on the active locale, a further attribute value, or the weather. A means to solve this has been indicated above: Carry the available attribute values with you using data attributes, and compute the texts to be displayed for them on the basis of the parameters of your choice. When saving, look up the value of the data attribute belonging to the selected item and store it as the new attribute value.

Happy editing!