Hi. My goal is to AJAXify the facet blocks. This would entail clicking on a link in a facet block and having the main content area refresh via AJAX with no reload of the main page. The pager is already AJAX-enabled simply by turning it on in the view.

I'm not sure how to move ahead with this, but I did a pretty thorough test with the apachesolr_ajax module, including directions on setting it up.
http://drupal.org/project/apachesolr_ajax
http://drupal.org/node/704582#comment-2685518

They are using this library:
http://evolvingweb.github.com/ajax-solr

I'd appreciate any advice or suggestions from a views ninja on the best way to approach this or perhaps another module somewhere that implements something similar for views.

CommentFileSizeAuthor
#4 734882_4.patch5.56 KBrjbrown99
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Scott Reynolds’s picture

Interesting though doubt you need that at all.

Just need a Drupal.behavior that selects all the facet links on the page and uses the Views AJAX library on click. Should be really really straight forward.

edit: well bad news is views ajax is still a bit of a mess. Still needs to be ported to CTools. But still should have been straight forward.

rjbrown99’s picture

Thanks Scott - I have been coming to the same conclusion after searching around. This could actually be pretty interesting as it would be MUCH easier to implement an AJAX-based facet using apachesolr_views over the apachesolr_ajax module (and would use less Javascript code and third-party libraries to do it.)

I have been reading a bunch about ajax_view.js and it seems like that's the way to go.

My main goal is to allow for a replacement of my decimal facet (such as ranges of prices $100 to $200) with a Javascript slider that will submit back the changes in numeric values via ajax. Similar to this:
http://blog.egorkhmelev.com/2009/11/jquery-slider-safari-style/

rjbrown99’s picture

So here's where I am with this.

Looking at views, it seemed like they were using the hook_preprocess_views_view() to insert the javascript. I more or less copied their approach into apachesolr_views with something that looks like this:

function apachesolr_views_preprocess_views_view(&$vars) {
  $view = $vars['view'];
  static $dom_id = 1;
  $vars['dom_id'] = !empty($view->dom_id) ? $view->dom_id : $dom_id++;

  if ($view->use_ajax) {
    $settings = array(
      'views' => array(
        'ajax_path' => url('views/ajax'),
        'ajaxViews' => array(
          array(
            'view_name' => $view->name,
            'view_display_id' => $view->current_display,
            'view_args' => implode('/', $view->args),
            'view_path' => $_GET['q'],
            'view_base_path' => $view->get_path(),
            'view_dom_id' => $vars['dom_id'],
            'pager_element' => $view->query->pager->get_pager_id(),
          ),
        ),
      ),
    );

    drupal_add_js($settings, 'setting');
    drupal_add_js(drupal_get_path('module', 'apachesolr_views') . "/js/ajax_facet.js");
  }
}

This seems to work and loads up my ajax_facet.js script. This script started out to be very similar to the ajax_view.js from the views module, except I am looking for the CSS classes a.apachesolr-facet and a.apachesolr-unclick.

It's not quite working at the moment... unfaceting/faceting is causing the view to reload in the block and not the main content area. It was also choking when there were two views present (the main Apachesolr Views view + facet blocks, and one additional block that comes from a second view.) I think that was resolved by adding the $vars towards the top of that code.

I'm mostly posting this in case you think I'm headed down the wrong path.

rjbrown99’s picture

Status: Active » Needs work
FileSize
5.56 KB

OK I figured it was worth posting a patch. Here's what this is.

1) The apachesolr_views module has a new addition of hook_preprocess_views_view(). This is very similar to how the views module sets up the view and Javascript. It's in views inside of theme/theme.inc as template_preprocess_views_view if anyone is interested in the source/inspiration for this code.

2) The new ajax_facet.js is derived from the Views module's js/ajax_view.js module. I also pulled in a views argument parser function that was originally in Views js/base.js because the if statement was failing when using my arguments (which are correct). I guess this is because the arguments for apachesolr_views look a little different.

