Shohel
Shohel

Reputation: 3934

Select2 group with Infinite scroll

I am using select2 group option with infinite scroll and data are coming by Ajax calling per page 10. Here is some problem arises, suppose user 1 has 15 data and user 2 has 15 data, at first 10 data are coming from user 1 and in next page 10 (5 data for user1 and 5 data for user2). no problem for data getting but the problem is user 1 group showing double. How can I prevent double display to my select2 options group? Has there any way to make an option group again?

HTML CODE

<div class="container">
  <form id="frm">
    <h1>Solution 1</h1>
    <div class="row">
      <div class="col-4">
        <div class="form-group">
          <label for="tagInput">Get data by ajax calling</label>
          <select class="form-control" id="pictures_tag_input">
</select>
          <small class="form-text text-muted"><p class="text-info">Infinite Scroll</p></small>
        </div>
      </div>
    </div>
  </form>
</div>

JS CODE

$(document).ready(function() {
  // solution 1
  //example.com/articles?page[number]=3&page[size]=1
  http: $("#pictures_tag_input").select2({
    placeholder: "Search for options",
    ajax: {
      url: "https://jsonplaceholder.typicode.com/users/1/todos",
      dataType: "json",
      global: false,
      cache: true,
      delay: 250,
      minimumInputLength: 2,
      data: function(params) {
        // console.log(params.page || 1);
        return {
          q: params.term, // search term
          _page: params.page || 1,
          _limit: 10 // page size
        };
      },
      processResults: function(data, params) {
        params.page = params.page || 1;
        var datx = getNestedChildren(data);
        // console.log(datx);

        return {
          results: datx,
          pagination: {
            more: true
          }
        };
      } //end of process results
    } // end of ajax
  });

  function getNestedChildren(list) {
    var roots = [];
    for (i = 0; i < list.length; i += 1) {
      node = list[i];

      if (roots.length === 0) {
        var obj = {
          text: "User " + node.userId,
          children: [{ id: node.id, text: node.title }]
        };
        roots.push(obj);
      } else {
        var obj = {
          text: "User " + node.userId,
          children: [{ id: node.id, text: node.title }]
        };
        var rootArray = $.map(roots, function(val, i) {
          var vl = "User " + node.userId;
          if (val.text === vl) return val;
          else return undefined;
        });
        if (rootArray.length > 0) {
          var obj1 = {
            id: node.id,
            text: node.title
          };
          rootArray[0].children.push(obj1);
        } else {
          roots.push(obj);
        }
      }
    }
    return roots;
  }
});

enter image description here

Demo https://codepen.io/mdshohelrana/pen/MLVZEG

Upvotes: 1

Views: 4496

Answers (5)

None of the previous resolved my issue. I ended up with this code:

select2({
    // ...
    ajax_url: {
        // ...
        processResults: (data, params) => {
            setTimeout(() => {
                const acc = []

                const optExists = (opt, opts) => {
                    return opts.some(o => opt.innerText === o.innerText)
                }
                
                const groups = document.querySelectorAll(".select2-results__option[role='group']")
    
                for (const group of groups) {
                    const titleElem = group.getElementsByClassName("select2-results__group")[0]
                    const listElem = group.getElementsByClassName("select2-results__options")[0]
                    const optionElems = Array.from(listElem.querySelectorAll(".select2-results__option[role='option']"))
    
                    let duplicated = false
    
                    for (const [_gElem, tElem, lElem, optElems] of acc) {
                        if (tElem.innerText === titleElem.innerText) {
                            for (const optionElem of optionElems) {
                                for (const optElem of optElems) {
                                    if (!optExists(optionElem, optElems)) {
                                        lElem.appendChild(optionElem)
                                    }
                                }
                            }
                            group.remove()
                            duplicated = true
                        }
                    }
    
                    !duplicated && acc.push([group, titleElem, listElem, optionElems])
                }
            }, 0)
    
            return {
                results: data.result.rows,
                pagination: {
                    more: data.result.more
                }
            }
        }
    }
})

It loops throughout all groups, if the group title exists, then moves all new options to the group and removes the duplicated group.

Upvotes: 1

Zolt&#225;n S&#252;le
Zolt&#225;n S&#252;le

Reputation: 1704

the accepted answer didn't work for me and I don't see why should that work. A return in the $.each will not return from the templateResult() function.

Here is an approach that worked for me.

  1. It is not necessary to build a nested list by getNestedChildren(list) on javascript side. It is much easier to build it on server side instead.
  2. The appearance of search results in the dropdown (options and the optgroups) can be customized by using the templateResult option. I removed the duplicated optgroups and labels by this option.

