DEV Community

Cover image for Day 7: FormLayout and Screen Generation for Multiple Front Ends
Jeff Tsai
Jeff Tsai

Posted on Edited on Originally published at hackmd.io

Day 7: FormLayout and Screen Generation for Multiple Front Ends

TableSchema and Database Structure Upgrades ended at physical tables, where one change means moving data and working out beforehand whether you need downtime. Today is back on the screen side, where the character is the exact opposite. Changing a screen means changing a definition, with no new release, and one change lands on every front end at once.

The order form in the case study works exactly like that. Desktop, browser, iOS and Android all read the same FormLayout and each grows its own look, with not one screen written by hand.

This post covers:

  1. Why FormLayout's vocabulary is deliberately small
  2. From one field to one native control
  3. One rendering layer per front end
  4. Why this layer stands on its own: it expires
  5. Why narrow screen reshaping is not written into the definition
  6. Screens come in two kinds, automatic and manual layout, and the data binding is the same for both

1. FormLayout's vocabulary is deliberately small

A FormLayout is made of two kinds of section:

  • Master sections: there can be several, and they all share one column count
  • Detail sections: there can be several or none at all, and they always run the full width below the master

A master section holds fields, and about a field the layout states only this much: which group it belongs to, what it comes after, whether it takes extra width, which kind of control it uses, whether it is visible, whether it is read only, and whether it is required. A detail section holds columns, and it can state much the same set, plus a width.

The layout does not talk about position. There is not one coordinate in the whole definition, and nothing that says put this at row three, column two. Position falls out of the rendering layer following the order: fields fill in one after another, a full row moves on to the next one, and a field marked as spanning two columns takes two cells. Even the column count is not a promise. It is an upper bound, and a narrow screen pushes it down.

Here is an extract from the case study's order layout, the full file being Order.FormLayout.xml:

<LayoutSection Name="Main" Caption="Order">
  <Fields>
    <LayoutField FieldName="order_date" Caption="Order Date" />
    <LayoutField FieldName="customer_rowid" Caption="Customer" ControlType="ButtonEdit"
                 DisplayFields="ref_customer_id,ref_customer_name" />
    <LayoutField FieldName="total_amount" Caption="Total Amount" ReadOnly="true" />
  </Fields>
</LayoutSection>
Enter fullscreen mode Exit fullscreen mode

A layout answers four questions and no more: which fields this form has, in what order, in which groups, and which kind of control each field uses. Visible, read only and required are states hanging off a field, and they do not change the shape.

A small vocabulary is this layer's specification, not a sign that it has yet to grow up. The layout is not the final thing to be drawn, it is the thing in the middle. Its downstream is not one kind of screen, it is each front end's own arrangement mechanism, and those mechanisms have nothing in common with each other: a layout control on the desktop on one side, the browser's grid on the other, and whatever the next front-end generation uses is unknown today. For an intermediate that has to feed all of them, the vocabulary has to be small enough that every one of them can translate it.

So the things that are not in it:

  • Placement, meaning alignment, margins and spacing
  • Styles, templates and animation, which are about what it gets drawn as, and belong to the rendering layer
  • Sections inside sections. A layout opened up is flat, so it can be scanned in bulk and checked programmatically

What is left out matters more than what is kept. A field pinned to a pixel position breaks on the next screen size, and if a definition can express that at all, what breaks is a thousand definitions.

Hundreds of screens that look the same

The users of an ERP, HRM or CRM system are almost all internal staff. They do not pick a screen to use, they are assigned a whole module, and before it goes live they have to learn dozens or hundreds of the forms in it.

If screens are written by hand one at a time, every one of them is free to look different. Details on the right here, underneath there. Picking a customer through a magnifying glass that opens a window here, a drop down there. The add button above the grid here, bottom right there. Each difference on its own is nothing. Added up, they are what the user has to remember.

With a small layout vocabulary, those differences have nowhere to happen. What the user learns is not hundreds of screens, it is one screen plus hundreds of sets of fields. Training cost therefore does not scale with the number of forms, and training has never been a small line in a rollout budget. Replace the staff or add a department and it gets paid again.

The cost: buying consistency means no screen is ever optimized for itself. A form used far more often than the rest can have its layout adjusted, but only within this vocabulary. Whether that trade is worth it depends on how many forms there are. Ten screens each optimized for itself is a good thing. Hundreds each optimized for itself is hundreds of operating habits.


2. Fields map to controls

If a field does not name a control, the kind of control is decided by its type in FormSchema:

Type declared in FormSchema Default kind of control Desktop Web
Boolean CheckEdit Check box control Check box input element
Date and datetime DateEdit Date input control Date input element
Long text MemoEdit Multiline text control Multiline text element
Small integer, integer, big integer, decimal and currency NumericEdit Numeric input control Text input element
Everything else TextEdit Text input control Text input element

