Developer Guide: Creating Page Types

From Mothership
Jump to: navigation, search

Creating a new page type

A page type determines what may be displayed on a particular page in your Mothership installation, including what content fields will be present when editing it. When you install Mothership using the installer, some page types will automatically be packaged with your installation, these are:

  • Generic - A page with some basic content fields
  • Home - A page designed to be a landing page for people visiting your site
  • Product - A page for displaying and selling products
  • Product listing - A page for displaying a listing of product pages
  • Redirect to first child - Pages are nested, so pages can have child pages. This is useful when generating menus. The Redirect to first child page type is a 'magic' page that will automatically redirect users to the first subpage.

PageTypeInterface methods

A page type is represented by a PHP class that implements the Message\Mothership\CMS\PageType\PageTypeInterface interface. This means that the class must have the following methods:

getName()

Should return a string that represents the page in the back end. This name of each page type should be unique so it might be an idea to 'namespace' it by starting them all with something like 'my_site_'.

The method should look something like:

public function getName()
{
    return 'my_site_newpagetype';
}

getDisplayName()

Should return a human readable name for the page. This is what will be displayed when selecting your page type on the page create screen. It should look something like:

public function getDisplayName()
{
    return 'New Page Type'
}

getDescription()

Should return a human readable string describing what the page is for. This will be displayed below the display name when selecting a page type on the page create screen. It should look something like:

public function getDescription()
{
    return 'A new page type made specifically for this installation'
}

allowChildren()

Should return a true or false value depending on whether or not it should be possible to create child pages for this page. Should look something like:

public function allowChildren
{
    return true; // Will allow child pages to be created from this page
}

getViewReference()

Should return a string stating where to find the '.html.twig' view file. This is comprised of two main parts: the namespace, and the path to the file within that namespace's view directory. These are separated with a double colon (::).

To render a page, you need to create a view file, which uses the Twig templating engine. For this example, we are going to assume that you created the view file under /app/site/resources/view/page_type/newpagetype.html.twig

The default namespace for a Mothership installation is Mothership:Site. For more information on namespaces, see the namespace guide.

The path maps the view file from within the namespace's view directory, which by default is /app/site/resources/view, so in our case this would be page_type/newpagetype.html.twig. However, because this is a representation, we replace the slashes with colons, and we can omit the .html.twig, so the path is actually page_type:newpagetype.

Therefore, our method will end up looking like this:

public function getViewReference()
{
    return Mothership:Site::page_type:newpagetype
}

setFields(Message\Cog\Field\Factory $factory)

The `setFields` method is where content fields are set on the page type. These are the fields displayed when editing the page.

The most commonly used fields are 'text', 'richtext' and 'file'. There are also 'product' fields and 'productoption' fields. Adding a field to the pagetype is done using a combination the factories `add` and `getField` methods.

For example:

$factory->add($factory->getField('file', 'thumbnail', 'Thumbnail'));
$factory->add($factory->getField('text', 'title', 'Title'));
$factory->add($factory->getField('richtext', 'content', 'Content'));

To break down the examples above, the add() method is called on $factory, which adds a field. $factory->getField() returns an instance of Message\Cog\Field\FieldInterface. The three arguments getField() takes are:

  • Field type
  • Name of the field
  • Label for the field

See the Cog Field component: Available fields for a list of all available fields in Mothership.

The getField() method on the $factory returns a Field object, which ties a value to a Symfony Form field. Each field type has a corresponding Symfony Form field, and you can add options to this field using the setFieldOptions() method on the Field object, for example:

$factory->add($factory->getField('choice', 'link_colour', 'Select a link colour')
    ->setFieldOptions([
        'choices'  => [
            'red'   => 'Red',
            'blue'  => 'Blue,'
        ],
        'multiple' => false,
]));

The above example uses a choice field allow users to set the colour of a link in the view, and this maps to a Symfony Form 'choice' field. We use the setFieldOptions() method to state that the the options should be Red or Blue, by declaring the choices key in the options array as an array of choices. The key to the array (i.e. 'red' and 'blue', all in lower case) is what is saved to the database. The value (i.e. 'Red' or 'Blue', in title case), is what displays as an option. We also set multiple to false, to make sure users can only set one value.

Groups

Fields can also be grouped together using the addGroup() method on the factory:

$factory->addGroup('header', 'Header')
    ->add($factory->getField('text', 'title', 'Title'))
    ->add($factory->getField('text', 'subtitle', 'Subtitle'))
;

Note that the fields are added using to the group using the same methods as when adding an ungrouped field, and the same methods apply e.g. setFieldOptions. Groups can also be repeatable by calling the setRepeatable()' method on the group. For instance, if you had a staff page and wanted to list people, you might write something like this:

