Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

UncleCheese
16th January 2015

In the previous lesson, we developed a structure for our Travel Guides section that provides a list view of articles, each with a link to their own detail page. Most of these templates are still hardcoded with static content, however, and need to be integrated with the CMS. In this lesson, we’ll start looking at how to add some custom fields to these pages to make them really come to life.

Adding custom fields to a page

Level: Beginner

Duration: 12:33

In this lesson:

Taking inventory of custom fields

In the previous lesson, we developed a structure for our Travel Guides section that provides a list view of articles, each with a link to their own detail page. Most of these templates are still hardcoded with static content, however, and need to be integrated with the CMS. In this lesson, we’ll start looking at how to add some custom fields to these pages to make them really come to life.

Let’s first look at the list page and see if we can identify some fields that will be need to be editable in the CMS. At first glance, the following fields stand out:

The date of the article: Every record in is timestamped with its creation time, which is contained in the $Created variable. This could work in a pinch, but most of the time, content authors will want to use a custom date for the articles, since the date they are created is not necessarily they are published to the website. We’ll therefore need to create a custom field for the date.

The number of comments: We can skip this one for now, as that will be addressed when we cover adding comments to the articles.

The list of categories: This is another piece that we can cover in a future lesson that will cover handling data relationships.

The image: Each article should have its own image that will render as a thumbnail in list view and full size on the detail page. We’ll need the CMS to provide an image uploader for each article, which is a bit out of scope for this lesson, so we’ll skip it for now.

The teaser: The short snippet of text that appears in list view to give a preview of the article contents is often called a “teaser.” Often times, this is just the first sentence or paragraph of the article, but to give the content author more control, we’ll provide a custom field for the teaser. If it isn’t populated, we’ll fallback on the first sentence of the article. That will cover our bases nicely.

Clicking through to the detail page, we see that one additional field has been added for the author of the post.

Adding new database fields

Since all these fields appear on the ArticlePage records, we’ll only need to be working with that class. Remember, even though we see the fields on the ArticleHolder template, it doesn’t necessarily mean those fields belong to the ArticleHolder. In this case, they appear in a loop of ArticlePage records, so that’s where we’ll define them. It’s very important to maintain a clear conceptual separation of the template and the model.

Let’s define a new private static variable in ArticlePage.php called $db. Set it to an empty array.

class ArticlePage extends Page {

  private static $db = array ();

}

The $db array is the cornerstone of data modelling in SilverStripe. Its function is pretty simple -- it maps a field name to a field type. So the keys of the array are going to represent the variables that you can invoke on the object, such as $Content, $Title, etc, and the values of the array will be the field type. We’ll talk more about field types in just a moment, but for now, let’s populate this array with the fields we need.

class ArticlePage extends Page {

  private static $db = array (
    'Date' => 'Date',
    'Teaser' => 'Text',
    'Author' => 'Varchar'
  );

We’ve defined a field called $Date that will store, appropriately, as a date. The $Teaser field will be simple text, and the $Author field will also be text, but since we know it will contain a short string of characters (a person’s name), we can declare this as a Varchar, which by default is limited to 100 characters.

Since we made changes to the database, let’s run /dev/build and we should see some green text indicating that new fields were created.

About Field Types

If you’ve done a bit of databasing, you’re probably familiar with field types. Simply put, it’s a bit of metadata that informs the database what type of data will be stored in a given column. There are a number of reasons why this is a good idea. First, it wouldn’t make sense to allocate the same amount of memory to a field that will only ever contain a postal code as you would a field that will contain 100,000 words of content. Second, by assigning a field type, the database can interact with each column in its own way, for instance, two date fields will compare differently than two text fields, and trying to store the text “lol fail” in a decimal field will simply... fail.

Common field types in MySQL include:

  • Varchar: A string of characters, with variable length
  • Boolean: True or false values
  • Integer: A number, with no decimals
  • Date: The year, month, and day

And many, many more.

SilverStripe puts its own layer of abstraction over these database field types. While many of them overlap, such as “Varchar,” “Boolean,” and “Date,” a lot of them are unique to the SilverStripe Framework, as well. This is because a field type in SilverStripe not only informs the database how to store the data, but also how the template should display the data. For instance, a field with the type Text is will escape any HTML, but HTMLText will not. In the database, they’re both stored as Text, but the custom field type HTMLText has a very important effect on how SilverStripe handles the rendering of the data. We’ll talk more about all the features afforded by custom field types in future lessons, but for now, it is important to be mindful of what kinds of data you expect to be storing in each field and how you will display it.

So how do you know which field types are available? Your best resource in this case is the source code. Just browse framework/model/fieldtypes and you’ll see a list of PHP classes whose names can all be used as field types. As of version 3.1.8, they include:

