Daniel Rotter
Core Developer – Sulu GmbH
Core developer and support guru. Passionate traveler and soccer player.

Address and bank fields in contact form

It has been a while since the last alpha release of Sulu 2. That's not because we haven't been working on it a lot, actually the exact opposite is the reason. We've accomplished a lot in the last few weeks and merged 160 pull requests. The only reason we waited so long with this release is that we've done a few breaking changes and we wanted to bundle them within a single release, so that you hopefully don't have to struggle with similar changes in future releases.

Make sure to check out our UPGRADE.md file in the repository to learn about all the breaking changes with introduced.

Rename Datagrid to List

In order to make this component more understandable for everyone, we have decided to rename the "datagrid" to "list". This affects all other components, configuration, constants etc. available,  both on client and server side code. There is an extensive section in our upgrade document explaining how to update the most important parts in this regard. However, this instruction is not complete.

Refactoring of resources

The biggest change in this release is that we had to refactor how our forms and lists (formerly known as datagrids) work. Previously they were attached to a single resourceKey which described the endpoint as well as the corresponding form and list. So there was a one-to-one relationship from the resourceKey to the form and to the list. This was problematic, because we e.g. could use the same endpoint for the page details and settings tab but we were forced to have two different endpoints loading the same data only because we wanted to have two different forms showing different parts of the same entity.

We've solved this problem by assigning a resourceKey and formKey to the route for the form and completely decouple the two in our implementation. This works great, now we are able to have different forms for the same entity. I think this a very powerful feature and justifies the BC break (especially since we are still an alpha version). Our upgrade has a separate section for this topic which hopefully explains well how to migrate from an older alpha version.

Rename ContentBundle to PageBundle

We have already renamed our "nodes" to "pages" in our REST API to be more consistent with the naming. To further improve that, we have decided to also move the SuluContentBundle to SuluPageBundle because it is responsible for pages. Content is a very broad term which could also be applied to snippets, articles and so on.

This change introduces quite some BC breaks, but it will allow us to eventually abstract logic into a new SuluContentBundle later, which can be reused across pages, articles, snippets and maybe more.

Change to single front controller

We have now switched to a single front controller. Instead of having an admin.php and website.php in our public folder, there will only be an index.php file. This file automatically recognizes if the /admin URL is accessed and switches, based on this decision, between admin and website context.

This makes running Sulu in a cloud environment and on a standard webserver easier since there is only one entry point left.

Disable form field based on conditions

In the last release we have implemented the so called visibilityCondition which allows to hide certain form fields based on some conditions. In this relase we've added another property called disabledCondition (and renamed the visibilityCondition to visibleCondition to better match names). The following example shows how our position field in the contact form is only enabled if an account has been assigned to the contact.

    disabledCondition="account == null"
Disable form field based on conditions

Ask for confirmation when leaving form

A very cool new feature is that, when any form contains unsaved data, a confirmation dialog appears if somebody tries to navigate somewhere else, no matter if this happens inside our application or the browser URL is changed or a reload is about to happen. This should massively help to avoid accidental data loss.

Offering route builder to easier configure routes

Until now the Admin classes in Sulu had to create the routes that are returned from their getRoutes completely on their own, only being supported by the very minimal interface the Route class offers. This has changed with this release. While the Route classes still exist and can be used (especially for you custom built react views) there were also some so-called RouteBuilders added.

These RouteBuilder are coupled more to a specific route and therefore improve the experience for the developer. We have RouterBuilder for lists and forms, as well as for the ResourceTabs component. These builders can be retrieved by the RouteBuilderFactory class (which can also be overridden to deliver your own builder classes, in case there will be a use case for this).

The following example shows how these two approaches compare.

// before
(new Route(static::CONTACT_LIST_ROUTE, '/contacts', 'sulu_admin.list'))
    ->addOption('resourceKey', 'contacts')
    ->addOption('listKey', 'contacts');

// after
$this->routeBuilderFactory->createListRouteBuilder(static::CONTACT_LIST_ROUTE, '/contacts')                                                                                                            
Offering route builder to easier configure routes

Hide tabs based on conditions

The 1.x series hid some tabs when certain settings in the settings tab of the pages applied. This was some custom developed JavaScript. But for Sulu 2.0 we decided that we want to do that in a more elegant way, not requiring any JavaScript from the developer. What we came up with is another option on the route called "tabCondition", which is a JEXL expression, and being evaluated with the data from the current entity. If this condition returns true the tab is displayed, otherwise it is not. The following code should make that example more clear. It tells the route being shown as a tab that it should only be shown if the entity loaded has a nodeType of 1 and is not a shadow.

