0

I'm coding an autocomplete feature and I'm stuck with a problem. When my tracked list value is updated with the array that had just been sorted, sometimes an error happens and the list is not updated. It seems to happen only when the size of the initial array (unsorted) is high It gives me the following error :

Uncaught (in promise) TypeError: Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'.

The code :

Controller :

/* eslint-disable no-console */
import {LightningElement, track, api} from 'lwc';
import getUserBAUsableObject from '@salesforce/apex/TOCK_Main_CTRL_CLS.getUserBAUsableObject'
import TOCK_ActivityFilterLabel from '@salesforce/label/c.TOCK_ActivityFilterLabel';
import TOCK_ActivityFilterProjects from '@salesforce/label/c.TOCK_ActivityFilterProjects';
import TOCK_ActivityFilterTraining from '@salesforce/label/c.TOCK_ActivityFilterTraining';
import TOCK_ActivityFilterAll from '@salesforce/label/c.TOCK_ActivityFilterAll';
import TOCK_AddActivities from '@salesforce/label/c.TOCK_AddActivities'
import addTockUserCurrBA from '@salesforce/apex/TOCK_Main_CTRL_CLS.addTockUserCurrBA'
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
import TOCK_SelectActivityPlaceholder from '@salesforce/label/c.TOCK_SelectActivityPlaceholder';
import TOCK_SearchActivity from '@salesforce/label/c.TOCK_SearchActivity';
import fuse from '@salesforce/resourceUrl/fuse';
import {loadStyle, loadScript} from 'lightning/platformResourceLoader';

export default class SelectActivity extends LightningElement {

    // Labels
    filter = TOCK_ActivityFilterLabel;
    projects = TOCK_ActivityFilterProjects;
    training = TOCK_ActivityFilterTraining;
    all = TOCK_ActivityFilterAll;
    placeholder = TOCK_SelectActivityPlaceholder;
    search = TOCK_SearchActivity;
    addActivities = TOCK_AddActivities;
    fuse = fuse;

    @api
    mobile;

    @track
    activities = { // Activities displayed in the list (possibly filtered)
        list: []
    };

    allActivities = { // All of the user's activities (unfiltered)
        list: []
    };

    connectedCallback() {
        this.loadActivities();
        loadScript(this, this.fuse).then(() => {
            console.log("=== FUSE LOADING ===");
            console.log("Fuse loaded");
        }).catch(error => {
            console.log("=== FUSE LOADING ===");
            console.log("Fuse not loaded")
        });


    }

    // ======================================== BA List =================================================

    @api
    loadActivities() {
        getUserBAUsableObject()
            .then(result => {
                let listBA = JSON.parse(result)
                this.activities.list = listBA;
                this.allActivities.list = [];
                for (let activity of this.activities.list) {
                    const name = String(activity.fullName).split(' / ');
                    this.allActivities.list.push({
                        usable: Boolean(activity.usable),
                        type: String(activity.type),
                        fullName: String(activity.fullName),
                        defaultActivity: Boolean(activity.defaultActivity),
                        activityId: String(activity.activityId),
                        baName: name[name.length - 1]
                    })
                }
                console.log("=== ACTIVITIES LIST ===");
                console.log(this.allActivities.list);
                this.searchList = this.allActivities.list;
            })
            .catch(error => {
                this.dispatchEvent(new ShowToastEvent({
                    title: "Load activities error",
                    message: "Something went wrong, please report this error",
                    variant: "error",
                }));
                console.error(error);
            });
    }

    toggleList() {

        const list = this.template.querySelector(".activity-list");
        const input = this.template.querySelector(".main-input");
        if (list.classList.contains("slds-is-open")) {
            list.classList.remove("slds-is-open");
            input.placeholder = this.placeholder;
        } else {
            list.classList.add("slds-is-open");
            input.placeholder = this.search;
        }
    }

    blockOnClick(event) {
        event.stopPropagation();
    }

    @api
    closeListAndFilter() {
        const input = this.template.querySelector(".main-input");
        const list = this.template.querySelector(".filter-list");
        const filter = this.template.querySelector(".activity-list");
        if (!this.mobile) {
            if (list) {
                list.classList.remove("slds-is-open");
            }
            if (filter) {
                filter.classList.remove("slds-is-open");
            }
            if (input) {
                input.placeholder = this.placeholder;
            }
        }


    }

    // ================================== Adding business activities ====================================

    @track
    addBtn = false;

