2 * Copyright 2010-2013 Ben Birch
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this software except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 (function( $, app, i18n ) {
18 var ui = app.ns("ui");
19 var ut = app.ns("ut");
21 ui.QueryFilter = ui.AbstractWidget.extend({
23 metadata: null, // (required) instanceof app.data.MetaData
24 query: null // (required) instanceof app.data.Query that the filters will act apon
28 this.metadata = this.config.metadata;
29 this.query = this.config.query;
30 this.el = $(this._main_template());
33 "date" : "QueryFilter.DateRangeHelp"
35 requestUpdate: function(jEv) {
36 if(jEv && jEv.originalEvent) { // we only want to update on real user interaction not generated events
37 this.query.setPage(1);
41 getSpec: function(fieldName) {
42 return this.metadata.fields[fieldName];
44 _selectAlias_handler: function(jEv) {
45 var indices = (jEv.target.selectedIndex === 0) ? [] : this.metadata.getIndices($(jEv.target).val());
46 $(".uiQueryFilter-index").each(function(i, el) {
48 if(indices.contains(jEl.text()) !== jEl.hasClass("selected")) {
52 this.requestUpdate(jEv);
54 _selectIndex_handler: function(jEv) {
55 var jEl = $(jEv.target).closest(".uiQueryFilter-index");
56 jEl.toggleClass("selected");
57 var selected = jEl.hasClass("selected");
58 this.query.setIndex(jEl.text(), selected);
60 var types = this.metadata.getTypes(this.query.indices);
61 this.el.find("DIV.uiQueryFilter-type.selected").each(function(n, el) {
62 if(! types.contains($(el).text())) {
67 this.requestUpdate(jEv);
69 _selectType_handler: function(jEv) {
70 var jEl = $(jEv.target).closest(".uiQueryFilter-type");
71 jEl.toggleClass("selected");
72 var type = jEl.text(), selected = jEl.hasClass("selected");
73 this.query.setType(type, selected);
75 var indices = this.metadata.types[type].indices;
76 // es throws a 500 if searching an index for a type it does not contain - so we prevent that
77 this.el.find("DIV.uiQueryFilter-index.selected").each(function(n, el) {
78 if(! indices.contains($(el).text())) {
82 // es throws a 500 if you specify types from different indices with _all
83 jEl.siblings(".uiQueryFilter-type.selected").forEach(function(el) {
84 if(this.metadata.types[$(el).text()].indices.intersection(indices).length === 0) {
89 this.requestUpdate(jEv);
91 _openFilter_handler: function(section) {
92 var field_name = section.config.title;
93 if(! section.loaded) {
94 var spec = this.getSpec(field_name);
95 if(spec.core_type === "string") {
96 section.body.append(this._textFilter_template(spec));
97 } else if(spec.core_type === "date") {
98 section.body.append(this._dateFilter_template(spec));
99 section.body.append(new ui.DateHistogram({ printEl: section.body.find("INPUT"), cluster: this.cluster, query: this.query, spec: spec }));
100 } else if(spec.core_type === "number") {
101 section.body.append(this._numericFilter_template(spec));
102 } else if(spec.core_type === 'boolean') {
103 section.body.append(this._booleanFilter_template(spec));
104 } else if (spec.core_type === 'multi_field') {
105 section.body.append(this._multiFieldFilter_template(section, spec));
107 section.loaded = true;
109 section.on("animComplete", function(section) { section.body.find("INPUT").focus(); });
111 _textFilterChange_handler: function(jEv) {
112 var jEl = $(jEv.target).closest("INPUT");
114 var spec = jEl.data("spec");
115 var uqids = jEl.data("uqids") || [];
116 uqids.forEach(function(uqid) {
117 uqid && this.query.removeClause(uqid);
120 if(jEl[0] === document.activeElement && jEl[0].selectionStart === jEl[0].selectionEnd) {
121 val = val.replace(new RegExp("(.{"+jEl[0].selectionStart+"})"), "$&*");
123 uqids = val.split(/\s+/).map(function(term) {
124 // Figure out the actual field name - needed for multi_field, because
125 // querying for "field.field" will not work. Simply "field" must be used
126 // if nothing is aliased.
127 var fieldNameParts = spec.field_name.split('.');
128 var part = fieldNameParts.length - 1;
129 var name = fieldNameParts[part];
131 if (fieldNameParts[part] !== fieldNameParts[part - 1]) {
132 name = fieldNameParts[part - 1] + "." + name;
136 return term && this.query.addClause(term, name, "wildcard", "must");
139 jEl.data("uqids", uqids);
140 this.requestUpdate(jEv);
142 _dateFilterChange_handler: function(jEv) {
143 var jEl = $(jEv.target).closest("INPUT");
145 var spec = jEl.data("spec");
146 var uqid = jEl.data("uqid") || null;
147 var range = window.dateRangeParser.parse(val);
148 var lastRange = jEl.data("lastRange");
149 if(!range || (lastRange && lastRange.start === range.start && lastRange.end === range.end)) {
152 uqid && this.query.removeClause(uqid);
153 if((range.start && range.end) === null) {
158 value["gte"] = range.start;
161 value["lte"] = range.end;
163 uqid = this.query.addClause( value, spec.field_name, "range", "must");
165 jEl.data("lastRange", range);
166 jEl.siblings(".uiQueryFilter-rangeHintFrom")
167 .text(i18n.text("QueryFilter.DateRangeHint.from", range.start && new Date(range.start).toUTCString()));
168 jEl.siblings(".uiQueryFilter-rangeHintTo")
169 .text(i18n.text("QueryFilter.DateRangeHint.to", range.end && new Date(range.end).toUTCString()));
170 jEl.data("uqid", uqid);
171 this.requestUpdate(jEv);
173 _numericFilterChange_handler: function(jEv) {
174 var jEl = $(jEv.target).closest("INPUT");
176 var spec = jEl.data("spec");
177 var uqid = jEl.data("uqid") || null;
178 var lastRange = jEl.data("lastRange");
179 var range = (function(val) {
180 var ops = val.split(/->|<>|</).map( function(v) { return parseInt(v.trim(), 10); });
182 return { gte: (ops[0] - ops[1]), lte: (ops[0] + ops[1]) };
183 } else if(/->|</.test(val)) {
184 return { gte: ops[0], lte: ops[1] };
186 return { gte: ops[0], lte: ops[0] };
189 if(!range || (lastRange && lastRange.lte === range.lte && lastRange.gte === range.gte)) {
192 jEl.data("lastRange", range);
193 uqid && this.query.removeClause(uqid);
194 uqid = this.query.addClause( range, spec.field_name, "range", "must");
195 jEl.data("uqid", uqid);
196 this.requestUpdate(jEv);
198 _booleanFilterChange_handler: function( jEv ) {
199 var jEl = $(jEv.target).closest("SELECT");
201 var spec = jEl.data("spec");
202 var uqid = jEl.data("uqid") || null;
203 uqid && this.query.removeClause(uqid);
204 if(val === "true" || val === "false") {
205 jEl.data("uqid", this.query.addClause(val, spec.field_name, "term", "must") );
207 this.requestUpdate(jEv);
209 _main_template: function() {
210 return { tag: "DIV", id: this.id(), cls: "uiQueryFilter", children: [
211 this._aliasSelector_template(),
212 this._indexSelector_template(),
213 this._typesSelector_template(),
214 this._filters_template()
217 _aliasSelector_template: function() {
218 var aliases = Object.keys(this.metadata.aliases).sort();
219 aliases.unshift( i18n.text("QueryFilter.AllIndices") );
220 return { tag: "DIV", cls: "uiQueryFilter-section uiQueryFilter-aliases", children: [
221 { tag: "SELECT", onChange: this._selectAlias_handler, children: aliases.map(ut.option_template) }
224 _indexSelector_template: function() {
225 var indices = Object.keys( this.metadata.indices ).sort();
226 return { tag: "DIV", cls: "uiQueryFilter-section uiQueryFilter-indices", children: [
227 { tag: "HEADER", text: i18n.text("QueryFilter-Header-Indices") },
228 { tag: "DIV", onClick: this._selectIndex_handler, children: indices.map( function( name ) {
229 return { tag: "DIV", cls: "uiQueryFilter-booble uiQueryFilter-index", text: name };
233 _typesSelector_template: function() {
234 var types = Object.keys( this.metadata.types ).sort();
235 return { tag: "DIV", cls: "uiQueryFilter-section uiQueryFilter-types", children: [
236 { tag: "HEADER", text: i18n.text("QueryFilter-Header-Types") },
237 { tag: "DIV", onClick: this._selectType_handler, children: types.map( function( name ) {
238 return { tag: "DIV", cls: "uiQueryFilter-booble uiQueryFilter-type", text: name };
242 _filters_template: function() {
243 var _metadataFields = this.metadata.fields;
244 var fields = Object.keys( _metadataFields ).sort()
245 .filter(function(d) { return (_metadataFields[d].core_type !== undefined); });
246 return { tag: "DIV", cls: "uiQueryFilter-section uiQueryFilter-filters", children: [
247 { tag: "HEADER", text: i18n.text("QueryFilter-Header-Fields") },
248 { tag: "DIV", children: fields.map( function(name ) {
249 return new app.ui.SidebarSection({
251 help: this.helpTypeMap[this.metadata.fields[ name ].type],
252 onShow: this._openFilter_handler
257 _textFilter_template: function(spec) {
258 return { tag: "INPUT", data: { spec: spec }, onKeyup: this._textFilterChange_handler };
260 _dateFilter_template: function(spec) {
261 return { tag: "DIV", children: [
262 { tag: "INPUT", data: { spec: spec }, onKeyup: this._dateFilterChange_handler },
263 { tag: "PRE", cls: "uiQueryFilter-rangeHintFrom", text: i18n.text("QueryFilter.DateRangeHint.from", "")},
264 { tag: "PRE", cls: "uiQueryFilter-rangeHintTo", text: i18n.text("QueryFilter.DateRangeHint.to", "") }
267 _numericFilter_template: function(spec) {
268 return { tag: "INPUT", data: { spec: spec }, onKeyup: this._numericFilterChange_handler };
270 _booleanFilter_template: function(spec) {
271 return { tag: "SELECT", data: { spec: spec }, onChange: this._booleanFilterChange_handler,
272 children: [ i18n.text("QueryFilter.AnyValue"), "true", "false" ].map( function( val ) {
273 return { tag: "OPTION", value: val, text: val };
277 _multiFieldFilter_template: function(section, spec) {
279 tag : "DIV", cls : "uiQueryFilter-subMultiFields", children : acx.eachMap(spec.fields, function(name, data) {
280 if (name === spec.field_name) {
281 section.config.title = spec.field_name + "." + name;
282 return this._openFilter_handler(section);
284 return new app.ui.SidebarSection({
285 title : data.field_name, help : this.helpTypeMap[data.type], onShow : this._openFilter_handler
292 })( this.jQuery, this.app, this.i18n );