1

Trying to learn how to properly reuse code. Basics of a clock updating every second. Would like to reuse the JS code for different UI LWC but running into various issues

Basic structure

/lwc 
......Account LWC 
.............HTML, JS, CSS, Meta 
......Clock LWC (Service)
.............JS

UI say for account. Maybe one for case or contact. This code would be repeated (currently)

UI Javascript

import {LightningElement, api, wire, track} from 'lwc';
import { getRecord } from 'lightning/uiRecordApi'
import { getFieldValue } from 'lightning/uiRecordApi';
import { Clock } from 'c/clock';

export default class AccountTzClock extends LightningElement {
    timeIntervalInstance;
    accountRecord;

    @api recordId;
    @wire(getRecord, { recordId: '$recordId', fields: ['Account.Name', 'Account.UTC_Offset_Minutes__c', 'Account.GoogleAPI_Exception_Message__c'] })
    dataLoaded(result){
        if(result.data){
            this.accountRecord = result.data;
            this.startClock();
        }

    }

    startClock() {
        let utcos = getFieldValue(this.accountRecord, 'Account.UTC_Offset_Minutes__c') | 0;

        if(Clock.updateClock( this, utcos)) {
            this.timeIntervalInstance = setInterval(Clock.updateClock(this, utcos), 1000)
        }else{
            //Do stuff
        }
    }

    get errorMsg(){
        return getFieldValue(this.accountRecord, 'Account.GoogleAPI_Exception_Message__c');
    }


}

The LWC Service JS that I would like to reuse is the JS that actually updates the clock. I have gotten to the point where it works on load. Not sure if there is a better way to gain access to the template and this other than passing in this like I am.

The problem now is that I cannot get setInterval to work. I get "bind must be called on a function" but I have tried to placed bind in every conceivable place to no avail. (Yea I am green)

Any suggestions on the best way to reuse this service class and be able to use setTimeout with access to this from the caller to gain access to the callers properties and template?

Clock Service JS

    const updateClock = (that, utcOffset) => {
        if(that.errorMsg != null){
            return false;
        }

        var date = new Date();
        var h = date.getUTCHours(); // 0 - 23
        var m = date.getUTCMinutes(); // 0 - 59
        var s = date.getUTCSeconds(); // 0 - 59
        var o = utcOffset;

        var hAdj = o / 60;
        var session = "AM";

        //Local Hour
        h = h + hAdj;

        //Rollover code
        if( h < 0){
            h = 24 + h;
        }else if( h > 23){
            h = h - 24;
        }

        if(h >= 8 && h < 18){
            that.clockBackgroundColor = 'slds-theme_success';
        }else{
            that.clockBackgroundColor = 'slds-theme_error';
        }

        if (h >= 12) {
            h = h > 12 ?  h - 12 : h;
            session = "PM";
        }

        if (h == 0) { //Midnight
            h = 12;
        }


        h = (h < 10) ? "0" + h : h;
        m = (m < 10) ? "0" + m : m;
        s = (s < 10) ? "0" + s : s;

        var time = h + ":" + m + ":" + s + " " + session;
        var clockEle = that.template.querySelector("div.clock");

        if(clockEle){
            clockEle.innerText = time;
        }

        return true;


}

const Clock = {
    updateClock: updateClock
}

export {Clock}

I did come up with a way to make it work but honestly it feels all wrong. Surely there has be a better way to do this...

To make it work I added the line:

let uc = function(){Clock.updateClock(this,utcos);}.bind(this);

to the startClock method and replace the places where I was calling Clock.updateClock with uc making it like this:

startClock() {


        let utcos = getFieldValue(this.accountRecord, 'Account.UTC_Offset_Minutes__c') | 0;
        let uc = function(){Clock.updateClock(this,utcos);}.bind(this);

        if(uc) {
            this.timeIntervalInstance = setInterval(uc, 1000)
        }else{
            //do stuff
        }
    }
  • what is UTC_Offset_Minutes__c and GoogleAPI_Exception_Message__c? – salesforce-sas Aug 28 at 3:21
  • Not relevant to the issue. Just fields on the record – Eric Aug 28 at 3:27
  • I am trying to understand what you are doing, both are just number fields? – salesforce-sas Aug 28 at 3:29
  • Number and Text. But they are not relevant to the issue other than I am passing one in and referencing the other. I could not use get field value in the export function so I just passed it in. – Eric Aug 28 at 3:31
  • I understand they are not relevant - just trying to understand - using other double and text (Name) fields – salesforce-sas Aug 28 at 3:36
