0

I have a main form and multiple related inline forms. When submitting the main form, all other inline forms should be submitted along.

However, if any of the inline forms fail (we flag it in the reponse), the main form won't be sumibtted.

The problem is that submitInlineForm returns a promise. How can we pass along whether the form submission was successfull or not so that we can handle it in submitForm?

let submitForm = $form => {
    let formsToSubmit = [];
    $('.form-inline').each(function () {
        const $f= $(this);
        if (submittable($f)) formsToSubmit.push(submitInlineForm($f));
    });

    $.when(formsToSubmit).done(() => {
        if (!allFormsSuccessfull) return false;  // how to determine all forms were successfull?
        $.ajax({...});
    })
};


let submitInlineForm = $form => {
    return $.ajax({
        url: $form.attr('action'),
        type: 'post',
        success: response => {
            if (response.success) {
                // ???????
                // what to do here to indicate the inline form was successfully submitted?
            } else {
                // ???????
            }
        }
    });
};
dabadaba
  • 9,064
  • 21
  • 85
  • 155

1 Answers1

0

Good question, and maybe not as straightforward as initially meets the eye.

First, you don't need to return additional values along with promise.

Barring synchronous errors, the entirety of the form submission's success/failure is conveyed in the promise returned by submitInlineForm() .... but there are pifalls in that ...

  • the promises returned by jQuery.ajax() are non standard.
  • any single failure in a set of promises aggregated with jQuery.when() or Promise.all() will (without measures to prevent it) cause the aggregated promise to take its error path.

... so:

  • in order to count successes and failures you can reflect the promises returned by submitInlineForm(), otherwise any single failure will cause the promise returned by Promise.all() to fail.
  • in order to allow jqXHRs to be successfully reflected (in a standard way), you need first to normalise the multiple parameters exposed to the jqXHRs' .then() callbacks.

Fortunately a couple of utility functions allow the main code to be pretty straightforward.

let reflect = (promise) => {
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
};

let normalise = (jqXHR) => {
    // Recastast jqXHR as native js Promise by wrapping in `Promise.resolve()`.
    return Promise.resolve(jqXHR.then((response, textStatus, jqXHR) => {
        // this is a jQuery success handler.
        if (response.success) {
            // normalise the params into one js plain object
            return { response, textStatus, jqXHR };
        } else {
            return $.Deferred().reject(new Error('response.success was falsy')); // throw the jQuery way (not strictly necessary in jQuery v3+)
        }
    }, (jqXHR, textStatus, errorThrown) => {
        // This is a jQuery error handler.
        // Normalise the params into one js Error object.
        return $.Deferred().reject(new Error(textStatus || errorThrown)); // throw the jQuery way (not strictly necessary in jQuery v3+)
    }));
};

With the utilities in place, the main code is pretty simple.

let submitForm = () => {
    let formsToSubmit = $('.form-inline').get().filter(submittable);

    return Promise.all(formsToSubmit.map(submitInlineForm).map(normalise).map(reflect)) // Yay, the two pitfalls are overcome in a single line!
    .then(outcomes => {
        // Because all the promises in formsToSubmit were reflected, you will end up here regardless of any error(s).
        // Now separate out the successes and errors
        let successes = outcomes.filter(o => o.status === 'fulfilled').map(o => o.v.response); // array of raw ajax responses
        // let successes = outcomes.filter(o => o.status === 'fulfilled').map(o => o.v.response.values); // alternatively, you might choose to extract the data of interest fom the raw responses at this point.
        let errors = outcomes.filter(o => o.status === 'rejected').map(o => o.e); // array of errors
        if (errors.length > 0) { // were there any errors?
            return false;
        } else {
            // All forms were successfull (`successes` should be congruous with `outcomes`).
            // Do something with the successes.
            return $.ajax({...}); // or whatever
        }
    });
};

let submitInlineForm = $form => {
    return $.ajax({
        'url': $form.attr('action'),
        'type': 'post'
    });
};

untested

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Please note, this is not a completey generic solution. In `normalise()` the test `if(response.success)` is specific to this SO question. – Roamer-1888 Jan 25 '20 at 03:38