  • Boolean
  • CompositeDBField
  • Currency
  • Date
  • Datetime
  • Decimal
  • Double
  • Enum
  • Float
  • ForeignKey
  • HTMLText
  • HTMLVarchar
  • Int
  • Money
  • MultiEnum
  • Percentage
  • PrimaryKey
  • Text
  • Time
  • Varchar
  • Year

It looks a bit daunting, but rest assured that 90% of the time, you’re going to using one of six or seven common field types.

Adding CMS Interface

Our database is ready to store these new fields on our page type, so now it’s time to offer the user a way to populate those fields in the CMS. Once again, we’ll be dealing strictly with the ArticlePage class for this.

Let’s define a new method that exposes the API for updating the CMS interface for this page.

class ArticlePage extends Page {
  private static $db = array (
    'Date' => 'Date',
    'Teaser' => 'Text',
    'Author' => 'Varchar'
  );

  public function getCMSFields() {
    $fields = parent::getCMSFields();

    return $fields;
  }

The method getCMSFields is what the CMS invokes to create all of the tabs and fields that we see in the editing interface. It should return a FieldList object. By storing the result of parent::getCMSFields() in a variable, we can start with all of the fields that are in the parent class. If we skipped this step, the page would be missing many critical fields, such as Title, Content, URLSegment, etc. We can always remove some of those fields piecemeal, but it’s best to start with everything in the parent class and make our changes from there.

In this case, we’re going to want three new fields. Let’s use the FieldList api to add some new form inputs.

  public function getCMSFields() {
    $fields = parent::getCMSFields();
    $fields->addFieldToTab('Root.Main', DateField::create('Date','Date of article'));   
    $fields->addFieldToTab('Root.Main', TextareaField::create('Teaser'));
    $fields->addFieldToTab('Root.Main', TextField::create('Author','Author of article'));

    return $fields;
  }

Let’s walk through this step by step:

addFieldToTab('Root.Main'): The tabs in a field list are identified using dot-separated syntax. This is because tabs can be nested in other tabsets, and digging through several levels to get the one tab set you want to add to would be tedious. The dot-separated string identifier allows you traverse the tab set ancestrally, where the highest level is on the left, and the tab you want to add to is all the way on the right. The 'Root' tab is the highest level tab set, assigned to all pages. Most fields in the CMS are on the “Main” tab, including Title, MenuTitle, and Content, but if you were to choose any arbitrary name, such as ‘Root.Sausages’, the FieldList would automatically create that new tab for you.

[FormField]::create(): Every form input has its own class in SilverStripe, and the number of available form fields reaches far beyond just those that are in the HTML5 specification, for instance, the rich text editor you see in the CMS is an HtmlEditorField. Form fields aren’t about HTML abstraction so much as they are about creating a UI for storing data. It is not uncommon in a SilverStripe project to create your own custom form fields that offer the rich editing experience you’d like to have.

The first argument of the form field constructor is the field name. This should correspond exactly to the $db field it is saving to. The second argument is an optional label for the field. If left null, the label will be the name of the field, as we see in the case of our Teaser field.

Let’s go into the CMS and edit any ArticlePage. Notice our new fields at the bottom of the page. Try populating them, and ensure that it saves state.

There are a couple usability issues we can address here. One is that the fields are all pinned to the bottom of the page, below the content editor, making it difficult for the user to find. Fortunately, the addFieldToTab() method accepts an optional argument to specify an existing field that the new field should come before. In this case, we want these fields before the Content field.

  public function getCMSFields() {
    $fields = parent::getCMSFields();
    $fields->addFieldToTab('Root.Main', DateField::create('Date','Date of article'),’Content’);
    $fields->addFieldToTab('Root.Main', TextareaField::create('Teaser'),’Content’);
    $fields->addFieldToTab('Root.Main', TextField::create('Author','Author of article'),’Content’);

    return $fields;
  }

Second, the date field is a bit awkward. It’s not clear what date format it’s expecting, and asking the user to know the date rather than simply pointing to one on a calendar isn’t very friendly. Let’s configure the date field to show a calendar.

