cubicweb/web/data/cubicweb.facets.js
changeset 11057 0b59724cb3f2
parent 10279 d7479a5ac553
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 /** filter form, aka facets, javascript functions
       
     2  *
       
     3  *  :organization: Logilab
       
     4  *  :copyright: 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6  */
       
     7 
       
     8 var SELECTED_IMG = DATA_URL + 'black-check.png';
       
     9 var UNSELECTED_IMG = DATA_URL + 'no-check-no-border.png';
       
    10 var UNSELECTED_BORDER_IMG = DATA_URL + 'black-uncheck.png';
       
    11 
       
    12 function copyParam(origparams, newparams, param) {
       
    13     var index = $.inArray(param, origparams[0]);
       
    14     if (index > - 1) {
       
    15         newparams[param] = origparams[1][index];
       
    16     }
       
    17 }
       
    18 
       
    19 
       
    20 function facetFormContent($form) {
       
    21     var names = [];
       
    22     var values = [];
       
    23     $form.find('.facet').each(function() {
       
    24         var facetName = $(this).find('.facetTitle').attr('cubicweb:facetName');
       
    25         // FacetVocabularyWidget
       
    26         $(this).find('.facetValueSelected').each(function(x) {
       
    27             names.push(facetName);
       
    28             values.push(this.getAttribute('cubicweb:value'));
       
    29         });
       
    30         // FacetStringWidget (e.g. has-text)
       
    31         $(this).find('input:text').each(function(){
       
    32             names.push(this.name);
       
    33             values.push(this.value);
       
    34         });
       
    35     });
       
    36     // pick up hidden inputs (required metadata inputs such as 'facets'
       
    37     // but also RangeWidgets)
       
    38     $form.find('input[type="hidden"]').each(function() {
       
    39         names.push(this.name);
       
    40         values.push(this.value);
       
    41     });
       
    42     // And / Or operators
       
    43     $form.find('select option[selected]').each(function() {
       
    44         names.push(this.parentNode.name);
       
    45         values.push(this.value);
       
    46     });
       
    47     return [names, values];
       
    48 }
       
    49 
       
    50 
       
    51 // XXX deprecate vidargs once TableView is gone
       
    52 function buildRQL(divid, vid, paginate, vidargs) {
       
    53     $(CubicWeb).trigger('facets-content-loading', [divid, vid, paginate, vidargs]);
       
    54     var $form = $('#' + divid + 'Form');
       
    55     var zipped = facetFormContent($form);
       
    56     zipped[0].push('facetargs');
       
    57     zipped[1].push(vidargs);
       
    58     var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_build_rql', null, zipped[0], zipped[1]));
       
    59     d.addCallback(function(result) {
       
    60         var rql = result[0];
       
    61         var $bkLink = $('#facetBkLink');
       
    62         if ($bkLink.length) {
       
    63             var bkPath = 'view?rql=' + encodeURIComponent(rql);
       
    64             if (vid) {
       
    65                 bkPath += '&vid=' + encodeURIComponent(vid);
       
    66             }
       
    67             var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
       
    68             $bkLink.attr('href', bkUrl);
       
    69         }
       
    70         var $focusLink = $('#focusLink');
       
    71         if ($focusLink.length) {
       
    72             var url = BASE_URL + 'view?rql=' + encodeURIComponent(rql);
       
    73             if (vid) {
       
    74                 url += '&vid=' + encodeURIComponent(vid);
       
    75             }
       
    76             $focusLink.attr('href', url);
       
    77         }
       
    78         var toupdate = result[1];
       
    79         var extraparams = vidargs;
       
    80         if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
       
    81         // copy some parameters
       
    82         // XXX cleanup vid/divid mess
       
    83         // if vid argument is specified , the one specified in form params will
       
    84         // be overriden by replacePageChunk
       
    85         copyParam(zipped, extraparams, 'vid');
       
    86         extraparams['divid'] = divid;
       
    87         copyParam(zipped, extraparams, 'divid');
       
    88         copyParam(zipped, extraparams, 'subvid'); // XXX deprecate once TableView is gone
       
    89         copyParam(zipped, extraparams, 'fromformfilter');
       
    90         // paginate used to know if the filter box is acting, in which case we
       
    91         // want to reload action box to match current selection (we don't want
       
    92         // this from a table filter)
       
    93         extraparams['rql'] = rql;
       
    94         if (vid) { // XXX see copyParam above. Need cleanup
       
    95             extraparams['vid'] = vid;
       
    96         }
       
    97         d = $('#' + divid).loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('view', extraparams),
       
    98                                      null, 'swap');
       
    99         d.addCallback(function() {
       
   100             // XXX rql/vid in extraparams
       
   101             $(CubicWeb).trigger('facets-content-loaded', [divid, rql, vid, extraparams]);
       
   102         });
       
   103         if (paginate) {
       
   104             // FIXME the edit box might not be displayed in which case we don't
       
   105             // know where to put the potential new one, just skip this case for
       
   106             // now
       
   107             var $node = $('#edit_box');
       
   108             if ($node.length) {
       
   109                 $node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
       
   110                     'rql': rql
       
   111                 },
       
   112                 'ctxcomponents', 'edit_box'), 'GET', 'swap');
       
   113             }
       
   114             $node = $('#breadcrumbs');
       
   115             if ($node.length) {
       
   116                 $node.loadxhtml(AJAX_BASE_URL, ajaxFuncArgs('render', {
       
   117                     'rql': rql
       
   118                 },
       
   119                 'ctxcomponents', 'breadcrumbs'), null, 'swap');
       
   120             }
       
   121         }
       
   122         var mainvar = null;
       
   123         var index = $.inArray('mainvar', zipped[0]);
       
   124         if (index > - 1) {
       
   125             mainvar = zipped[1][index];
       
   126         }
       
   127 
       
   128         var d = loadRemote(AJAX_BASE_URL, ajaxFuncArgs('filter_select_content', null, toupdate, rql, mainvar));
       
   129         d.addCallback(function(updateMap) {
       
   130             for (facetName in updateMap) {
       
   131                 var values = updateMap[facetName];
       
   132                 // XXX fine with jquery 1.6
       
   133                 //$form.find('div[cubicweb\\:facetName="' + facetName + '"] ~ div .facetCheckBox').each(function() {
       
   134                 $form.find('div').filter(function () {return $(this).attr('cubicweb:facetName') == facetName}).parent().find('.facetCheckBox').each(function() {
       
   135                     var value = this.getAttribute('cubicweb:value');
       
   136                     if ($.inArray(value, values) == -1) {
       
   137                         if (!$(this).hasClass('facetValueDisabled')) {
       
   138                             $(this).addClass('facetValueDisabled');
       
   139                         }
       
   140                     } else {
       
   141                         if ($(this).hasClass('facetValueDisabled')) {
       
   142                             $(this).removeClass('facetValueDisabled');
       
   143                         }
       
   144                     }
       
   145                 });
       
   146             }
       
   147         });
       
   148     });
       
   149 }
       
   150 
       
   151 
       
   152 function initFacetBoxEvents(root) {
       
   153     // facetargs : (divid, vid, paginate, extraargs)
       
   154     root = root || document;
       
   155     $(root).find('form').each(function() {
       
   156         var form = $(this);
       
   157         // NOTE: don't evaluate facetargs here but in callbacks since its value
       
   158         //       may changes and we must send its value when the callback is
       
   159         //       called, not when the page is initialized
       
   160         var facetargs = form.attr('cubicweb:facetargs');
       
   161         if (facetargs != undefined && !form.attr('cubicweb:initialized')) {
       
   162             form.attr('cubicweb:initialized', '1');
       
   163             var jsfacetargs = cw.evalJSON(form.attr('cubicweb:facetargs'));
       
   164             form.submit(function() {
       
   165                 buildRQL.apply(null, jsfacetargs);
       
   166                 return false;
       
   167             });
       
   168             var divid = jsfacetargs[0];
       
   169             if ($('#'+divid).length) {
       
   170                 var $loadingDiv = $(DIV({id:'facetLoading'},
       
   171                                         facetLoadingMsg));
       
   172                 $($('#'+divid).get(0).parentNode).append($loadingDiv);
       
   173             }
       
   174             form.find('div.facet').each(function() {
       
   175                 var $facet = $(this);
       
   176                 $facet.find('div.facetCheckBox').each(function(i) {
       
   177                     this.setAttribute('cubicweb:idx', i);
       
   178                 });
       
   179                 $facet.find('div.facetCheckBox').click(function() {
       
   180                     var $this = $(this);
       
   181                     // NOTE : add test on the facet operator (i.e. OR, AND)
       
   182                     // if ($this.hasClass('facetValueDisabled')){
       
   183                     //          return
       
   184                     // }
       
   185                     if ($this.hasClass('facetValueSelected')) {
       
   186                         facetCheckBoxUnselect($this);
       
   187                     } else {
       
   188                         facetCheckBoxSelect($this);
       
   189                     }
       
   190                     facetCheckBoxReorder($facet);
       
   191                     buildRQL.apply(null, jsfacetargs);
       
   192                 });
       
   193                 $facet.find('select.facetOperator').change(function() {
       
   194                     var nbselected = $facet.find('div.facetValueSelected').length;
       
   195                     if (nbselected >= 2) {
       
   196                         buildRQL.apply(null, jsfacetargs);
       
   197                     }
       
   198                 });
       
   199                 $facet.find('div.facetTitle.hideFacetBody').click(function() {
       
   200                     $facet.find('div.facetBody').toggleClass('hidden').toggleClass('opened');
       
   201                     $(this).toggleClass('opened');
       
   202 
       
   203                 });
       
   204 
       
   205             });
       
   206         }
       
   207     });
       
   208 }
       
   209 
       
   210 // facetCheckBoxSelect: select the given facet checkbox item (.facetValue
       
   211 // class)
       
   212 function facetCheckBoxSelect($item) {
       
   213     $item.addClass('facetValueSelected');
       
   214     $item.find('img').attr('src', SELECTED_IMG).attr('alt', (_("selected")));
       
   215 }
       
   216 
       
   217 // facetCheckBoxUnselect: unselect the given facet checkbox item (.facetValue
       
   218 // class)
       
   219 function facetCheckBoxUnselect($item) {
       
   220     $item.removeClass('facetValueSelected');
       
   221     $item.find('img').each(function(i) {
       
   222         if (this.getAttribute('cubicweb:unselimg')) {
       
   223             this.setAttribute('src', UNSELECTED_BORDER_IMG);
       
   224         }
       
   225         else {
       
   226             this.setAttribute('src', UNSELECTED_IMG);
       
   227         }
       
   228         this.setAttribute('alt', (_("not selected")));
       
   229     });
       
   230 }
       
   231 
       
   232 // facetCheckBoxReorder: reorder all items according to cubicweb:idx attribute
       
   233 function facetCheckBoxReorder($facet) {
       
   234     var sortfunc = function (a, b) {
       
   235         // convert from string to integer
       
   236         a = +a.getAttribute("cubicweb:idx");
       
   237         b = +b.getAttribute("cubicweb:idx");
       
   238         // compare
       
   239         if (a > b) {
       
   240             return 1;
       
   241         } else if (a < b) {
       
   242             return -1;
       
   243         } else {
       
   244             return 0;
       
   245         }
       
   246     };
       
   247     var $items = $facet.find('.facetValue.facetValueSelected')
       
   248     $items.sort(sortfunc);
       
   249     $facet.find('.facetBody').append($items);
       
   250     var $items = $facet.find('.facetValue:not(.facetValueSelected)')
       
   251     $items.sort(sortfunc);
       
   252     $facet.find('.facetBody').append($items);
       
   253     $facet.find('.facetBody').animate({scrollTop: 0}, '');
       
   254 }
       
   255 
       
   256 // trigger this function on document ready event if you provide some kind of
       
   257 // persistent search (eg crih)
       
   258 function reorderFacetsItems(root) {
       
   259     root = root || document;
       
   260     $(root).find('form').each(function() {
       
   261         var form = $(this);
       
   262         if (form.attr('cubicweb:facetargs')) {
       
   263             form.find('div.facet').each(function() {
       
   264                 var facet = $(this);
       
   265                 var lastSelected = null;
       
   266                 facet.find('div.facetCheckBox').each(function(i) {
       
   267                     var $this = $(this);
       
   268                     if ($this.hasClass('facetValueSelected')) {
       
   269                         if (lastSelected) {
       
   270                             lastSelected.after(this);
       
   271                         } else {
       
   272                             var parent = this.parentNode;
       
   273                             $(parent).prepend(this);
       
   274                         }
       
   275                         lastSelected = $this;
       
   276                     }
       
   277                 });
       
   278             });
       
   279         }
       
   280     });
       
   281 }
       
   282 
       
   283 // change css class of facets that have a value selected
       
   284 function updateFacetTitles() {
       
   285     $('.facet').each(function() {
       
   286         var $divTitle = $(this).find('.facetTitle');
       
   287         var facetSelected = $(this).find('.facetValueSelected');
       
   288         if (facetSelected.length) {
       
   289             $divTitle.addClass('facetTitleSelected');
       
   290         } else {
       
   291             $divTitle.removeClass('facetTitleSelected');
       
   292         }
       
   293     });
       
   294 }
       
   295 
       
   296 // we need to differenciate cases where initFacetBoxEvents is called with one
       
   297 // argument or without any argument. If we use `initFacetBoxEvents` as the
       
   298 // direct callback on the jQuery.ready event, jQuery will pass some argument of
       
   299 // his, so we use this small anonymous function instead.
       
   300 $(document).ready(function() {
       
   301     initFacetBoxEvents();
       
   302     $(cw).bind('facets-content-loaded', onFacetContentLoaded);
       
   303     $(cw).bind('facets-content-loading', onFacetFiltering);
       
   304     $(cw).bind('facets-content-loading', updateFacetTitles);
       
   305 });
       
   306 
       
   307 function showFacetLoading(parentid) {
       
   308     var loadingWidth = 200; // px
       
   309     var loadingHeight = 100; // px
       
   310     var $msg = $('#facetLoading');
       
   311     var $parent = $('#' + parentid);
       
   312     var leftPos = $parent.offset().left + ($parent.width() - loadingWidth) / 2;
       
   313     $parent.fadeTo('normal', 0.2);
       
   314     $msg.css('left', leftPos).show();
       
   315 }
       
   316 
       
   317 function onFacetFiltering(event, divid /* ... */) {
       
   318     showFacetLoading(divid);
       
   319 }
       
   320 
       
   321 function onFacetContentLoaded(event, divid, rql, vid, extraparams) {
       
   322     $('#facetLoading').hide();
       
   323 }
       
   324 
       
   325 $(document).ready(function () {
       
   326     if ($('div.facetBody').length) {
       
   327         var $loadingDiv = $(DIV({id:'facetLoading'},
       
   328                                 facetLoadingMsg));
       
   329         $('body').append($loadingDiv);
       
   330     }
       
   331 });