Галерея сотрудников с динамической фильтрацией

Я пытаюсь научиться писать лучше, более эффективный код JavaScript и хотел бы запустить эту функцию, которую я написал вам, и посмотреть, могут ли люди предложить улучшенный метод.

В основном это фильтр людей, основанный на двух переменных.

Демо-версия

Он работает нормально, но если добавляются дополнительные категории (пока их три), тогда код станет излишним.

var family = 'director';
var expertise = 'asset';


function showConsultant(){ 
   $('.profile').stop(true,true).fadeOut(100);
   $('.profile[data-family="'+family+'"][data-expertise="'+expertise+'"]').delay(101).fadeIn();
}

showConsultant();

$('.top-level li').on('click', function() {

  $('.top-level li').removeClass('selected');
  $(this).addClass('selected');

  if( $(this).index() === 0 ){
     family = 'director';
     showConsultant();
  } else if( $(this).index() === 1 ){
      family = 'consultant';
      showConsultant();
  } else if( $(this).index() === 2 ){
      family = 'support';
      showConsultant();
  }

});

$('.second-level li').on('click', function() {

  $('.second-level li').removeClass('selected');
  $(this).addClass('selected');

  if( $(this).index() === 0 ){
     expertise = 'asset';
     showConsultant();
  } else if( $(this).index() === 1 ){
      expertise = 'insurance';
     showConsultant();
  } else if( $(this).index() === 2 ){
      expertise = 'wealth';  
     showConsultant();
  }

});
11 голосов | спросил Liam 19 J000000Tuesday16 2016, 13:33:19

4 ответа


3

Я удалил большую часть того, что казалось ненужным, чтобы код делал то же самое. Добавляя массив и используя индекс в качестве ссылки, вы можете устранить необходимость в предложениях if, then else.

var family = 'director';
var expertise = 'asset';

var familyArray = ['director', 'consultant', 'support'];
var expertiseArray = ['asset', 'insurance', 'wealth'];

function showConsultant(){ 
   $('.profile').stop(true,true).fadeOut(100);
   $('.profile[data-family="'+family+'"][data-expertise="'+expertise+'"]').delay(101).fadeIn();
}

showConsultant();

$('.top-level li').on('click', function() {

  $('.top-level li').removeClass('selected');
  $(this).addClass('selected');
  family = familyArray[$(this).index()];
  showConsultant();

});

$('.second-level li').on('click', function() {

  $('.second-level li').removeClass('selected');
  $(this).addClass('selected');
  expertise = expertiseArray[$(this).index()];
  showConsultant();

});

Вы, вероятно, могли бы пойти еще на один шаг и написать обработчик функции для события отдельно, чтобы одна и та же функция могла обрабатывать оба события щелчка для лидера верхнего уровня и второго уровня. Вам просто нужно сделать чек, для какого класса подпадает ли (то есть верхнего уровня или второго уровня) и изменить соответствующую переменную (т. Е. Семью или опыт).

var family = 'director';
var expertise = 'asset';

var familyArray = ['director', 'consultant', 'support'];
var expertiseArray = ['asset', 'insurance', 'wealth'];

function showConsultant(){ 
   $('.profile').stop(true,true).fadeOut(100);
   $('.profile[data-family="'+family+'"][data-expertise="'+expertise+'"]').delay(101).fadeIn();
}

function clickHandler () {
  $(this).siblings().removeClass('selected');
  $(this).addClass('selected');
  $(this).parent().hasClass('top-level')
    ? family = familyArray[$(this).index()]
    : expertise = expertiseArray[$(this).index()];

  showConsultant();
}

showConsultant();

$('.top-level li').on('click', clickHandler);
$('.second-level li').on('click', clickHandler);
ответил Levi Mootz 19 J000000Tuesday16 2016, 18:59:24
8

Сделайте таблицу поиска. Затем доступ по индексу:

var expertises = ["asset", "insurance", "wealth"];
expertise = expertises[$(this).index()];
showConsultant();

Если вы беспокоитесь о выходе за пределы, добавьте проверку диапазона:

var expertises = ["asset", "insurance", "wealth"];
var index = $(this).index();
if (0 <= index && index < expertises.length) {
    expertise = expertises[index];
    showConsultant();
}

Вы даже можете определить глобальные названия этих списков, чтобы вы могли написать enums.getExpertise(index).

ответил Pimgd 19 J000000Tuesday16 2016, 13:41:27
2

Добавленные комментарии объясняют мой код:

// Maybe rename this to something more intuitive
// This will allow you to add unlimited click combinations without additional logic
// This is the main piece which will help in avoiding redundancy
var list = [
    { // index 0
        family:'director',
        expertise:'asset'
    },
    { // index 1
        family:'consultant',
        expertise:'insurance'
    },
    { // index 2
        family:'support',
        expertise:'wealth'
    }
];

var family_index = 0;
var expertise_index = 0;

// Don't pollute the global space with functions
var showConsultant = function(){ 

    // Take advantage of the callback parameter of the fadeOut() function to guarantee the proper execution order
    // because .delay(101) might not fire off precisely after fadeOut() has finished
    $('.profile').stop(true,true).fadeOut(100, function(){

        // Call upon your list and the presently set indexes to figure out what to fadein()
        $('.profile[data-family="'+list[family_index].family+'"][data-expertise="'+list[expertise_index].expertise+'"]').fadeIn();
    });
};

showConsultant();

// Listen for clicks in either level and use .hasClass() to determine which level we are dealing with
$('.top-level li, .second-level li').on('click', function() {

    // $(this) is needed several times so just cache it
    var $this = $(this);
    var index = $this.index();
    var level = ($this.hasClass('top-level') ? 'top' : 'second');

    // Dynamically target the level
    $('.'+level+'-level li').removeClass('selected');
    $this.addClass('selected');

    // Make sure that what the user clicked is a defined choice in your list variable
    if( index < list.length ){

        if(level === 'top'){
            family_index = index;
        } else {
            expertise_index = index;
        }

        showConsultant();
    }

});
ответил MonkeyZeus 19 J000000Tuesday16 2016, 16:06:37
2

Похоже, что вы пытаетесь реализовать визуальную фильтрацию элементов на основе выбранных значений различных элементов управления. Каждый элемент управления может фильтровать по одному возможному фильтрующему «размеру» для элементов - семейства, опыта, других вещей, возможно, в будущем.

По этой причине я не понимаю парадигму родителя-ребенка, которую вы пытаетесь применить здесь. Я думаю о проблеме как о том, как ввести произвольный элемент управления, который будет фильтровать набор элементов на любом количестве фильтрующих измерений.

Чтобы достичь этого, какими могут быть общие фрагменты кода, которые вам нужно иметь?

  • Ручка для захвата всех элементов, подлежащих фильтрации. В этом случае это может означать селектор jQuery, который может представлять все возможные элементы. Я бы предложил общий класс. Поэтому вместо поиска таких вещей, как *-level li, вы просто выбираете $('.filterableElement') и примените класс filterableElement к каждому из этих элементов. Это также отделяет функциональность фильтрации от любых структурных элементов HTML (т.е. ---- +: = 3 =: + ----, ul и т. д.)
  • Способ применения управления фильтрацией к определенному набору элементов (например, присоединить обработчики событий для запуска фильтрации).
  • Способ привязки каждого элемента управления фильтрацией к набору элементов, которые он скрывает.
  • Код для фактического выполнения операций фильтрации (show /hide).

Если у вас все это на месте, вы можете масштабировать этот код для обработки произвольного количества элементов фильтрации.

Что я предлагаю ниже, это немного отход от вашего нынешнего подхода, но хотелось бы представить подход к тому, как можно подойти к проблеме с целями повторного использования (может ли это быть использовано в более чем одном месте?) и гибкость (могу ли я использовать ту же логику для решения ряда вариантов использования, таких как реализация произвольного набора элементов управления фильтрацией?). Я возьму подход внедрения класса в javascript, который может обеспечить функциональность, отмеченную выше.

