Search code examples
javascriptjavascript-objectsjquery-select2

javascript self references in complex objects


I have refactored some old javascript code that is redundant. Our page uses an old version of Select2 and populates new Select2 dropdown/search boxes in several places.

I took the code that was redundant and built an object out of the code. The object has self-references within some functions.

When I use the object, I make a shallow copy of the object. My concern is that the self-references within the copies may be pointing back to the element of the original object, not the element of the copy.

What is a better way to structure this object so shallow copies do not point back to the original object?

I have done a few hours of research. Not knowing exactly what words to google made this difficult to research. Please excuse me if this is something easy to fix.

I did review MDN on getters and this page on self-referencing in objects. I understood it but I do not know how to use one.

    ajax: {
        main: function () {

            let results = (mainObject.ajax.type.toLowerCase === 'new' ? mainObject.ajax.resultNew : mainObject.ajax.resultPreExisting);

            if (typeof (results) != 'function') {
                mainObject.error;
            }
            ajaxObj = {
                url: someOutSideVariableForURL,
                dataType: "json",
                quietMillis: 100,
                data: function (c, d) {
                    return {
                        searchFor: c,
                        maxPerPage: 30,
                        page: d
                    };
                },
                results: results
            }
            return ajaxObj;
        },
        type: "",
        resultNew: function (e, d) {
            //new result function
            var c = {
                results: e.results,
                more: false
            };
            if (d < e.info.totalPages) {
                c.more = true;
            }
            if (c.results.length) {
                $("#someIDTag input[name=firstStuff_" + mainObject.idCount + "]").data("hasResults", true);
            }
            return c;
        },
        resultPreExisting: function (e, d) {
            // Pre existing result function
            var c = {
                results: e.results,
                more: false
            };
            if (d < e.info.totalPages) {
                c.more = true;
            }
            $("#someIDTag input[name=firstStuff_" + mainObject.idCount + "]").data("hasResults", false);
            if (c.results.length) {
                $("#someIDTag input[name=firstStuff_" + mainObject.idCount + "]").data("hasResults", true);
            }

            return c;
        }
    },
    createSearchChoice: function (d) {

        var c = {
            hasResults: $("#someIDTag input[name=firstStuff_" + mainObject.idCount + "]").data("hasResults"),
            results: null
        };
        if ($.trim(d).length) {
            c.results = {
                id: 0,
                text: d
            };
        }
        return c.results;
    },
    error: function(){
        alert("mainObject improper configuration")
        return null
    },
    formatResult: function (item, container, escapeMarkup) {
        var d = {
            response: item.text,
            markup: []
        };
        window.Select2.util.markMatch(d.response, escapeMarkup.term, d.markup);
        if (item.id == 0) {
            d.response += '<br/><i style="font-size:0.8em;"><i class="icon-plus-sign"></i> Add new stuff</i>';
        } else {
            d.response = d.markup.join("");
        }
        if (item.data && item.data.businessFunctions.length) {
            d.response += '<br /><span style="font-size:0.75em;opacity:0.55;">(' + item.data.businessFunctions + ")</span>";
        }
        return d.response;
    },
    formatSearching: function () {
        return ' <i class="icon-spinner icon-spin"></i> Searching stuff...';
    },
    formatSelection: function (item, container) {

        $("#someOtherIDTag  input[name=stuff_" + mainObject.idCount + "]").val(item.text);
        $("#someOtherIDTag input[name=stuff_other_" + mainObject.idCount + "]").val(item.id);
        if (item.id == 0) {
            $(container).append('<i class="pull-right" style="font-size:0.8em;color:#049cdb;font-weight:bold;"><i class="icon-warning-sign"></i> New</i>');
        }
        return item.text;
    },
    getIdCount: function(){ //tried this but do not know how to use it properly
        return this.idCount
    },
    idCount: 0,
    initSelection: function (c, d) {

        d({
            id: $("input[name=dealCompanyID_" + mainObject.idCount + "]").val(),
            text: c.val()
        });
    },
    S2Base: {
        dropdownCssClass: SELECT2FIXED,
        placeholder: "",
        minimumInputLength: 2
    }

} 


// As you can see mainObject is accessed in a few places. Please remember this is not my code. I am just trying to refactor it into an object as the project expands. 

// This is how I use the object (and this is done a few times each time with a different name for newCopyObject):

$(".someClass").each(function(){
    const newCopyObject = {...mainObject};

    newCopyObject.idCount = fromDataAttrOnHTMLNode;
    newCopyObject.ajax.type = 'new';
    $(this).select2({
        ...newCopyObject.S2Base,
        formatSearching: newCopyObject.formatSearching,
        initSelection: newCopyObject.initSelection,
        ajax: newCopyObject.ajax.main(),
        createSearchChoice: newCopyObject.createSearchChoice,
        formatResult:newCopyObject.formatResult,
        formatSelection: newCopyObject.formatSelection
    });
});


// ...do a bunch of other things

Solution

  • Looks to me like you might want to refactor. I don't totally get what's going on, but if mainObject has references to itself, then after const newCopyObject = {...mainObject}; newCopyObject the new copies of those will be pointing back to mainObject.

    Using _.cloneDeep from lodash or the like would be one approach. I think using the ES6 class syntax might be the quickest and most understandable (to the next coder) way to refactor it.