$factory->addGroup('staff', 'Staff')
    ->setRepeatable()
    ->add($factory->getField('text', 'name', 'Name'))
    ->add($factory->getField('text', 'position', 'Position'))
;

Users will now be able to add as many people as they like, and they can be looped and output in the view.

Note: Text will display on groups using translation strings. In the interest of a good user experience, you will want to add some translations to the translations/en.yml file. The structure of the translation string is page.[page type].[group name].[information type]. The 'information types' in this instance are:

  • name - will display at the top of the group
  • description - will display as text beneath the name to explain to users what the group is for
  • singularName - will display on the button to add more to the group, i.e. 'Add a [singularName]'

So the if we assume that the page type in the example above is called 'our_team', the translation YAML will look like this:

page:
    our_team:
        staff:
            name: Staff
            description: Add people employed by the company
            singularName: staff member

Views

The view file uses Twig. The files must be named in the structure of [file name].[render type].twig, so for an HTML view file called 'template', the filename would be 'template.html.twig'.

To output a variable, use {{ }} tags. For functional calls such as if statements, use {% %} tags. For comments, use {# #] tags.

Two main variables are passed to the view from the page type:

  • page - The page object itself
  • content - The content that belongs to the page.

Variables can be called from these objects using the . operator. For instance, page.title.

The 'page' variable

Available calls for page are:

  • page.id - Outputs the ID of the page
  • page.title - Outputs the page title
  • page.slug - Outputs the page slug
  • page.metaTitle - Outputs the meta title
  • page.metaDescription - Outputs the meta description
  • page.tags - An array of the page tags, cannot be called on its own and must be looped through like so:
{% for tag in tags %}
    {{ tag }}
{% endfor %}

The 'content' variable

The content variable is the one you will probably make more use of. It is more dynamic than the page variable, and instead of having set calls, you call the name of the field directly, for instance if you have a field called 'description', you would call:

{{ content.field }}

To call a variable from a group field, simply call the name of that field from the group. For instance, if the group name is 'header', and it has a 'title' field and a 'subtitle' field, you might output that in the view file like so:

<h1>{{ content.header.title }}</h1>
<h2>{{ content.header.subtitle }}</h2>

On a repeatable group, like the staff example above, you would want to loop through the content:

<ul>
    {% for staffMember in content.staff %}
        <li>
            <p>Name: {{ staffMember.name }}</p>
            <p>Position: {{ staffMember.position }}</p>
        </li>
    {% endfor %}
</ul>

In this example, we use the {% for %} loop. This works similarly to a foreach() loop in PHP. We loop through each repeated group in content.staff, and assign it to a variable called staffMember. We then call the name variable on staffMember to get the value for that particular repeated group.

Other useful Twig tools

Twig offers some functionality to basic markup, allowing for the creation of clean and dynamic view files. For the full documentation, check the official guide, but otherwise here are some useful tools to get started:

{% for %}

A for loop allows an array of content to be looped through and output in the same way. The syntax is as follows:
{% for key, value in array %}
    {# output your content here #}
{% endfor %}

It is integral that {% endfor %} is called to let the view know that the for loop has finished. A syntax error will be thrown if this is forgotten.

{% if %}

An if statement can check a variable and decide what to output on that basis. The syntax is as follows:

{% if var = 1 %}
   {# do something #}
{% elseif var = 2 %}
    {# if the variable is equal to two, do something else #}
{% else %}
    {# if the variable is neither 1 nor 2, do something else #}
{% endif %}

Note that like the for loop, an if statement must be closed with {% endif %} Note: The fields on on the content object cannot be checked using {% if content.field %} as this will always resolve true. The reason for this is that fields are objects that have a __toString() method which allows them to be easily converted into content in the view. However, it is still an object even if there is no content, so in that call you are merely checking that the object exists, which it does. To make such a check, you will need to call {% if content.field.value %}. This calls the getValue() method on the field object, which will return the content.

{% set %}

This allows you to set a variable within the view itself, eg:

{% set var = 1 %}

| raw

By default, Twig escapes all HTML characters for security reasons. However, if you want to output HTML, you will want to use the | raw filter. This is integral for richtext fields:

{{ content.richtext | raw }}

| trans

Following up a translation string with | trans will automatically translate pull the correct content from the locale's corresponding translation YAML file:

{{ 'translation.yaml.string' | trans }}

| price

The | price filter will automatically add a currency symbol to the price. It takes an argument of the currency ID (defaulting to the installation's default currency):

{{ '10' | price('GBP') }} {# Will output '£10.00' #}