3B Onboarding Portal Components: Difference between revisions

no edit summary
(Created page with "== Intro == The 3B Portals platform empowers developers to create custom components that seamlessly integrate into the portal-building process. By utilizing Custom Metadata definitions and Static Resources, developers can design, configure, and implement their own components with ease. This documentation provides a detailed guide on how to develop and integrate custom components within 3B Portals. == Custom Metadata Definition == === Parameters === When creating a cust...")
 
No edit summary
 
Line 463: Line 463:
"default": "{{contactUser.Id}}"
"default": "{{contactUser.Id}}"
}]
}]
</syntaxhighlight>
== New Custom Component Shell ==
Below is an example of a new custom component implementation
=== Using Shadow Root ===
<syntaxhighlight lang="javascript" line="1">
//from ./myNewComponent.html.js
export default function html(){
    return `
    <div>
        <loading-spinner active="false" position="relative" variant="loading-bar" class="loading-spinner"></loading-spinner>
    </div>
    `;
}
//from ./myNewComponent.css.js
export default function css(){
    return `
      /* Only Style This Component */
    `;
}
//from ./myNewComponent.js
import { Component } from '../../../b3o__GeneralUtils/framework/component.js'
import css from './myNewComponent.css.js';
import html from './myNewComponent.html.js';
const APEX_CONTROLLER = 'namespace.MyApexClassName';
class MyNewComponent extends Component {
    template;
    _isLoading = false;
    get isLoading() {
        return this._isLoading;
    }
    set isLoading(val) {
        this._isLoading = val;
        this.template.querySelector('loading-spinner.loading-spinner').setAttribute('active', ''+val);
    }
   
    /**
    * This is an example of handling user logged in session
    */
    get contactId(){
        //Get the contact Id for the logged in user from the attribute
        if(this.userId && this.userId.length > 0) return this.userId;
        //Attribute user-id was not provided, attempt to get from URL
        const urlParams = Utils.getParamsMap();
        return urlParams?.get('userId');
    }
    set contactId(val){
        //When contact id is set, cache it in local storage
        localStorage.setItem('USER_ID', val);
    }
    /**
    * Component Labels
    * - These are overriden through the window.globals.labels object
    */
    labels = {
        btnRefreshLabel: 'Refresh',
    }
    constructor() {
      super();
      //Create a shadow root and attach it to the template
      this.template = this.attachShadow({mode: 'open'});
    }
   
    /**
    * Component created/moved in DOM
    */
    async connectedCallback() {
        if(window?.globals?.siteUrl === undefined){
            console.warn('Globals -> siteUrl is not defined');
        }
        //Merge external labels
        this.getLabelOverrides();
        //Obligatory render call
        this.render();
        //Register listeners
        this.template.addEventListener('customEvent', this.customEventHandler.bind(this), false);
        //Init component
        await this.initComponent();
    }
    /**
    * If the window.globals object contains a labels
    * object, merge the provided label translations with
    * the internal component labels
    */
    getLabelOverrides(){
        if(!window.globals?.labels) return;
        this.labels = Object.assign(this.labels, window.globals?.labels);
    }
    /**
    * Component removed from DOM
    */
    disconnectedCallback() {
        //Remove global listeners to avoid memory leaks
        this.template.removeEventListener('customEvent', this.customEventHandler.bind(this), false);
    }
    /**
    * Component HTML created
    */
    renderedCallback(){
        //Add reactivity to the component
        Reactivity.addTemplateBindings.bind(this).call();
    }
    async initComponent(){
        this.isLoading = true;
        await Services.callout(APEX_CONTROLLER, {
            endp: 'myMethod',
            userId: this.contactId
        }).then(response => {
            if(response.success){
                console.info(response);
            }else{
                console.warn(response);
            }           
        }).catch(error => {
            console.error(error);
        }).finally(() => {
            this.isLoading = false;
            this.render();
        })
    }
   
    /**
    * Observed Attributes - add any html attributes
    * that we need to reflect into the component class
    */
    static get observedAttributes() {
        return [
            'user-id'
        ];
    }
    /**
    * Add any attributes that would cause a re-render
    * of the component if they changed
    */
    get rerenderAttributes() {
        return [
            'user-id'
        ];
    }
    /**
    * On observed attribute change listener
    */
    attributeChangedCallback(attrName, oldVal, newVal) {
        //Convert hyphen case to camelcase
        const attrKey = attrName.replace(/-([a-z])/g, function(k){
            return k[1].toUpperCase();
        });
        debug(`${attrName}: `, newVal);
        if (oldVal !== newVal) {
            this[attrKey] = newVal;
            //Re-render template if attribute changes and it requires a re-render
            if(this.rerenderAttributes.includes(attrName)){
                this.render();
            }           
        }
    }
    /**
    * Main template renderer
    */
    render(){
        this.template.innerHTML = this.getTemplateElement().innerHTML;
        this.renderedCallback();
    }
    /**
    * Get element HTML
    */
    getTemplateElement() {
        let templateElement = document.createElement('template');
        templateElement.innerHTML = `
            <style>
                ${css.bind(this).call()}
            </style>
            ${html.bind(this).call()}
        `;
        return templateElement;
    }
    reportValidity(){
        return true;
    }
    checkValidity(){
        return this.reportValidity();
    }
 
    userId;
}
export default MyNewComponent
</syntaxhighlight>
</syntaxhighlight>
[[Category:3B Portals]]
[[Category:3B Portals]]