Upon change in exposed filters, refresh node listing via AJAX

nedjo - February 4, 2007 - 23:25
Project:Views
Version:6.x-2.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:nedjo
Status:closed
Description

Attached patch implements AJAX loading for views exposed filters.

When users submit an exposed filter, the content is new view is loaded through AJAX. The view is modified to return only the view portion (not the header or the exposed filter, which will already be on the page).

If a view has a pager, clicking the pager links also loads via AJAX.

I did only very basic testing. Needs testing in different situations. Issues will arise when loading views that require onload javascript events. Maybe, if there's interest in this behaviour, it should be a setting for an exposed filters as not all filters will work properly in javascript.

AttachmentSize
views-ajax.patch5.2 KB

#1

nedjo - February 4, 2007 - 23:27

Forgot to mention, this depends on a function in my Javascript Tools module so will only work if jstools.module is enabled. The function it uses, though, is not large and could be copied into the activeviews.js file to avoid this dependency.

#2

moshe weitzman - February 5, 2007 - 00:15

subscribing. sounds pretty neat.

#3

lilou - February 5, 2007 - 01:46

subscribe.

#4

appel - February 8, 2007 - 14:16

Subscribing!

#5

nedjo - February 8, 2007 - 16:52

Here's an improved patch, mainly tweaks to the js. I've added a drupal_add_js() call I'd missed for progress.js.

Patch is on the 5.x dev branch of Views but applies also to HEAD. Use with the 5.x dev version of jstools, http://drupal.org/node/113162.

I've put up a demo at http://islandnet.com/~nedjo/nicaragua/. To try the pager, select all three content types and scroll to the bottom of the page.

AttachmentSize
views-ajax-filters.patch5.57 KB

#6

Tobias Maier - February 8, 2007 - 18:30

wohoo nedjo, this is more than great!

I tried your test site
what I think is missing is some message if there was no result found.
In views we can define a message, which gets shown if we don't have any result. Does your patch send this to the client in this specific case?

#7

nedjo - February 8, 2007 - 19:01

I just hadn't defined a message. It appears now. To trigger it on the demo, you can select "Is None Of" and select all three content types.

This approach could easily be extended to enable loading of views in place, e.g., click on a link and the link is replaced by the view, or the view is loaded into a designated page element.

The main remaining work here looks to be handling cases where javascript errors will occur on ajax-loaded views, e.g., gmap views. This is part of a more general issue--how do we reattach behaviours to AJAX-loaded content? How should we handle that? Do we want to add a setting to each view to enable/disable loading by ajax? A drawback is that that would depend on user knowledge and trial and error.

#8

nedjo - February 11, 2007 - 02:06
Status:patch (code needs review)» patch (code needs work)

Two things bother me about the approach I've sketched in.

1. In general, I like to see the ability to invoke existing methods for AJAX, rather than the methods themselves having to say 'is this AJAX'? So, in that sense, adding an internal test as I've done in views_view_page() seems a bit clunky.

2. I don't like introducing extra logic into theme functions, so, similarly, my 'is this AJAX' test in theme_views_view() seems suboptimal.

I'm going to have a crack at implementing this in a contrib module - dynamicload, part of jstools - to see if this can be implemented cleanly without patching views. At the least, this will provide a basis for comparison.

#9

mfredrickson - March 8, 2007 - 18:05

I just submitted a patch to refactor out the handling of exposed arguments, so that you can pass the values into views_build_view, instead of relying on the $_GET query string.

You might find it helpful in your work on this:

http://drupal.org/node/125973

#10

lunas - March 24, 2007 - 03:39

This no longer works. When you hit submit, it loads your entire page in the main content area where it used to just show the results. As such it looks like there are two versions of the site on the page, one cramped into the main content area and the outside one. You can see it on your demo site listed above as well. Any idea why? I liked this functionality and it worked until I updated a number of modules today. Jstools was one of them - not sure if that had an impact. Views was not upgraded.

#11

markhope - April 17, 2007 - 17:03

subscribing - demo looks interesting!

#12

JamesHayton - April 30, 2007 - 09:05

Subscribing

#13

noahb - May 28, 2007 - 01:41

tracking...

#14

csc4 - June 7, 2007 - 23:59

subscribing

#15

mrgoltra - June 8, 2007 - 18:03

subs-track

#16

Wim Leers - June 8, 2007 - 18:23

Subscribing.

#17

momper - June 11, 2007 - 09:26

Subscribing

#18

txcrew - July 13, 2007 - 02:47