2

The reusable methods are meant to do re-usable processing and not really meant to modify DOM i.e., Not all the classes which import clock will have div.clock element. Although, you can make it work by binding, I think there are better ways to do this.

Below are the 2 ways in which you can implement Clock:

1. Just do processing in Clock module and return the processed time:

Clock module:

const updateClock = (utcOffset = 0) => {
    var date = new Date();
    var h = date.getUTCHours(); // 0 - 23
    var m = date.getUTCMinutes(); // 0 - 59
    var s = date.getUTCSeconds(); // 0 - 59
    var o = utcOffset;

    var hAdj = o / 60;
    var session = 'AM';

    //Local Hour
    h = h + hAdj;

    //Rollover code
    if (h < 0) {
        h = 24 + h;
    } else if (h > 23) {
        h = h - 24;
    }

    if (h >= 12) {
        h = h > 12 ? h - 12 : h;
        session = 'PM';
    }

    if (h == 0) {
        //Midnight
        h = 12;
    }

    h = h < 10 ? '0' + h : h;
    m = m < 10 ? '0' + m : m;
    s = s < 10 ? '0' + s : s;

    var time = h + ':' + m + ':' + s + ' ' + session;
    return time;
};

and in your main.js:

startClock() {
    let utcos = getFieldValue(this.accountRecord, 'Account.NumberofLocations__c') | 0;

    if (Clock.updateClock(utcos)) {
        this.timeIntervalInstance = setInterval(() => {
            var clockEle = this.template.querySelector('div.clock');
            if (clockEle) {
                clockEle.innerText = Clock.updateClock(utcos);
            }
        }, 1000);
    } else {
        //Do stuff
    }
}

2. Use extended base class if you want the DOM modification in more than 1 place. In this case it is specific that we want to extend the DOM manipulation functionality as well.

Base.js:

import { LightningElement, wire, api, track } from 'lwc';
import { Clock } from 'c/utils';

export default class Base extends LightningElement {
    showClock(utcos) {
        if (Clock.updateClock(utcos)) {
            this.timeIntervalInstance = setInterval(() => {
                var clockEle = this.template.querySelector('div.clock');
                if (clockEle) {
                    clockEle.innerText = Clock.updateClock(utcos);
                }
            }, 1000);
        } else {
            //Do stuff
        }
    }
}

main.js:

import { wire, api, track } from 'lwc';
import Base from 'c/base';
import { getRecord } from 'lightning/uiRecordApi';
import { getFieldValue } from 'lightning/uiRecordApi';

export default class Poc extends Base {
    timeIntervalInstance;
    accountRecord;

    @api recordId = '00128000009j45qAAA';
    @wire(getRecord, {
        recordId: '$recordId',
        fields: [ 'Account.Name', 'Account.NumberofLocations__c' ]
    })
    dataLoaded(result) {
        if (result.data) {
            this.accountRecord = result.data;
            this.startClock();
        }
    }

    startClock() {
        let utcos = getFieldValue(this.accountRecord, 'Account.NumberofLocations__c') | 0;
        this.showClock(utcos);
    }

    get errorMsg() {
        return getFieldValue(this.accountRecord, 'Account.GoogleAPI_Exception_Message__c');
    }
}

You can as well implement the whole logic in Base class instead of re-usable utils module.

  • 1
    Thanks. You hit the nail on the head with trying to do things in a util class that should not be handled by the util class. Appreciate the fresh set of eyes as mine were clouded with frustration. – Eric Aug 28 at 16:53
  • sure @Eric. WC :) – salesforce-sas Aug 28 at 16:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.