patrickkunka / mixitup

A high-performance, dependency-free library for animated filtering, sorting, insertion, removal and more
https://www.kunkalabs.com/mixitup/
4.52k stars 735 forks source link

Creating a human-readable list of active filters #390

Closed nolybom closed 6 years ago

nolybom commented 6 years ago

I would like to show the number of items when mixitup is finished loading.

I couldn't find a way to achieve that so i am doing this workaround:

<h5>Showing <span class="itemCount">all</span> properties</h5>

onMixEnd: function(state){ $('.itemCount').html(state.totalShow) },

That way it indicates that properties are shown at the first place and the number gets refreshed after a filter has been triggered. Is there another path to take to display the number of items onload?

And in addition to that i would very much aim to print the active filters as html text into a defined div element. This is because some of my filters are hidden behind a push panel and not visible to the user. Therefor i think it is user friendlier to have the active filters be always visible.

console.log(state.activeFilter)

shows the active filters in the console as

selector: ".option1.filter1, .option2.filter1, ..."

But how would one print them in a human readable way into the html?

patrickkunka commented 6 years ago

Hi,

Regarding showing the number of total shown targets on load, I would decouple your functionality from the onMixEnd callback, and then call it firstly after instantiation, and then also on each onMixEnd callback.

For example:

function renderTotalShow(state) {
    $('.itemCount').html(state.totalShow)
} 

var mixer = mixitup(container, {
    callbacks: {
        onMixEnd: renderTotalShow
    }
});

var initialState = mixer.getState();

renderTotalShow(initialState);

Secondly, in terms of rendering a "human readable" list of active filters, the first question should be where do we get human readable "labels" for each filter from (because selectors alone are not human readable)? One option is your from own controls markup, assuming each control contains a human readable label inside it.

<button type="button" data-filter=".filter-1" data-label="Filter 1">Filter 1</button>

In the above example, you could each pull the label ("Filter 1") out of the button's textContent property, or a better way would be to include it as a second data attribute as bove (data-label).

When a mixitup control is active, it will have an active class added to it (e.g. '.mixitup-control-active'), that you can use to query it by. If it's a checkbox or a select option, it will have a checked or selected attribute set which you could query it by in lieu of an active class.

Therefore, using the onMixEnd callback again, we could query the DOM for active controls, iterate through them, reading in their data-label values, and output that in your HTML.

Here's a basic example, the final format of your list is up to you.

var mixer = mixitup(container, {
    callbacks: {
        onMixEnd: function(state) {
            renderTotalShow(state);
            renderActiveFilters();
        }
    }
});

function renderActiveFilters() {
    var activeControls = Array.from(document.querySelectorAll('.mixitup-control-active'));
    var activeFiltersDiv = document.querySelector('.active-filters-div');

    activeFilterDiv.innerHTML = '';

    activeLabels = activeControls.map(function(activeControl) {
        return activeControl.getAttribute('data-label');
    });

    activeFilterDiv.innerHTML = activeLabels.join(', '); // eg: 'Filter 1, Filter 2, Filter 3'
}
nolybom commented 6 years ago

Very logic! And it works perfectly with all filter buttons and checkboxes with the slightly fixed code:

function renderTotalShow(state) {
    $('.itemCount').html(state.totalShow)
} 

function renderActiveFilters() {
    var activeControls = Array.from(document.querySelectorAll('.mixitup-control-active, .checkbox-style:checked, .userInput'));
    var activeFiltersDiv = document.querySelector('.active-filters-div');

    activeFiltersDiv.innerHTML = '';

    activeLabels = activeControls.map(function(activeControl) {
        return activeControl.getAttribute('data-label');
    });

    activeFiltersDiv.innerHTML = activeLabels.join(', '); // eg: 'Filter 1, Filter 2, Filter 3'
}

var mixer = mixitup(containerEl, {
        callbacks: {
             onMixEnd: function(state) {
                         renderTotalShow(state);
                         renderActiveFilters();
        }
    }
});

var initialState = mixer.getState();

renderTotalShow(initialState);

To finish this up all there is still needed is the user input text and the select options.

Since the value of data-label on the input text field is depending on users input i thought mirroring users input might make sense:

$('.userInput').on('keyup', function() { if( !$(this).val() ) { var value = $(this).val(); $(this).attr('data-label', value); } });

It works but the output then looks like this when starting to filter with a button:

, , Filter 1

..and when typing in something then:

, textinput


Regarding getting info from a select option i have taken many paths but none threw the data-label out. I guess when using bootstrap-select one must maybe mirror the value as well because the markup is set by the script!?

I see light! So close to wrap this up. :-)

patrickkunka commented 6 years ago

Couple of ideas for you:

  1. Adding the value of a text input (no data-label required):
<input class="user-input" type="text"/>
function renderActiveFilters() {
    var activeControls = Array.from(document.querySelectorAll('.mixitup-control-active, .checkbox-style:checked'));
    var userInput = document.querySelector('.user-input');
    var activeFiltersDiv = document.querySelector('.active-filters-div');

    activeFiltersDiv.innerHTML = '';

    activeLabels = activeControls.map(function(activeControl) {
        return activeControl.getAttribute('data-label');
    });

    // Add the value from the text input

    if (userInput.value !== '') {
        activeLabels.push(userInput.value);
    }

    activeFiltersDiv.innerHTML = activeLabels.join(', '); // eg: 'Filter 1, Filter 2, Filter 3'
}
  1. Adding the label value of a selected <option>:
<select class="user-select">
    <option value=".filter-1" data-label="Filter 1">Filter 1</option>
    ...
</select>
function renderActiveFilters() {
    var activeControls = Array.from(document.querySelectorAll('.mixitup-control-active, .checkbox-style:checked'));
    var userInput = document.querySelector('.user-input');
    var userSelect = document.querySelector('.user-select');
    var activeFiltersDiv = document.querySelector('.active-filters-div');
    var selectedOption = userSelect.selectedOptions[0] || null;

    // Add selected option to list of active controls

    if (selectedOption) {
        activeControls.push(selectedOption);
    }

    activeFiltersDiv.innerHTML = '';

    activeLabels = activeControls.map(function(activeControl) {
        return activeControl.getAttribute('data-label');
    });

    // Add the value from the text input to active labels

    if (userInput.value !== '') {
        activeLabels.push(userInput.value);
    }

    activeFiltersDiv.innerHTML = activeLabels.join(', '); // eg: 'Filter 1, Filter 2, Filter 3'
}