check the templateResult: formatOptions, part of the code

$(document).ready(function() {
    $("#pictures_tag_input").select2({
        placeholder: "Search for options",
        templateResult: formatOptions,
        ajax: {
            url: "https://jsonplaceholder.typicode.com/users/1/todos",
            dataType: "json",
            global: false,
            cache: true,
            delay: 250,
            minimumInputLength: 2,
            data: function(params) {
                return {
                    q: params.term,
                    _page: params.page || 1,
                    _limit: 10
                };
            },
            processResults: function(data, params) {
                params.page = params.page || 1;

                return {
                    results: data,
                    pagination: {
                        more: true
                    }
                };
            } //end of process results

        } // end of ajax
    });

    function formatOptions(item, container, $el) {
        // optgroups section
        if (item.children && item.children.length > 0) {
            // don't format the repeated optgroups!
            if ($(".select2-results__group").text() === item.text) {
                return;
            }

            if ($('[aria-label="' + item.text + '"]').length > 0) {
                return;
            }

            // the first occasion of the given optgroup
            return $el;
        }

        // options section
        // here you can implement your own logic
        // if you want to customise the output of the options
        $el.addClass('something-special-result result');

        return $el;
    }

});

Upvotes: 1

cruzquer
cruzquer

Reputation: 311

I just found a better solution which does not result in a (duplicated) optgroup being rendered as an empty option:

processResults: function( json, params ){

    setTimeout( function() {

        var $prevOptions = false;
        var $prevGroup = false;

        // loop
        $('.select2-results__option[role="group"]').each(function(){

            // vars
            var $options = $(this).children('ul');
            var $group = $(this).children('strong');

            // compare to previous
            if( $prevGroup && $prevGroup.text() === $group.text() ) {
                $prevOptions.append( $options.children() );
                $(this).remove();
                return;
            }

            // update vars
            $prevOptions = $options;
            $prevGroup = $group;

        });

    }, 1 );

    return json;

}

Advanced Custom Fields uses the exact same code for their WordPress plugin in order to fix this issue, ajax-load and group posts from different post-types.

Upvotes: 0

Nure Alam
Nure Alam

Reputation: 24

Just try to use the following code

templateResult: function(data) {
 if (typeof data.children != 'undefined') {
  $(".select2-results__group").each(function() {
   if (data.text == $(this).text()) {
    return data.text = '';
   }
  });
 }
 return data.text;
}

NOTE: Need to group from server side, Other wise you have to make master details from client side.

Upvotes: 1

thiaguerd
thiaguerd

Reputation: 847

maybe the problem is a source of a data

You call user 1 .... server return a 1

You call user 2 .... server return a 1

You call user 3 .... server return a 2

You call user 4 .... server return a 2

You call user 5 .... server return a 3

You call user 6 .... server return a 3

curent_user = 1;
$(document).ready(function() {
  http: $("#pictures_tag_input").select2({
    placeholder: "Search for options",
    ajax: {
      url: "https://jsonplaceholder.typicode.com/users/1/todos",
      dataType: "json",
      global: false,
      cache: false,
      minimumInputLength: 2,
      data: function(params) {
       console.log("params",params || 1);
        return {
          q: params.term, // search term
          _page: curent_user,
          _limit: 10 // page size
        };
      },

      processResults: function(data, params) {
        curent_user += 2;
        var datx = getNestedChildren(data);
        console.log("data: ", data);

        return {
          results: datx,
          pagination: {
            more: true
          }
        };
      } //end of process results
    } // end of ajax
  });

  function getNestedChildren(list) {
    var roots = [];
    for (i = 0; i < list.length; i += 1) {
      node = list[i];

      if (roots.length === 0) {
        var obj = {
          text: "User " + node.userId,
          children: [{ id: node.id, text: node.title }]
        };
        roots.push(obj);
      } else {
        var obj = {
          text: "User " + node.userId,
          children: [{ id: node.id, text: node.title }]
        };
        var rootArray = $.map(roots, function(val, i) {
          var vl = "User " + node.userId;
          if (val.text === vl) return val;
          else return undefined;
        });
        if (rootArray.length > 0) {
          var obj1 = {
            id: node.id,
            text: node.title
          };
          rootArray[0].children.push(obj1);
        } else {
          roots.push(obj);
        }
      }
    }
    return roots;
  }
});

so if you skip a one step

You call user 1 .... server return a 1

You call user 3 .... server return a 2

You call user 5 .... server return a 3

Upvotes: 0

Related Questions