The kind of control gets translated into each front end's native control, which is what the two right hand columns of the table above are, and beyond the name the two sides have nothing in common.

One rule outranks the type when picking a control. A field that declares a cross form relationship and does not name a control gets a lookup window editor by default. An earlier post, How FormSchema Drives SQL, said the same relationship declaration is also at work on the screen side, and this is where it lands. That one control binds several fields at once. The value goes onto the relation key, what shows on screen is the display columns it brings back, and one pick writes all of them back together, so those display columns do not each take a cell of their own.

When a control is bound it reads back from FormSchema. A text control fetches the maximum length, a drop down fetches the option list, and a lookup window editor fetches which program it should open. So the layout only has to record which kind of control, and never restates what the field means.

The only part tied to a UI framework is that translation into native controls, and all of it sits inside the rendering layer. The parts before and after deal only with definitions, so changing front ends does not move a single word of them. LanguageResource owns the text, FormLayout owns the arrangement, and the ViewModel handles data binding.


3. One FormLayout, one rendering layer per front end

The layout layer does not know that UI frameworks exist, and that claim can be checked the same way The Purpose and Classification of Definition Files checked it: the definition layer package has an extremely short reference list, and no UI framework is anywhere on it.

So what it turns into happens entirely inside each front end's own rendering layer:

  • The column count is just a number in the layout. The desktop turns it into equal width columns on a layout control, and the web turns it into a few lines of browser grid style declarations.
  • A list layout is handed to a grid control to draw on the desktop, and laid out as an HTML table on the web. The columns, the order and the widths both sides read are the same ones.

The case study's four front ends are put together exactly that way. Desktop, browser, iOS and Android share one UI project, and each platform entry point is down to startup and the few files of the platform shell.

Writing a rendering layer sounds vague. Taken apart it is four things:

  1. Arrange the two kinds of section
  2. Map control kinds onto this framework's native controls
  3. Bind the controls to the ViewModel
  4. Decide how it reshapes on a narrow screen

None of those is easy, but all of them are finite, and once they are done this front end runs every form.

How to communicate with the back end is not on that list, because that part does not get rewritten. Every front end goes through the same client layer, so one more front-end framework moves not one line on the server. That client layer is the subject of The Connector and the Convergence of Client Responsibilities.

The rendering layer those four share is Bee.UI.Avalonia, and even the browser front end is the same code compiled to run there. Supporting one more front end costs one rendering layer. Not zero.


4. This layer stands on its own because it expires

The comparison in Why ERP Needs a Definition-Driven Framework had a row for what a new front-end generation costs. Today makes good on it.

Packaged ERP and HRM products have life cycles of ten or twenty years and up, and over that time the system has to grow a different look with each turn of front-end technology. Desktop applications, web, mobile apps: every time a generation arrives the question is the same one. Those hundreds of screens, do they all get rebuilt?

If screens are written by hand one at a time, the answer is only ever rebuild, the volume is proportional to the number of forms, and it has nothing at all to do with whether the business changed. A company's order form gained two fields in ten years, and its screen has been rewritten three times.

That is the question the split is there to answer. Structure is stable and follows the business; layout is volatile and follows the front-end generation.

The two rhythms differ in the shape of the spending:

Rhythm of change Shape of the spending
Structure Continuous Small amounts: a few more fields and a few changed rules a year, touching one or two forms each time
Layout Intermittent One large amount: nothing for ten years, then all of it at once

Hand written screens tie both rhythms to the same file, so that once a decade large payment gets multiplied by the total number of forms every time. Once they are separated, what gets paid is a fixed cost, once.

This is not free. Somebody has to write the rendering layer and get it right, and it is a point everything converges on: a hand written screen gets one screen wrong at a time, and the rendering layer gets all of them wrong at once. Whether it is worth it depends on how many forms there are. On a system with a thousand the gap is too wide to need calculating, and on a system with ten, writing them yourself is quicker.

Do the layout files themselves expire? They do. The difference is that they are data. Changing a piece of data is not the same thing as changing screen code and then recompiling, repackaging and redeploying.


5. Narrow screens reshape themselves, with no second layout

The usual approach is a separate layout for phones, so one form has two layouts, and the two evolve apart.

This framework has the rendering layer reshape itself instead:

  • Single record screens: the master column count is pushed down to one, and detail sections switch from editing in place to tapping a row and opening a row edit form
  • Lists: wide, it is a table with one row per record; narrow, it is one card per record, with each field taking a line on the card, caption on the left and value on the right
iOS Android
The Northwind order list on iOS The Northwind order list on Android