Мой ответ может показаться чересчур подробным /сложным для того, с чего вы начинаете, но я хотел дать вам понять, как вы могли бы построить это менее хрупким способом, когда ваш javascript сможет проверить атрибуты HTML и данных правильно настроены в момент создания объекта.

Так как этот класс имеет зависимость от jQuery, вы можете даже рассмотреть его как расширение jQuery (это должно быть довольно просто, используя это как отправную точку). Такой общий подход был бы тем, что я ожидал бы, если бы у меня был опытный разработчик javascript, назначенный для этой задачи кодирования - то есть, если это не простой одноразовый (что не похоже на случай, поскольку вы были спрашивая о расширяемости).

Я не делал попыток абстрагировать реализацию от вызывающего объекта (добавив конфиденциальность с помощью методов экземпляра, например), поскольку, как правило, при использовании методов прототипа предпочтительнее использовать методы экземпляра для производительности. Обычно я просто согласен с тем, что javascript на самом деле не разработан как другие объектно-ориентированные языки в отношении конфиденциальности и только реализует конфиденциальность экземпляра, когда для него есть сильный аргумент.

Сначала давайте посмотрим, как вы можете изменить HTML.

li

Это снова отделяет классы отображения (например, <!-- You can continue to pass data attributes via HTML as you are currently doing. You might also consider just injecting configuration data directly into javascript vs. using data attributes, which might end up with cleaner implementation, but I am not going to wander into that area here. Here the suggestion is to attach a name for the filtering "dimension" to the root filterControl and pass data on the target values and related control that each option toggles. It is important to note that all these data values should likely be limited to names that meet standard javascript object property syntax requirements. --> <ul class="top-level filterControl" data-filter-name="family"> <li class="filterOption" data-filter-target="directors" data-filter-control="family" >Directors</li> <li class="filterOption" data-filter-target="consultants" data-filter-control="family" >Consultants</li> <li class="filterOption" data-filter-target="support" data-filter-control="family" >Support</li> </ul> <ul class="second-level filterControl" data-filter-name="expertise"> <li class="filterOption" data-filter-target="assetManagement" data-filter-control="expertise" >Asset Management</li> <li class="filterOption" data-filter-target="insurance" data-filter-control="expertise" >Insurance</li> <li class="filterOption" data-filter-target="wealthManagement" data-filter-control="expertise" >Wealth Management</li> </ul> <!-- Here the filterable element may have any number of associated controls. Again passing all relevant filter options applicable to this element can be done via HTML data attributes, but this can begin to look a little odd. In this example we try to pass a valid JSON array representation with each array element being a string that indicates the filter dimension and option target value that this element relates to. This leverages jQuery.data() functionality that will automatically convert valid javascript literals (in this case an array literal) into the appropriate data structure as return to jQuery.data(). I make the conscious decision here to put the data attribute within single quoted string to prevent need to escape quotes inside the string. --> <div class="profile filterableElement" data-filter-tags='["family::director","expertise::assetManagement"]' > <span>James Emmet<br>director, asset</span> </div> ) от поведенческих, и позволит вам использовать любые элементы - они могут просто быть divs или tr /td комбинациями - в любом виде макета. Это не имеет значения, так как наши атрибуты данных будут сообщать классу javascript, как настроить себя.

Далее, давайте сосредоточимся на этом классе (назовем его top-level). Мысль здесь состоит в том, что этот класс может быть сброшен на любую страницу с помощью reference /include, так что он может использоваться повторно в любом приложении или других приложениях и не тесно связан с макетом отображения.

Этот классдолжны быть в состоянии понять различные типы элементов и предоставить единое место для реализации фрагментов кода кода, отмеченных выше. Одна вещь, которую мы собираемся сделать с этим классом, состоит в том, чтобы свести к минимуму необходимость повторного запроса DOM каждый раз, когда требуется операция фильтрации. Этот класс будет хранить ссылки выбора jQuery, необходимые для выполнения действий над DOM, которые являются результатом операций фильтрации. Это значительно повысило бы производительность в тех случаях, когда вы ожидаете, что элементы управления фильтрами будут сильно использоваться.