Tracking.

I was able to add and run a successful test on Drupal 5.1. However, the pagers didn't work quite right.

txcrew

#19

Prodigy - July 26, 2007 - 01:07

What about loading sorts through ajax? Same logic? What would be different?

#20

nedjo - August 4, 2007 - 04:13

The Dynamic Views module looks to include this functionality, http://drupal.org/project/dynamic_views.

#21

Wim Leers - August 4, 2007 - 07:33

@nedjo: not quite. It completely change the exposed filters (makes an ul out of every exposed filter, each choice of the exposed filter becomes a li), which is not desired at many places.

#22

.-_-. - August 7, 2007 - 10:09
Status:patch (code needs work)» patch (code needs review)

i have tried this technique [patch] and also AjaxSubmit in the jstools modules pack [are they the same?] and for me both produce the same error as mentioned in #10 , the header, filters and potential results are repeated in the body with no results, very wierd, so at the moment i can't find a working way of submitting a form " a views page with exposed filters and submit button" using ajax, i really need to be able to produce the results with out refreshing the filters part of the page.
any other leads to get Ajax to submit a views form?
will also mention this in jstools issue queue.

#23

rickvug - August 9, 2007 - 21:46

subscribing

#24

palik - November 22, 2007 - 09:43

subscribing - very interesting and cool feature :)

#25

baja_tz - February 21, 2008 - 16:20

subscribing

#26

gellpak - February 25, 2008 - 05:40

Subscribe

#27

merlinofchaos - March 11, 2008 - 00:15

Would anybody be interested in working on this for Views 2?

In general I have the ajax tools necessary to do it; but there are several challenges:

1) exposed filters may not always appear with the view. Therefore there needs to be some way to activate this.
2) We need a callback which includes all of the information necessary to recreate the view. That means the view name, the display ID and the arguments that were used to form it.

Hm. I guess that's all I can think of.

#28

nedjo - March 12, 2008 - 04:58
Status:patch (code needs review)» active

I'm in. I'll need to study the Views 2 code to begin to understand what's needed. Maybe we could connect and you could give me a quick primer on what's in place and what more is needed.

#29

merlinofchaos - March 12, 2008 - 05:18

I'd love to. Look me up on IRC; you know where I'll be =)

#30

nedjo - March 18, 2008 - 18:47
Status:active» patch (code needs review)

Here's a basic patch to get this rolling.

Earl and I discussed the problem and concluded we don't need special handling for exposed filters, but rather a generic way to load a view, any view, via AJAX.

Details of the patch:

* Introduce new .js file, ajaxView.js, with simple method for loading views via AJAX. Add behavior that attaches to exposed filters. Maybe this file should actually be ajax.js and the existing ajax.js be renamed e.g. ajaxAdmin.js, for use only on admin (views_ui) pages.
* Minor changes to existing .js files, e.g., need to test for UI tabs plugin before calling its method.
* Introduce test into views_page() to see if we're loading via AJAX and respond accordingly.

This last point is the one that needs the most examination. Maybe we actually need a new callback just for AJAX view requests. To determine whether this is an AJAX request I used a header sent by jQuery--one of many ways we could do this.

There's lots that we could look at adding here, e.g., visual cues about the request status, but let's look first at the basic implementation. What's good about the appoach? What needs changing?

AttachmentSize
views-ajax.diff4.72 KB

#31

merlinofchaos - March 18, 2008 - 19:01

I definitely feel we need a specific callback for ajax; otherwise, we're limiting this feature to just views with the 'page' display, but this would be very very useful for block displays. In fact, I could then relax the restrictions on the block pager. We could then update the use_pager setting from 'yes/no' to 'no/simple/ajax', and then the 'block' display can remove 'simple' from the list of choices. And it can turn on exposed filters; and we can turn on the exposed filter settings to do similar. That's probably phase 2 or 3, of course. We will also want a similar setting in the 'table' style for clicksorting.

For the same reason, we can't use $view->execute_display() -- because that returns data that is specific to that display type. For page, it works out just fine because that just returns raw html, but $block returns an array with 'subject' and 'content' which obviously won't display properly in this situation. ->preview(), however, will always just return HTML. Also, ->execute_display() will also try to do the other things specific to that type of display; for example, setting the breadcrumb, setting the page title, etc. These things should not be performed on an AJAX callback.

I'm ok with renaming the existing ajax.js to admin_ajax.js -- but for my filenames I'm sticking with underscores, not camelcase. We can then use ajax.js for this functionality. That is sensible.

