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