wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

LWC components with self contained images


When you create your LWC components, it is easy to include Salesforce predefined icons using lightning-icon. However once you need a custom icon, you point to an external URL, breaking the self containment. Unless you use url(data:). Here is what I did

A scalable check mark

Dynamic Lookup

For a list selection component I wanted a green check mark, like the picture above, indicate a selected record (more on the component in a later post). Since LWC doesn't allow (yet?) to store image assets inside a bundle and I wanted the component to be self contained.

The solution is to use data:image/svg+xml for a background image. The details were nicely outlined in css-tricks. I tried to use svg as source code directly, but failed to get it to work. So I resorted to use base64. It is an additional step, using an online Base64 encoder.

Making images

SVG is just an XML based text format, so you could create your image in notepad (take that jpg!). However you probably want to use a graphic editor. My choice here is Sketch (which gives me funny looks from designers: why a developer uses one of their tools). There were some steps worth to mention:

  • When using text (like the check mark), convert that to a svg path. Right click on text and select "Convert to outlines". This allows the text to scale with the rest of the image
  • Use the Edit-Copy-Copy SVG Code rather than use the export functionality
  • The resulting SVG is "talkative", you can edit and remove quite some content:
    • remove width and height attributes from the <svg> element, but keep the viewbox. Also remove the xlink name space
    • the <g> element doesn't need any attribute
    • <polygon> only needs fill and points attribute
    • All numeric values have many digits. You can round them up

Read more

Posted by on 18 April 2019 | Comments (0) | categories: Lightning Salesforce

Lightning Layouts, Input Fields and Field Level Security


The more control you want (or need) to exercise over the page layouts presented to your users, the more details you need to take care of. While the default record details and the lightning-record-form take care of hiding fields, without a trace, the current user doesn't have access to, you need to handle that yourself in a custom layout. Here is how.

Show me yours

A typical custom form layout might look like this:

<template>
    <lightning-record-edit-form
        record-id={recordId}
        object-api-name="Contact"
        onload={formLoadHandler}
        onsubmit={formSubmitHandler}
        onsuccess={formSuccessHandler}
    >
        <lightning-layout multiple-rows="true">
            <lightning-layout-item size="12">
                <lightning-messages> </lightning-messages>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="6">
                <lightning-input-field field-name="Name">
                </lightning-input-field>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="6">
                <lightning-input-field field-name="Department">
                </lightning-input-field>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="6">
                <lightning-input-field field-name="HomePhone">
                </lightning-input-field>
            </lightning-layout-item>
            <lightning-layout-item padding="around-small" size="6">
                <lightning-input-field field-name="CleanStatus">
                </lightning-input-field>
            </lightning-layout-item>
            <lightning-button
                class="slds-m-top_small"
                variant="brand"
                type="submit"
                name="save"
                label="Save"
            >
            </lightning-button>
        </lightning-layout>
    </lightning-record-edit-form>
</template>

Now, when a user doesn't have field level access to, let's say HomePhone (GDPR anyone?), the form would render with an empty space in the second row. To prevent this two steps are necessary:

  • Add a render condition to the lightning-layout-item
  • Compute the value for it in the onload event of the lightning-record-edit-form

A lightning-layout-item would look like this:

<lightning-layout-item padding="around-small" size="6" if:true={canSee.HomePhone}>
    <lightning-input-field field-name="HomePhone">
    </lightning-input-field>
</lightning-layout-item>

The only difference is the if:true={canSee.number}

In your JavaScript file you add @track canSee = {} to initialize your visibility tracker. Finally you amend the formLoadHandler to populate the canSee variable:

formLoadHandler(event) {
        let fields = event.detail.record.fields;

        for (let f in fields) {
            if (fields.hasOwnProperty(f)) {
            	this.canSee[f] = true;
            }
        }
    }    

As usual YMMV.


Posted by on 11 April 2019 | Comments (0) | categories: Lightning Salesforce

Mixing lightning-input-fields with custom data aware fields


Salesforce lightning offers a developer various ways to design custom forms when page layouts are not enough. The record-edit-form strikes a nice balance: it uses Lightning data service and allows one to design your own layout and field selection.

Beyond lightning-input-fields

Most of the time lightning-input-field is all you need for this forms. They auto-magically talk to the UI API and display the right input type.

However there are cases, where that's not what your users want. A recent example from a project: Phone numbers are stored as text field in Salesforce, but the users wanted a guided input: a country picker, then showing the area code picker (if the country has those) and an checker for field length for the main number (which varies greatly by country) and an eventual extension field (popular in the US, but not elsewhere).