    checkActivity() {
        const checkboxes = this.template.querySelectorAll(".activity-list input");
        for (const checkboxe of checkboxes) {
            if (checkboxe.checked) {
                this.addBtn = true;
                return;
            }
        }
        this.addBtn = false;
    }

    handleAdd() {
        const list = this.template.querySelector(".activity-list");
        if (list.classList.contains("slds-is-open")) {
            list.classList.remove("slds-is-open");
        }

        const checkboxes = this.template.querySelectorAll(".activity-list input");

        let dto = [];

        for (const checkboxe of checkboxes) {
            if (checkboxe.checked) {
                dto.push({activityId: checkboxe.name});
            }
        }

        addTockUserCurrBA({params: JSON.stringify(dto)})
            .then(() => {
                this.dispatchEvent(new CustomEvent('baupdate'));
                this.loadActivities();
                if (this.mobile) {
                    this.hideModal();
                }
            })
            .catch(error => {
                this.dispatchEvent(new ShowToastEvent({
                    title: "Add default user BA error",
                    message: "Something went wrong, please report this error",
                    variant: "error",
                }));
                console.error(error);
            });

    }

    // ======================================= Autocomplete =======================================

    searchList = [];

    handleInput(event) {
        let keyword = event.target.value;
        this.emptyButtonShown = keyword !== "";

        if (keyword === "") {
            this.activities.list = this.searchList;
        } else {
            let options = {
                keys: [{name: 'fullName', weight: 0.6}, {name: 'baName', weight: 0.4}],
                threshold: 0.6
            };
            let fuse = new Fuse(this.searchList, options);


            const result = fuse.search(keyword);

            console.log("=== TOCK CODE SEARCH RESULT ===");
            console.log(result);

            this.activities.list = [];
            /*
            for(let activity of result){
                this.activities.list.push(activity);
                console.log("Added Activity : " + activity.fullName);
            }
            */
            console.log(JSON.stringify(result));
            this.activities.list = [].concat(result);
        }

    }

    renderedCallback() {
        console.log("COMPONENT RERENDERD");
    }

    // ======================================= Filter ================================================

    @track
    currentFilter = TOCK_ActivityFilterAll;

    toggleFilter() {
        const list = this.template.querySelector(".filter-list");
        list.classList.toggle("slds-is-open");
    }

    get activityTypes() {
        return ["All", "Management", "Training", "Projects", "Services", "Development", "Buy&Resale"]
    }

    filterList(event) {

        this.template.querySelectorAll("c-select-activity-filter-item").forEach(e => {
            if (e.getActivityType() !== event.detail) {
                e.unselect();
            } else {
                e.select();
            }
        });
        this.currentFilter = event.detail;
        let newList = [];
        for (let activity of this.allActivities.list) {
            if (activity.type === event.detail || event.detail === "All") {
                newList.push(activity);
            }
        }
        this.activities.list = newList;
        this.searchList = newList;

        this.closeListAndFilter();
    }

    // ============================================= Mobile =======================================================

    @track
    modalShown = false;

    @track
    emptyButtonShown = false;

    showModal() {
        this.modalShown = true;
        document.body.scrollTop = document.documentElement.scrollTop = 0;
    }

    hideModal() {
        this.modalShown = false;
    }

    handleEmptyButton() {
        this.template.querySelector(".main-input").value = "";
        this.emptyButtonShown = false;
        this.activities.list = this.searchList;
    }

    minimize() {
        this.template.querySelector(".slds-modal__content").style.height = "20rem";
    }

    maximize() {
        this.template.querySelector(".slds-modal__content").style.height = "40.5rem";
    }
}

Template :

