{"id":107,"date":"2025-09-01T23:05:30","date_gmt":"2025-09-01T23:05:30","guid":{"rendered":"https:\/\/kerner.digital\/?p=107"},"modified":"2025-12-01T15:52:59","modified_gmt":"2025-12-01T15:52:59","slug":"timeline-connector-part-3-visualizing-ui-elements","status":"publish","type":"post","link":"https:\/\/kerner.digital\/?p=107","title":{"rendered":"Timeline Connector Part 3: Visualizing UI Elements"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><strong>Introduction<\/strong><\/h1>\n\n\n\n<p>Previously on&nbsp;<em>Timeline Connector Adventures<\/em>: in&nbsp;<strong><a href=\"https:\/\/kerner.digital\/?p=77\" data-type=\"link\" data-id=\"https:\/\/kerner.digital\/?p=77\">Part 1<\/a><\/strong>&nbsp;we laid the groundwork and defined the connector\u2019s structure, and in&nbsp;<strong><a href=\"https:\/\/kerner.digital\/?p=97\" data-type=\"link\" data-id=\"https:\/\/kerner.digital\/?p=97\">Part 2<\/a><\/strong>&nbsp;we tackled record fetching and filtering like responsible adults.<\/p>\n\n\n\n<p>Now we move to the glamorous part of the job:&nbsp;<strong>rendering UI elements<\/strong>&nbsp;in a way that doesn\u2019t look like a digital landfill.<\/p>\n\n\n\n<p>In this post we\u2019ll break down <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>what the timeline control actually lets you draw, <\/li>\n\n\n\n<li>how to structure your&nbsp;getRecordUX()&nbsp;output so users don\u2019t start a revolt,<\/li>\n\n\n\n<li>what it stubbornly refuses to support, <\/li>\n\n\n\n<li>and finish with the ancient developer tradition of complaining about platform gaps.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Timeline UI Anatomy (aka what pieces this control lets you draw)<\/strong><\/h2>\n\n\n\n<p>A timeline entry is basically a&nbsp;<strong>visual card<\/strong>&nbsp;living inside a scroll container it controls like a landlord. Each record you return gets rendered into a structure that (ideally) looks like this:<\/p>\n\n\n\n<p><strong>Top section (header):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Primary record title<\/li>\n\n\n\n<li>Optional metadata tag (status, category, mood of the dev that built it)<\/li>\n<\/ul>\n\n\n\n<p><strong>Middle section (content\/body):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Short description preview<\/li>\n\n\n\n<li>Images or thumbnails (if your source even has them)<\/li>\n\n\n\n<li>Nested layout blocks for structure<\/li>\n<\/ul>\n\n\n\n<p><strong>Bottom section (footer):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Timestamp (created\/modified time)<\/li>\n\n\n\n<li>Optional secondary actions or link expansion<\/li>\n<\/ul>\n\n\n\n<p>So realistically, the timeline card is composed in DOM terms roughly as:<\/p>\n\n\n\n<div class=\"timeline-record-card\">\n  <div class=\"timeline-header\"> <!-- header stuff lives here --> <\/div>\n  <div class=\"timeline-body\">   <!-- content stuff lives here --> <\/div>\n  <div class=\"timeline-footer\"> <!-- footer stuff lives here --> <\/div>\n<\/div>\n\n\n\n<p><span style=\"font-size: revert; white-space: normal;\">You&nbsp;<\/span><i style=\"white-space: normal;\">can<\/i><span style=\"font-size: revert; white-space: normal;\">&nbsp;ignore this clean hierarchy and render all text into one massive blob, but don\u2019t. The control doesn\u2019t clear your design sins, it just displays them.<\/span><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Building visuals using context.factory.createElement()<\/strong><\/h2>\n\n\n\n<p>To render anything on that card, you use the factory method provided by&nbsp;context, which constructs&nbsp;<strong>Fluent-compliant DOM elements<\/strong>&nbsp;the timeline control will accept without filing a restraining order.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>context.factory.createElement(type, options, children)\n<\/code><\/pre>\n\n\n\n<p class=\"has-medium-font-size\"><strong>Input types<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table><tbody><tr><td><strong>Parameter name<\/strong><\/td><td><strong>What does it do?<\/strong><\/td><\/tr><tr><td>type: string<\/td><td>Name of the type of component (see below) that renders html component(s)<\/td><\/tr><tr><td>props?: object<\/td><td>Attributes or configuration for this element. Typically includes a&nbsp;key,&nbsp;className, or&nbsp;style&nbsp;depending on what you\u2019re building<\/td><\/tr><tr><td>children: any<\/td><td>Nested elements or text nodes rendered inside the parent element. You pass them in sequence, not nested inside props.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Wiat, but what is the type you ask? Well it&#8217;s nothing you would expect. Or perhaps, knowing Microsoft it is exactly what you&#8217;d expect &#8211; a custom and undocumented list of magic strings that work (or not). Here&#8217;s what I managed to dig out of the trash bin (no guarantees that it&#8217;s all, it works, is not deprecated etc.):<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Factory call type<\/strong><\/td><td><strong>Suggested props<\/strong><\/td><td><strong>Typical DOM result<\/strong><\/td><\/tr><tr><td>&#8220;CRMIcon&#8221;<\/td><td>{ key, className, iconName }<\/td><td>&lt;span class=&#8221;ms-Icon&#8221;&gt;<\/td><\/tr><tr><td>&#8220;EntityIcon&#8221;<\/td><td>{ key, className, iconName, src? }<\/td><td>&lt;span class=&#8221;ms-Icon&#8221;&gt; or &lt;img&gt;<\/td><\/tr><tr><td>&#8220;MicrosoftIcon&#8221;<\/td><td>{ key, className, iconName }<\/td><td>&lt;span class=&#8221;ms-Icon&#8221;&gt;<\/td><\/tr><tr><td>&#8220;PresenceIndicator&#8221;<\/td><td>{ key, status? }<\/td><td>small &lt;span&gt; \/ badge &lt;div&gt;<\/td><\/tr><tr><td>&#8220;ProgressIndicator&#8221;<\/td><td>{ key, value?, label? }<\/td><td>&lt;progress&gt; or loader &lt;div&gt;<\/td><\/tr><tr><td>&#8220;Container&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;div&gt;<\/td><\/tr><tr><td>&#8220;ScrollContainer&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;div&gt; with overflow<\/td><\/tr><tr><td>&#8220;HorizontalScroll&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;div&gt; with horizontal overflow<\/td><\/tr><tr><td>&#8220;Popup&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;div role=&#8221;dialog&#8221;&gt;<\/td><\/tr><tr><td>&#8220;LivePersonaCardHoverTarget&#8221;<\/td><td>{ key, className }<\/td><td>&lt;div&gt; hover wrapper<\/td><\/tr><tr><td>&#8220;Button&#8221;<\/td><td>{ key, className, style?, disabled? }<\/td><td>&lt;button&gt;<\/td><\/tr><tr><td>&#8220;Hyperlink&#8221;<\/td><td>{ key, href, className, style?, target? }<\/td><td>&lt;a&gt;<\/td><\/tr><tr><td>&#8220;IMG&#8221;<\/td><td>{ key, src, className, alt?, width?, height? }<\/td><td>&lt;img&gt;<\/td><\/tr><tr><td>&#8220;Image&#8221;<\/td><td>{ key, src, className, alt? }<\/td><td>&lt;img&gt;<\/td><\/tr><tr><td>&#8220;EntityImage&#8221;<\/td><td>{ key, src, className, alt? }<\/td><td>&lt;img&gt;<\/td><\/tr><tr><td>&#8220;TextInput&#8221;<\/td><td>{ key, className, value?, placeholder? }<\/td><td>&lt;input&gt; \/ &lt;textarea&gt;<\/td><\/tr><tr><td>&#8220;Text&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;p&gt; or &lt;div&gt; text node<\/td><\/tr><tr><td>&#8220;TextBlock&#8221;<\/td><td>{ key, text, className?, style? }<\/td><td>&lt;p&gt; or &lt;div&gt;<\/td><\/tr><tr><td>&#8220;Label&#8221;<\/td><td>{ key, className, text }<\/td><td>&lt;label&gt; or &lt;span&gt;<\/td><\/tr><tr><td>&#8220;List&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;ul&gt; \/ &lt;ol&gt;<\/td><\/tr><tr><td>&#8220;ListItem&#8221;<\/td><td>{ key, className, style? }<\/td><td>&lt;li&gt;<\/td><\/tr><tr><td>&#8220;Table&#8221;<\/td><td>{ key, className, style?, width? }<\/td><td>&lt;table&gt;<\/td><\/tr><tr><td>&#8220;TableBody&#8221;<\/td><td>{ key, className }<\/td><td>&lt;tbody&gt;<\/td><\/tr><tr><td>&#8220;TableHeader&#8221;<\/td><td>{ key, className }<\/td><td>&lt;thead&gt;<\/td><\/tr><tr><td>&#8220;TableFooter&#8221;<\/td><td>{ key, className }<\/td><td>&lt;tfoot&gt;<\/td><\/tr><tr><td>&#8220;TableRow&#8221;<\/td><td>{ key, className?, style? }<\/td><td>&lt;tr&gt;<\/td><\/tr><tr><td>&#8220;TableCell&#8221;<\/td><td>{ key, className?, style? }<\/td><td>&lt;td&gt;<\/td><\/tr><tr><td>&#8220;TableCellIcon&#8221;<\/td><td>{ key, className?, style?, src? }<\/td><td>&lt;span&gt; or &lt;img&gt; inside &lt;td&gt;<\/td><\/tr><tr><td>&#8220;TableHeaderCellText&#8221;<\/td><td>{ key, text }<\/td><td>text \/ &lt;span&gt; in &lt;th&gt;\/&lt;td&gt;<\/td><\/tr><tr><td>&#8220;Select&#8221;<\/td><td>{ key, className?, style? }<\/td><td>&lt;select&gt;<\/td><\/tr><tr><td>&#8220;Option&#8221;<\/td><td>{ key, value }<\/td><td>&lt;option&gt;<\/td><\/tr><tr><td>&#8220;ComboBox&#8221;<\/td><td>{ key, className?, options? }<\/td><td>composite dropdown &lt;div&gt;\/&lt;select&gt;<\/td><\/tr><tr><td>&#8220;Radio&#8221;<\/td><td>{ key, checked?, name? }<\/td><td>&lt;input type=&#8221;radio&#8221;&gt;<\/td><\/tr><tr><td>&#8220;Boolean&#8221;<\/td><td>{ key, checked }<\/td><td>&lt;input type=&#8221;checkbox&#8221;&gt;<\/td><\/tr><tr><td>&#8220;FileInput&#8221;<\/td><td>{ key, accept? }<\/td><td>&lt;input type=&#8221;file&#8221;&gt;<\/td><\/tr><tr><td>&#8220;IFrame&#8221;<\/td><td>{ key, src, className? }<\/td><td>&lt;iframe&gt;<\/td><\/tr><tr><td>&#8220;Placeholder&#8221;<\/td><td>{ key, className?, style? }<\/td><td>&lt;div&gt; or &lt;span&gt; skeleton block<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>What this means:<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#8220;Container&#8221;&nbsp;is just your&nbsp;&lt;div&gt;&nbsp;landlord.<\/li>\n\n\n\n<li>&#8220;Label&#8221;&nbsp;becomes a semantic&nbsp;&lt;label&gt;&nbsp;or styled&nbsp;&lt;span&gt;.<\/li>\n\n\n\n<li>&#8220;TextBlock&#8221; contains the&nbsp;text&nbsp;node that becomes paragraph or block text.<\/li>\n\n\n\n<li>&#8220;Image&#8221;&nbsp;with&nbsp;&#8220;IMG&#8221;&nbsp;variants becomes&nbsp;&lt;img&gt;, so use it for icons or timeline thumbnails.<\/li>\n\n\n\n<li>&#8220;Button&#8221;&nbsp;becomes&nbsp;&lt;button&gt;, so yes, you finally have a clickable thing.<\/li>\n\n\n\n<li>&#8220;Table&#8221;&nbsp;becomes&nbsp;&lt;table&gt;&nbsp;and follows normal row\/cell parenting.<\/li>\n<\/ul>\n\n\n\n<p>Now that we have it, we can finally dive into building something that actually&nbsp;looks like it had a design, and more importantly,&nbsp;can be implemented without making future-you cry. The factory gives you the raw ingredients, but you\u2019re still the one cooking, so now the goal is to architect a UI structure that feels native to the timeline control while still translating external data into DOM-safe visual blocks. With the outline and mapping nailed down, we can move from theory into the part that resembles real engineering:&nbsp;rendering the header, body, and footer containers into HTML elements using createElement().<\/p>\n\n\n\n<p>I like to keep everything in order, so I will move the implementation of UI elements to another file, but that&#8217;s not necessary. <\/p>\n\n\n\n<p>Our method will look like this:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/piboke\/600a9767aef800b38dc8e73e7d800619.js\"><\/script>\n\n\n\n<p>As you see, each time the getRecordUX is called we&#8217;re returning a timelineRecord. Each record should (must?) contain commands, header, body, footer, icon etc. We&#8217;ll go through each of those in this article starting with header:<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/piboke\/2ce41fc82fbffda1153f964abdd2ff78.js\"><\/script>\n\n\n\n<p>Plain and simple, we add a label using createElement(&#8220;Label&#8221;, &#8230;) and that&#8217;s it. We can use the isExpanded flag to show more information if the record is expanded, or collapsed.<\/p>\n\n\n\n<p>For the body we&#8217;ll try something fancy: I&#8217;ll add some buttons and a table when the record is expanded. Please take a look on how we embed components into one another. Is it good? No. Could it work better? Definitely. I would assume one could imagine inserting a ready html component with css files designed by someone who actually has a taste and not a developer like myself.<\/p>\n\n\n\n<script src=\"https:\/\/gist.github.com\/piboke\/e9a50920535bc874ae1aa2363e06c549.js\"><\/script>\n\n\n\n<p>And here&#8217;s how it looks like expanded:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"647\" height=\"476\" src=\"https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-4.png\" alt=\"\" class=\"wp-image-116\" srcset=\"https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-4.png 647w, https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-4-300x221.png 300w\" sizes=\"auto, (max-width: 647px) 100vw, 647px\" \/><\/figure>\n\n\n\n<p>and collapsed:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"639\" height=\"166\" src=\"https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-5.png\" alt=\"\" class=\"wp-image-120\" srcset=\"https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-5.png 639w, https:\/\/kerner.digital\/wp-content\/uploads\/2025\/11\/image-5-300x78.png 300w\" sizes=\"auto, (max-width: 639px) 100vw, 639px\" \/><\/figure>\n\n\n\n<p>There&#8217;s just one small problem if you&#8217;ll try to copy-paste the above code. The compiler will scream in terror! The createElement method is not accepting an array as the third parameter! This is a mistake on Microsoft side, here&#8217;s the correct definition that you should put in the d.ts file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface IFactory{\n   createElement(element: string, options: any, data?: <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\"><strong>any<\/strong><\/mark>) : any;\n}<\/code><\/pre>\n\n\n\n<p>Now nothing stops you from doing beautiful renderings full of components added via createElement method with inline styles! With perhaps the last thing that is really annoying. Icons. But we will cover that in another post.<\/p>\n\n\n\n<p>I also said that we&#8217;ll cover everything related to the UI, but honestly there&#8217;s so much more we need to do, I&#8217;ll cover that in the next post.<\/p>\n\n\n\n<p>happy coding!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Previously on&nbsp;Timeline Connector Adventures: in&nbsp;Part 1&nbsp;we laid the groundwork and defined the connector\u2019s structure, and in&nbsp;Part 2&nbsp;we tackled record fetching and filtering like responsible adults. Now we move to the glamorous part of the job:&nbsp;rendering UI elements&nbsp;in a way that doesn\u2019t look like a digital landfill. In this post we\u2019ll break down Timeline UI [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":124,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":{"0":"post-107","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-uncategorized","8":"czr-hentry"},"_links":{"self":[{"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/posts\/107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kerner.digital\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=107"}],"version-history":[{"count":17,"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/posts\/107\/revisions"}],"predecessor-version":[{"id":133,"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/posts\/107\/revisions\/133"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kerner.digital\/index.php?rest_route=\/wp\/v2\/media\/124"}],"wp:attachment":[{"href":"https:\/\/kerner.digital\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kerner.digital\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kerner.digital\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}