The fields on the card are not a second list written somewhere else. The wide table and the narrow card read the same list layout, the same set of columns in the same order, and the only difference is what it gets drawn as.

There are not two definitions kept in sync, there is one definition with two ways of drawing it, and that is its most practical value. One more copy is one more chance of the two not lining up, and a list missing a column is usually only found when a user asks why the amount is not visible on their phone.

Not one word of those reshaping decisions is written in the layout definition. The layout says which fields there are and how many columns they sit in; how narrow counts as narrow, and how details get edited once it is narrow, are the rendering layer's own judgment. The framework source leaves a comment right there: how details are edited is the UI layer's business, and the shared layout definition stays neutral.

There is a reason the line is drawn here. The moment a layout definition starts describing what happens on a phone, it has written a set of assumptions about screens into the definition. Today's assumption is two widths, phone and desktop. The next generation may be some other shape, and changing an assumption written into definitions means changing a thousand of them.

The cost: the same layout does not come out looking identical on different rendering layers. That is deliberate. Demanding pixel level sameness across front ends is demanding that every one of them give up its own platform conventions, and users expect platform conventions far more strongly than they expect sameness.


6. The standard is set, so where is the flexibility

The doubt raised in The Northwind Case Study: Eight Forms and a Custom Business Object, that a fully standardized flow leaves no room for flexibility, is sharper at this layer, because screens are what users look at every day. Screens come in two kinds, automatic layout and manual layout, and the part where data is connected is the same for both.

Approach Where the screen comes from The route for
Automatic layout Goes through FormLayout. A layout is produced once at design time and adjusted as needed after that: how many sections, how many columns, which field spans two, which ones are hidden, which column is read only, and which columns a detail shows. It is data, so nothing is recompiled when it changes Standard forms, which is to say the vast majority of them
Manual layout Placing controls yourself and deciding positions yourself, without going through the layout layer at all Shapes the layout vocabulary cannot arrange

Manual layout does not shut the framework out. Once the controls are placed, filling in the table name and field name is enough for reading and writing values, change notification, maximum length and option lists to connect as usual. The only difference between the two is whether the screen was arranged or placed by hand. The binding part is identical.

The layout vocabulary does not set out to describe every shape. It cannot, and it should not: dashboards, charts, drag and drop scheduling and maps all go through manual layout. Layout is where people most often ask for an exception to the line that a framework that can do anything has made no decisions for you. One more kind of section, one more style attribute, and one more kind of screen is covered. Every one added is one more thing every rendering layer has to deal with.

The flexibility is in the granularity, not in the capability. There is no cliff between automatic and manual layout, because the part where data is connected is the same on both sides.


7. The case study: what the order layout says and does not say

The case study's definition folder holds eight layouts, one per form, all produced at design time and adjusted afterwards. The order layout extracted earlier says this much:

  • One master section, two columns, one detail section
  • The three relationship fields on the master and the one on the detail all use a lookup window editor, and the display columns they bring back no longer take cells of their own
  • The amount column is marked read only in FormSchema and came across when the layout was produced, so on screen it simply cannot be typed into
  • Four front ends read the same one and grow four platform specific looks

What the order layout does not say outweighs what it does. It does not say where each field ends up on screen, what a lookup window editor looks like, how things are arranged on a phone, which language the captions get translated into, or how details are edited on a narrow screen. Precisely because none of that is its responsibility, it translates onto four front ends.


What gets rewritten that day

One layout definition growing into screens on four front ends: the real content of that sentence is not that screens can be described by definitions too. Being describable is only the entry ticket. The content is the sum of three things:

  • This layer's vocabulary is small, small enough for every front end to translate, and small enough that hundreds of forms look alike
  • It does not know what it will be translated into, so one more front end means one more rendering layer rather than going back to edit definitions
  • It is the part of the definition set most likely to expire, and when it does expire what gets replaced is the rendering layer, not the structure

I do not know what front ends will exist in ten years, and twenty years ago nobody guessed things would look like this. There is only one thing you can arrange in advance: that when that day comes, what has to be rewritten is finite, and unrelated to how many forms there are.

The next post, Declarative Business Rules and Computed Fields, covers computed fields and rules that run before saving, and how far both can be pushed into declarations.


๐Ÿ“š Part of the thirty-post series ERP Architect's Notes: Designing a Definition-Driven Framework

๐Ÿ“˜ Original article on HackMD, in Traditional Chinese:
๐Ÿ‘‰ https://hackmd.io/@jeff377/day-07-one-layout-many-uis

๐Ÿ“ข You are welcome to republish this, please credit the source.
๐Ÿ“ฌ Follow for more engineering notes:
Facebook ๏ฝœ HackMD ๏ฝœ GitHub ๏ฝœ NuGet

Top comments (0)