Overall I'm happy with this direction. Thank you very much for working on this; I think it will be an awesome feature and people will really love what it gives them.

#32

moshe weitzman - March 18, 2008 - 19:50

Looks reasonable to me. Perhaps you could add URL where someone like me could learn more regarding this comment "// See if we have any settings to extend. Do this first so that behaviors can access the new settings easily."

If we wanted to research a bit more, the Services module already will provide a View in JSON and other formats. Maybe a chance to unify some code here.

#33

nedjo - March 18, 2008 - 22:56

Okay, here's a new patch.

We use an AJAX-specific callback, controlling access through the regular test for $view->access. Is this right?

We need a way to determine, from within the view output, the view name and display_id. We could pass these in Drupal.settings, but that in itself doesn't help us link the data with the content on the page. Instead I've added two classes, view-name-viewname and view-display-id-displayid, where viewname is the view name and displayid the display id. We can't reuse the existing view-viewname class because it's converted for CSS use (underscores replaced with dashes). This code could probably be done more efficiently. It shd also be in a distinct method, as - if we take this approach - we're going to need to determine the name and display in all cases where we're ajax-processing a view, e.g., for handling paging links.

I haven't changed the file names (ajax.js to ajax_admin.js) because it made the patch too hard to read.

Things I don't yet understand:

1. How can we pass arguments to the view? I guess we could pass these in as a request variable?

$args = $_REQUEST['args'];

2. How can we handle $_GET['q'] for paging? See // TODO comment in views_ajax() in views.module. We could reset $_GET['q'], but how do we know what it's supposed to be?

3. How do we determine the display_id of the view to pass it to the theming layer? See // TODO comment in theme/theme.inc.

Once we have these questions straightened out, we could look at handling paging links--it shd be relatively easy to point them to the same handler.

AttachmentSize
views-ajax.patch7.56 KB

#34

nedjo - March 19, 2008 - 05:28

Here's a fuller version of the patch, addressing the problem of how to load dependent data along with content (which is what the settings data handling Moshe referred to is for).

Whenever we're loading new content via AJAX, there is the potential that the content may need any of four supporting data pieces:

* css files that weren't loaded in the original page load
* js files that weren't loaded in the original page load
* new js settings data
* inline scripts

For example, a view might not show a particular piece of content (a map) the first time it's loaded, but after filtering this piece of content is shown, and its display requires a css file, a .js file and associated behaviors, and data passed to the Drupal.settings object.

New in this version of the patch (and not fully tested):

1. views_ajax_data() in views.module, assembles JS and CSS data for return with the JSON object.
2. Drupal.Views.Ajax.loadFiles and Drupal.Views.Ajax.loadComplete in ajax_views.js, merges in new settings data and loads any new JS and CSS files.

Along with the content, data are passed to the browser indicating the JS and CSS files and the JS settings data to be loaded.

I think the JS loading will break under JS aggregation, because of the possibility that existing methods are overwritten when new aggregated versions of the same files are pulled in. Not sure what to do about this.... Maybe we have to turn off the whole AJAX view loading JS is enabled.

This version of the patch introduces additional complexity and would some focused testing. I think it's needed, though, if we're going to be loading content that we don't know the associated data needs of in advance. The alternative, I guess, would be to provide a UI selector for admins to be able to turn AJAX loading off for a particular view if they tested and found it broke, but that seems like a poor fallback.

AttachmentSize
views-ajax.patch9.97 KB

#35

merlinofchaos - March 19, 2008 - 15:58

Just a quick update: The correct display ID will be in $view->current_display so that's easy to acquire.

I'm a little concerned about the complexity required to add a bunch of new js and css later. I realize that it's something that can happen, but it seems to me to be a relatively rare operation.

1) ajax loading won't be required on views, it will be strictly optional
2) something viewed via node_load is much more likely to use additional js/css than something that's just fields

It seems like we might be adding complexity to prepare for an edge case that we just don't have to. If there's a problem, we can always instruct them to turn off the ajax paging and use regular paging.

I do have a method of getting additional Drupal.settings into the system already, but it's not terribly robust. In fact, it's the lack of robustness in that system that has me worried that we might cause more harm than good with this; doing new javascript in an ajax load is very tricky.

#36

nedjo - March 19, 2008 - 18:48

Yes, it would be easier to leave out the complexities of loading the JS and CSS data. I'll work on a new patch without this. The problem of loading associated data is a general one anyway and maybe belongs in its own module.

