Published on September 15, 2016 by Jan Haderka
When you need to build a dialog, Magnolia offers you an extensive list of different fields. And if that's not enough, by combination of Composite and Multi fields you can achieve quite a lot.
But what if it's still not enough? What if you need something extra? Say you need to fetch data from an external source and pre-populate fields with it? What if you have so many sub-fields that you need to optimize their size to achieve a good distribution of space?
I just ran into exactly the same thing recently and after looking at documentation I've come to realize there's no tutorial to guide you through. And to make it even better, after a few hours of trying I've also come to realize that it is indeed not so trivial (unless you know exactly what to do.) Hopefully what I have discovered might help you to write your own.
To tell the full story, one day I got hit with this requirement: our search rankings were suboptimal due to missing meta data for linked YouTube videos. And while one could work around it easily by simply adding fields with info to populate meta fields for search engines, that would be quite inconvenient for editors. So that requirement was that editors should only have to enter IDs or URLs to video just as they had been doing up until now and the rest should happen automatically.
Fortunately YouTube metadata are accessible over REST API that is quite well documented.
Also, fortunately for me, Magnolia already has a REST client module that I could use to get a connection working w/o having to mess up with anything. This part boiled down (akin to REST demo I was showing years back at the conference) to:
Now to the fun part (the dialog field)
Since I had to come up with a solution with only a few hours remaining to the release, the simplest solution I could find was to retrieve and store meta data of the videos while saving the dialog using value pre-filled in existing video link field as the pointer to correct metadata. Here’s how that looks like.
While the creation of such action was indeed a matter of an hour or so to write the code, such a solution has a few obvious drawbacks:
… suffice to say, I was not thrilled with the solution.
A better solution, encapsulating everything in one composite field and taking care of saving data via custom transformer. That’s exactly why composite fields exist. And indeed this worked too. I was able to create the dialog and use the fields and store all there was to be stored. At first, I wanted editors just show the metadata so I set fields to read only, but that looked quite ugly:
So to improve the look, I changed them to read-write later:
This worked better, and as you can see the dialog you can probably also guess how those fields were composited together and that thumbnails are done as multifields with inner composite field. It was all better, but quite long and far from perfect. Again the code of definition and of the transformer are available for your inspection.
To summarize, the drawbacks of this solution:
So the time comes to bite the bullet and write a completely custom field.
This solution was the hardest. Not in the amount of code produced in the end, but in a way to discover what the code should be.
There are no tutorials on building pure Vaadin based fields for Magnolia and after wasting a day in attempts and not seeing an end to my problems, I simply gave up on pure field and based everything on custom
CompositeField. The advantage was that I could still use the
Transformer for storing of data (or so I thought) and separation of backend data manipulation from layout of the visual elements.
So I extended
CompositeField and in it created all fields like ID, Title, Description, all the thumbmnails and so on as plain Vaadin elements. Only to realize that I could not plug in Magnolia’s/Composite's mechanism for reading/storing values via transformers. Yikes!
Maybe I was doing something silly, but after spending extra days trying to figure it out, I again fell-back on the defaults of Magnolia and created my fields via
TextFieldDefinition of Magnolia to get them bound to the data storage mechanism and get
Transformer working properly. You can see details of it in
createTextField(String, PropertysetItem, String) method. Additionally, I’ve added pure Vaadin button with action to populate the data from remote call (something previously existing in the save action in #1 or in Transformer in #2). This simplified transformer to nearly carbon copy of default
MultiValueChildNodeTransformer (the notable difference being using real IDs for storing/reading values instead of just numbers and not needing reordering of properties propagated to the UI).
The result is more visually pleasing than previous attempts:
Here’s a link to the final code of the field. If you inspect the code, you will see that there is:
This is the class that Magnolia requires you to create solely for the purpose of registering the field. As you can see, code is fairly trivial. All we do here is change the default layout and change the default transformer. As for the layout, we could also have ignored it and made the change directly in the field implementation.
This is the class that Magnolia requires you to create solely for the purpose of building the instance of the field. In our implementation we are not reusing fields or not doing anything else that would be special or fancy so it is really just a one liner for instantiation of the field and some boiler plate code around.
Transformer is the class composite fields use to store/read values. In our implementation, we create a subnode named after the field and store all the values in this subnode. The business of the
Transformer is again very straightforward even though you will see more code than with previous two classes. In
readFromItem() method, transformer will read values from the node and populate individual fields and in opposite in
writeToItem() method it will read from fields and store values in properties of the node. I was a bit surprised that there is no such transformer OOTB as it seemed to be rather a common use case, but perhaps it’s not. Oh well, it exists now so you can reuse, or perhaps Magnolia might adapt it at some point if there is enough interest from the community.
This is the main body of the functionality. As I have already mentioned above, rather then using straight Vaadin fields for everything, I’ve opted for reusing default Magnolia provided fields (via their definitions) as much as possible to ensure smooth binding of the data and calls to transformer upon changing the values. The field itself extends
CompositeField so to initialize all of the sub-fields I’ve overriden
initFields(PropertysetItem values) method. There you can see initialization of each of the fields and adding them to the final outer field (which in reality is just
VerticalLayout of Vaadin).
What you can also observe in the code (method called
createEntryComponent() ) is how to better distribute available screen space between different fields by setting their expand ratio.
Finally, what you can also observe in the initFields method is the creation of Vaadin button, placing it in the layout of the field and attaching click listener that performs the REST call to Youtube API.
On clicking we first read the value of the ID field and extract video identifier from it (in case we were provided with the whole URL instead of just the video ID).
The REST call itself is in fact 2 calls, each charged by Google for 2 credits. In the first call we obtain title, description, publishing date and all the thumbnails, and in the second call we read the duration of the video and the quality in which it was recorded. And of course once we read those values, we also set them directly to the fields. The only little catch here is that dimensions of thumbnails are returned in JSON response from the REST call as numbers so we need to read them via
getLongValue() call and we need to store them as text (because we are passing them to text field and Vaadin will not convert
String automagically for us. Otherwise it should be pretty straightforward.
As I like to do, all the relevant pieces (
Transformer) are bundled with the
U2BField itself in one class since those only exist to aid the field creation and implementation.
You can download and install the end result as a Magnolia module. It will also add
neat-u2b/components/youtube component so you can use it in your pages straightaway if YouTube player with the embeded search metadata is all that you need.
One extra thing - The field is not i18n-aware. There are no sublabels for other languages and it will not allow you to store different videos with different metadata for different languages. But if you ever need it, please fork the project and contribute the patch back.
Thanks and enjoy
Jan is Magnolia's Chief Technology Officer. He has been developing software since 1995. On this blog, he'll write about Magnolia's connectors, integrations and coding ...with the odd Magnolia usability and development tip thrown in for good measure. He lives in the Czech Republic with his wife and three children. Follow him on Twitter @rah003.
See all posts on Jan Haderka