FilterController

Использование может выглядеть так:

// FilterController Class
// On class instantiation, we must pass the class names that will be used
// for different element types.  The class constructor logic builds
// everything it needs to implement filtering from these.
function FilterController(
    filterControlClass,
    filterOptionClass,
    filterElementClass
) {
    // validate passed parameters are as expected
    // here we use spread operator to convert arguments object to array
    this.validateClassParameters([...arguments]);

    // set passed class names
    this.filterControlClass = filterControlClass;
    this.filterOptionClass = filterOptionClass;
    this.filterElementClass = filterElementClass;

    // Object defining map to filter control elements.
    this.filterControls = {};

    // Store jQuery collection for all filter controls
    this.$filterControls = null;

    // Store jQuery collection for all filter options 
    this.$filterOptions = null;

    // Store Jquery collection for all filterable elements
    this.$filterableElements = null;

    // Object defining maps of controls to elements to be filtered.
    // this ultimately replaces querying the DOM with each click event
    // in favor of storing the mappings for optimized performance
    this.filteredElementTree = {};

    // Allow for setting of callbacks that allow caller to pass
    // in specific functions to execute during various filter operations
    // A default callback implementation is shown based on your hide/show
    // logic, but the intent is to allow user to override these to
    // whatever needs they have for their UI.
    // These callbacks are listed here in order callbacks are triggered
    // during filtering operation, though callbacks themselves would execute
    // asynchronously so there is no guarantee of callback completion order
    // One could guarantee synchronous execution of certain steps if needed.
    // That would require introducing Deferred/Promises or similar.
    this.callbacks = {
        // callback to execute on first receiving valid filter request
        // currently not implemented by default
        filterStart: function() {},

        // callback to execute when valid selection option has been made
        // we pass selected option DOM element and jQuery collection
        // of all option for that related filter control
        // this allows one to pass
        filterOptionSelected: function(el, $optionGroup) {
            // removed selected class all control options related
            // to control, which are passed here as jQuery collection
            $optionGroup.removeClass('selected');
            // add class to selected element
            $(el).addClass('selected');
        },

        // callback to execute when we have determined the currently
        // triggered filter control.  We pass this callback both the
        // selected control DOM element and jQuery collectoin of all
        // filter control elements so they can be operated against.
        // I didn't implement example call here as you didn't have
        // such behavior in your example. This could be something
        // similar to filterOptionSelected callback above.
        filterControlSelected: function(el, $filterControls) {},

        // callback to execute before filtering is applied.  This where
        // you would potentially reset filterable elements back to
        // their pre-filtered state.  We only execute this callback if
        // we know we have a valid filter control/option combination
        // specified so as not to change current screen display
        // under error conditions.
        // The callback expects a jQuery collection representing all
        // available filterable elements.
        resetFilters: function($allFilterableElements) {
            $allFilterableElements.stop(true,true).fadeOut(100);
        },

        // Callback to actually implement filtering on UI. This
        // callback expects jQuery collection representing elements.
        applyFilter: function($filteredElements) {
            $filteredElements.delay(101).fadeIn(); 
        },

        // callback to execute after filters have been applied
        // currently not implemented by default
        filterComplete: function() {}
    };

    // call methods to make this a functional object
    this.setFilterControls()
        .setFilterableElements()
        .buildFilteredElementTree();
}