Here's a possible approach. We pass the AJAX response data through drupal_alter() before returning it to the client:

<?php
  drupal_alter
('ajax_data', $object);
?>

An external module could return data on required JS and CSS and also specify a JS callback for these data to be sent to. What do you think?

Forgot to mention that in the latest version of the patch I introduced path handling. We pass the page's path in the settings array then pass that back as part of the AJAX request and set $_GET['q'] to the path. I'm vaguely thinking that may help with paging of results. There's the related question of feeding path-based arguments to the view. Do we need to extract these and explicitly send them to the $view->preview call, or will they just be recognized from the $_GET['q'] value?

#37

nedjo - March 21, 2008 - 05:04

Here are two new versions.

In the first patch, views-ajax-plain.patch, we simply strip out the JS and CSS handling.

In the second, we strip it out but pass the AJAX data through drupal_alter() before sending it to the client. As well as data on CSS and JS files and settings, modules implementing hook_ajax_data_alter() can specify one or more JavaScript callbacks to be called and fed the resulting data object. I've attached a draft ajax_load module that implements the hook. The idea is that we don't implement any special JS/CSS handling in Views but leave this open for other modules.

Both patches now read in the display ID from $view->current_display.

AttachmentSize
ajax_load.tar_.gz1.37 KB
views-ajax-alter.patch8.13 KB
views-ajax-plain.patch7.75 KB

#38

merlinofchaos - March 21, 2008 - 15:45

Looks like the only real problem we have left here is that $_GET['q'] isn't enough to get the arguments for the view. They need to be passed into $view->preview() as an array in the 2nd argument.

We've got 2 choices:

1) We can store the arguments somewhere on generate to make it easy to for the js to find them and pass them through
2) We can try to parse them out of the path.

I don't like 2. Not all views even have a path; but they may still have arguments.

I believe I'm in favor of the _alter hook. alter hooks are good in general, and this keeps the door open for doing extra stuff.

#39

nedjo - March 21, 2008 - 17:05

Thanks for the further review.

For passing the args, I agree that passing them to the client to be passed with the AJAX request seems to be the better option. I guess we need to send them in the Drupal.settings data. I'll look for the right place to do so. In other places IIRC settings data are passed for a single view (since, in admin, only one view is being handled at a time). Here, I assume, we'll need multiple views (since more than one can be displayed on a page) identified by a combination of view name and display_id.

As the patch stands, AJAX loading for exposed filters isn't optional--it applies to all exposed filters. To make it optional for specific views, we would need to add a view setting? If so, I guess we should think ahead to other AJAX loading, particularly paging. Do we want a "load this exposed filter through AJAX" setting, or is what we want a more general "load this view via AJAX" setting that would apply to exposed filters, paging links, and whatever else? Earl, any direction on where such a setting would go and how to store it?

I may have a go at the paging links in the next version of this patch. I'm assuming the following desired behavior:

* A given page has several views in e.g. panels, each with paging links
* When a paging link is clicked without JS present, a new page is loaded where, typically, the view is the main content and the next page of data are viewed.
* With JS, however, the next page of data are loaded directly into the existing view content (e.g., within a panel)

Does this sound right? Good idea to include paging, or should we just limit this patch to the initial use case of exposed filters?

