Select2 group with Infinite scroll - javascript

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;
}
});
Demo
https://codepen.io/mdshohelrana/pen/MLVZEG

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.

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.
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.
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;
}
});

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

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.

Related

I can't crawl the website quotestoscrape using artoo.js Multi level recursive site crawling

I want to use artoo.js to scrape the website https://quotes.toscrape.com/
For handling pagination, I can do it! But for crawling the website ie for each page scrape the quotes and authors. Then take the link of the author and scrape the dob and pob for example. Finally handle the pagination.
Any help is much appreciated, here's my code:
var base_url = 'https://quotes.toscrape.com';
// empty list init
var my_list = []
// define the logic of the first scraper
var scraper1 = {
iterator: 'div.quote',
data: {
'quotes': {
sel: 'span'
},
'author': {
sel: 'small.author'
},
'link': {
sel: 'a',
attr: 'href'
}
}
};
// define the logic of the second scraper
var scraper2 = {
iterator: 'div.author-details',
data: {
'dob': {
sel: 'span.author-born-date'
},
'pob': {
sel: 'span.author-born-location'
}
}
}
// pagination
function nextUrl($page) {
return $page.find('li.next > a').attr('href');
}
artoo.log.debug('Starting the scraper...');
var frontpage = artoo.scrape(scraper1);
// spider
var my_list = []
// artoo spider
function pagination() {
artoo.ajaxSpider(
function(i, $data) {
//console.log($data.innerHTML);
return nextUrl(!i ? artoo.$(document) : $data);
}, {
limit: 1, // number of pages to scrape
scrape: scraper1,
concat: true,
done: function(data) {
artoo.log.debug('Finished retrieving data. Downloading...');
console.log(data);
for (var i = 0; i < my_list.length; i++) {
my_list.push(base_url + data[i].link)
}
console.log(my_list)
}
})
return my_list;
}
// Append links in a list
//my_list.push(base_url + data[0].link);
function crawl(mylist) {
artoo.ajaxSpider(
my_list, {
limit: 1, // number of pages to scrape
scrape: scraper2,
concat: true,
done: function(data) {
console.log(data);
artoo.log.debug('Finished retrieving data. Downloading...');
}
})
}
//var ll = null;
let links = pagination();
crawl(links)

Creating minLength exception in jQueryUI autocomplete

I have a collection of data whereby the requirements are for the autocomplete to show results after 3 characters have been written. However, there is one piece of data that which is only two characters long.. ('LG').
So my question is;
Is there a way to keep :minLength:3 whilst creating an exception for when certain characters are typed, such as ('LG')?
I've been trying to hard code the result within the success parameter, as can be seen below but it's not working as intended. I'm hoping I'm on the right track though?
Here is a code snippet, and plunk of complete code thus far;
success: function (LG, resp) {
if (LG.length === 2) {
LG.push({
label: 'LG',
value: 'LG',
});
}
response(LG);
var results = [];
$.each(resp.Q0, function(k, v) {
if (v.indexOf(request.term.toUpperCase()) >= 0) {
results.push(v);
}
});
response(results);
}
});
},
https://plnkr.co/edit/wfAoi0sZDdDd0pCufQi9
I think an option is modify the minChars and modify the success function.
Here i add you the code, tell me if would suit your problem.
$(document).ready(function() {
setTimeout(function() {
$('#_Q0').autocomplete({
source: function(request, response) {
$.ajax({
url: "brands.json",
dataType: "JSON",
type: "GET",
success: function (resp) {
var results = [];
$.each(resp.Q0, function(k, v) {
if (request.term.length >= 3 && v.indexOf(request.term.toUpperCase()) >= 0) {
results.push(v);
}
if (request.term.length == 2 && request.term.toUpperCase() == v) {
results.push(v);
}
});
response(results);
}
});
},
autoFocus: true,
minLength: 2,
response: function(event, ui) {
if (!ui.content.length) {
var noResult = {
value: "",
label: "No results found"
};
ui.content.push(noResult);
}
}
});
var render = $('#_Q0').autocomplete('instance')._renderMenu;
$('#_Q0').autocomplete('instance')._renderMenu = function(ul, items) {
items.push({
label: 'AUTRE MARQUE',
value: 'AUTRE MARQUE',
last: true
});
render.call(this, ul, items);
};
}, 100);
});
The point i dont like too much is i had to hardcode the minChars into the success function, maybe we could find the way to recover the value from the property "minChars"
https://plnkr.co/edit/ygtMteIH1J5EI5KUMU3C?p=preview

