initSubmit potential bug using promise and MJoin options instance

initSubmit potential bug using promise and MJoin options instance

rf1234rf1234 Posts: 2,801Questions: 85Answers: 406
edited May 2019 in Editor

@allan @colin

I tried to use initSubmit with a promise today to make sure my ajax call to make some database updates completes before Editor keeps going.

The database updates I make relate to an options instance in an MJoin. At the front end I use Selectize and the user is allowed to enter new values which of course is a problem because then text is passed to the server instead of selected ids.

What I wanted to do is to
a) make the database inserts of the values newly entered by the user to make sure the MJoin can make the link table inserts to them
b) replace the id array that contains text as well with an array that contains valid ids only

The result was that Editor didn't wait for the promise to be resolved but tried to make the MJoin link table inserts with the (by then non-replaced) text fields which led to a crash due to referential integrity constraints.

I think this is truly a bug; you might want to check this.

I don't know whether the promise would have worked if it wasn't about an MJoin. But Editor didn't wait for promise resolution when it processed the MJoin. I am sure because I traced it in the debugger. There were no other bugs.

I implemented a work around now. No (false) promises any longer ... I now use a combination of initSubmit and submitSuccess to get it done relying on a global variable to keep the ids until submit success.

So this is my work around:

Editor selectize field allowing new entries:

}, {
    label: lang === 'de' ? 'Labels:' : 'Labels:',
    name:  "ctr_label[].id", 
    type: "selectize", 
    opts: {
        create: true,
        createFilter: function(val) {
          return ( isNaN(val) || val.indexOf('.') > -1 || val.indexOf('-') > -1 ) ? val : false;
        },
        maxItems: null,
        openOnFocus: true,
        allowEmptyOption: false,
        placeholder: lang === 'de' ? 
            "Bitte Labels wählen oder hinzufügen" : 
            "Please select labels or add some",                
        render: {
            option_create: function(data, escape) {
                var add = lang === 'de' ? "Neu: " : "Add ";      
                return '<div class="create">' + add + '<strong>'
                       + escape(data.input) + '</strong>&hellip;</div>';
            }
          }
        },
},  {

Editor initSubmit and submitSuccess events:

.on ( 'initSubmit', function ( e, action ) {     
    //new labels need to be filtered out because the Editor options
    //instance will crash due to referential integrity
    if ( action !== "remove" ) {
        ids = ctrEditor.field('ctr_label[].id').val();
        var sanitizedLabelIdArray = [];
        for (i=0; i < ids.length; i++) {
            if ( ( ! isNaN(ids[i]) ) && ids[i].indexOf('.') < 0
                                     && ids[i].indexOf('-') < 0 ) {
                sanitizedLabelIdArray.push( ids[i] );
            }
        }
        ctrEditor.field('ctr_label[].id').val(sanitizedLabelIdArray);
    } else {
        ids = [];
    }
})
.on('submitSuccess', function (e, json, data, action) {   
    //we need to process potential new labels!
    if ( action !== "remove") {
        $.ajax({
            type: "POST",
            url: 'actions.php?action=processLabelIdArray',
            data: {
        //labelIdArray can also contain text of newly created labels!
                ctrId: json.data[0].DT_RowId.substr(4),
                labelIdArray: JSON.stringify(ids)
            },
            success: function () {   
                ajaxReloadTbls([ctrTable]);
                ids = [];
            }
        });
    } else {
        ajaxReloadTbls([ctrTable]);
        ids = [];
    }
});

and the PHP Mjoin with options instance:

->join(
Mjoin::inst( 'ctr_label' )
    ->link( 'ctr.id', 'ctr_has_ctr_label.ctr_id' )
    ->link( 'ctr_label.id', 'ctr_has_ctr_label.ctr_label_id' )
    ->order( 'ctr_label.label_text asc' )
    ->fields(
        Field::inst( 'id' )->set( false )
            ->options( Options::inst()
                ->table('ctr_label, user_has_available_label')
                ->value('ctr_label.id')
                ->label( 'ctr_label.label_text' )
                ->order( 'ctr_label.label_text asc' )
                ->where( function($q) {
                    $q ->where( function($r) {
                        $r ->where('user_has_available_label.user_id', $_SESSION['id']);
                        $r ->where('ctr_label.id', 'user_has_available_label.ctr_label_id', '=', false); //join
                    } );
                    //We want to be able to keep existing ctr labels even though they are not in the user's domain
                    $ctrId = 0;
                    if ( isset($_SESSION['ctr_id'] ) ) {
                        $ctrId = $_SESSION['ctr_id'];
                    }
                    $q  ->or_where( 'ctr_label.id', 
                        '( SELECT DISTINCT ctr_label_id  
                             FROM ctr_has_ctr_label
                            WHERE ctr_id = :ctrId
                            )', 'IN', false); 
                    $q  ->bind( ':ctrId', $ctrId );       
                } )
            ),
        Field::inst( 'label_text' )->set( false )
    )
)

I use Editor 1.9.

Replacing the Editor field contents is no problem in my work around.

ctrEditor.field('ctr_label[].id').val(sanitizedLabelIdArray);

I tried to do the same with a promise - but Editor didn't wait for resolution of the promise.

This was (approx.) the code that didn't work:

.on ( 'initSubmit', function ( e, action ) {
    return new Promise( function ( resolve ) {
        $.ajax({
            type: "POST",
            url: 'actions.php?action=processLabelIdArray',
            data: {
        //labelIdArray can also contain text of newly created labels!
                labelIdArray: JSON.stringify(ctrEditor.field('ctr_label[].id').val())
            },
            dataType: "json",
            success: function (data) {   
        //the new array only contains real ids - no text any longer
                ctrEditor.field('ctr_label[].id').val(data);
                resolve(); //Editor continues after this!
            }
        });
    });    
})

Replies

  • allanallan Posts: 61,435Questions: 1Answers: 10,049 Site admin

    resolve(); //Editor continues after this!

    That is what should happen. But from your description it isn't waiting for that. Is that correct? Are you able to give me a link to the page so I can take a look at the timing?

    Thanks,
    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406
    edited May 2019

    Unfortunately I can't Allan. I know that is a bit frustrating but maybe you can figure it out with a different example. Sorry. And yes, it isn't waiting for "resolve()".

  • allanallan Posts: 61,435Questions: 1Answers: 10,049 Site admin

    Are you running the non-min version of Editor? Can you check this this code is present in the _event function:

            // Allow for a promise to be returned and execute a callback
            if ( promiseComplete ) {
                if ( e.result && typeof e.result === 'object' && e.result.then ) {
                    // jQuery and "real" promises both provide "then"
                    e.result.then( promiseComplete );
                }
                else {
                    // If there wasn't a promise returned, then execute immediately
                    promiseComplete();
                }
            }
    

    That is what should be used to allow for a Promise and Colin was testing this just the other week.

    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Yes this code is present in Editor. Just checked it.

  • allanallan Posts: 61,435Questions: 1Answers: 10,049 Site admin

    I've just tried it here and it appears to work as expected: http://live.datatables.net/nuvihosi/11/edit .

    The console shows:

    1. initSubmit
    2. resolving
    3. preSubmit
    4. postSubmit

    with a 5 second wait between 1 and 2.

    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Tried promises again on "initSubmit" - and this time it works. I use a newer Editor version now, maybe that is the reason. Or maybe because I return "true" in the resolve statement. Don't know.

    .on('initSubmit', function (e, action) {
        return new Promise( function ( resolve ) {
            var selected = contractTable.row({selected: true});
            $.ajax({
                type: "POST",
                url: 'actions.php?action=getCurrentAccountBalance',
                data: {
                    contractId:    selected.data().contract.id,
                    paymentDate:   paymentDate
                },
                dataType: "json",
                success: function (d) {
                    ... do something ...
                    resolve( true );
                }
            });
        });
    })
    
This discussion has been closed.