So I started digging. Shouldn't it be possible to have something like <c-phone-helper field-name="Phone" /> and the same data magic as for lightning-input-field would happen? Turns out: not so fast. With events and a little code it would be possible, but that glue code needed to be applied to any custom field.

This got me thinking. The solution, it turns out, was to "extend" the record-edit-form to handle "rough" input components. You can give the result a try in experiment 8

Design goals

  • The component should be a drop-in "replacement" for record-edit-form
  • Structure of a page should be similar to they way one builds record-edit-form based forms
  • All lightning-input-fields should work out of the box
  • No additional glue code should be required in the component hosting the new form
  • Custom input field types should be easy to build. Once I figure out extensions, based on a base component
  • Opinionated: form layout is using a lightning-layout

Results

The replacement for lightning-record-form is c-extended-form (from experiment 8).
"Replacement" is a mouth-full, since the component just wraps around a lightning-record-form. A few components are ready to be used for it:

  • specialInput a little test component. It just returns the input in upper case. Not very useful other than studying the boiler plate
  • uxDebouncedInput returns changed values after a debounce period. Default is 300ms, the attribute delay allows to specify duration. The component shows different behavior depending on the attribute field-name being present with a value. The original purpose of the field is to be used in uxQuickLookup, now you can use it standalone
  • uxQickLookup which allows you to lookup an object. It works in lightning apps, mobile and communities and can serve as a stop-gap for the missing lookup on mobile. I recently updated it to show additional fields beside the object name

How it works


Read more

Posted by on 06 April 2019 | Comments (1) | categories: Lightning Salesforce WebComponents

Dynamic Lookup for LWC - update


There's always room for some improvement. So I updated

Dynamic Lookup

Is this the account you are looking for?

The dynamic lookup works like a charm, including on the Salesforce mobile app. The only catch: with only the object name visible it was less useful than the standard lookup. There are too many objects with the same name.

The solution: I added a new parameter fields that allows to specify the fields you want to be displayed. To separate them, I choose Badges. IMHO there isn't much value in showing fieldNames and mess up the display.

The source code can be found in Experiment 5, enjoy! As usual YMMV!

Next stop: proper Jest test harness


Posted by on 06 April 2019 | Comments (0) | categories: Lightning Salesforce

Massive leak of Credit Card Pin Numbers


It seems that Data leaks are considered unavoidable like the flu, taxed or measles outbreaks for vaccination-deprived kids.

There's quite a list:

Kiss goodbye your PIN

You might think, it couldn't get worse, but it did. In an unprecedented development all, yes all of the 6 digit credit card pins have been revealed and posted online.

You can check: your pin will be in that stash. But be careful! Online searches are captured, potentially allowing an attacker to link the numbers back to you. So when inspecting the stash, either look manually or download and search locally.

Of course: you got that file on your local disk, someone might start asking questions. So be considerate!

Update

A single forgotten console.log(...) statement was the source that leaked all those numbers.

The full code, released on APRIL 01 is here:

/* Generates all 6 digit pins in a random order */
const max = 1000000;
const ppl = 10;
const pins = [];

// Prepopulate sequentially
for (let i = 0; i < max; i++) {
  pins[i] = (' 000000' + i).slice(-6);
}

// Randomize location
for (let i = max; i >= 0; i--) {
  let p = Math.floor(Math.random() * i);
  let n = pins[p];
  pins[p] = pins[i];
  pins[i] = n;
  if (i % ppl == 0) {
    console.log(pins.slice(i, i + ppl).toString() + (i == 0 ? '' : ','));
  }
}

Hope you enjoyed it!


Posted by on 01 April 2019 | Comments (2) | categories: Salesforce Singapore

Extract field definitions from object.xml


The other department asked: can I have a csv list of fields with Name,Type,length from Salesforce. The only source they have are the object.xml files from a meta data API export

Some counting required

The object.xml file contains size information for text files only. For date and numbers that's not an issue. The sticky part are pick lists and multi select pick lists.

A little XSLT goes a long way. The magic is in the expression max((s:valueSet/s:valueSetDefinition/s:value/s:fullName/string-length())) For your enjoyment, the full style sheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
    xmlns:s="http://soap.sforce.com/2006/04/metadata"
    exclude-result-prefixes="xs xd"
    version="2.0">
   
   <xsl:output method="text" encoding="UTF-8" />
    
    <xsl:template match="/">
name,type,length
<xsl:apply-templates select="/s:CustomObject/s:fields" />
    </xsl:template>
    
    <xsl:template match="s:fields">