<template>
    <div if:false={mobile} class="slds-grid" onclick={blockOnClick}>
        <div class="slds-col slds-size_12-of-12 slds-large-size_4-of-12 slds-grid">
            <div class="slds-dropdown-trigger slds-dropdown-trigger_click filter-list slds-col slds-size_4-of-12">
                <button onclick={toggleFilter}
                        class="toggle-filter slds-button slds-button_icon slds-button_icon-border-filled"
                        aria-haspopup="true" title="Show More">
                    {currentFilter}
                    <lightning-icon icon-name="utility:down" size="x-small"></lightning-icon>
                </button>
                <div class="slds-dropdown slds-dropdown_left">
                    <ul class="slds-dropdown__list" role="menu" aria-label="Show More">
                        <template for:each={activityTypes} for:item="type">
                            <c-select-activity-filter-item activity-type={type} key={type}
                                                           onselect={filterList}></c-select-activity-filter-item>
                        </template>
                    </ul>
                </div>
            </div>
            <div class="main-input-wrapper slds-form-element__control slds-input-has-icon slds-input-has-icon_left slds-col slds-size_8-of-12">
                <lightning-icon class="slds-icon slds-input__icon slds-input__icon_left slds-icon-text-default"
                                icon-name="utility:search" size="x-small"></lightning-icon>
                <input
                        type="text"
                        class="slds-input main-input"
                        onclick={toggleList}
                        oninput={handleInput}
                        placeholder={placeholder}
                />
            </div>
            <div
                    class="slds-dropdown-trigger activity-list slds-dropdown-trigger_click"
            >
                <div class="slds-dropdown activity-list activity-list-responsive slds-dropdown_small">
                    <ul class="slds-dropdown__list" role="menu" aria-label="Show More">
                        <template for:each={activities.list} for:item="activity">
                            <li key={activity.activityId} class="slds-dropdown__item slds-is-selected"
                                role="presentation">
                                <a aria-checked="true" role="menuitemcheckbox" tabindex="0">
                        <span class="slds-truncate" title={activity.fullName}>
                            <div class="slds-checkbox" onclick={checkActivity}>
                                    <template if:true={activity.defaultActivity}>
                                            <input type="checkbox" name={activity.activityId} id={activity.activityId}
                                                   disabled/>
                                    </template>
                                    <template if:false={activity.defaultActivity}>
                                            <input type="checkbox" name={activity.activityId} id={activity.activityId}/>
                                    </template>
                                    <label class="slds-checkbox__label" for={activity.activityId}>
                                            <span class="slds-checkbox_faux"></span>
                                            <template if:true={activity.defaultActivity}>
                                              <span class="slds-form-element__label" style="color: #cccccc">&nbsp; {activity.fullName}</span>
                                            </template>
                                            <template if:false={activity.defaultActivity}>
                                              <span class="slds-form-element__label">&nbsp; {activity.fullName}</span>
                                            </template>



                                    </label>
                                </div>
                        </span>
                                </a>
                            </li>
                        </template>
                    </ul>
                </div>
            </div>
        </div>
        <div class="slds-col slds-size_12-of-12 slds-large-size_2-of-12">
            <template if:true={addBtn}>
                <template if:false={mobile}>
                    <button class="slds-button add-button" onclick={handleAdd}>
                        <lightning-icon icon-name="utility:add" variant="inverse" size="x-small"></lightning-icon>
                    </button>
                </template>
            </template>

        </div>
        <div class="slds-col slds-size_0-of-12 slds-large-size_6-of-12"></div>
    </div>

    <!-- ============================================= Mobile  ============================================= -->


    <div if:true={mobile}>
        <button class="slds-button green-button modalButton" onclick={showModal}>{addActivities}</button>
        <div if:true={modalShown}>
            <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true"
                     aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
                <div class="slds-modal__container">
                    <header class="slds-modal__header">
                        <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">{addActivities}</h2>
                    </header>
                    <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">

                        <div class="slds-dropdown-trigger slds-dropdown-trigger_click filter-list slds-col slds-size_4-of-12">
                            <button onclick={toggleFilter}
                                    class="toggle-filter slds-button slds-button_icon slds-button_icon-border-filled"
                                    aria-haspopup="true" title="Show More">
                                {currentFilter}
                                <lightning-icon icon-name="utility:down" size="x-small"></lightning-icon>
                            </button>
                            <div class="slds-dropdown slds-dropdown_left">
                                <ul class="slds-dropdown__list" role="menu" aria-label="Show More">
                                    <template for:each={activityTypes} for:item="type">
                                        <c-select-activity-filter-item activity-type={type} key={type}
                                                                       onselect={filterList}></c-select-activity-filter-item>
                                    </template>
                                </ul>
                            </div>
                        </div>
                        <div class="main-input-wrapper slds-form-element__control slds-input-has-icon slds-input-has-icon_left slds-col slds-size_8-of-12">
                            <lightning-icon
                                    class="slds-icon slds-input__icon slds-input__icon_left slds-icon-text-default"
                                    icon-name="utility:search" size="x-small"></lightning-icon>
                            <input
                                    type="text"
                                    class="slds-input main-input"
                                    oninput={handleInput}
                                    placeholder={placeholder}
                                    onfocusin={minimize}
                                    onfocusout={maximize}
                            />

                        </div>
                        <lightning-button-icon if:true={emptyButtonShown} onclick={handleEmptyButton}
                                               class="slds-icon slds-input__icon slds-input__icon_right slds-icon-text-default"
                                               variant="bare" icon-name="utility:close"
                                               alternative-text="remove"></lightning-button-icon>
                        <br><br>
                        <div class="activity-list">
                            <template for:each={activities.list} for:item="activity">
                                <div key={activity.activityId} class="slds-checkbox" onclick={checkActivity}>
                                    <template if:true={activity.defaultActivity}>
                                        <input type="checkbox" name={activity.activityId} id={activity.activityId}
                                               disabled/>
                                    </template>
                                    <template if:false={activity.defaultActivity}>
                                        <input type="checkbox" name={activity.activityId} id={activity.activityId}/>
                                    </template>
                                    <label class="slds-checkbox__label" for={activity.activityId}>
                                        <span class="slds-checkbox_faux"></span>
                                        <template if:true={activity.defaultActivity}>
                                            <span class="slds-form-element__label" style="color: #cccccc">&nbsp; {activity.fullName}</span>
                                        </template>
                                        <template if:false={activity.defaultActivity}>
                                            <span class="slds-form-element__label">&nbsp; {activity.fullName}</span>
                                        </template>


                                    </label>
                                </div>
                            </template>
                        </div>
                    </div>
                    <footer class="slds-modal__footer slds-grid">
                        <button class="slds-button slds-col slds-size_1-of-2 slds-button--neutral" onclick={hideModal}>
                            Cancel
                        </button>
                        <button class="slds-button slds-col slds-size_1-of-2 green-button" onclick={handleAdd}>
                            {addActivities}
                        </button>
                    </footer>
                </div>
            </section>
            <div class="slds-backdrop slds-backdrop_open"></div>
        </div>

    </div>

