3bc3d8bd9b3241fdb6ca0f92e81c50a29fc65bd3
[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 /*
8  * It's a best practice to pass jQuery to an IIFE (Immediately Invoked Function
9  * Expression) that maps it to the dollar sign so it can't be overwritten by
10  * another library in the scope of its execution.
11  */
12
13 (function($){
14
15     var PLUGIN_NAME = 'QueryEditor';
16
17     // routing calls
18     jQuery.fn.QueryEditor = function( method ) {
19                 if ( methods[method] ) {
20                         return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
21                 } else if ( typeof method === 'object' || ! method ) {
22                         return methods.init.apply( this, arguments );
23                 } else {
24                         jQuery.error( 'Method ' +  method + ' does not exist on jQuery.' + PLUGIN_NAME );
25                 }    
26     };
27
28     /***************************************************************************
29      * Public methods
30      ***************************************************************************/
31
32     var methods = {
33
34         /**
35          * @brief Plugin initialization
36          * @param options : an associative array of setting values
37          * @return : a jQuery collection of objects on which the plugin is
38          *     applied, which allows to maintain chainability of calls
39          */
40         init : function ( options ) {
41
42             return this.each(function() {
43
44                 var $this = $(this);
45
46                 /* An object that will hold private variables and methods */
47                 var plugin = new QueryEditor(options);
48                 $this.data('Manifold', plugin);
49
50             }); // this.each
51         }, // init
52
53         /**
54          * @brief Plugin destruction
55          * @return : a jQuery collection of objects on which the plugin is
56          *     applied, which allows to maintain chainability of calls
57          */
58         destroy : function( ) {
59
60             return this.each(function() {
61                 var $this = $(this);
62                 var hazelnut = $this.data('Manifold');
63
64                 // Unbind all events using namespacing
65                 $(window).unbind(PLUGIN_NAME);
66
67                 // Remove associated data
68                 hazelnut.remove();
69                 $this.removeData('Manifold');
70
71                 $this.set_query_handler(options.query_uuid, hazelnut.query_handler);
72                 $this.set_record_handler(options.query_uuid, hazelnut.record_handler); 
73
74                 /* XXX Subscribe to query updates to maintain current state of query (multiple editors) */
75                 jQuery.subscribe('/query/' + options.query_uuid + '/changed', {instance: $this}, query_changed);
76                 jQuery.subscribe('/query/' + options.query_uuid + '/diff', {instance: $this}, query_changed_diff);
77                 /* Subscribe to results in order to redraw the table when updates arrive */
78                 jQuery.subscribe('/results/' + options.query_uuid + '/changed', {instance: $this}, update_autocomplete);
79
80             });
81         }, // destroy
82
83     }; // var methods;
84
85     /***************************************************************************
86      * Plugin object
87      ***************************************************************************/
88
89     function QueryEditor(options)
90     {
91
92         /* member variables */
93         this.options = options;
94
95         var object = this;
96
97         this.initialize_table = function(data) 
98         {
99
100             var d = data;
101             
102             jQuery('.queryeditor-filter').change(function(event) { 
103                 query = data.current_query;
104                 var key=getKeySplitId(event.target.id,"-");
105                 var op='=';
106                 var value=event.target.value;
107                             
108                 if(value){                
109                     query.update_filter(key, op, value);
110                     //add_ActiveFilter(event.target.id, '=',event.target.value,data);
111                 }else{
112                     query.remove_filter(key,op,"");
113                     //remove_ActiveFilter(event, data, event.target.id,'=');
114                 }
115                 // Publish the query changed, the other plugins with subscribe will get the changes
116                 jQuery.publish('/query/' + query.uuid + '/changed', query);
117             });
118             jQuery('.queryeditor-filter-min').change(function(event) {
119                 query = data.current_query;
120                 var key=getKeySplitId(event.target.id,"-");
121                 var op='>';
122                 var value=event.target.value;
123                 
124                 if(value){
125                     query.update_filter(key, op, value);
126                     //add_ActiveFilter(event.target.id,'>',event.target.value,data);
127                 }else{
128                     query.remove_filter(key,op,"");
129                     //remove_ActiveFilter(event, data, event.target.id,'>');
130                 }
131                 // Publish the query changed, the other plugins with subscribe will get the changes
132                 jQuery.publish('/query/' + query.uuid + '/changed', query);
133             });
134             jQuery('.queryeditor-filter-max').change(function(event) {
135                 query = data.current_query;
136                 var key=getKeySplitId(event.target.id,"-");
137                 var op='<';
138                 var value=event.target.value;
139                 
140                 if(value){
141                     query.update_filter(key, op, value);
142                     //add_ActiveFilter(event.target.id,'<',event.target.value,data);
143                 }else{
144                     query.remove_filter(key,op,"");
145                     //remove_ActiveFilter(event, data, event.target.id,'<');
146                 }
147                 // Publish the query changed, the other plugins with subscribe will get the changes
148                 jQuery.publish('/query/' + query.uuid + '/changed', query);
149             });
150
151             jQuery('.queryeditor-check').click(function() { 
152                 manifold.raise_event(object.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, this.value);
153                 /*
154                     var column = this.id.substring(6);
155                     query = data.current_query;
156                     if (this.checked) {
157                         if (jQuery.inArray(column, query.fields) == -1) {
158                             query.fields.push(column);
159                             jQuery.publish('/query/' + query.uuid + '/changed', query);
160                         }
161                     } else {
162                         query.fields = jQuery.grep(query.fields, function(value) {return value != column;});
163                         jQuery.publish('/query/' + query.uuid + '/changed', query);
164                     }
165                 */
166                 });
167
168             //onFunctionAvailable('jQuery.fn.dataTable', function() {
169
170                 var nCloneTh = document.createElement( 'th' );
171                 var nCloneTd = document.createElement( 'td' );
172                 nCloneTd.innerHTML = "<span class='ui-icon ui-icon-triangle-1-e' style='cursor:pointer'></span>";
173                 //nCloneTd.innerHTML = '<img src="/components/com_tophat/images/details_open.png">';
174                 nCloneTh.innerHTML = '<b>Info</b>';
175                 nCloneTd.className = "center";
176                 nCloneTh.className = "center";
177          
178                 jQuery('#'+this.options.plugin_uuid+'_fields thead tr').each( function () {
179                     this.insertBefore( nCloneTh, this.childNodes[0] );
180                 });
181          
182                 jQuery('#'+this.options.plugin_uuid+'_fields tbody tr').each( function () {
183                     this.insertBefore(  nCloneTd.cloneNode( true ), this.childNodes[0] );
184                 });
185          
186                 var  metaTable = jQuery('#'+this.options.plugin_uuid+'-table').dataTable( {
187                     bFilter: false,
188                     bPaginate: false,
189                     bInfo: false,
190                     sScrollX: '100%',       /* Horizontal scrolling */
191                     sScrollY: "200px",
192                     bJQueryUI: true, // Use jQuery UI
193                     bProcessing: true, // Loading
194                     aaSorting: [[ 1, "asc" ]], // sort by column fields on load
195                     aoColumnDefs: [ {"bSortable": false, "aTargets": [ 0 ]},
196                                       { "sWidth": "8px", "aTargets": [ 0 ] },
197                                       { "sWidth": "8px", "aTargets": [ 4 ] }
198                     ]
199                 });
200
201                 jQuery('#'+this.options.plugin_uuid+'_fields tbody td span').live('click', function () {
202                     var nTr = this.parentNode.parentNode;
203                     // use jQuery UI instead of images to keep a common UI
204                     // class="ui-icon treeclick ui-icon-triangle-1-s tree-minus"
205                     //East oriented Triangle class="ui-icon-triangle-1-e"
206                     //South oriented Triangle class="ui-icon-triangle-1-s"
207                     
208                     if(this.className=="ui-icon ui-icon-triangle-1-e"){
209                         this.removeClass("ui-icon-triangle-1-e");
210                         this.addClass("ui-icon-triangle-1-s");
211                         metaTable.fnOpen( nTr, this.fnFormatDetails(metaTable, nTr, this.options.plugin_uuid+'_div'), 'details' );
212                     }else{
213                         this.removeClass("ui-icon-triangle-1-s");
214                         this.addClass("ui-icon-triangle-1-e");
215                         metaTable.fnClose( nTr );
216                     }
217                     /*
218                     if ( this.src.match('details_close') ) {
219                         this.src = "/components/com_tophat/images/details_open.png";
220                         metaTable.fnClose( nTr );
221                     }
222                     else {
223                         this.src = "/components/com_tophat/images/details_close.png";
224                         metaTable.fnOpen( nTr, this.fnFormatDetails(metaTable, nTr, this.options.plugin_uuid+'_div'), 'details' );
225                     }
226                     */
227                 });
228
229                 jQuery('#'+this.options.plugin_uuid+'_fields_wrapper').css({'padding-top':'0em','padding-bottom':'0em'});
230
231             //}); // onfunctionAvailable
232
233         } // initialize_table
234
235         this.print_field_description = function(field_header, div_id) { 
236             
237             //var selected = all_headers[field_header];
238             var selected = getMetadata_field('resource',field_header);
239
240             field_header = div_id+"_"+field_header;
241
242             var output = "<div id='desc"+field_header+"'>";
243
244             output += "<div id='divinfo"+field_header+"'>";
245             output += '<p><span class="column-title">'+selected['title']+'</span></p></span>'; 
246             output += '<p><span class="column-detail">'+selected['description']+'</span></p></span>'; 
247
248             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>";
249
250             if (selected['value_type'] == 'string') {
251
252                 var values_select = "<p><select id='selectvalues"+field_header+"' MULTIPLE size=3>";
253
254                 output += '<p>Values: ';
255
256                 var values_list = selected['allowed_values'].split(",");
257
258                 for (var value_index = 0; value_index < values_list.length ; value_index++) {
259                     var value_desc = values_list[value_index].split("-");
260                     if (value_index > 0)
261                         output += ', ';
262                     output += '<span class="bold">'+value_desc[0]+'</span>';
263                     values_select += "<option value ='"+value_desc[0]+"'>&nbsp;"+value_desc[0];
264                     if (value_desc[1]!='') 
265                         output += ' ('+value_desc[1]+')';
266
267                     values_select += "&nbsp;</option>";
268                 }
269                 values_select += "</select>";
270             }
271             else
272                 output+='<p>Unit: '+selected['unit'];
273
274             output+= '</p>';
275
276             output += '<p>Source: <a class="source-url" target="source_window" href="'+selected['platform_url']+'">'+selected['platform']+'</a>';
277
278             //if (selected['via'] != '') 
279                 //output += ' via <a class="source-url" target="source_window" href="http://'+selected['via_url']+'">'+selected['via']+'</a>';
280
281             output += '</p>';
282             output += "</div>";
283
284     /*
285             output += "<div id='divgroup"+field_header+"'>";
286             output += "<p>Group resources with the same value <input type=checkbox></input>";
287             output += "<p>Select aggregator : <select><option>Count</option><option selected=true>Average</option><option>Maximum</option><option>Minimum</option></select>";
288             output += "</div>";
289             output += "<div id='divtime"+field_header+"'>";
290             output += "<p>Select timestamp : ";
291             output += period_select;
292             output += "</div>";
293     */
294             output += "</div>";
295
296             return output;
297         }
298
299         this.update_autocomplete = function(e, rows, current_query)
300         {
301             var d = data;
302             d.current_query = current_query;
303             var availableTags={};
304             jQuery.each (rows, function(index, obj) {                    
305                 jQuery.each(obj,function(key,value){                       
306                     value = get_value(value); 
307                     if(!availableTags.hasOwnProperty(key)){availableTags[key]=new Array();}
308                     //availableTags[key].push(value);
309                     var currentArray=availableTags[key];
310                     if(value!=null){
311                         if(jQuery.inArray(value,currentArray)==-1){availableTags[key].push(value);}
312                     }
313                 });                    
314             });
315             jQuery.each(availableTags, function(key, value){
316                 value.sort();
317                 jQuery("#"+options.plugin_uuid+"-filter-"+key).autocomplete({
318                             source: value,
319                             selectFirst: true,
320                             minLength: 0, // allows to browse items with no value typed in
321                             select: function(event, ui) {
322                                 var key=getKeySplitId(this.id,"-");
323                                 var op='=';
324                                 var val=ui.item.value;
325                                 
326                                 query=d.current_query;
327                                 query.update_filter(key,op,val);
328                                 // Publish the query changed, the other plugins with subscribe will get the changes
329                                 jQuery.publish('/query/' + query.uuid + '/changed', query);
330                                 //add_ActiveFilter(this.id,'=',ui.item.value,d);
331                             }
332                 });
333             });                
334         } // update_autocomplete     
335
336         /**
337          * This function is used to update autocomplete
338          */
339         this.record_handler = function(e, event_type, record)
340         {
341             // elements in set
342             switch(event_type) {
343                 case NEW_RECORD:
344                     /* NOTE in fact we are doing a join here */
345                     if (object.received_all)
346                         // update checkbox for record
347                         object.set_checkbox(record);
348                     else
349                         // store for later update of checkboxes
350                         object.in_set_buffer.push(record);
351                     break;
352                 case CLEAR_RECORDS:
353                     // nothing to do here
354                     break;
355                 case IN_PROGRESS:
356                     manifold.spin($(this));
357                     break;
358                 case DONE:
359                     if (object.received_all)
360                         manifold.spin($(this), false);
361                     object.received_set = true;
362                     break;
363             }
364         };
365
366         this.query_handler = function(e, event_type, data)
367         {
368             // This replaces the complex set_query function
369             // The plugin does not need to remember the query anymore
370             switch(event_type) {
371                 // Filters
372                 // When Query changed, Then we need to update the filters of
373                 // QueryEditor plugin if the filter is active, set the value
374                 // (the update can come from another plugin) else set the
375                 // filter value to null PB if the filter is composed of MIN/MAX
376                 // values
377                 case FILTER_ADDED:
378                     filter = data;
379                     // Set the value of the filter = to query filter value
380                     // Necessary if the filter has been modified by another plugin (QuickFilter)
381                     if(filter[1]=="="){
382                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]).val(filter[2]);
383                     }else if(filter[1]=="<"){
384                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]+'-max').val(filter[2]);
385                     }else if(filter[1]==">"){
386                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]+'-min').val(filter[2]);
387                     }
388                 case FILTER_REMOVED:
389                     filter = data;
390                     if(filter[1]=="="){
391                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]).val(null);
392                     }else if(filter[1]=="<"){
393                         //502124d5a5848-filter-asn-max
394                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]+'-max').val(null);
395                     }else if(filter[1]==">"){
396                         //502124d5a5848-filter-asn-min
397                         jQuery('#'+this.options.plugin_uuid+'-filter-'+filter[0]+'-min').val(null);
398                     }
399                 case CLEAR_FILTERS:
400                     break;
401
402                 // Fields
403                 /* Hide/unhide columns to match added/removed fields */
404                 case FIELD_ADDED:
405                     $('#check_' + data).attr('checked', true);
406                     break;
407                 case FIELD_REMOVED:
408                     $('#check_' + data).attr('checked', false);
409                     break;
410                 case CLEAR_FIELDS:
411                     alert(PLUGIN_NAME + '::clear_fields() not implemented');
412                     break;
413             } // switch
414
415
416         }
417         this.fnFormatDetails  = function( metaTable, nTr, div_id ) {
418             var aData = metaTable.fnGetData( nTr );
419             var sOut = '<blockquote>';
420             //sOut += prepare_tab_description(aData[1].substr(21, aData[1].length-21-7), div_id);
421             sOut += this.print_field_description(aData[1].substring(3, aData[1].length-4), div_id);
422             sOut += '</blockquote>';
423          
424             return sOut;
425         }
426        
427             
428         /**
429          *
430          */
431         this.initialize = function() {
432             //XXX
433             this.initialize_table(jQuery(this).data());
434         }
435         /* Constructor */
436
437         this.initialize();
438
439     } // function PresView
440
441 })( jQuery );