<xsl:value-of select="s:fullName"/>,<xsl:value-of select="s:type"/>,<xsl:value-of select="s:length"/>
        <xsl:text>&#xa;</xsl:text>
    </xsl:template>    
    
    <xsl:template match="s:fields[s:type='MultiselectPicklist']">
        <xsl:value-of select="s:fullName"/>,<xsl:value-of select="s:type"/>,<xsl:value-of select="sum((s:valueSet/s:valueSetDefinition/s:value/s:fullName/string-length()))+count(s:valueSet/s:valueSetDefinition/s:value/s:fullName)" />
        <xsl:text>&#xa;</xsl:text>
    </xsl:template>
    
    <xsl:template match="s:fields[s:type='Picklist']">
        <xsl:value-of select="s:fullName"/>,<xsl:value-of select="s:type"/>,<xsl:value-of select="max((s:valueSet/s:valueSetDefinition/s:value/s:fullName/string-length()))" />
        <xsl:text>&#xa;</xsl:text>
    </xsl:template>    

<!-- stuff without use -->
<xsl:template match="s:fields[s:formula]" />
    <xsl:template match="s:fields[not(s:type)]" />
<xsl:template match="s:fields[s:type='Picklist' and not(s:valueSet)]" />  
    
</xsl:stylesheet>

One of the little stumbling blocks: a namespace for http://soap.sforce.com/2006/04/metadata is required.

As usual: YMMV


Posted by on 29 March 2019 | Comments (0) | categories: Salesforce

Re-Usable Dynamic Custom Lookup LWC edition


Over at sfdcmonkey there's a nice AURA component that allows for dynamic lookup of a given object. Super nice and useful. I wondered what it would take to be rebuild in LWC

Dynamic Lookup

Same, Same but different

I want to achieve the same functionality, but would accept subtle differences. This is what I got:

  • The original component shows the object icon on the left. My version shows the search symbol that comes out of the box with lightning-input
  • I use 3 components instead of two. Input fields that trigger network calls are fairly common and it makes sense to debounce the input. So I created c-ux-debounced-input that signals entered data only after a period of 300ms
  • The component dispatches an event when a result has been selected or cleared, so it can be used inside other components
  • For now: it can be directly put on a lightning page in page builder and configured there. Useful for demos and test
  • When you clear the selected object, the result list opens up again, so no second network call is made until you change the input value

Read more

Posted by on 27 March 2019 | Comments (0) | categories: Lightning Salesforce Software

Mapping recordIds to Object Names - Offline edition


Lightning in Communities is "Same Same but different". When you want to build neutral components, you need to know what object you are dealing with

ObjectApiName, ObjectName and recordId

In Lightning Aura components one can use force:hasSObjectName to get access to an attribute sObjectName. In Lightning Web components one uses @api objectApiName. Except neither of those work in Communities.

The workaround is to look at the recordId and use DescribeSObjectResult.getKeyPrefix to map a record to the object name. There's a comprehensive list by David and my version as JSON object.

However, depending on your org, that list might vary. So I created a small component that lists out the objects in your current org. Enjoy:

ObjectIdSpy.cls

public without sharing class ObjectIdSpy {  
    @AuraEnabled(cacheable=true)
    public static Map<String,String> getObjectIdMappings(){
        Map<String,String> result = new Map<String,String>();
        Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
        for(String key : gd.keySet()) {
            Schema.SObjectType ot = gd.get(key);
            String curPrefix = ot.getDescribe().getKeyPrefix();
            String curName = ot.getDescribe().getName();
            // Fair warning: will omit objects that share the prefix
            result.put(curPrefix, curName);
        }
      return result;
    } 
}

objectIdSpy.html

<template>
    <lightning-card title="Object Spy">
        <pre>
{idList}
        </pre>
    </lightning-card>
</template>

objectIdSpy.js

import { LightningElement, track, wire } from 'lwc';
import idSpy from '@salesforce/apex/ObjectIdSpy.getObjectIdMappings';

export default class ObjectIdSpy extends LightningElement {
  @track idList;

  @wire(idSpy)
  spiedUpon({ error, data }) {
    if (data) {
      this.idList = JSON.stringify(data, null, 2);
    } else if (error) {
      this.idList = JSON.stringify(error, null, 2);
    }
  }
}

objectIdSpy.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="ObjectIdSpy">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Object Id Spy</masterLabel>
    <description>Generates a JSON object of Id and object names</description>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

As usual YMMV!


Posted by on 20 March 2019 | Comments (0) | categories: Salesforce Singapore

Finding Strings in recursively zipped files


