3148ad9ea7f4537a0b16bf4f52514fcc5788d5e2
[ccsdk/features.git] /
1 /**
2  * Copyright 2010-2013 Ben Birch
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 (function( $, app, i18n ) {
17
18         var ui = app.ns("ui");
19         var ut = app.ns("ut");
20
21         ui.QueryFilter = ui.AbstractWidget.extend({
22                 defaults: {
23                         metadata: null,   // (required) instanceof app.data.MetaData
24                         query: null       // (required) instanceof app.data.Query that the filters will act apon
25                 },
26                 init: function() {
27                         this._super();
28                         this.metadata = this.config.metadata;
29                         this.query = this.config.query;
30                         this.el = $(this._main_template());
31                 },
32                 helpTypeMap: {
33                         "date" : "QueryFilter.DateRangeHelp"
34                 },
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);
38                                 this.query.query();
39                         }
40                 },
41                 getSpec: function(fieldName) {
42                         return this.metadata.fields[fieldName];
43                 },
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) {
47                                 var jEl = $(el);
48                                 if(indices.contains(jEl.text()) !== jEl.hasClass("selected")) {
49                                         jEl.click();
50                                 }
51                         });
52                         this.requestUpdate(jEv);
53                 },
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);
59                         if(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())) {
63                                                 $(el).click();
64                                         }
65                                 });
66                         }
67                         this.requestUpdate(jEv);
68                 },
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);
74                         if(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())) {
79                                                 $(el).click();
80                                         }
81                                 });
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) {
85                                                 $(el).click();
86                                         }
87                                 }, this);
88                         }
89                         this.requestUpdate(jEv);
90                 },
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));
106                                 } 
107                                 section.loaded = true;
108                         }
109                         section.on("animComplete", function(section) { section.body.find("INPUT").focus(); });
110                 },
111                 _textFilterChange_handler: function(jEv) {
112                         var jEl = $(jEv.target).closest("INPUT");
113                         var val = jEl.val();
114                         var spec = jEl.data("spec");
115                         var uqids = jEl.data("uqids") || [];
116                         uqids.forEach(function(uqid) {
117                                 uqid && this.query.removeClause(uqid);
118                         }, this);
119                         if(val.length) {
120                                 if(jEl[0] === document.activeElement && jEl[0].selectionStart === jEl[0].selectionEnd) {
121                                         val = val.replace(new RegExp("(.{"+jEl[0].selectionStart+"})"), "$&*");
122                                 }
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];
130                                         while (part >= 1) {
131                                                 if (fieldNameParts[part] !== fieldNameParts[part - 1]) {
132                                                         name = fieldNameParts[part - 1] + "." + name;
133                                                 }
134                                                 part--;
135                                         }
136                                         return term && this.query.addClause(term, name, "wildcard", "must");
137                                 }, this);
138                         }
139                         jEl.data("uqids", uqids);
140                         this.requestUpdate(jEv);
141                 },
142                 _dateFilterChange_handler: function(jEv) {
143                         var jEl = $(jEv.target).closest("INPUT");
144                         var val = jEl.val();
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)) {
150                                 return;
151                         }
152                         uqid && this.query.removeClause(uqid);
153                         if((range.start && range.end) === null) {
154                                 uqid = null;
155                         } else {
156                                 var value = {};
157                                 if( range.start ) {
158                                         value["gte"] = range.start;
159                                 }
160                                 if( range.end ) {
161                                         value["lte"] = range.end;
162                                 }
163                                 uqid = this.query.addClause( value, spec.field_name, "range", "must");
164                         }
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);
172                 },
173                 _numericFilterChange_handler: function(jEv) {
174                         var jEl = $(jEv.target).closest("INPUT");
175                         var val = jEl.val();
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); });
181                                 if(/<>/.test(val)) {
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] };
185                                 } else {
186                                         return { gte: ops[0], lte: ops[0] };
187                                 }
188                         })(val || "");
189                         if(!range || (lastRange && lastRange.lte === range.lte && lastRange.gte === range.gte)) {
190                                 return;
191                         }
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);
197                 },
198                 _booleanFilterChange_handler: function( jEv ) {
199                         var jEl = $(jEv.target).closest("SELECT");
200                         var val = jEl.val();
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") );
206                         }
207                         this.requestUpdate(jEv);
208                 },
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()
215                         ] };
216                 },
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) }
222                         ] };
223                 },
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 };
230                                 })}
231                         ] };
232                 },
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 };
239                                 })}
240                         ] };
241                 },
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({
250                                                 title: name,
251                                                 help: this.helpTypeMap[this.metadata.fields[ name ].type],
252                                                 onShow: this._openFilter_handler
253                                         });
254                                 }, this ) }
255                         ] };
256                 },
257                 _textFilter_template: function(spec) {
258                         return { tag: "INPUT", data: { spec: spec }, onKeyup: this._textFilterChange_handler };
259                 },
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", "") }
265                         ]};
266                 },
267                 _numericFilter_template: function(spec) {
268                         return { tag: "INPUT", data: { spec: spec }, onKeyup: this._numericFilterChange_handler };
269                 },
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 };
274                                 })
275                         };
276                 },
277                 _multiFieldFilter_template: function(section, spec) {
278                         return {
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);
283                                         }
284                                         return new app.ui.SidebarSection({
285                                                 title : data.field_name, help : this.helpTypeMap[data.type], onShow : this._openFilter_handler
286                                         });
287                                 }, this)
288                         };
289                 }       
290         });
291
292 })( this.jQuery, this.app, this.i18n );