kendo grid inline edit dropdown will not expand on tab navigation

I'm having an issue with Kendo Grid in Angular where the custom drop down I've implemented will not open when tab navigating to that column. The built in text and number editor fields are editable on tab navigation but my custom drop down will not expand. I have to click on it to get the drop down effect.
My goal here is to allow the user to log an an entire row of data without having to take their hands off the keyboard.
My column is defined like so:
gridColumns.push({
field: currentField.FieldName.replace(/ /g, "_"),
title: currentField.FieldName,
editor: $scope.dropDownAttEditor,
template: function (dataItem) {
return $scope.dropDownTemplate(dataItem, currentField.FieldName);
}
});
My gridOptions are defined as follows:
$scope.gridOptions = {
dataSource: new kendo.data.DataSource({
transport: {
read: {
url: appconfig.basePath + '/api/DrillHoleManager/DrillHoleInterval',
type: 'POST',
contentType: 'application/json'
},
update: {
url: appconfig.basePath + '/api/DrillHoleManager/DrillHoleIntervalUpdate',
type: 'POST',
contentType: 'application/json'
},
parameterMap: function (data, operation) {
if (operation === "read") {
data.DrillHoleId = $scope.entity.Id;
data.DrillHoleIntervalTypeId = $stateParams.ddhinttypeid;
// convert the parameters to a json object
return kendo.stringify(data);
}
if (operation === 'update') {
// build DrillHoleIntervalDto object with all ATT/CMT values to send back to server
var drillHoleInterval = { Id: data.Id, Name: data.Name, From: data.From, To: data.To };
drillHoleInterval.Attributes = [];
drillHoleInterval.Comments = [];
var attributeFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalAttribute });
$.each(attributeFields, function (idx, attributeField) {
drillHoleInterval.Attributes.push({
Id: attributeField.AttributeDto.Id,
LookupId: data[attributeField.FieldName.replace(/ /g, "_")]
});
});
var commentFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalComment });
$.each(commentFields, function (idx, commentField) {
drillHoleInterval.Comments.push({
Id: commentField.CommentDto.Id,
Value: ((data[commentField.FieldName.replace(/ /g, "_")] != "") ? data[commentField.FieldName.replace(/ /g, "_")] : null)
});
});
return kendo.stringify(drillHoleInterval);
}
// ALWAYS return options
return options;
}
},
schema: { model: { id : "Id" }},
serverPaging: false,
serverSorting: false,
serverFiltering: false
//,autoSync: true
}),
columns: gridColumns,
dataBound: onDataBound,
autoBind: false,
navigatable: true,
scrollable: false,
editable: true,
selectable: true,
edit: function (e) {
var grid = $("#ddhintgrid").data("kendoGrid");
//grid.clearSelection();
grid.select().removeClass('k-state-selected');
// select the row currently being edited
$('[data-uid=' + e.model.uid + ']').addClass('k-state-selected');
e.container[0].focus();
}
};
Here is a custom event to handle the 'Tab' keypress. The point of this is I want a new record automatically added to the grid if the user presses 'Tab' at the end of the last line:
$("#ddhintgrid").keydown(function (e) {
if (e.key == "Tab") {
var grid = $("#ddhintgrid").data("kendoGrid");
var data = grid.dataSource.data();
var selectedItem = grid.dataItem(grid.select());
var selectedIndex = null
if (selectedItem != null) {
selectedIndex = grid.select()[0].sectionRowIndex;
if (selectedIndex == data.length - 1) { // if the bottom record is selected
// We need to manually add a new record here so that the new row will automatically gain focus.
// Using $scope.addRecord() here will add the new row but cause the grid to lose focus.
var newRecord = { From: grid.dataSource.data()[selectedIndex].To };
var currentCmtFields = $.grep($scope.currentFields, function (e) { return e.DrillHoleTabFieldType == DrillHoleTabFieldTypeEnum.IntervalComment; });
$.each(currentCmtFields, function (idx, currentCmtField) {
newRecord[currentCmtField.FieldName.replace(/ /g, "_")] = null;
});
grid.dataSource.insert(data.length, newRecord);
// edit the new row
grid.editRow($("#ddhintgrid tr:eq(" + (data.length) + ")"));
}
}
}
});
Here is my template for the drop down column:
$scope.dropDownTemplate = function (dataItem, fieldName) {
var currentLookups = $.grep($scope.currentFields, function (e) { return e.FieldName == fieldName; })[0].AttributeDto.Lookups;
var selectedLookup = $.grep(currentLookups, function (e) { return e.Id == dataItem[fieldName.replace(/ /g, "_")]; })[0];
// With the custom dropdown editor when going from null to a value the entire lookup object (id, name) is placed in the
// dataItem[field_name] instead of just the id. We need to replace this object with just the id value and return the name of
// the lookup to the template.
if (typeof selectedLookup == 'undefined' && dataItem[fieldName.replace(/ /g, "_")] != null) {
selectedLookup = angular.copy(dataItem[fieldName.replace(/ /g, "_")]);
dataItem[fieldName.replace(/ /g, "_")] = selectedLookup.Id;
}
if (selectedLookup != null && selectedLookup != '') {
return selectedLookup.Name;
}
else {
return '';
}
};
And finally here is the custom editor for the drop down column:
$scope.dropDownAttEditor = function (container, options) {
var editor = $('<input k-data-text-field="\'Name\'" k-data-value-field="\'Id\'" k-data-source="ddlDataSource" data-bind="value:' + options.field + '"/>')
.appendTo(container).kendoDropDownList({
dataSource: $.grep($scope.currentFields, function (e) { return e.FieldName == options.field.replace(/_/g, ' '); })[0].AttributeDto.Lookups,
dataTextField: "Name",
dataValueField: "Id"
});
};
I know this is a lot to take in but if you have any questions just let me know.
My ultimate goal is that I want the user to be able to navigate the grid using the 'Tab' key and edit every field without having to use the mouse.