  public function getCMSFields() {
    $fields = parent::getCMSFields();
    $fields->addFieldToTab('Root.Main', DateField::create('Date','Date of article')
          ->setConfig('showcalendar', true)
      ,'Content');
    $fields->addFieldToTab('Root.Main', TextareaField::create('Teaser'),'Content');
    $fields->addFieldToTab('Root.Main', TextField::create('Author','Author of article'),'Content');

    return $fields;
  }

Every form field exposes a public API that you can use to configure the field. In this case the setConfig method is native to the DateField class, but other fields expose different methods. On a text field, for instance, you might call setDescription(), or setMaxLength().

Reload the edit screen in the CMS and see that our changes have taken effect.

Adding the fields to the template

Most of the hard work is done now. It’s time to insert all the variables in to our templates to pull in the CMS content. In ArticleHolder.ss and ArticlePage.ss replace any references to the date with $Date, the author with $Author, and the teaser with $Teaser. Reload the page to see the the articles are pulling in the new content.

One issue we can see is that the date is not formatting the way it should. Since we casted the field as Date, we have some control over that at the template level. The Long method will give us what we need. Replace $Date with $Date.Long.

There are a number of other methods available on the Date class to help you get the format you want. You might try $Date.Nice, or, if you’re in the USA, $Date.NiceUS for a format that puts the month first. If all else fails, you can always invoke $Date.Format which essentially exposes PHP’s date() method, allowing you to pass a string of text to get the format you need.

Lastly, we said earlier that we would like the teaser to be an optional field, falling back on the first sentence of the content if it isn’t populated. Let’s set that up.

    <div class="info-blog">
      <ul class="top-info">
        <li><i class="fa fa-calendar"></i> July 30, 2014</li>
        <li><i class="fa fa-comments-o"></i> 2</li>
        <li><i class="fa fa-tags"></i> Properties, Prices, best deals</li>
      </ul>
      <h3>
        <a href="$Link">$Title</a>
      </h3>
        <% if $Teaser %>
          <p>$Teaser</p>
        <% else %>
          <p>$Content.FirstSentence</p>
        <% end_if %>
    </div>

All text-based field types in SilverStripe offer methods such as FirstSentence, FirstParagraph, LimitCharacterCount, and more, to give you some control over the presentation on the template. Using these methods to create teasers is very common.

Questions and Feedback

Is there a list of all the fields types with possible configurations and explanations/examples somewhere? IE: I want to change the size of a HTMLtext field. How do I configure that so say, half the height of the default. I only want this on one page. Thank you.

by Ruth at 03:35am, 23 March 2015

Author

Hi, Ruth,

So just to make sure the concept is clear, there are two components to saving data in the CMS

