Customer Portal

Get Involved. Join the Conversation.

Topic

    Shiloh Madsen
    Answers/List and Reports: Filter passingAnswered
    Topic posted September 9, 2013 by Shiloh MadsenBronze Trophy: 5,000+ Points 
    1921 Views, 23 Comments
    Title:
    Answers/List and Reports: Filter passing
    Content:

    Sorry for what amounts to, essentially a fishing expedition, but I simply cannot find good documentation on what seems to be one of the more complicated aspects of CP functionality. I am working on a few widget extensions, one that presents a different form of product selector as a typeahead and I am having a headache of a time getting that to hook into the search answers life cycle. In addition to that, some other functionality I have coded surrounding primary product seems to actually be affecting my searches (primary product always gets filled in on a blank search) and I simply don't know why. 

    So basically what I am trying to understand is what actually happens when i click the search answers button. Where are the product, category and keyword filters being read from and how do I overrride those. Looking at the submit button itself was pretty nearly useless...its code is so minor that I know the heavy lifting is going on elsewhere. It seems that at some point work ends up at getReportData in ajaxRequest.php but there must be one or more stops in between. Would anyone out here have any more insight into what those extra stops are?

    Best Comment

    Patrick Walsh

    Hi Shiloh,

    The ProductCategorySearchFilter widget is probably what is modifying your search.  When you click the search button it is raising an event and all of the search aware widgets can feed in filters.  So the KeywordText widget is adding any keywords, the filters for product and category come from the ProductCategorySearchFilter widget, etc.  The KeywordText widget makes it easier to see what's going on here.  Here are the salient parts of the code from KeywordText:

    1. RightNow.Widgets.KeywordText = RightNow.SearchFilter.extend({

    We extend from the SearchFilter base.

    2. constructor: function() { ... this.searchSource().on("search", this._onGetFiltersRequest, this); ... }

    In the constructor, we register for the search event and call a handler, _onGetFiltersRequest.
     
    3. this._eo.filters.data = this.Y.Lang.trim(this._textElement.get("value"));
            this._searchedOn = this._eo.filters.data;
            return this._eo;
     
    Finally we setup some filters and return them in the _onGetFiltersRequest handler.  
     
    So knowing all of this, you should be able to either extend the product filter widget and modify it to do what you want or to create a widget that extends from RightNow.SearchFilter and to modify the filters when the search event is raised.
     
    Hope that helps!
     
    ..Patrick
     
     

     

    Comment

    • Patrick Walsh

      Hi Shiloh,

      The ProductCategorySearchFilter widget is probably what is modifying your search.  When you click the search button it is raising an event and all of the search aware widgets can feed in filters.  So the KeywordText widget is adding any keywords, the filters for product and category come from the ProductCategorySearchFilter widget, etc.  The KeywordText widget makes it easier to see what's going on here.  Here are the salient parts of the code from KeywordText:

      1. RightNow.Widgets.KeywordText = RightNow.SearchFilter.extend({

      We extend from the SearchFilter base.

      2. constructor: function() { ... this.searchSource().on("search", this._onGetFiltersRequest, this); ... }

      In the constructor, we register for the search event and call a handler, _onGetFiltersRequest.
       
      3. this._eo.filters.data = this.Y.Lang.trim(this._textElement.get("value"));
              this._searchedOn = this._eo.filters.data;
              return this._eo;
       
      Finally we setup some filters and return them in the _onGetFiltersRequest handler.  
       
      So knowing all of this, you should be able to either extend the product filter widget and modify it to do what you want or to create a widget that extends from RightNow.SearchFilter and to modify the filters when the search event is raised.
       
      Hope that helps!
       
      ..Patrick
       
       

       

    • Tracy Livengood

      To add a bit more detail to what Patrick has described:

      The majority of the heavy lifting for the client side search code is handled in the `SearchFilter.js` module. This module provides a handful of different events (more detail can be found in the JS documentation) which facilitate all of the synchronization required to perform a search on the client. These events are published to any widget which needs to be involved in the search process via the SearchFilter extension. The events can be invoked by other interested widgets, in this case the SearchButton widget, triggering a full filter collection (via the search event) and submission process (via the send event), which ultimately fires a request to the `getReportData` endpoint. 

      Hope that helps,

      Tracy L

    • Shiloh Madsen

      Hm. I appear to have worked myself into something of a place here. I made the mistake of extending RightNow.Widgets.ProductCategoryInput...which needless to say is NOT a report search filter and thus won't give me access to the RightNow.SearchFilter namespace and all of its functions. Is my only option here to recreate the widget from scratch extending a more appropriate widget or is there some way to do multiple inheritance and expose the functions of both superclasses to my widget?

    • Tracy Livengood

      Hey Shiloh, 

      I'm guessing you want to use the autocomplete prodcat widget in two different places, in forms and searches. If that's the case, you should be able to pull out the common autocomplete handling code into a separate JS module and create two separate custom widgets which use the shared code. The code can be included using the `addJavascriptInclude` function in the widget's controller. In the ProdcatSearchFilter custom widget, you can take advantage of the search events and in the ProdcatInput custom widget, the form submission events. 

      Hope that helps,

      Tracy L

    • Shiloh Madsen

      It does, but for now I'm going to push abstraction off to version 2. For right now I have the search instance of the widget up and running again. By up and running I mean the AC functionality is working. Now I am trying to parse through and understand how I have to return the eo to the search function in a way that it can use. As Patrick mentioned above, the keyword example is easy to understand as its just a single string filter submitted to the filters list. Product or category data, however, appears to be a JSON blob from what I'm reading...and without a clear view of what a good submission, i'm kinda stumbling about a bit. Figure i can turn back on the product picker and throw some logs to see what comes out the other side of that, then try to replicate it with my AC data. Will let you guys know how this goes, but thanks so much for the assist!

       

      P.S. I really hate working with the hierarchical menus :)

    • Shiloh Madsen

      Ok, so I thought I had the whole filtering thing nailed, but I'm hitting a second roadblock. So I have my typeahead passing its data to the filters request, and on a first search, things work fine. Where things break is on follow up searches. I suspect it has something to do with my logic for getFiltersRequest...which I sort of had to hack to get it to work. I'm thinking perhaps my hack was done poorly and that's why the follow up requests seem to have data not populated in the places its looking. Heres my getFiltersRequest code:

       

       _getFiltersRequest: function(type, args){
                  this.parent();
                  //If there are multiple instances of prodCatSF for one report, we need to sync them.
                  this._getFiltersRequest.lastInstance[this.data.attrs.filter_type] = this._getFiltersRequest.lastInstance[this.data.attrs.filter_type] || null;
                  this._getFiltersRequest.cachedWidgetHier = this._getFiltersRequest.cachedWidgetHier || {};
                  var filterName = (this._eo.filters.searchName || "") + this.data.attrs.report_id,
                      filterKey = filterName + this.baseDomID,
                      mostRecentFilterKey = filterName + this._getFiltersRequest.lastInstance[this.data.attrs.filter_type],
                      cachedHier = this._getFiltersRequest.cachedWidgetHier[mostRecentFilterKey];
                  this._eo.filters.data.reconstructData = [];
       
                  //The tree is built and a value is selected, construct the data array. Overriding this behavior to look for Autocomplete data
                  var autocomplete = this.Y.one('#'+this.instanceID+'_prodCatInput');
                  //console.dir(autocomplete._node.value);
       
       
                  if(autocomplete._node.value)
                  {
                      var acarr = autocomplete._node.value.split(',');
                      var label = acarr[1];
                      var value = this.data.js.selectedProduct;
       
                      this._eo.filters.data[0].push(value);
       
                      this._eo.filters.data.reconstructData.reverse();
                      if (cachedHier && filterKey !== mostRecentFilterKey) {
                          this._eo.filters.data = cachedHier;
                      }
                      else {
                          this._getFiltersRequest.cachedWidgetHier[filterKey] = RightNow.Lang.cloneObject(this._eo.filters.data);
                      }
                  }
                  //Widget isn't completely initialized, but may still have a cached value of another widget on the page
                  else if(cachedHier)
                  {
                      this._eo.filters.data = cachedHier;
                      this._eo.data.value = cachedHier[0][0];
                  }
                  //Widget has nothing, just send empty data.
                  else
                  {
                      this._eo.filters.data[0] = [];
                      this._eo.data.value = 0;
                  }
                  this._lastSearchValue = this._eo.filters.data[0].slice(0);
       
                  return this._eo;
              }
       
      The problem comes on follow up searches. When I do a search on the next page it bombs here:
       
       filterContainer.one('#' + templateData.filterData[templateData.filterData.length - 1].linkID).set('href', 'javascript:void(0)'); 
       
      cannot read property linkid of undefined. It would seem that the templateData variable is null here? I'm not entirely certain why...but hoping someone might be able to illuminate this problem a bit...everything ive tried just breaks it worse. 
    • Shiloh Madsen

      I seem to recall in the past there was a way to force non -ajax report loading. I tried simply putting in a report page url param in the search button, but if its the same, it still tries to render via ajax. As a stopgap...making the answer function with a page transition would likely fix the problem while looking at a better solution. Anyone remember/know how to do that?

    • Ernie Turner

      For where it's bombing out on the templateData.filterData line, where is that code at? Is that in your custom widget, or is that in one of the standard widgets? 

      As for forcing searches to always be on the same page, I don't think there's an easy way to do this as far as I know.

    • Shiloh Madsen

      That code is out of box. While I can override it from my extended widget (I'm extending ProductCategorySearchFilter), when I did, everything I did to try to fix it made things worse. The function in question is onReportResponse

    • Jeremy Watson

      Shiloh,

      So, the templateData call is coming from the DisplaySearchFilters widget, which is expecting prod/cat filters to have a reconstructData field to supply the prod/cat labels at each level of the hierarchy.

      The standard ProductCategorySearchFilter widget has this code in _getFiltersRequest (some interleaved lines of code removed for brevity):

              this._eo.filters.data.reconstructData = [];

              //The tree is built and a value is selected, construct the data array.
              if(this._tree && this._currentIndex && this._currentIndex !== this._noValueNodeIndex)
              {
                  var currentNode = this._tree.getNodeByIndex(this._currentIndex),
                      hierValues,
                      level;

                  while(currentNode && !currentNode.isRoot())
                  {
                      level = currentNode.depth + 1;
                      hierValues = this._eo.filters.data[0].slice(0, level).join(",");
                      this._eo.filters.data.reconstructData.push({"level" : level, "label" : currentNode.label, "hierList" : hierValues});
                      currentNode = currentNode.parent;
                  }
                  this._eo.filters.data.reconstructData.reverse();
              }

      You will probably need something similar to create the reconstructData field with the autocomplete functionality.

      James

    • Shiloh Madsen

      You rock man! I had to tweak things a bit due to not actually having a node ID in this instance, but once I got it working right, everything seems to be working fine...at leat at unit test. We will see what shakes loose after this :)

    • Shiloh Madsen

      Ok, its starting to feel like touching the search filters is a land mine scenario :) Found a new issue that I'm not sure where to begin fixing. So everything works fine on initial search, but when you get your search results and click any of the pagination links, the product selected is lost. It seems to keep the keyword, but when I just search on a product (using the typeahead widget spoken of profusely above) the product filter seems to drop off the page URL and drop out of the search filters widget I have on the page. Does the pagination widget have a listener that im not feeding and/or is it firing an event that my widget isn't listening for?

    • Shiloh Madsen

      Not sure its related, but I'm also noticing that if I search on a product, then search the same product again on the results page, I get told there are no results...might be a completely different problem, or could be related to the above. 

    • Shiloh Madsen
      It might help to include the _getFiltersRequest function:
      

       

      _getFiltersRequest: function(type, args){
                  this.parent();
                  //If there are multiple instances of prodCatSF for one report, we need to sync them.
                  this._getFiltersRequest.lastInstance[this.data.attrs.filter_type] = this._getFiltersRequest.lastInstance[this.data.attrs.filter_type] || null;
                  this._getFiltersRequest.cachedWidgetHier = this._getFiltersRequest.cachedWidgetHier || {};
                  var filterName = (this._eo.filters.searchName || "") + this.data.attrs.report_id,
                      filterKey = filterName + this.baseDomID,
                      mostRecentFilterKey = filterName + this._getFiltersRequest.lastInstance[this.data.attrs.filter_type],
                      cachedHier = this._getFiltersRequest.cachedWidgetHier[mostRecentFilterKey];
                  this._eo.filters.data.reconstructData = [];
       
                  //The tree is built and a value is selected, construct the data array. Overriding this behavior to look for Autocomplete data
                  var autocomplete = this.Y.one('#'+this.instanceID+'_prodCatInput');
                  //console.dir(autocomplete._node.value);
       
       
                  if(autocomplete._node.value)
                  {
                      var acarr = autocomplete._node.value.split(',');
       
                      //Only perform this if the array has more than one item
                      if (acarr.length > 1){
                          var label = acarr[1];
                          var value = acarr[0];
                      }else{
                      //Otherwise get the value programmatically.
                          var id = null;
                          //Get the ID of a product based on Name
                          this.Y.Object.each(this.data.js.fullTree, function(item){
                              if (item.subItems){
                                  for (var i=0; i < item.subItems.length; i++){
                                      if (item.subItems[i].label == acarr[0]){
                                          id = item.subItems[i].id;
                                      }
                                  }
                              }
                          }, this);
       
                          acarr.push(id);
                          var label = acarr[0];
                          var value = acarr[1];
                      }
                      this._eo.filters.data[0].push(value);
       
                      this._eo.filters.data.reconstructData.reverse();
                      if (cachedHier && filterKey !== mostRecentFilterKey) {
                          this._eo.filters.data = cachedHier;
                      }
                      else {
                          this._getFiltersRequest.cachedWidgetHier[filterKey] = RightNow.Lang.cloneObject(this._eo.filters.data);
                      }
                  }
                  //Widget isn't completely initialized, but may still have a cached value of another widget on the page
                  else if(cachedHier)
                  {
                      this._eo.filters.data = cachedHier;
                      this._eo.data.value = cachedHier[0][0];
                  }
                  //Widget has nothing, just send empty data.
                  else
                  {
                      this._eo.filters.data[0] = [];
                      this._eo.data.value = 0;
                  }
                  this._lastSearchValue = this._eo.filters.data[0].slice(0);
       
                  //Attempting to build ReconstructData array
                  this._eo.filters.data.reconstructData = [];
       
                  //The tree is built and a value is selected, construct the data array.
                  if(this._tree && this._currentIndex && this._currentIndex !== this._noValueNodeIndex)
                  {
       
                      var currentNode = this._tree.getNodeByProperty('label', label),
                          hierValues,
                          level;
       
                      while(currentNode && !currentNode.isRoot())
                      {
                          level = currentNode.depth + 1;
                          hierValues = this._eo.filters.data[0].slice(0, level).join(",");
                          this._eo.filters.data.reconstructData.push({"level" : level, "label" : currentNode.label, "hierList" : hierValues});
                          currentNode = currentNode.parent;
                      }
                      this._eo.filters.data.reconstructData.reverse();
                  }
       
                  return this._eo;
              }
    • luke davison

      Hey Shiloh,

      Can you please provide a sample of form data from the different AJAX requests that are experiencing the issue? If you're using Chrome, you can find them in the Network tab of the Developer Tools. I'm assuming the request is for /ci/ajaxRequest/getReportData, if you click on the request you can find the form data in the Headers.

      Thanks,
      Luke.