Right now, it seems to work OK in that it reloads the view content area based on the Javascript request. Things that need work:

  • It does not yet update the facet blocks to show that you are now faceted on a different link. I hadn't thought about that, and it's an interesting problem.
  • It does not update the URL in any way, so bookmarking the page won't work. The same is true of the back button. This is where the apachesolr_ajax module used the Yahoo YUI framework.
  • My theme also gets messed up. I thought it was because of the throbber, but it's the Javascript. The AJAX response comes with all of the site's CSS and re-applies it to the area upon faceting. Not sure why it's doing that, but basically I run into css issues based on ordering. It seems to do this normally without this module, which seems redundant. I'm really stuck on this one as to how to have views NOT send back CSS with its reply.

Thoughts, suggestions, and feedback are welcome!

Scott Reynolds’s picture

Ya thats going to be trouble. The simple solution would be to add another function on a.apachesolr-facet.click() that would reload the blocks (i.e. call $module_block('view', $delta)). The problem with that is it would be a seperate request so apachesolr_has_searched() would return false, thus not rendering a block at all.

So essentially, the blocks have to come back with the View result.

rjbrown99’s picture

Thanks for the reply and assistance Scott.

I have another thing I don't like about the code from views that I based this on - it iterates through all of the links on the page looking for the facet links. In my case, I could have up to a few hundred facet links (not all are visible to the user without a different javascript show/hide toggle.) The initial page load time slows down quite a bit based on the javascript search for all of the links.

I'm wondering if it's better to only look for the 'destination'

where the content will be replaced during the initial load. Then we hook any click action on the page, and when a link is clicked evaluate that specific link to see if it has either of our apachesolr-facet/apachesolr-unclick classes. If so, intercept them and process them as AJAX requests. That would get out of the business of searching out all of the facet links.

Having the blocks come back with the View result seems like a challenge. I'm still stuck on trying to figure out a way to make sure the CSS doesn't come back with the View result.

I'm also thinking about how to handle the bookmarking / URL updating / back button issue. I'm not fond of including more third-party libraries and getting into the Yahoo YUI world like apachesolr_ajax did. I'm sure there is some type of jQuery module to do this.

rjbrown99’s picture

OK I think I'm starting to close in on an approach, almost completely ignoring the patch I submitted above.

Assumption: We are only trying to handle a single click/facet at a time. I.E. we are not planning to allow the user to click two facets and then 'submit' both at the same time using a button.

Implementation:

1) Search the page for the correct .view-dom-id-X and remember this DIV in a variable. This will be the div where content will be refreshed after the AJAX call completes.

2) Bind an event handler to the click event at the top level. This would be a global click handler for any click on the page.

3) In the event something is clicked, evaluate the element to determine if it has the class of a.apachesolr-facet or a.apachesolr-unclick. If it has one of those two, proceed down the ajax rabbit hole. If not, release it and handle it as a normal browser click. This gets out of the business of iterating over the entire webpage when loading all of the facets since that requires looping over the DOM using classes which is not fun.

4) Since we now know we are dealing with a facet link, traverse upwards with parent/prevUntil or something similar until we wind up with an id=block-whatever. This will tell us the ID of the facet block we are dealing with.

5) To recap: at the moment we know the .view-dom-id-X where the new content will go, we know a link was clicked, we know it was a facet/unfacet link, and we know the block the facet link came from.

6) Implement a new apachesolr_views_ajax_data_alter(), which is a hook that is called from the bottom of the views_ajax() function inside of the Views module's includes/ajax.inc. This should allow us to extend the ajax response data before it comes back. In our case, we would use this to load the facet block we need to change/update.
Here's an implementation of this hook elsewhere: http://drupal.org/project/ajax_load
(Side note: that random CSS that was being reloaded probably is coming from this module since I have it enabled for a different reason.)

