I may be missing something basic as why is it happening.
GET: example.com/users
//gives all data
GET: example.com/users?status=1
//gives data with status = 1
GET: example.com/users // this does not work
gives same data as pervious API condition with status=1
On third hit, self.whereObj is not initialising to default empty object instead it takes previous value of {'status' = '1'}, however self.page and self.limit is taking default value if no query parameter is provided in query string.
example.com/users?limit=3, // takes override to 3 form default value of 5
example.com/users // self.limit takes default 5 and this works fine
So my question is why the self.limit (simple string variable) is initialising however self.whereObj is not ?
var Bookshelf = require('../../dbconfig').bookshelf;
Bookshelf.Collection = Bookshelf.Collection.extend({
limit: 5,
page: 1,
whereObj: {}
myFetch: function (query_params,expectedWhereFields) {
var self = this;
var whereObj = self.whereObj ; // this is not initializing
// var whereObj = {}; this is initialising
var page = self.page;
var limit = self.limit; //this is not showing nay initialisation error
for (var x in query_params) {
if (expectedWhereFields.includes(x)) {
whereObj[x] = query_params[x];
}
if (x === 'page') {
page = query_params[x];
}
if (x === 'limit') {
limit = query_params[x];
}
}
var offset = (page - 1) * limit;
function fetch() {
return self.constructor.forge()
.query({where: whereObj})
.query(function (qb) {
qb.offset(offset).limit(limit);
})
.then(function (collection) {
return collection;
})
.catch(function (err) {
return err
});
}
return new fetch();
}
});
module.exports = Bookshelf;
UPDATED
service.js
var Model = require('./../models/Users');
var express = require('express');
var listUsers = function (query_params, callback) {
var expectedWhereFields = ["type", "status", "name"];
Model.Users
.forge()
.myFetch(query_params, expectedWhereFields)
.then(function (collection) {
return callback(null, collection);
})
.catch(function (err) {
return callback(err, null);
});
};
module.exports = {
listUsers: listUsers
};
model/Users.js
var Bookshelf = require('../../dbconfig').bookshelf;
var Base = require('./base');
// Users model
var User = Bookshelf.Model.extend({
tableName: 'user_table'
});
var Users = Bookshelf.Collection.extend({
model: User
});
module.exports = {
User: User,
Users: Users
};
So my question is why the self.limit (simple string variable) is initialising however self.whereObj is not?
Because objects are reference values. When you set var whereObj = self.whereObj;, both refer to the same object, and when you copy the query parameters into the object properties you are effectively writing into your defaults instance. This does not happen with primitive values such as strings - they don't have mutable properties.
I am trying to manipulate a json file, so I am trying JSLINQ, but I can't figure out why I hit an error at groupBy().
The website that led me to this code.
var query = JSLINQ(json);
var result = query.groupBy(function (i) { //HERE is where the error hits.
return i.CustomerName; //Attribute of json
})
.select(function (i) {
console.log(i);
return {
CustomerName: i.Key, data: i.elements //I read that I get groupBy result like this.
.select(function (j) {
x = j.x, y = j.y //x and y are attributes
})
}
}).toArray();
query.groupBy is not a function
Ask and ye shall receive young padawan ...
var result = jslinq(data)
.groupBy(function (i) { return i.CustomerName; })
.select(function(g) {
return {
key: g.key,
items: jslinq(g.elements).select(function(i) { return { x: i.x, y: i.y } }).items
};
})
.toList();
console.log(result);
....
Key differences between yours and mine ...
jslinq has been lowered in the version listed on github
element collections in the groups need to be wrapped in jslinq() too to be queried
I am using select2 with custom data adapter. All of the data provided to select2 is generated locally in web page (so no need to use ajax). As query method can generate a lot of results (about 5k) opening select box is quite slow.
As a remedy, I wanted to use infinite scroll. Documentation for custom data adapter says that query method should receive page parameter together with term:
#param params.page The specific page that should be loaded. This is
typically provided when working with remote data sets, which rely
on pagination to determine what objects should be displayed.
But it does not: only term is present. I tried to return more: true or more: 1000, but this didn't help. I guess this is because, by default, infinite scroll is enabled iff ajax is enabled.
I am guessing that enabling infinite scroll will involve using amd.require, but I am not sure what to do exactly. I tried this code:
$.fn.select2.amd.require(
["select2/utils", "select2/dropdown/infiniteScroll"],
(Utils, InfiniteScroll) =>
input.data("select2").options.options.resultsAdapter =
Utils.Decorate(input.data("select2").options.options.resultsAdapter, InfiniteScroll)
)
This is coffee script, but I hope that it is readable for everyone. input is DOM element containing select box - I earlier did input.select2( //options )
My question is basically, how do I enable infinite scroll without ajax?
Select2 will only enable infinite scroll, if ajax is enabled. Fortunately we can enable it and still use our own adapter. So putting empty object into ajax option will do the trick.
$("select").select2({
ajax: {},
dataAdapter: CustomData
});
Next, define your own data adapter. Inside it, inn query push pagination info into callback.
CustomData.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var data = {};
# you probably want to do some filtering, basing on params.term
data.results = items.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < items.length;
callback(data);
};
Here is a full fiddle
Expanding on this answer to show how to retain the search functionality that comes with select2. Thanks Paperback Writer!
Also referenced this example of how to achieve infinite scrolling using a client side data source, with select2 version 3.4.5.
This example uses the oringal options in a select tag to build the list instead of item array which is what was called for in my situation.
function contains(str1, str2) {
return new RegExp(str2, "i").test(str1);
}
CustomData.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var pageSize = 50;
var results = this.$element.children().map(function(i, elem) {
if (contains(elem.innerText, params.term)) {
return {
id:[elem.innerText, i].join(""),
text:elem.innerText
};
}
});
callback({
results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
pagination:{
more:results.length >= params.page * pageSize
}
});
};
Here is a jsfiddle
I found it was just easier to hijack the ajax adapter rather than creating a whole new CustomAdapter like the above answers. The above answers don't actually seem to support paging because they all start from array, which doesn't support paging. It also doesn't support delayed processing.
window.myarray = Array(10000).fill(0).map((x,i)=>'Index' + i);
let timer = null;
$('select[name=test]')
.empty()
.select2({
ajax: {
delay: 250,
transport: function(params, success, failure) {
let pageSize = 10;
let term = (params.data.term || '').toLowerCase();
let page = (params.data.page || 1);
if (timer)
clearTimeout(timer);
timer = setTimeout(function(){
timer = null;
let results = window.myarray // your base array here
.filter(function(f){
// your custom filtering here.
return f.toLowerCase().includes(term);
})
.map(function(f){
// your custom mapping here.
return { id: f, text: f};
});
let paged = results.slice((page -1) * pageSize, page * pageSize);
let options = {
results: paged,
pagination: {
more: results.length >= page * pageSize
}
};
success(options);
}, params.delay);
}
},
tags: true
});
<link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.full.min.js"></script>
<select name='test' data-width="500px"><option>test</option></select>
I felt the answers above needed better demonstration. Select2 4.0.0 introduces the ability to do custom adapters. Using the ajax: {} trick, I created a custom dataAdapter jsonAdapter that uses local JSON directly. Also notice how Select2's 4.0.0 release has impressive performance using a big JSON string. I used an online JSON generator and created 10,000 names as test data. However, this example is very muddy. While this works, I would hope there is a better way.
See the full fiddle here: http://jsfiddle.net/a8La61rL/
$.fn.select2.amd.define('select2/data/customAdapter', ['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
function CustomDataAdapter($element, options) {
CustomDataAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomDataAdapter, ArrayData);
CustomDataAdapter.prototype.current = function (callback) {
var found = [],
findValue = null,
initialValue = this.options.options.initialValue,
selectedValue = this.$element.val(),
jsonData = this.options.options.jsonData,
jsonMap = this.options.options.jsonMap;
if (initialValue !== null){
findValue = initialValue;
this.options.options.initialValue = null; // <-- set null after initialized
}
else if (selectedValue !== null){
findValue = selectedValue;
}
if(!this.$element.prop('multiple')){
findValue = [findValue];
this.$element.html(); // <-- if I do this for multiple then it breaks
}
// Query value(s)
for (var v = 0; v < findValue.length; v++) {
for (var i = 0, len = jsonData.length; i < len; i++) {
if (findValue[v] == jsonData[i][jsonMap.id]){
found.push({id: jsonData[i][jsonMap.id], text: jsonData[i][jsonMap.text]});
if(this.$element.find("option[value='" + findValue[v] + "']").length == 0) {
this.$element.append(new Option(jsonData[i][jsonMap.text], jsonData[i][jsonMap.id]));
}
break;
}
}
}
// Set found matches as selected
this.$element.find("option").prop("selected", false).removeAttr("selected");
for (var v = 0; v < found.length; v++) {
this.$element.find("option[value='" + found[v].id + "']").prop("selected", true).attr("selected","selected");
}
// If nothing was found, then set to top option (for single select)
if (!found.length && !this.$element.prop('multiple')) { // default to top option
found.push({id: jsonData[0][jsonMap.id], text: jsonData[0][jsonMap.text]});
this.$element.html(new Option(jsonData[0][jsonMap.text], jsonData[0][jsonMap.id], true, true));
}
callback(found);
};
CustomDataAdapter.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var jsonData = this.options.options.jsonData,
pageSize = this.options.options.pageSize,
jsonMap = this.options.options.jsonMap;
var results = $.map(jsonData, function(obj) {
// Search
if(new RegExp(params.term, "i").test(obj[jsonMap.text])) {
return {
id:obj[jsonMap.id],
text:obj[jsonMap.text]
};
}
});
callback({
results:results.slice((params.page - 1) * pageSize, params.page * pageSize),
pagination:{
more:results.length >= params.page * pageSize
}
});
};
return CustomDataAdapter;
});
var jsonAdapter=$.fn.select2.amd.require('select2/data/customAdapter');
My solution for angular 9
this.$select2 = this.$element.select2({
width: '100%',
language: "tr",
ajax: {
transport: (params, success, failure) => {
let pageSize = 10;
let page = (params.data.page || 1);
let results = this.options
.filter(i => new RegExp(params.data.term, "i").test(i.text))
.map(i => {
return {
id: i.value,
text: i.text
}
});
let paged = results.slice((page - 1) * pageSize, page * pageSize);
let options = {
results: paged,
pagination: {
more: results.length >= page * pageSize
}
};
success(options);
}
}
});
}
Here's a shorter, searchable version for Select2 v4 that has paging. It uses lo-dash for
searching.
EDIT New fiddle: http://jsfiddle.net/nea053tw/
$(function () {
items = []
for (var i = 0; i < 1000; i++) {
items.push({ id: i, text : "item " + i})
}
pageSize = 50
jQuery.fn.select2.amd.require(["select2/data/array", "select2/utils"],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var results = [];
if (params.term && params.term !== '') {
results = _.filter(items, function(e) {
return e.text.toUpperCase().indexOf(params.term.toUpperCase()) >= 0;
});
} else {
results = items;
}
if (!("page" in params)) {
params.page = 1;
}
var data = {};
data.results = results.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < results.length;
callback(data);
};
$(document).ready(function () {
$("select").select2({
ajax: {},
dataAdapter: CustomData
});
});
})
});
The search loop is originally from these old Select4 v3 functions: https://stackoverflow.com/a/25466453/5601169
This is not a direct answer: after struggling a lot with this for a while, I ended up switching to selectize. Select2's support for non-Ajax search, since version 4, is terribly complicated, bordering the ridiculous, and not documented well. Selectize has explicit support for non-Ajax search: you simply implement a function that returns a list.
None of this worked for me. I don't know what original question meant but in my case, I am using angular and HTTP calls and services and wanted to avoid AJAX calls.
So I simply wanted to call a service method in place of AJAX. This is not even documented on the library's site but somehow I found the way to do it using transport
ajax: {
delay : 2000,
transport: (params, success, failure) => {
this.getFilterList(params).then(res => success(res)).catch(err => failure(err));
}
}
If anyone like me came here for this then There you go!
How can I convert calls to server API, with pagination support, to a Bacon.js / RxJs stream?
For the pagination I want to be able to store the last requested item-index, and ask for the next page_size items from that index to fill the stream.
But I need the 'load next page_size items' method to be called only when all items in stream already been read.
Here is a test that I wrote:
var PAGE_SIZE = 20;
var LAST_ITEM = 100;
var FIRST_ITEM = 0;
function getItemsFromServer(fromIndex) {
if (fromIndex > LAST_ITEM) {
return [];
}
var remainingItemsCount = LAST_ITEM-fromIndex;
if (remainingItemsCount <= PAGE_SIZE) {
return _.range(fromIndex, fromIndex + remainingItemsCount);
}
return _.range(fromIndex, fromIndex + PAGE_SIZE);
}
function makeStream() {
return Bacon.fromBinder(function(sink) {
var fromIndex = FIRST_ITEM;
function loadMoreItems() {
var items = getItemsFromServer(fromIndex);
fromIndex = fromIndex + items.length;
return items;
}
var hasMoreItems = true;
while (hasMoreItems) {
var items = loadMoreItems();
if (items.length < PAGE_SIZE) { hasMoreItems = false; }
_.forEach(items, function(item) { sink(new Bacon.Next(item)); });
}
return function() { console.log('done'); };
});
}
makeStream().onValue(function(value) {
$("#events").append($("<li>").text(value))
});
http://jsfiddle.net/Lc2oua5x/10/
Currently the 'getItemsFromServer' method is only a dummy and generate items locally. How to combine it with ajax call or a promise that return array of items? and can be execute unknown number of times (depends on the number of items on the server and the page size).
I read the documentation regarding Bacon.fromPromise() but couldn't manage to use it along with the pagination.
You need to use flatMap to map pages to streams created with Bacon.fromPromise. Here is a working example, where I use the jsfiddle echo endpoint ( it sends the same data back )
Bacon.sequentially(0, _.range(0,5))
.map(toIndex)
.flatMap(loadFromServer)
.onValue(render)
function toIndex(page) {
return page * PAGE_SIZE
}
function loadFromServer(index) {
var response = getItemsFromServer(index)
return Bacon.fromPromise($.ajax({
type: 'POST',
dataType: 'json',
url: '/echo/json/',
data : { json: JSON.stringify( response ) }
}))
}
function render(items) {
items.forEach(function(item) {
$("#events").append($("<li>").text(item))
})
}
http://jsfiddle.net/1eqec9g3/2/
Note: this code relies on responses coming from the server in the same order they were sent.
With my angular application i need to display all indexes on elastic search. I am able to query a particular index using the documentation, not able to get all the indexes on the elastic search.
Here is the Documentation
Here is my code:
$scope.getallIndex = function(){
$scope.Indexes = ejs.Request().query(ejs.MatchAllQuery()
.doSearch().then(function (body) {
$scope.Indexes = body.hits.hits;
console.log($scope.Indexes);
}
const indices = await client.cat.indices({format: 'json'})
I am using elasticsearch.js which is the Browser build for Elastic Search that can be used in the browser . Refer link . I have maintained a factory :
.factory('ElasticService', [ 'esFactory', function (elasticsearch) {
var client = elasticsearch({
host: ip + ':' + port,
});
client.cat.indices("b",function(r,q){
console.log(r,q);
}) }]);
That will return all the indices .Refer link for full configuration.
Edited :
Below is the full factory for retrieving data from a specific index .
.factory('ElasticService', ['$q', 'esFactory', '$location', function ($q, elasticsearch, $location) {
var client = elasticsearch({
host: ip + ':' + port
});
var search = function (index, body) {
var deferred = $q.defer();
client.search({
index: index,
type: type,
body: body
}).then(function (result) {
var took = result.took;
var size = result.hits.total;
var ii = 0, hits_in, hits_out = [],aggs = [], highlight = [];
hits_in = (result.hits || {}).hits || [];
/* For the timebeing i have maintained this variable to save the aggregations */
aggs = result.aggregations;
for (; ii < hits_in.length; ii++) {
hits_in[ii].fields.id = hits_in[ii]._id;
hits_out.push(hits_in[ii].fields);
// hits_out[hits_in[ii]._id]=hits_in[ii].fields: use this method if you wanna keep _id as the index
// if there is a highlight coming along with the data we add a field highlight and push it to built an array
if (hits_in[ii].highlight) {
highlight.push(hits_in[ii].highlight);
}
}
if (highlight.length) {
deferred.resolve({hits: hits_out, highlight: highlight,aggs:aggs, size: size, took: took});
}
else {
deferred.resolve({hits: hits_out, size: size,aggs:aggs, took: took});
}
}, deferred.reject);
return deferred.promise;
};
return {search: search}; }]);
And so the controller you can use this factory for fetching data from a particular index
ElasticService.search('<index>', <query>).then(function (resp) {
// resp has the content
});
I did query like below in javascript and it returned me index list
client.cat.indices({
h: ['index']
}).then(function (body) {
console.log(body);
});
The elasticsearch JS API has a method to query all the indices on the instance: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#api-cat-indices
Only thing is that it doesn't really return to you in a readable JSON format. So what I end up doing is something like the following
client.cat.indices({
h: ['index', 'docs.count']
}).then(function (body) {
let lines = body.split('\n');
let indices = lines.map(function (line) {
let row = line.split(' ');
return {name: row[0], count: row[1]};
});
indices.pop(); //the last line is empty by default
});