return [
    $this->routeBuilderFactory->createFormRouteBuilder('sulu_page.page_edit_form.details', '/details')                                                                                                     
        ->setTabCondition('nodeType == 1 && shadowOn == false')                                                                                                                                            
Hide tabs based on conditions

Toolbar actions for datagrid

For the form view we've already implemented toolbar actions, allowing to customize the toolbar of a form. The same possibility was added now to the datagrid view. This means, instead of using booleans to define what elements should appear in the toolbar, it is now also required to add toolbar actions by defining the keys.

Read our upgrade file contains instructions on how to migrate to the new way of doing things.

External storages for media uploads

This is probably one of the most requested features in Sulu: We now allow to store the original uploads of images and other media in an external storage like S3 or Google Cloud Storage. There is already some documentation for that in the develop branch of our documentation.

Missing preview functionality

The preview was missing a little bit of functionality compared to the 1.x series. We've added a dropdown in the toolbar, which allows to switch between different devices like a desktop, tablet or smartphone. It is also possible to open the preview in a separate window, while still updateing it with the changes made in the form.

Tables and headings in CKEditor

We are also using the new version of the CKEditor in Sulu 2.0. With the fifth alpha we've added the tables and headings plugin, so you are able to add tables and headings to your rich text.

Autowiring & autoconfigure support

We have added a few aliases to support the Symfony autowiring feature. In addition to that we've added some configuration to allow using autoconfigure as well, which will add tags to services with certain interfaces automatically. This makes defining services even easier!

Media tabs for history and formats

The media section was still missing some functionality. In this release we've added the history and formats tab. The history tab allows to see old versions of the media and the format tab allows to get the URLs of the different formats.

Move media, folders and categories

Moving different entities to a new parent is a task that happens quite a lot in any hierarchical data structures. This is also available for media assets, the media folder structure and for categories. We've decided to use the ColumnList view for all of these cases because it is the most convenient to quickly find exactly one existing item.

Set focus point for media

Another nice new feature is the ability to set a focus point. The focus point can be set using an overlay (similar to the 1.x series). The focus point decides what definitely has to be included when cropping images. This way the content manager can make sure that the important part of the picture is not cropped away when using different formats.

Additions in page settings

The page settings have almost been completed in the previous release. We have added the options to create a shadow page and the abillity to select the author (being a contact, not necessarily connected to a user).

Show indicators in webspace overview

Until now it was not possible to see in the Webspace overview if a page was an external or internal link or even a shadow page. With this release icons indicating the type of the page have been added.

Multi select field type

In case you were missing it, we've implemented a form field type to select multiple options.

Show some property as title in ResourceTabs

In our UI we usually show some property of the entity as the title in all tabs except for the first one. So e.g. the first tab of the contact does show a form without a heading, but all the other available tabs show the full name of the contact as the header. The only thing you have to do now o make that default behavior work for your custom entities is to tell the ResourceTabsRouteBuilder which property should be used for the heading.

$this->routeBuilderFactory->createResourceTabRouteBuilder(static::CONTACT_EDIT_FORM_ROUTE, '/contacts/:id')                                                                                           
Show some property as title in ResourceTabs

Address and bank fields in contact form

The address and bank fields for contacts and accounts have been added now. It also shows how easy it is to use our form in a different setup, in that case an overlay.

Still missing are the fields for adding email addresses, phone number and so on, but we are getting there.

ResourceSingleSelect field type

For the contacts we've needed two more selects, containing the title and the position of the contact. The special thing about these selects is, if you click on the edit option, you can add even more titles and positions using an overlay. Because of that we've implemented a ResourceSingleSelect field type, which can do that. It is the component communicating with the server to load and add entities, and it can also be used in other cases.

Because this component seemed so useful to us, we've added the possibility to use the field_type_options configuration - as explained in the blog post for the first alpha - to add this for even more entities.

Persist user settings

The system now also remembers the user settings. Multiple changes in user settings are collected. If nothing happens for a certain threshold, then all the settings are updated at once. This avoids having the same request being sent multiple times as well as sending too many requests.

Persist user settings

The system now also remembers the user settings. Multiple changes in user settings are collected, and if nothing happens for a certain threshold, then all the settings are updated at once. This avoids having the same request being sent multiple times as well as sending too many requests.

Persist user settings

The system now also remembers the user settings. Multiple changes in user settings are collected, and if nothing happens for a certain threshold, then all the settings are updated at once. This avoids having the same request being sent multiple times as well as sending too many requests.