7) Continuing from step #5, we now bundle up the viewRequest and send it along to the views module. It grabs the new updated views content, grabs the updated facet block, and returns both of these to the calling Javascript.

8) Deal with updating both the view area as well as the block.

Does that sound like it would reasonably solve the block handling issue? If so I'll get to actually doing it.

Scott Reynolds’s picture

2) Bind an event handler to the click event at the top level. This would be a global click handler for any click on the page.

3) In the event something is clicked, evaluate the element to determine if it has the class of a.apachesolr-facet or a.apachesolr-unclick. If it has one of those two, proceed down the ajax rabbit hole. If not, release it and handle it as a normal browser click. This gets out of the business of iterating over the entire webpage when loading all of the facets since that requires looping over the DOM using classes which is not fun.

This is not a good idea, its slower, and will get in the way of other modules click() handlers.

The reason is slower is because of the way browsers implement this. So what happens is the browser keeps a map table of all class -> elements and ids -> elements. So what that means is searching for a class name is a shortcut. Your assuming that the Javascript iterates over the entire page, that is an VERY false assumption. The page has already been iterated over and shortcuts have been made.

Dealing with multiple click handlers generally isn't a hard thing except when one click handler tells it to do execute its default action and the other click handlers are trying to tell it not do execute its default action.

Short of that sounds like it would work.

rjbrown99’s picture

Thanks for the explanation of the click handler. I had a few console.log()s in there and the output led me to believe it was an iterative process.

There's one other design thought - ALL of the facet blocks are going to have to be updated on the response, not just the one that was clicked. The other blocks have facet URL links that would otherwise not be updated and would point to the link before the user just click/faceted. In that case clicking on a different URL would effectively 'unfact' your most recent action.

I adopted the module_invoke() block detection method used in apachesolr_ajax, which is to list all of the solr modules and their blocks in PHP, stick them in the $settings array, and pass all of that to the javascript. That way we won't have to do any searching for blocks that might need updating.

I'm going to see if the jQuery Hashchange Event plugin will work as an option to handle bookmarking/back button, even though they state it was only tested with jQuery 1.3 and above: http://benalman.com/projects/jquery-hashchange-plugin

It's just the extracted bits that handle those events split out from the jQuery BBQ library. BBQ is in Drupal 7 core so hashchange would more closely align the code against what might be useful in the future.

dpalmer’s picture

Subscribing as I would very much like to "ajaxify" my faceted blocks :)

rjbrown99’s picture

#10: any interest in helping with development? I have not had time but will likely pick this up in another few weeks.

Equinger’s picture

subscribe

torgosPizza’s picture

Subscribing. Would also love to see this as an option ('use ajax').

ollynevard’s picture

Have you made any progress on this? I can help with development if you haven't had the time.

rjbrown99’s picture

I have not, but it's on my to-do list but likely in another few weeks to a month or two. If you have time to help I'd be happy to collaborate.

My latest thinking was to require jquery_update (to get to 1.3.2) and then use the full jQuery BBQ for the on-page stuff. Again my reason behind that design is because jQuery BBQ has been included by default in Drupal 7 so I foresee it as a viable upgrade path. I ran into some walls with the hashchange as it wasn't full-featured enough and I was having to manually re-implement parts of BBQ.

Per #1 maybe there's also a possible way to leverage ctools but I haven't developed around that module so I can't speak to what's there for use.

dabeast’s picture

Very interested in this cool functionality. Subscribing.

rjbrown99’s picture

For the moment I stopped working on it, as I started using this:
http://drupal.org/project/views_infinite_scroll

It's not full Ajax faceting, but it does get rid of the pager and allows for an infinite scroll for solr results. It should work out of the box with apachesolr_views.

yaelsho’s picture

Issue summary: View changes

Hello,
Did anyone succeed to AJAXify the facets on Drupal 7?
I'm looking into it for quite a while with no success.
Thanks, Yael