Why the `-init ajax` option has to be a 'plainobject' rather than just 'object'?

Why the `-init ajax` option has to be a 'plainobject' rather than just 'object'?

KontatakeshiKontatakeshi Posts: 5Questions: 1Answers: 0
edited November 2020 in Free community support

Description of problem:
I was trying to encapsulate the ajax option into an ES6 class like:

class MyAjaxOptions {
    constructor(path, getParamsFunc, dataFilterFunc, dataSrcFunc){
        this.#path += path
        this.data = getParamsFunc
        this.dataFilter = dataFilterFunc
        this.dataSrc = dataSrcFunc
    }
    #path = '/my-prefix'
    get url(){
        return myHostname + this.#path
    }
}

class MyDtOptionsWithAjax {
    constructor(columns, ajaxOptions){
        this.columns = columns
        this.ajax = ajaxOptions
    }
    /** A bunch of my preferred init settings 
     * like searching, ordering...*/
}

function getMyDtParams(data) {
    const params = {
        test_id : $('#test-id').val(),
        /**blah blah blah... */
    }
    return params
}

const testAjax = new MyAjaxOptions('/test/get/list', getMyDtParams, trivialHere, trivialHere2)
const testInitOptions = new MyDtOptionsWithAjax([{title:'test',data:'test_id'}], testAjax)
$('#example-table').DataTable(testInitOptions)

And it turns out the function ajax.data is never called, but the DataTable did initialized. So the outer option object created by a class i.e. a constructor function did works.

I had no clue until I referred to DataTable's source code at line 48 was there a condition:

    if ( $.isPlainObject( ajax ) && ajax.data )

and since my ajax is neither created from an object literal {} nor a new Object() call, it fails the test and rejected out of the block that calls my ajax.data

    if ( $.isPlainObject( ajax ) && ajax.data )
    {
        ajaxData = ajax.data;

        var newData = typeof ajaxData === 'function' ?
            ajaxData( data, oSettings ) :  // fn can manipulate data or return
            ajaxData;                      // an object object or array to merge

        // If the function returned something, use that alone
        data = typeof ajaxData === 'function' && newData ?
            newData :
            $.extend( true, data, newData );

        // Remove the data property as we've resolved it already and don't want
        // jQuery to do it again (it is restored at the end of the function)
        delete ajax.data;
    }

Will there be an adjustment of this condition or I have to use a plain object for ajax ?

This question has an accepted answers - jump to answer

Answers

  • KontatakeshiKontatakeshi Posts: 5Questions: 1Answers: 0

    And for the url property, it doesn't invoke the getter so I have to change my class into this to have the datatable send request to the right location.

    class MyAjaxOptions {
        constructor(path, getParamsFunc, dataFilterFunc, dataSrcFunc){
            this.#path += path
            this.data = getParamsFunc
            this.dataFilter = dataFilterFunc
            this.dataSrc = dataSrcFunc
            this.url = myHostname + this.#path
        }
        #path = '/my-prefix'
    }
    
  • KontatakeshiKontatakeshi Posts: 5Questions: 1Answers: 0

    Alright, I solved my problem simply make a copy of my constructed ajax like this:

    const ajax = Object.assign({}, testAjax)
    

    Maybe I can do some research to see if I can submit a PR improving this.

  • allanallan Posts: 61,667Questions: 1Answers: 10,096 Site admin
    Answer ✓

    Hi,

    Thanks for posting up this - it an interesting thread you've started!

    To a large extent the use of plainObject is legacy, but it is also in keeping with how the object is handled. For example, just a few lines on from that check we use jQuery.extend to combine two objects. That doesn't work with a class instance:

    class Allan { construction() { this.a = 'test'; }}
    let allan = new Allan()
    $.extend({}, allan)
    // results in {}
    

    So even just removing that plain object check and looking for a general object wouldn't be enough of a change. I'd be happy with that if it were to work, but replacing $.extend would be more of an undertaking!

    Ideally we'd use Object.assign(), but we aim for long term backwards compatibility with DataTables, so that isn't an option. We could include a polyfill, but jQuery already gives us $.extend and the library is a decent size already.

    So what to do? Honestly, I don't have a perfect answer. Currently, I think working with a plain object is probably easiest - unless you can see a way I'm missing?

    Thanks,
    Allan

  • KontatakeshiKontatakeshi Posts: 5Questions: 1Answers: 0
    edited November 2020

    Hi Allan,

    Thanks a lot for your kind answer.
    But there is one thing I have to figure out. That is the class instances actually work with $.extend. And in your case, $.extend didn't work because there is a typo which should be constructor() other than construction()

    class TestA {
        construction() {
            this.a = 'test'
        }
    }
    class TestB {
        constructor() {
            this.b = 'test'
        }
    }
    class TestC {
        constructor() {
            this.c = 'test'
        }
        method(foo) {
            this.foo = foo
        }
        get bar() {
            return this.c + ' getter'
        }
    }
    class TestD {
        constructor() {
            this.d = 'test'
        }
        fn = () => console.log(this.d)
    }
    
    let testA = new TestA(), // TestA {}
        testB = new TestB(), // TestB {b: "test"}
        testC = new TestC(), // TestC {c: "test"} and a getter for bar
        testD = new TestD()  // TestD {d: "test", fn: ƒ}
    
    Object.assign({}, testA) // {}
    $.extend({}, testA)      // {}
    Object.assign({}, testB) // {b: "test"}
    $.extend({}, testB)      // {b: "test"}
    Object.assign({}, testC) // {c: "test", foo: undefined} getter not copied
    $.extend({}, testC)      // {c: "test"} getter not copied
    Object.assign({}, testD) // {d: "test", fn: ƒ}
    $.extend({}, testD)      // {d: "test", fn: ƒ}
    

    As my case shows $.extend overally works with class instances but they do their jobs in diffrent ways. So I agree it's a good idea to work with plainobjects.

    Thanks again

This discussion has been closed.