2
\$\begingroup\$

I previously asked how can I simplify a validation process:

How can I simplify this JQuery validation process?

But in hindsight, what I really meant to ask was how can I turn that process into a reusable function.

I'm going through some old code that I wrote, and I am trying to clean it up so there's not a lot of redundant code.

I have many onClick events that look like this:

$('#addSubmit').on('click', function(){
  var addcriteria = {
    addcity: $('#addcity').val(),
    addregion: $('#addregion').val(),
    addloctype: $('#addloctype').val(),
    // several more values
  }

  if(addcriteria.addcity == "" || addcriteria.addcity == null){
    $('#errorModal').modal('show');
    $('.message').text('You must enter a City.');       
    $('#errorModal').on('hidden.bs.modal', function(){
        document.forms["addEquipForm"].addcriteria.addcity.focus();
    });
    return false;       
  }
  if(addcriteria.addregion == ""){
    $('#errorModal').modal('show');
    $('.message').text('You must select a Region.');        
    $('#errorModal').on('hidden.bs.modal', function(){
        document.forms["addEquipForm"].addcriteria.addregion.focus();
    });
    return false;       
  }
  // several more checks
  else{
    $.post('api/updateEquip.php', {addcriteria:addcriteria}, function(data){
        if(data == "Success"){
            $('#successModal').modal('show');
            $('.message').text('New Equipment has been added.');
            $('#successModal').on('hidden.bs.modal', function(){
                location.reload();
            });     
        }
        else{
            $('#errorModal').modal('show');
            $('.message').text('The Equipment could not be added.');        
            return false;                   
        }
    });
  }
});

As stated, I have many onClick events that look similar to the above. I want to get better at creating custom functions, and I think this would be a good start.

This was my attempt at creating a custom reusable function for the above code...

I started by creating the function:

function criteriaCheck(checkCriteria){
  if(checkCriteria == ""){
    $('#errorModal').modal('show');
    $('.message').text('Error message of some sort.');      
    $('#errorModal').on('hidden.bs.modal', function(){
        document.forms["addEquipForm"].checkCriteria.focus();
    });
    return false;   
  }
  else{
    // not sure what to return here
    // but I can console log a success message
    console.log('Success');
  }
}

Then, inside my onClick event, I can call the function and pass the value into the function:

var checkCriteria = '';

criteriaCheck(checkCriteria = addcriteria.addcity);

However, using these updates, I can only get "Success" in the console, even when I try to leave a blank form value.

With all of this said, how can I build a custom reusable function using the above onClick event?

\$\endgroup\$
1
  • \$\begingroup\$ Does this question meet the criteria for this board? You’re passing a boolean into your function (checkCriteria=addcriteria.addcity), so your function is only receiving TRUE. Just pass the object itself. \$\endgroup\$ – Tim Morton Nov 17 '20 at 14:41
2
\$\begingroup\$

The basic idea that I suggest is to decompose every bit of logic into small reusable functions. In my answer, I will touch only on validation logic because that part of your code has the most code duplication.

The following code is how I would like to see validation:

$('#addSubmit').on('click', function (e) {
    e.preventDefault();

    const validator = getValidator([
        [$('#addcity'), [
            combine(empty, emptyError),
            combine(notCapitalized, notCapitalizedError)]
        ],
        [$('#addregion'), [
            combine(emptyOrNull, emptyError)]
        ],
    ]);

    if (validator.hasErrors()) {
        const { element, error } = validator.getFirstError();
        $('.message').text(error.message);
        $('#errorModal').modal('show').on('hidden.bs.modal', element.focus.bind(element));
    } else {
        // Post request...
    }
});

Here we use the validator object to check if there are any errors and to get the first occurred error to display its message on the modal dialog. For the getValidator function we need to provide an element that we want to validate and a list of functions that find errors. Since functions that check for errors and actual errors are independent we need the ability to combine them. For example, the line combine(empty, emptyError) means that we want to check if a value is empty and return emptyError if so.

Also, I've made additional validation for the city field (see notCapitalizedError) to show how easy is to add new validation logic. To not hardcode error messages I've added data-name="" attributes to all inputs:

<input type="text" id="addcity" data-name="City" placeholder="City">
<input type="text" id="addregion" data-name="Region" placeholder="Region">

The implementation code is a bit hard to read and the following:

const empty = (str) => (str === "");
const emptyError = ($el) => ({ name: "empty", message: `You must enter a ${$el.data('name')}.` });
const notCapitalized = (str) => (!empty(str) && str[0] === str[0].toLowerCase());
const notCapitalizedError = ($el) => ({ name: "not-capitalized", message: `${$el.data('name')} should be capitalized.` });
const emptyOrNull = (val) => (empty(val) || val == null);


const combine = (hasError, createErr) => function($el) {
    return (hasError($el.val()) ? createErr($el) : null);
}

const getErrors = ($element, fns) => fns.map((fn) => fn($element)).filter(res => res !== null);
const getValidator = (data) => {
    const map = new Map();
    data.forEach(([$el, functions]) => {
        map.set($el, functions);
    })

    const elements = Array.from(map.keys());
    const getFunctions = ($el) => map.get($el) || [];
    const elErrors = ($el) => getErrors($el, getFunctions($el));
    const elHasErrors = ($el) => (elErrors($el).length > 0);
    const hasErrors = () => elements.some(elHasErrors);

    return {
        hasErrors,
        getFirstError: () => {
            if (hasErrors()) {
                const $element = elements.find(elHasErrors);
                return { element: $element, error: elErrors($element)[0]};
            }

            return null;
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your input. I will test your suggestion asap and let you know if I am successful. \$\endgroup\$ – John Beasley Nov 19 '20 at 14:15

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.