</template>
  • I think that is coming from fuse js. Can you verify – salesforce-sas Jul 22 '19 at 14:48
  • Thank you for your answer. I don't think it comes from fuse.js, because the result of fuse search is correclty printed, the problem occurs after if assign this.activities.list to result (during the list rerender if think). – Ombrelin Jul 22 '19 at 14:53
0

Can you try this:

this.activities.list = [].concat(result);

This will basically create new list and assign to activities list instead of modifying previous memory location.

| improve this answer | |
  • Thank you for your answer. I tried your code but it doesn't work, however the given error has changed : > Uncaught (in promise) TypeError: Cannot read property 'Symbol(ViewModel)' of undefined – Ombrelin Jul 22 '19 at 15:09
  • can you put console.log(JSON.stringify(result)); and paste the log string here – salesforce-sas Jul 22 '19 at 15:31
  • The result is huge so I hosted it here : myjson.com/ljk0d – Ombrelin Jul 22 '19 at 15:48
  • you get 'Symbol(ViewModel)' of undefined error when you try to modify api variable but i dont see any api variable. Perhaps you can show full html and js. and also Apex method if small. – salesforce-sas Jul 22 '19 at 16:00
  • Hi, thank you for helping me so much. I updated my post with my component's full code. Can't post APEX because it's huge but it just produce the JSON I already posted, so I don't think it's relevant. – Ombrelin Jul 23 '19 at 7:52
0

I finally reached a solution. I think the problem was a conflict between the template's re rendering and the list update.

So I moved the list update in the renderedCallback. The code is the following :


 searchResultList = [];

    /**
     * Handle input on the activity list's autocomplete search field
     * @param event ChangeEvent
     */
    handleInput(event) {
        let keyword = event.target.value;
        this.emptyButtonShown = keyword !== "";
        console.log(keyword);


        let options = {
            keys: [{name: 'fullName', weight: 0.6}, {name: 'baName', weight: 0.4}],
            threshold: 0.6
        };

        let fuse = new Fuse(this.searchList, options);
        const result = fuse.search(keyword);

        console.log("=== TOCK CODE SEARCH RESULT ===");
        console.log(result);

        /**
         * Due to a problem with the view re rendering we can't reassign the new list value here
         */

        this.searchResultList = [].concat(result);
        this.activities.list = [];
        if(keyword === ""){
            this.searchResultList = [].concat(this.searchList);
        }

    }

    renderedCallback() {
        // If the activity list search result has been updated, assign the new value
        if (this.activities.list.length !== this.searchResultList.length) {
            this.activities.list = [].concat(this.searchResultList);
        }
    }

| improve this answer | |

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.