Complex dialog fields

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)

 

#1 Quick & dirty

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:

  • A call to Google API is made every time dialog is saved, even when editing irrelevant parts of it. And Google charges you 4 credits for the calls, so there’s a price to be paid. One could mitigate this issue by an extra check and making the call only if metadata don’t exist yet, but what if they were updated and the user wants you to update?
  • There’s no visual verification for users to check retrieved meta data prior to saving the dialog.
  • There’s no way to check/update metadata except by examining the html code of the page in which component is used
  • Approach breaks encapsulation and separation of fields tying it to the save action. It also means that
    • you could only have one such customization on the dialog at any give time
    • not only do you need to configure the field, you also need to re-configure save action to use it and tie field and action together somehow

… suffice to say, I was not thrilled with the solution.

 

#2 Use what you get OOTB

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:

  • too long/cumbersome dialog field with too much space wasted on subfields that didn’t need to be big, e.g. dimensions of thumbnails. This could probably have been improved upon a bit with more nested levels of composite fields, but I just didn’t feel convinced that it would lead to something visually pleasing anyway
  • a still unclear workflow regarding updates by users (allow editing or override? how to signal that a user wants override or e.g. leave some field empty?) 
  • still executing remote calls on every save of the dialog

So the time comes to bite the bullet and write a completely custom field.

 

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:

 

 

Additionally

  • it is much more compact, so it’s easier to review all the values.
  • the “Fetch button” allows user to control when remote call should be made to set/override values in the field
  • editable fields allow users to override whatever values are being fetched from YouTube
  • the number of remote calls is no longer the same as the number of times dialog is saved, so possibly you save some extra credits
  • the code can be used as a tutorial (of sorts) for building more complex custom fields

 

Here’s a link to the final code of the field. If you inspect the code, you will see that there is:

 

Custom Definition

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.

 

Custom Factory

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.

 

Custom Transformer

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.

 

Custom Field implementation itself

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 long to String automagically for us. Otherwise it should be pretty straightforward.

 

As I like to do, all the relevant pieces (Definition, Factory and 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

 



Comments



{{item.userId}}   {{item.timestamp | timestampToDate}}

About the author Jan Haderka

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

Demo site Contact us Free trial