[Eventually, I suppose, we may want a different kind of AJAX loading behavior, one in which new records are appended to the existing ones. This would be e.g. for an image carousel, where it's desirable to be able to continue to browse previously-loaded images as well as new ones. But I assume that's beyond the scope of what we're trying to do in this issue.]

#40

nedjo - March 25, 2008 - 18:28

Here's a new patch. Main changes:

  • Added UI for designating whether a view should use AJAX. This is mostly working but has a bug. Earl, can you have a look?

    The new setting is use_ajax ("Use AJAX") in the "Basic settings" in view admin. I tried to follow the approach used for the use_pager setting.

    The UI appears to be working and the new setting is saved, but when viewing a view the new setting doesn't stick. For testing purposes I've temporarily overridden the setting so that the AJAX behavior will always be on:

       function pre_execute() {
    +    $this->view->set_use_ajax($this->use_ajax());
    +    // TODO: remove this.
    +    $this->view->set_use_ajax(1);

  • Implemented AJAX for paging links.
  • Data on each ajaxified view are sent to Drupal.settings. This includes the args, which are posted back to the AJAX callback.

I've used strings to pass an array of function callbacks and then in the AJAX callback passed them through eval(). It would be nice not to have to use eval, but due to limitations in drupal_to_js() I don't think we have a way of passing unquoted js function names.

I've attached an updated version of the demo ajax_load module, showing how the AJAX functionality can be extended.

I think this is getting close. Testing would be great. Some of the needed testing:

1. Create a view that includes paged results. Browse the view. Click on the pager. Do the correct pages load via AJAX?
2. Create a view that includes both an exposed filter and paged results. Browse the view. Submit the filter. Do the expected results load via AJAX? Now click the pager on the AJAX-loaded results. Working?
3. Create a view with arguments and paged results. Browse the view. Click the pager. Working?

AttachmentSize
ajax_load.tar_.gz1.34 KB
views-ajax.patch15.23 KB

#41

moshe weitzman - March 25, 2008 - 19:09
Title:Load exposed filters through AJAX» Upon change in exposed filters, refresh node listing via AJAX

#42

nedjo - March 25, 2008 - 19:16

Minor fix to JS issue (prevented paged links from working on initial page load).

AttachmentSize
views-ajax.patch15.23 KB

#43

ezra-g - March 26, 2008 - 15:17

Subscribing. Hope to test soon.

#44

merlinofchaos - March 28, 2008 - 19:19
Status:patch (code needs review)» fixed

Ok, I ran through this and did some testing. I found 1 bug that is worth noting for future reference:

When multiple views were on the screen, the ajax_path will turn into an array, with the actual path repeated once per view that is in the list. As near as I can tell, this is an affect of drupal_add_js and I don't see a way to stop it. So I added a check to the path in the .js to see if it's an array, and if it is to just use the first element. There's probably a cleaner way to do that; if you want to patch it, great.

I extended it to work on tablesorts.

I opened up blocks to use paging.

I added theme_views_mini_pager which is a cut down pager that just has next, previews, and Page X of Y on it. Turns out that was really easy to do; take theme_pager and...cut out most of the code. =)

and this is now committed!

#45

merlinofchaos - March 28, 2008 - 19:27

Oh! I removed the hardcode that set use ajax to true. I think the reason you might've been having trouble with it is that existing views don't have the setting to use the default, so you may have been seeing your page views override the setting. IN any case, I simply removed the hard-set and it worked for me. Let me know if you still have problems with that.

#46

merlinofchaos - March 28, 2008 - 19:51

Related issue; it was pointed out that we need some kind of visual feedback. So here's an issue for a throbber: http://drupal.org/node/239911

#47

nedjo - March 29, 2008 - 19:28
Status:fixed» patch (code needs review)

Thanks for reviewing and improving this, it's nice to see it go in!

I realized I'd left in a JS helper function that's now not used (since we went to the approach of passing data on the views in Drupal.settings). Attached patch removes this obsolete function.

Also, I think the issue with the ajax setting not sticking was that I was overriding the 'frontpage' default view. I suspect we need to add 'use_ajax' => true; to the exported view. I noticed that, when exporting a view, the 'use_ajax' property exports as a string ('0') rather than a boolean like use_pager. I couldn't immediately find where to fix this. So I think some further work is needed on these two areas: format of the use_ajax property and inclusion of use_ajax in default views.

AttachmentSize
views-js.patch697 bytes

#48

merlinofchaos - March 30, 2008 - 16:39
Status:patch (code needs review)» fixed

Committed. Thanks! And thanks *very* much for the hard work on this patch!

#49

Anonymous (not verified) - April 13, 2008 - 16:42
Status:fixed» closed

Automatically closed -- issue fixed for two weeks with no activity.

#50

nrasmus - April 22, 2008 - 14:39

I'm sorry if this is not the place to post this: I'm very interested in being able to display exposed filter results in the same panels 2 pane as the original view. I've set the panel to make the view's URL the same as the panel, and that allows the pager to work just fine, but using my exposed filter loads the default page view of the view.

I may be missing something simple, but this patch seems to promise this capability (with AJAX). I'd be fine with a non-AJAX solution to this, but this would be great. Is it currently possible to get it working on 5.x? I tried patching with the patch supplied in #5, but the first hunk failed.

#51

merlinofchaos - April 22, 2008 - 15:41

This is a Drupal 6 feature only; it won't work at all with Drupal 5. Sorry.

#52

nedjo - April 22, 2008 - 16:55

Views 6.x will include this functionality in a form completely different from the early 5.x patch. There are no plans that I know of to backport to 5.x.

 
 

Drupal is a registered trademark of Dries Buytaert.