I had an itch to scratch. After using Field Trip (which I like a lot) to determine unused fields, the team managing the external Informatica integration claimed they would need weeks to ensure none of the fields are used in any of their (hundreds) of pipelines.

ZIP inception

My first reaction (OK, the second, first one isn't PC) was: Let's go after the source code and just use an editor of choice to do a find in files. Turns out: not so fast. The source export offered by the team was a zip file with an elaborate directory structure containing, tada, zip files. So each of the pipes would need multiple zip operations.

Itch defined

I needed a tool that would start in a directory with a bunch of zip files, unpack them all. Check for zip files in the unpacked result, unzip these and repeat. Once done, take a list of strings and search for occurrences of those and generate a report which shows the files containing these strings

Itch scratched

I created findstring, a command line tool that takes a directory as starting point unzips what can be unzipped (optional) and searches for the occurrence of strings provided in a text file.

Initially I contemplated to render the output as XML, so the final report could be designed in whatever fashion using XSLT. However following KISS, I ended up using Markdown. I might add the XML option later on.

Recursion

The key piece of the tool is recursion (until you stack overflow ;-) ). Reading a directory and dive into directories found. I could have avoided that using Guava and its fileTraverser, but I like some Inception style coding. The key piece is this:

    private boolean expandSources(final File sourceDir) throws IOException {
        boolean result = false;
        final File[] allFiles = sourceDir.listFiles();

        for (final File f : allFiles) {
            if (f.isDirectory()) {
                result = result || this.expandSources(f);

            } else if (f.getName().endsWith(".zip")) {
                final String newDirName = f.getAbsolutePath().replace(".zip", "");
                final File newTarget = new File(newDirName);

                // Need to scan the new directory too
                if (this.expandFile(f, newTarget)) {
                    result = result || this.expandSources(newTarget);
                }
            }
        }
        return result;

    }

The function will return true as long as there was a zip file to be unzipped. The string finding operation (case insensitive) follows the same approach

Use cases

  • Find field usage in ZIP files. Works with a package downloaded from the meta data api or what Informatica exports
  • Check a source directory (doesn't need to contain zips) for keywords like TODO, FIXME, XXX

The command line syntax is very simple:

java -jar findString.jar -d directory -s strings [-o output]

  • -d,?dir <arg> directory with all zip files
  • -s,?stringfile <arg> Filename with Strings to search, one per line
  • -o,?output <arg> Output file name for report in MD format
  • -nz,?nz Rerun find operation on a ready unzipped structure - good for alternate finds

Limits

In its current form the utility will check for strings in any file short of zip. Zip gets unpacked and the result checked. When your directory contains binary files (e.g. images) it will still look for the string occurrence inside. File extension filters might be a future enhancement (share your opinion).

Files are read into memory. So if your directory contains huge files, you will blow your heap. Source code files hardly pose an issue, so the approach worked for me. Alternatively a scanner could be used, should the need arise.

Go give it a spin and keep in mind: YMMV


Posted by on 16 March 2019 | Comments (1) | categories: Salesforce Singapore

Testing Aura and LWC in a single Test


You drank the CoolAid and noticed that the Aura framework has been archived. You are hell bend to migrate your components.

Regression Test required

Aura components were testable using the Lightning Testing Service, while Lightning Web Components get tested using lwc-jest. These tests are not compatible.

UI-licious to the rescue. UI-licious is a testing framework for UI tests. They use a simple JavaScript syntax to provide testing and a rather clever addressing of elements. Other than Selenium, they don't rely on CSS selectors or XPath expressions (You still can use those).

To be very clear: A UI level testing library is not a replacement for proper unit testing. UI-licious has two use cases here: top of the pyramid UI testing and spotting UI level regressions. To learn more about the "testing pyramid", check out Martin Fowler's essay.

To give it a try I created 2 components with identical functionality: one in Aura, one as LWC. The components show a dialog where you can pick values for radio buttons. Shi Ling, the CEO provided the test script (the login subroutine omitted for brevity):

I.wait(30) // wait for salesforce to be ready
I.click("App Launcher")
I.click("Clown around")

I.see("Having 2 components of the same type")

test("The aura version")
test("The LWC version")

function test(btn){
  I.click(btn)
  I.see("Pick an Opportunity and Color")
  I.click("Product Opportunity")
  I.click("Red")
  I.click("Select")
  I.see("Nicely done")
}  

Watch the result for yourself:

What I really like: UI-licious builds the collaboration feature around testing, so stakeholders can see any time what's going on. Give them a try!


Posted by on 14 March 2019 | Comments (0) | categories: Lightning Salesforce WebComponents