Cannot reload data in Fuelux Datagrid

I have tried to reload the data populated by an ajax call but I cant get it to work, it shows the old data even after using the reload method. The thing is that if I change some variables to populate a different data and try to call the following code without refreshing the page it does not reload the updated data =/ Here is my code:
function populateDataGrid() {
$.ajaxSetup({async: false});
var gridinfo="";
$.post("lib/function.php",{activity: activity, shift: shift, date: date},
function (output){
gridinfo = JSON.parse(output);
});
$.ajaxSetup({async: true});
// INITIALIZING THE DATAGRID
var dataSource = new StaticDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) {
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
data: gridinfo,
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-fast-appointment-results').modal({show:true});
}
I found a solution... I had to create a new DataSource (lets call it "AjaxDataSource") and add the ajax request functionality within the data constructor:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore'], factory);
} else {
root.AjaxDataSource = factory();
}
}(this, function () {
var AjaxDataSource = function (options) {
this._formatter = options.formatter;
this._columns = options.columns;
this._delay = options.delay || 0;
this._data = options.data;
};
AjaxDataSource.prototype = {
columns: function () {
return this._columns;
},
data: function (options, callback) {
var self = this;
setTimeout(function () {
var data;
$.ajax({
url: 'getdata.php',
type: 'POST',
data: 'param1:param1,param2,param2,...,paramN:paramN', // this is optional in case you have to send some params to getdata.php
dataType: 'json',
async: false,
success: function(result) {
data = result;
},
error: function(data){
//in case we want to debug and catch any possible error
// console.log(data);
}
});
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
var match = false;
_.each(item, function (prop) {
if (_.isString(prop) || _.isFinite(prop)) {
if (prop.toString().toLowerCase().indexOf(options.search.toLowerCase()) !== -1) match = true;
}
});
return match;
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
}, this._delay)
}
};
return AjaxDataSource;
}));
After defining the new DataSource, we just need to create it and call the datagrid as usual:
function populateDataGrid(){
// INITIALIZING THE DATAGRID
var dataSource = new AjaxDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) { // in case we want to add customized items, for example a button
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-results').modal({show:true});
}
So now we have our datagrid with data populated via ajax request with the ability to reload the data without refreshing the page.
Hope it helps someone!

Select2 limit number of tags

