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