  • The fieldtype: How the record is stored and read from the database (HTMLText, Varchar, Int, Currency, etc..)
  • The FormField: How the user experiences editing that field (TextField, TextareaField, HtmlEditorField, etc..)

Your question is about the user experience part of it -- i.e. how many rows are in the field. The size of the HTMLText field in the database is pretty much limitless.

To adjust the rows of the HtmlEditorField, you can use ->setRows().

$fields->addFieldToTab('Root.Main', HtmlEditorField::create('MyField')
    ->setRows(5)
);

by UncleCheese at 04:21pm, 23 March 2015

Thank you so much for the clarification. Your example was perfect too. Is there somewhere I can find additional information of how to configure the various formfields?

by Ruth at 01:56am, 25 March 2015

Author

Hi, Ruth,

Your best bet for documentation at that level is going to be the API docs. For instance, here is all the documentation for the core FormField class: http://api.silverstripe.org/3.1/class-FormField.html

TextareaField is here: http://api.silverstripe.org/3.1/class-TextareaField.html

I know it's a bit crude, but it's challenging to have thorough documentation on each class. Each class aims to be very well documented in its source code, so we typically rely on that for a method-by-method tutorial of a given class.

by UncleCheese at 10:56am, 25 March 2015

UncleChees those links doesn't work man

by Erick at 03:33am, 11 April 2015

Author

What do you mean they "don't work?"

by UncleCheese at 04:14pm, 11 April 2015

Is there a way to change the language of the calendar? $fields->addFieldToTab('Root.Main', DateField::create('Date','Date of article') ->setConfig('showcalendar', true)

I saw this, at the documentation: http://api.silverstripe.org/3.1/class-DateField.html

But I have no idea how to use that, thx.

by Erick at 07:46am, 11 April 2015

Author

Hi, Erick,

By default, the DateField will configure itself to the locale set in i18n::get_locale(). In the CMS that should be set to whatever the locale is in your user profile. If you would like to force the locale to something other than the user's preference, you can use setLocale() on the DateField.

by UncleCheese at 04:17pm, 11 April 2015

Hi UncleCheese i made something like $fields->addFieldToTab('Root.Main', DateField::create('Date','Date of article') ->setConfig('showcalendar',true),'Content') ->setLocale('es');

by Erick at 12:22am, 14 April 2015

But it doesn't work

by Erick at 12:23am, 14 April 2015

Author

Try ->setLocale('es_ES').

by UncleCheese at 02:35pm, 14 April 2015

Why is it that to add a field to the content tab you have to put 'Root.Main'? Could you go maybe a little more in depth here? how would I add a field to the settings tab? is there a place I can look to see what all the default tab names actually are (knowing there are only 2 or three, don't know if the history tab is it's own thing or not)?

by BuckeyeSam89 at 07:19am, 1 June 2015

Author

Hi, BuckeyeSam89. Re: Root.Main, this is explained around 7:10 in the video. It's a bit tricky to explain, so I'll have another go at it here. Tab objects are part of a parent TabSet object, the same way form fields are part of a parent FieldList object. Tab sets can be nested, so you can imagine how cumbersome it would be to add a field to a tab that is several levels deep:

$fields->getTabSet('TabSet1')->getTabSet('TabSet2')->getTab('Content')->addField();

*NB Those methods don't actually exist. This is just an example.

To avoid the complication of traversing several objects, addFieldToTab accepts a dot-separated name to identify the tab in the hierarchy using a string. In the above example, it would be TabSet1.TabSet2.Content. It's much like how you can identify a tree in the SiteTree using slash-separated syntax. about-us/staff/unclecheese.

Does that make a bit more sense?

Regarding your question on updating the "Settings" tab, there is an API for that. Use getSettingsFields() exactly the same way you use getCMSFields(). There is no API for the "History" tab, however, and the top three tabs themselves are immutable. You cannot, for instance, add a fourth tab to that level. Main and Settings are the only two that are exposed to you.

by UncleCheese at 11:37am, 2 June 2015

Yes that makes much more sense now. Thank you!

by BuckeyeSam89 at 08:16am, 3 June 2015

Is it possible to move the default form fields such as URL segment to be below navigation label? How would I even find out the field name of "URL segment"?

by BuckeyeSam89 at 09:15am, 1 June 2015

Author

To get the name of the field, the easiest way is to inspect the element in your web developer toolbar and check the "name" attribute of the input. Another way is to look at the SiteTree class definition and look at the $db array. Remember, form field names always align with the $db fields that they update. In this case, you want URLSegment.

To move form fields, you can use the second argument of addFieldToTab(), which is the name of the field that the new field should be inserted before. So you'll want to add all your fields before Title.

$fields->addFieldToTab('Root.Main', TextField::create('MyCustomField'), 'Title');

by UncleCheese at 11:45am, 2 June 2015

This also helps more thanks!

by BuckeyeSam89 at 08:17am, 3 June 2015

Hi I have the i18n::set_locale('da_DK'); in my _config.php. And it's working fine.

But in ArticleHolder, when I use $Date.Long - I get the english version of the month, not the danish.

I have tried $Date.Long-> set_locale('da_DK'), but it's not working.

How can I get the danish month names where I use $Date.Long in ss files?

by Kim K at 05:16am, 12 February 2016

Hi, Kim,

I think you need to check your PHP locale setting. See http://php.net/date

by UncleCheese at 03:34pm, 9 March 2016

With the field types i want to use mysql's Geometry field and create it through the dataobject but there is not one in silverstripe so how do i extend the DBField to allow for my new datatype so that i can use it in the dataobject creation? Is there a tutorial on this?

by Lee at 02:57pm, 13 July 2016

Am I wrong to think that LimitCharacterCount() has been replaced by LimitCharacters() ? I haven't found LimitCharacterCount anywhere else than this tutorial. Silverstripe's documentation and people's code talk about LimitCharacters. That's what I've tried and it worked.

by Samuel Carpentier at 02:05pm, 14 November 2016

Very useful tutorial on adding a Staff Profiles section to your Silverstripe site here: https://docs.silverstripe.org/en/3.3/tutorials/extending_a_basic_site/

Used it before and it worked fine. I have a page called StafffHolder.php and one called StaffPage.php. Have just added it to another site -- the CMS backend part works fine -- images have uploaded to /assets/uploads but are not showing on the page??

I’ve also reviewed the video tutorial https://www.silverstripe.org/learn/lessons/adding-custom-fields-to-a-page?ref=hub which explains how to add different filed types to the CMS very nicely — unfortunately this doesn’t extend to images…

As per the tutorial, code to display images is

$Photo.ScaleWidth(150)

No sign of an image on my page. Have I missed something??

thanks in advance! mike

by mike at 02:43pm, 24 November 2016

Stuck on something? Have something to share? Don't be shy!

Keep learning!

The holder/page pattern

In this tutorial we’re going to focus on the Travel Guides section of our website for this topic. As we can see in the designs, there...

Working with Multiple Templates

Typically a site has more than one distinct template used across all pages. The home page, for instance, is likely to have a different layout than...

Adding Dynamic Content

In this tutorial, we’ll start using SilverStripe’s native template syntax to inject dynamic content into our site, such as navigation, page titles, CMS content, and...