manifold: fix in plugin.js
[myslice.git] / plugins / query_editor / query_editor.js
1 /**
2  * Description: QueryEditor plugin
3  * Copyright (c) 2012-2013 UPMC Sorbonne Universite
4  * License: GPLv3
5  */
6
7 // XXX TODO This plugin will be interested in changes in metadata
8 // What if we remove a filter, is it removed in the right min/max field ???
9 //  -> no on_filter_removed is not yet implemented
10 // XXX if a plugin has not declared a handler, it might become inconsistent,
11 // and the interface should either reset or disable it
12 (function($){
13
14     var QueryEditor = Plugin.extend({
15
16         event_filter_added: function(op, suffix) {
17             suffix = (typeof suffix === 'undefined') ? '' : manifold.separator + suffix;
18             var self = this;
19             return function(e) {
20                 var array = self.array_from_id(e.target.id);
21                 var key   = self.field_from_id(array); // No need to remove suffix...
22                 var value = e.target.value;
23
24                 if (value) {
25                     // XXX This should be handled by manifold
26                     //manifold.raise_event(object.options.query_uuid, FILTER_UPDATED, [key, op, value]);
27                     manifold.raise_event(self.options.query_uuid, FILTER_ADDED, [key, op, value]);
28                 } else {
29                     // XXX This should be handled by manifold
30                     manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, [key, op]);
31                 }
32             }
33         },
34
35         init: function(options, element) {
36             this._super(options, element);
37
38             this.listen_query(options.query_uuid);
39
40             this.els('queryeditor-auto-filter').change(this.event_filter_added('='));
41             this.els('queryeditor-filter').change(this.event_filter_added('='));
42             this.els('queryeditor-filter-min').change(this.event_filter_added('>'));
43             this.els('queryeditor-filter-max').change(this.event_filter_added('<'));
44
45             var self = this;
46             this.els('queryeditor-check').click(function() { 
47                 manifold.raise_event(self.options.query_uuid, this.checked?FIELD_ADDED:FIELD_REMOVED, this.value);
48             });
49
50             /* The following code adds an expandable column for the table */
51             // XXX Why isn't it done statically ?
52             var nCloneTh = document.createElement( 'th' );
53             var nCloneTd = document.createElement( 'td' );
54             nCloneTd.innerHTML = "<span class='ui-icon ui-icon-triangle-1-e' style='cursor:pointer'></span>";
55             //nCloneTd.innerHTML = '<img src="/components/com_tophat/images/details_open.png">';
56             nCloneTh.innerHTML = '<b>Info</b>';
57             nCloneTd.className = "center";
58             nCloneTh.className = "center";
59             // XXX
60             jQuery('#'+this.options.plugin_uuid+'_fields thead tr').each( function () {
61                 this.insertBefore( nCloneTh, this.childNodes[0] );
62             });
63             // XXX
64             jQuery('#'+this.options.plugin_uuid+'_fields tbody tr').each( function () {
65                 this.insertBefore(  nCloneTd.cloneNode( true ), this.childNodes[0] );
66             });
67          
68             // We are currently using a DataTable display, but another browsing component could be better
69             //jQuery('#'+this.options.plugin_uuid+'-table').dataTable...
70             var  metaTable = this.el('table').dataTable({
71                 bFilter     : false,
72                 bPaginate   : false,
73                 bInfo       : false,
74                 sScrollX    : '100%',         // Horizontal scrolling
75                 sScrollY    : '200px',
76                 bJQueryUI   : true,           // Use jQuery UI
77                 bProcessing : true,           // Loading
78                 aaSorting   : [[ 1, "asc" ]], // sort by column fields on load
79                 aoColumnDefs: [
80                     { 'bSortable': false, 'aTargets': [ 0 ]},
81                     { 'sWidth': '8px', 'aTargets': [ 0 ] },
82                     { 'sWidth': '8px', 'aTargets': [ 4 ] } // XXX NB OF COLS
83                 ]
84             });
85
86             var self = this;
87             // Actions on the newly added fields
88             this.el('table tbody td span').on('click', function() {
89                 var nTr = this.parentNode.parentNode;
90                 // use jQuery UI instead of images to keep a common UI
91                 // class="ui-icon treeclick ui-icon-triangle-1-s tree-minus"
92                 // East oriented Triangle class="ui-icon-triangle-1-e"
93                 // South oriented Triangle class="ui-icon-triangle-1-s"
94                 
95                 if (this.className=="ui-icon ui-icon-triangle-1-e") {
96                     this.removeClass("ui-icon-triangle-1-e").addClass("ui-icon-triangle-1-s");
97                     // XXX ??????
98                     metaTable.fnOpen(nTr, this.fnFormatDetails(metaTable, nTr, self.options.plugin_uuid+'_div'), 'details' );
99                 } else {
100                     this.removeClass("ui-icon-triangle-1-s").addClass("ui-icon-triangle-1-e");
101                     metaTable.fnClose(nTr);
102                 }
103             });
104
105             this.el('table_wrapper').css({
106                 'padding-top'   : '0em',
107                 'padding-bottom': '0em'
108             });
109
110         }, // init
111
112         /* UI management */
113
114         check_field: function(field)
115         {
116             this.el('check', field).attr('checked', true);
117         },
118
119         uncheck_field: function(field)
120         {
121             this.el('check', field).attr('checked', false);
122         },
123
124         update_filter_value: function(filter, removed)
125         {
126             removed = !(typeof removed === 'undefined'); // default = False
127
128             var key   = filter[0];
129             var op    = filter[1];
130             var value = filter[2];
131
132             var id = this.id_from_field(key);
133
134             if (op == '=') {
135                 var element = this.el(id);
136             } else {
137                 var suffix;
138                 if (op == '<') {
139                     this.el(id, 'max').val(value);
140                 } else if (op == '>') {
141                     this.el(id, 'min').val(value);
142                 } else {
143                     return;
144                 }
145                 var element = this.el(id, suffix);
146             }
147
148             element.val(removed?null:value);
149
150         },
151
152         /* Events */
153
154         on_filter_added: function(filter)
155         {
156             this.update_filter_value(filter);
157         },
158
159         on_filter_removed: function(filter)
160         {
161             this.update_filter_value(filter, true);
162         },
163
164         on_field_added: function(field)
165         {
166             this.check_field(field);
167         },
168
169         on_field_removed: function(field)
170         {
171             this.uncheck_field(field);
172         },
173
174         /* Former code */
175
176         print_field_description: function(field_header, div_id) 
177         { 
178             //var selected = all_headers[field_header];
179             var selected = getMetadata_field('resource',field_header);
180
181             field_header = div_id+"_"+field_header;
182
183             var output = "<div id='desc"+field_header+"'>";
184
185             output += "<div id='divinfo"+field_header+"'>";
186             output += '<p><span class="column-title">'+selected['title']+'</span></p></span>'; 
187             output += '<p><span class="column-detail">'+selected['description']+'</span></p></span>'; 
188
189             var period_select = "<select id='selectperiod"+field_header+"'><option value='Now'> Now </option><option value='latest'> Latest  </option><option value=w> Week </option><option value=m> Month </option><option value=y> Year </option></select>";
190
191             if (selected['value_type'] == 'string') {
192
193                 var values_select = "<p><select id='selectvalues"+field_header+"' MULTIPLE size=3>";
194
195                 output += '<p>Values: ';
196
197                 var values_list = selected['allowed_values'].split(",");
198
199                 for (var value_index = 0; value_index < values_list.length ; value_index++) {
200                     var value_desc = values_list[value_index].split("-");
201                     if (value_index > 0)
202                         output += ', ';
203                     output += '<span class="bold">'+value_desc[0]+'</span>';
204                     values_select += "<option value ='"+value_desc[0]+"'>&nbsp;"+value_desc[0];
205                     if (value_desc[1]!='') 
206                         output += ' ('+value_desc[1]+')';
207
208                     values_select += "&nbsp;</option>";
209                 }
210                 values_select += "</select>";
211             }
212             else
213                 output+='<p>Unit: '+selected['unit'];
214
215             output+= '</p>';
216
217             output += '<p>Source: <a class="source-url" target="source_window" href="'+selected['platform_url']+'">'+selected['platform']+'</a>';
218
219             //if (selected['via'] != '') 
220                 //output += ' via <a class="source-url" target="source_window" href="http://'+selected['via_url']+'">'+selected['via']+'</a>';
221
222             output += '</p>';
223             output += "</div>";
224
225     /*
226             output += "<div id='divgroup"+field_header+"'>";
227             output += "<p>Group resources with the same value <input type=checkbox></input>";
228             output += "<p>Select aggregator : <select><option>Count</option><option selected=true>Average</option><option>Maximum</option><option>Minimum</option></select>";
229             output += "</div>";
230             output += "<div id='divtime"+field_header+"'>";
231             output += "<p>Select timestamp : ";
232             output += period_select;
233             output += "</div>";
234     */
235             output += "</div>";
236
237             return output;
238         },
239
240         update_autocomplete: function(e, rows, current_query)
241         {
242             var d = data;
243             d.current_query = current_query;
244             var availableTags={};
245             jQuery.each (rows, function(index, obj) {                    
246                 jQuery.each(obj,function(key,value){                       
247                     value = get_value(value); 
248                     if(!availableTags.hasOwnProperty(key)){availableTags[key]=new Array();}
249                     //availableTags[key].push(value);
250                     var currentArray=availableTags[key];
251                     if(value!=null){
252                         if(jQuery.inArray(value,currentArray)==-1){availableTags[key].push(value);}
253                     }
254                 });                    
255             });
256             jQuery.each(availableTags, function(key, value){
257                 value.sort();
258                 jQuery("#"+options.plugin_uuid+"-filter-"+key).autocomplete({
259                             source: value,
260                             selectFirst: true,
261                             minLength: 0, // allows to browse items with no value typed in
262                             select: function(event, ui) {
263                                 var key=getKeySplitId(this.id,"-");
264                                 var op='=';
265                                 var val=ui.item.value;
266                                 
267                                 query=d.current_query;
268                                 query.update_filter(key,op,val);
269                                 // Publish the query changed, the other plugins with subscribe will get the changes
270                                 jQuery.publish('/query/' + query.uuid + '/changed', query);
271                                 //add_ActiveFilter(this.id,'=',ui.item.value,d);
272                             }
273                 });
274             });                
275         }, // update_autocomplete     
276
277         fnFormatDetails: function( metaTable, nTr, div_id ) 
278         {
279             var aData = metaTable.fnGetData( nTr );
280             var sOut = '<blockquote>';
281             //sOut += prepare_tab_description(aData[1].substr(21, aData[1].length-21-7), div_id);
282             sOut += this.print_field_description(aData[1].substring(3, aData[1].length-4), div_id);
283             sOut += '</blockquote>';
284          
285             return sOut;
286         }
287     });
288
289     $.plugin('QueryEditor', QueryEditor);
290
291 })(jQuery);