0
\$\begingroup\$

enter image description here

First here is my settings object below. insight, spike and momentum are binded to 3 checkboxes in the markup. The objective here is to prevent the user from deselecting all choices, 1 choice must remain selected.

var settings = {
    user_id            : 0,
    insight            : true,
    spike              : true,
    momentum           : true,
    all_tickers        : true,
    errorMsg           : false,
    port_tickers       : [],
    fav_tags           : [],
    sensitivity        : 'medium',
    email_notification : true
};

So what I'm doing below is I create an Array with just the 3 keys from the settings object I want. Next I filter that array down to only the keys that are set to true. Next if the checkbox model is the same as the last item in the trueFilters Array I can then send back errorMsg = true.

function updateFilters(type) {
    settings[type] = !settings[type];

    // Create Array of filters that are true
    var filterKeys = _.map(settings, function(filter, key) {
        if (key === 'insight' || key === 'spike' || key === 'momentum') {
            if (filter) {
                return key;
            } else {
                return null;
            }
        } else {
            return null;
        }
    });

    // Remove any filters that are null/false
    var trueFilters = _.remove(filterKeys, function(n) {
        return n != null;
    });

    // If this filter is the last, prevent user from unselecting
    if (trueFilters.length === 0) {
        settings[type] = !settings[type];
        settings.errorMsg = true;
    }
}
\$\endgroup\$
1
\$\begingroup\$
if (key === 'insight' || key === 'spike' || key === 'momentum') {

Instead of looping through and checking these specific keys using an if, why not just create an array with the keys you're interested in and loop through that?

var filterKeys = ['insight', 'spike', 'momentum'];

Running on this, you can easily use map to gather the values, filter to reduce to only those that are true. Then checking if it's the last true filter will just be a matter of checking if what remains from a filter is an array with 1 as length.

Additionally, this function can be made so that it doesn't introduce side-effects or reduces the surface area of a side-effect. You can simply split off the logic to another function and return a boolean, while leaving the logic with side-effects (one that alters settings) in the caller.

function isLastFilter(settings) {
  var filters = ['insight', 'spike', 'momentum'];

  return filters.map(filter => settings[filter])
                .filter(value => value === true)
                .length === 1;
}

function updateFilters(type) {
  var shouldUpdate  = !isLastFilter(settings);
  settings[type]    = shouldUpdate ? settings[type] : !settings[type];
  settings.errorMsg = !shouldUpdate;
}

On other things, settings.errorMsg shouldn't be part of the data. It's a computation of other settings. In this case, it's derived from a state where there's only one filter present. The problem here is that updateFilters is essentially a "sync" function, syncing settings.errorMsg to the state of the other settings. Lose this, and you'll get an invalid state.

Drop settings.errorMsg. If you're using an ng-if or something to toggle the error in the UI, it should be something like ng-if="isLastFilter(settings)".

\$\endgroup\$

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.