Skip to content

Commit b82f56e

Browse files
author
Reinier Lamers
committed
Initial check-in
0 parents  commit b82f56e

File tree

2 files changed

+252
-0
lines changed

2 files changed

+252
-0
lines changed

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
## kendo-elasticsearch
2+
3+
A Kendo DataSource extension so you can load data into your [Kendo UI Grid](http://docs.telerik.com/kendo-ui/api/javascript/ui/grid) from an [ElasticSearch](https://www.elasticsearch.org/) index.
4+
5+
It supports filtering, searching and sorting in ElasticSearch for date and string fields. Other scenarios are not deliberately supported and have not been tested.
6+
7+
### Usage
8+
9+
Here is an example HTML file defining a Kendo grid talking to ElasticSearch:
10+
11+
```
12+
<!DOCTYPE html>
13+
<html>
14+
<head>
15+
<title>ElasticSearch Kendo DataSource example</title>
16+
<link href="kendo.common.min.css" rel="stylesheet">
17+
18+
<!-- Include dependencies -->
19+
<script src="moment.min.js"></script>
20+
<script src="jquery.min.js"></script>
21+
<script src="kendo.web.min.js"></script>
22+
23+
<!-- Include kendo-elasticsearch itself -->
24+
<script src="kendo-elasticsearch.js"></script>
25+
26+
</head>
27+
<body>
28+
<div id="example">
29+
<div id="grid"></div>
30+
31+
<script>
32+
$(document).ready(function () {
33+
$('#grid').kendoGrid({
34+
35+
// so here go configuration options for the Kendo UI Grid
36+
37+
// configure the datasource to be an ElasticSearchDataSource
38+
dataSource: new kendo.data.ElasticSearchDataSource({
39+
40+
// point it to the URL where ElasticSearch search requests can go
41+
transport: {
42+
read: {
43+
url: "http://localhost:9200/_all/_search/"
44+
}
45+
},
46+
47+
pageSize: 20,
48+
49+
// specify the fields to bind
50+
schema: {
51+
model: {
52+
fields: {
53+
message: { type: "string" },
54+
55+
// you can specify a different ElasticSearch name for the field,
56+
// to deal with ElasticSearch field names that Kendo can't handle
57+
timestamp: { type: "date", esName: "@timestamp" }
58+
}
59+
}
60+
},
61+
62+
// server-side paging, filtering and sorting are enabled by default.
63+
// Set filters as you like
64+
sort: { field: "timestamp", dir: "desc" },
65+
filter: { field: "message", operator: "eq", value: "accepted" }
66+
}),
67+
68+
// other grid options besides the datasource
69+
sortable: true,
70+
pageable: true,
71+
filterable: true,
72+
columns: [
73+
{ field: "timestamp" },
74+
{ field: "message" }
75+
]
76+
});
77+
});
78+
</script>
79+
</div>
80+
</body>
81+
</html>
82+
```
83+
84+
### Dependencies
85+
86+
Requires kendo-ui and also [Moment.js](http://momentjs.com/).

kendo-elasticsearch.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* A Kendo DataSource that gets its data from ElasticSearch.
3+
*
4+
* Read-only, supports paging, filtering and sorting.
5+
*/
6+
(function(kendo) {
7+
'use strict';
8+
9+
var data = kendo.data;
10+
11+
if (!window.moment) throw new Error('Moment.js has to be loaded for ElasticSearchDataSource to work');
12+
13+
// Helper functions for conversion of query parameters from Kendo to ElasticSearch format
14+
function arrayify(myArg) {
15+
var _argArray = [];
16+
17+
if(myArg && myArg.constructor == Array) {
18+
_argArray = myArg;
19+
}
20+
else {
21+
if (!(myArg === void 0))
22+
_argArray.push(myArg);
23+
}
24+
25+
return _argArray;
26+
}
27+
28+
data.ElasticSearchDataSource = data.DataSource.extend({
29+
init: function(initOptions) {
30+
var self = this;
31+
32+
if (!initOptions.transport || !initOptions.transport.read || !initOptions.transport.read.url)
33+
throw new Error('transport.read.url must be set to use ElasticSearchDataSource');
34+
35+
initOptions.transport.read.dataType = initOptions.transport.read.dataType || 'json';
36+
37+
// Create a map mapping Kendo field names to ElasticSearch field names. We have to allow ElasticSearch field
38+
// names to be different because ES likes an "@" in field names while Kendo fails on that.
39+
var fields = initOptions.schema.model.fields;
40+
this._esFieldMap = [];
41+
for (var k in fields) {
42+
if (fields.hasOwnProperty(k)) {
43+
this._esFieldMap[k] = fields[k].hasOwnProperty('esName')
44+
? fields[k].esName
45+
: k;
46+
}
47+
}
48+
49+
initOptions.transport.parameterMap = function(data, type) {
50+
var sortParams = arrayify(data.sort);
51+
52+
var esParams = {
53+
from: data.skip,
54+
size: data.take
55+
};
56+
57+
if (sortParams.length > 0)
58+
esParams.sort = self._esFieldMap[sortParams[0].field] + ":" + sortParams[0].dir;
59+
60+
if (data.filter)
61+
esParams.q = self._kendoFilterToESParam(data.filter);
62+
63+
return esParams;
64+
};
65+
66+
var schema = initOptions.schema;
67+
schema.parse = function(response) {
68+
var hits = response.hits.hits;
69+
var dataItems = [];
70+
for (var i = 0; i < hits.length; i++) {
71+
var hitSource = hits[i]._source;
72+
var dataItem = {};
73+
74+
dataItem.id = hits[i]._id;
75+
for (var k in self._esFieldMap) {
76+
dataItem[k] = hitSource[self._esFieldMap[k]];
77+
}
78+
79+
dataItems.push(dataItem);
80+
}
81+
return {
82+
total: response.hits.total,
83+
data: dataItems
84+
};
85+
};
86+
87+
schema.data = schema.data || 'data';
88+
schema.total = schema.total || 'total';
89+
schema.model.id = schema.model.id || '_id';
90+
91+
initOptions.serverFiltering = initOptions.serverFiltering || true;
92+
initOptions.serverSorting = initOptions.serverSorting || true;
93+
initOptions.serverPaging = initOptions.serverPaging || true;
94+
95+
data.DataSource.fn.init.call(this, initOptions);
96+
},
97+
98+
_kendoFilterToESParam: function(kendoFilterObj) {
99+
// there are three possible structures for the kendoFilterObj:
100+
// * {value: "...", operator: "...", field: " ..."}
101+
// * {logic: "...", filters: ...}
102+
// * [ ... ]
103+
104+
if (kendoFilterObj.operator) {
105+
return this._kendoOperatorFilterToESParam(kendoFilterObj);
106+
} else if (kendoFilterObj.logic) {
107+
return this._kendoFilterListToESParam(kendoFilterObj.logic, kendoFilterObj.filters);
108+
} else if (kendoFilterObj.constructor == Array) {
109+
return this._kendoFilterListToESParam("and", kendoFilterObj.filters);
110+
} else {
111+
throw new Error("Don't know how to turn this Kendo filter object into ElasticSearch search parameter: "
112+
+ kendoFilterObj);
113+
}
114+
},
115+
116+
_kendoOperatorFilterToESParam: function(kendoFilterObj) {
117+
var fieldEscaped = this._asESParameter(this._esFieldMap[kendoFilterObj.field]);
118+
var valueEscaped = this._asESParameter(kendoFilterObj.value);
119+
120+
var simpleBinaryOperators = {
121+
eq: "",
122+
lt: "<",
123+
lte: "<=",
124+
gt: ">",
125+
gte: ">="
126+
};
127+
128+
if (simpleBinaryOperators[kendoFilterObj.operator] !== void 0) {
129+
var esOperator = simpleBinaryOperators[kendoFilterObj.operator];
130+
return fieldEscaped + ":" + esOperator + valueEscaped;
131+
} else {
132+
switch (kendoFilterObj.operator) {
133+
case "neq":
134+
return "NOT (" + fieldEscaped + ":" + valueEscaped + ")";
135+
case "contains":
136+
return fieldEscaped + ":*" + valueEscaped + "*";
137+
case "startswith":
138+
return fieldEscaped + ":" + valueEscaped + "*";
139+
case "endswith":
140+
return fieldEscaped + ":*" + valueEscaped;
141+
default:
142+
throw new Error("Kendo search operator '" + kendoFilterObj.operator + "' is not yet supported");
143+
}
144+
}
145+
},
146+
147+
// logicalConnective can be "and" or "or"
148+
_kendoFilterListToESParam: function(logicalConnective, filters) {
149+
var esParams = [];
150+
151+
for (var i = 0; i < filters.length; i++) {
152+
esParams.push(" (" + this._kendoFilterToESParam(filters[i]) + ") ");
153+
}
154+
155+
return esParams.join(logicalConnective.toUpperCase())
156+
},
157+
158+
_asESParameter: function(value) {
159+
if (value.constructor == Date)
160+
value = value.toISOString();
161+
162+
return value.replace("\\", "\\\\").replace(/[+\-&|!()\{}\[\]^:"~*?:\/ ]/g, "\\$&");
163+
}
164+
})
165+
166+
})(window.kendo);

0 commit comments

Comments
 (0)