Is there a way to limit the number of tags a user can add to an input field using Select2?
I have:
$('#tags').select2({
containerCssClass: 'supplierTags',
placeholder: "Usual suppliers...",
minimumInputLength: 2,
multiple: true,
tokenSeparators: [",", " "],
placeholder: 'Usual suppliers...',
createSearchChoice: function(term, data) {
if ($(data).filter(function() {
return this.name.localeCompare(term) === 0;
}).length === 0) {
return {id: 0, name: term};
}
},
id: function(e) {
return e.id + ":" + e.name;
},
ajax: {
url: ROOT + 'Call',
dataType: 'json',
type: 'POST',
data: function(term, page) {
return {
call: 'Helpers->tagsHelper',
q: term
};
},
results: function(data, page) {
return {
results: data.tags
};
}
},
formatResult: formatResult,
formatSelection: formatSelection,
initSelection: function(element, callback) {
var data = [];
$(element.val().split(",")).each(function(i) {
var item = this.split(':');
data.push({
id: item[0],
name: item[1]
});
});
callback(data);
}
});
It would be great if there could be/is a simple parameter like limit: 5 and a callback to fire when the limit is reached.
Sure, with maximumSelectionLength like so:
$("#tags").select2({
maximumSelectionLength: 3
});
Maximum Selection Length
Select2 allows the developer to limit the number of items that can be
selected in a multi-select control.
http://ivaynberg.github.io/select2/
It has no native callback, but you can pass a function to formatSelectionTooBig like this:
$(function () {
$("#tags").select2({
maximumSelectionLength: 3,
formatSelectionTooBig: function (limit) {
// Callback
return 'Too many selected items';
}
});
});
http://jsfiddle.net/U98V7/
Or you could extend formatSelectionTooBig like this:
$(function () {
$.extend($.fn.select2.defaults, {
formatSelectionTooBig: function (limit) {
// Callback
return 'Too many selected items';
}
});
$("#tags").select2({
maximumSelectionLength: 3
});
});
Edit
Replaced maximumSelectionSize with the updated maximumSelectionLength. Thanks #DrewKennedy!
method 1
$("#tags").select2({
maximumSelectionLength: 3
});
method 2
<select data-maximum-selection-length="3" ></select>
list of all available options https://select2.org/configuration/options-api
The accepted answer doesn't mention that the maximumSelectionLength statement should be inside the document.ready function. So for anyone who is having the same trouble I did, here is the code that worked for me.
$(document).ready(function() {
$("#id").select2({
maximumSelectionLength: 3
});
});
$("#keywords").select2({
tags : true,
width :'100%',
tokenSeparators: [','],
maximumSelectionLength: 5,
matcher : function(term,res){
return false;
},
"language": {
'noResults': function(){
return "Type keywords separated by commas";
}
}
}).on("change",function(e){
if($(this).val().length>5){
$(this).val($(this).val().slice(0,5));
}
});
Try like this. It'll short up to 5 keywords.
This is not working for me, I am getting query function not defined for Select2, so here is another workaround.
var onlyOne=false;
$("selector").select2({
maximumSelectionSize:function(){
if(onlyOne==true)
return 1;
else
return 5;
}
});
This setting can be defined as function and it's called every time you start searching something.
Important thing is that you have something defined outside this select2 closure so you can check it (access it). In this case you could somewhere in your program change value of onlyOne and of course this returned limit can also be dynamical.
This is working for me.
$("#category_ids").select2({ maximumSelectionLength: 3 });
Send the Get Request to action method and the Map the class properties to drop down id and text property
$("#DropDownId").select2({
minimumInputLength: 3,
maximumSelectionLength: 10,
tags: [],
ajax: {
url: "#Url.Action("ActionName", "ControllerName")",
type: "get",
dataType: 'json',
delay: 250,
data: function (params) {
return {
Title: params.term // search term
};
},
processResults: function (response) {
return {
results: $.map(response, function (item) {
return {
text: item.Title,
id: item.Id
}
})
};
}
}
});
Action Method
[HttpGet]
public JsonResult ActionName(string Title)
{
ClassName obj= new ClassName ();
obj.Title = "PMPAK";
obj.Id= -1;
obj.Add(nibafInstitute);
return Json(obj, JsonRequestBehavior.AllowGet);
}
public class ClassName
{
public int Id{ get; set; }
public string Title { get; set; }
}

Categories

Resources