Showing posts with label conditional. Show all posts
Showing posts with label conditional. Show all posts

Saturday, 18 February 2023

Conditional LWC Directives in Spring '23

 Introduction

The Spring '23 release of Salesforce is now fully live, after a last minute delay in the US. One of the new features was a set of Lightning Web Component directives for conditional rendering. A relatively minor change, but in my view one with a large impact, as it adds clarity. And as we all know, clarity is the gift that keeps on giving to everyone that works on the component after you.

Old Mechanism

The previous conditional directives were if:true and if:false. Nothing wrong with these per se, but once you start nesting things the clarity starts to leak away. Consider a made up example of a search results page where I want to :

  • display a placeholder if a search hasn't been run
  • display a message if the search has been run but there are no results
  • display the results if the search has been run and there are results
  • display the result count on the right hand side if the search is run and there are results
  • display an additional message if there are more than the 100 results displayed on the page

To assist me I have a few properties - searchRun, hasResults, hasMoreResults, resultCount, and my markup is:
<template if:true={searchRun}>
    <template if:true={hasResults}>
        Results go here!
        <template if:true={hasMoreResults}>
            More than 10 results - please refine your search
        </template>
    </template>
    <div style="float:right">{resultCount} results</div>
    <template if:false={hasResults}>
        Search returned no results
    </template>
 </template>
 <template if:false={searchRun}>
     No search run.
 </template>

While it's not terrible, it's also a bit confusing - lots of true/false and I have to examine the property binding to figure out the relationship between them. The eagle-eyed will also spot the bug - my result count sits outside of the conditional for hasResults, so will be displayed in all cases. This is easily done when the if and else conditionals are independent of each other.

New Mechanism

The new conditionals are lwc:if, lwc:elif and lwc:else. Note that these are dependent on each other - you can't have an lwc:else unless it follows an lwc:if, and it is executed if the lwc:if evaluates to false.

Reworking the markup from above gives me:

<template lwc:if={searchRun}>
    <template lwc:if={hasResults}>
        Results go here!
        <template lwc:if={hasMoreResults}>
            More than 10 results - please refine your search
        </template>
        <div style="float:right">{resultCount} results</div>
    </template>
    <template lwc:else>
        Search returned no results
    </template>
</template>
<template lwc:else>
    No search run.
</template>

which I think is much clearer. I can easily see the markup that will render when the properties evaluate to true or not, and I can see the dependencies as the lwc:else (or lwc:elif) always follows an lwc:if (or lwc:elif). Even more so when I collapse the if template body in VS Code:

Another benefit is that I can't misplace my result count div - if I try to place markup between two dependent lwc conditional directives, in this case between lwc:if and lwc:else, it is immediately called out as a problem:


Related Posts

I've waxed lyrical about clarity in the past, regarding the System.Assert class introduced in Winter '23.




Saturday, 30 June 2012

HTML Comments in Visualforce Pages

After seeing the presentation from Wes Nolte at Cloudstock London, I've been playing around with knockout.js in my spare time over the last couple of weeks and am very impressed.  I've been building some apps using JQuery Mobile and an area of concern has been the way that I've been striping HTML through javascript functions in order to update page sections with the latest data - using knockout.js I don't have to worry about this any more as I can bind the data and respond to user interaction via an almost Apex controller-like javascript object.

One of the feature of knockout.js is the ability to iterate an array of data via a foreach.  With HTML containers that naturally contain a repeating body (e.g. TBODY or UL) the data can be bound directly to that element. However, if you have a block of markup that you want to repeat without a container (in my case it was simply a DIV element per entry in the array), you make use of HTML comments to wrap the body.  An example of this is shown below, where there is a single static list item and the rest are generated from an array:

<ul>
<li><strong>Days of week:</strong></li>
 <!-- ko foreach: daysOfWeek -->
 <li>
  <span data-bind="text: $data"></span>
 </li>
 <!-- /ko -->
</ul>

<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};
ko.applyBindings(new viewModel());
</script>

In this case the <!-- ko foreach: daysOfWeek --> comment indicates that everything between it and the <!-- /ko --> end comment should be repeated for each element in the daysOfWeek array of Strings, and the <span> inside each list element should be populated with the array element via the data-bind="text: $data" attribute.  


After saving this markup in my Visualforce page, I viewed the page to be disappointed by the sight of a single list item with the text 'null' in the embedded span.  As I was green to this framework, my initial assumption was that I'd done something wrong.  Observable elements need to be accessed as a function rather than an attribute, so maybe I needed some additional brackets in there?  That just made things worse.  I then added in some debug to output the size of the array in case I'd made a mistake there, and that showed that there were 7 elements as expected.  To check that there wasn't an issue with the javascript file I'd downloaded, I rewrote it to use the UL element as a container and that worked fine.  Some googling showed that nobody else was having this problem, so it was clearly something that I was doing.  After some more head scratching and unsuccessful tweaks a synapse fired and I remembered having a similar issue when attempting to use CSS conditional comments for IE. These comments are interpreted by IE and ignored by all other browsers, so allow specific code or includes to be executed for the IE browser:
<!--[if IE 6]>
Special instructions for IE 6 here
<![endif]-->

I couldn't remember the exact issue, but I'd ended up writing a controller that inspected the USER-AGENT header as I couldn't make it work in the page.  Once I'd found the notes it came flooding back - Visualforce removes HTML comments.  Checking the source of my rendered page confirmed this - my bounding comments were nowhere to be seen.  Moving the logic to the controller wasn't an option here, so I started looking for a workaround.

My first thought was to place the comments inside outputtext tags with the escape attribute set to false:

<apex:outputText escape="false" value="< -- ko foreach: daysOfWeek -->" />

While this was successful in outputting an HTML comment, the body was replaced with asterisks, hardly helpful:

<-- ********************** -->

I really don't understand why this happens - either leave the comment alone or remove it. Mangling helps nobody and adds unnecessary characters to the page.

After a bit more experimentation I came up with the following notation:

<apex:outputText value="<" escape="false"/>!-- ko foreach: daysOfWeek --<apex:outputText value=">" escape="false"/>

It looks pretty ugly but does the trick.  By the way, if you are thinking that this could be made more readable using a custom component, I thought the same, but custom components get wrapped in a bonus <span> element, which broke things.

Upon changing my example markup to:

<ul>
<li><strong>Days of week:</strong></li>
 <apex:outputText value="<" escape="false"/>!-- ko foreach: daysOfWeek --<apex:outputText value=">" escape="false"/>
 <li>
  <span data-bind="text: $data"></span>
 </li>
 <apex:outputText value="<" escape="false"/>!-- /ko --<apex:outputText value=">" escape="false"/>
</ul>
<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};
ko.applyBindings(new viewModel());
</script>

Everything started working as expected and my faith in knockout.js was restored.  If you agree that HTML comments should be left in the resulting page, please vote up my idea.