// add methods to the FilterController class
FilterController.prototype = {

    // method used on instantiation to build all data structures
    // related to the filter controls and options into the object
    setFilterControls: function() {
        // get elements based on className
        $filterControls = $('.' + this.filterControlClass);
        // validate we have at least one filter control to work with
        if $filterControls.length === 0) {
            throw new Error('Unable to find any valid filter controls');
        }

        // store filter control collection on object
        this.$filterControls = $filterControls;   
        $filterControls.each(i, el) {
            // get filtering dimension name
            var filterName = this.getDataAttribute(el, 'filter-name');

            // build object to store control information.
            var control = {
                'domElement': el,
                'options': {},
                '$options': null
            };

            // Find all control options. Find all filter option
            // element which have this filter control as target        
            $controlOptions = $('.' + this.filterOptionClass +
                '[data-filter-control="' + filterName + '"]');

            // validate that we have at least one option related
            // to this filter
            if($controlOptions.length === 0) {
                throw new Error(
                    'No valid options found for control filter: ' + filterName;
                );
            }

            // set options jQuery collection on control object
            control.$options = $controlOptions;

            // iterate through options, setting info on control object
            $controlOptions.each(i, contEl) {            
                var target = this.getDataAttribute(contEl, 'filter-target');
                // we are not using the filter-control attribute here
                // but we want to go ahead and read it so that we can
                // validate it is properly configured 
                this.getDataAttribute(contEl, 'filter-control');
                control.options[target] = {
                    domElement: contEl
                };
            }

            // set control into filterControls map
            this.filterControls[filterName] = control; 
        }

        // return this for method chaining
        return this;
    },

    // method used upon instantiation to validate query collection
    // of filterable elements and set on object
    setFilterableElements: function() {
        // let's find all our filterable elements
        var $filterableElements = $('.' + this.filterElementClass);
        if ($filterableElements.length === 0) {
           throw new Error('No filterable elements found on page.');
        }
        // set jQuery collection on object
        this.$filterableElements = $filterableElements;

        return this;   
    },

    // method used on instantiation to build data structure
    // that maps filter controls and objects related filterable
    // elements and store that data structure on the object.
    buildFilteredElementTree: function() {
        // some local variable declarations for convenience
        var tree = this.filteredElementTree;
        var $elements = this.$filterableElements;

        // let's iterate through our filterable elements and map them
        // to their applicable controls
        $elements.each(i, el) {
            var data = this.getDataAttibute('filter-tags', el); 

            // iterate through each tag in data array
            for(var i = 0, length = data.length; i < length; i++) {
                var tagParts = data[i].split('::');
                // validate tag format
                if (tagParts.length !== 2) {
                    throw new Error(
                        "Invalid filter tag format. One '::' separator " +
                        "expected. Provided string: '" + data[i] + "'"
                    );
                }
                var dimension = tagParts[0];
                var optionValue = tagParts[1];
                // validate control dimension and value exist
                // and add to element tree if it does
                this.validateControlExists(dimension, optionValue);

                // if tree node does not yet exist we want
                // to place an empty array on that node
                if(!this.treeNodeExists(dimension, optionValue) {
                    // it does not, so we want to put empty array at that node
                    tree[dimension][optionValue] = [];
                }
                // put element into array at node
                tree[dimension][optionValue].push() = el;
            }
         }

         return this;
     },

     // Method to set custom callbacks.
     // We will use jQuery.extend() to overwrite any default handlers
     // with those passed.
     setCallbacks(callbacks) {
         $.extend(this.callbacks, callbacks);
     },

     // Main method to perform filtering activities.
     // This accepts DOM element representing the option that
     // has been selected in the UI
     filter: function(triggeredOptionElement) {         
         // trigger filterStart callback
         this.callbacks.filterStart();

         // based on the passed control option element we
         // get the appropriate dimension and option values
         // that will be used to perform filtering
         var filterSettings = this.getFilterSettings(triggeredOptionElement);
         var dimension = filterSettings.dimension;
         var optionValue = filterSettings.optionValue;

         var $optionGroup = this.filterControls[dimension].control.$options;

         // if we were able to retrieve filter settings that means
         // the passed element is a valid option control.
         // trigger option control selection callback
         this.callbacks.filterOptionSelected(
             triggeredOptionElement, $optionGroup
         );

         // trigger filterControlSelected callback
         this.callbacks.filterControlSelected(
             this.filterControls[dimension].domElement,
             this.$filterControls
         ); 

         // get filtered elements
         var filteredElementArray = this.getFilteredElements(
             dimension, optionValue
         );

         // we are now ready to update display
         // trigger resetFilters callback
         this.callbacks.resetFilters(this.$filterableElements);

         // trigger applyFilter callback
         this.callbacks.applyFilter($(filteredElementArray));          

         // trigger filterComplete callback
         this.callbacks.filterComplete();    
     },

     // helper method called during filtering to determine the
     // filter settings that are to be used based on data attributes
     // of the selected filter option.
     // This also validate that the passed element matches a known
     // filter option element in the object's data structure.
     getFilterSettings: function(el) {
         // validate that element is in list of option elements
         var $filteredOption = this.$filterOptions.filter(el);
         if ($filterOption.length !== 1) {
             throw new Error(
                 'Unable to find matching filter control for ' +
                 'passed option element.'
             );
         )
         // since we know this is a valid option element now,
         // we can read data attributes to determine dimension
         // and option value combination that we will use for filtering
         var filterSettings = {
             dimension: this.getDataAttribute(el, 'filter-control'),
             optionValue: this.getDataAttribute(el, 'filter-target')
         }
         return filterSettings;       
     }, 

     // method to get array of DOM elements that meet the passed
     // filtering criteria.  This method may also have some value
     // as a publicly available way to pass in string filter settings
     // to the class and get set of matching filterable elements
     getFilteredElements: function(dimension, optionValue) {
         // validate input
         if(!(
             this.isNonZeroLengthString(dimension) ||
             this.isNonZeroLengthString(optionValue)
         )) {
             throw new Error(
                 'Invalid parameter passed to FilterController' +
                 '.getFilteredElements(). ' +
                 'All parameters must be non-zero-length strings'
             )
         }
         // validate that the dimension and optionValue exist on tree
         this.validateTreeNodeExists(dimenstion, optionValue);

         return this.filteredElementTree[dimension][optionValue];
     },

     // generic handler for getting HTML data attributes from given element.
     // also performs some basic validation of attribute value
     getDataAttribute: function(element, key) {
         value = $(element).data(key);
         if(typeof value === 'undefined') {
             throw new Error(
                 "'" + key + "' data attribute is not set on element"
             );
         }
         // validate that want don't have an "emtpy" object to work with
         if(isNonZeroLengthString(value) || isNonZeroLengthArray(value)) {
             return value;
         }
         throw new Error(
             "Empty value provided for data attribute '" + key + "'"
         );
     },
     // Type verification and validation methods
     isNonZeroLengthString: function(value) {
         if(!(typeof value === 'string' || value instanceof String)) {
             return false;
         }
         if(value.length === 0) {
             return false;
         );
         return true;
     },

     isNonZeroLengthArray: function(arr) {
         if(!Array.isArray(arr)) {
             return false;
         }
         if(arr.length === 0) {
             return false;
         }
         return true;
     },

     validateClassNameParameters: function(argArray) {
        if(argArray.length !== 3) {
            throw new Error(
               'Invalid number of arguments passed at ' +
               'FilterContoller instantionation'
            );
        }
        argArray.forEach(function(value) {
            if(!this.isZonZeroLenthString) {
                throw new Error(
                    'All parameters passed to instantiate FilterController ' +
                    'must be strings of non-zero length'
                );
            }
        }
     },

     validateControlExists: function(dimension, optionValue) {
         if (typeof this.filterControls[dimension].options[optionValue]
             === 'undefined') {
             throw new Error(
                 'Filter tag without control set on element. ' +
                 'Filter dimension: ' + dimension + ' ' +
                 'Filter option value: ' + optionValue
             );
         }
     },  

     validateTreeNodeExists: function(dimension, optionValue) 
         if(!this.treeNodeExists(dimension, optionValue)) {
             throw new Error(
                 'Tree node request for filter does not exist. ' +
                 'Filter dimension: ' + dimension + ' ' +
                 'Filter option value: ' + optionValue
             );
         }
     },

     treeNodeExists: function(dimension, optionValue) {
         if (typeof this.filteredElementTree[dimension][optionValue]
             === 'undefined') {
             return false;
         }
         return true;
     }
}
ответил Mike Brant 25 J000000Monday16 2016, 17:11:07

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132