I came up with a way to exclude items in a view based on the contents of another view, and thought I'd share it. Please let me know if you have any questions or suggestions.

Here's a screencast explaining the technique: http://www.youtube.com/watch?v=cQrw7Ydo1to .

...and the template.php code:

function MYTHEME_preprocess_views_view(&$variables) {
  switch($variables['name']){
    // List all views whose NIDs you would like access to.
    case 'MYVIEW1':
    case 'MYVIEW2':

      // Store the view in $view for easier reference.
      $view = $variables['view'];
      
      // Store NIDs displayed by this view in the $nids array.
      foreach($view->result as $key=>$value){
        $nids[] = $value->nid;
      }
      // Turn the $nids array into an argument string (separated by + signs)
      // and store the string in $GLOBALS['VIEW_NAME_nids'], so that it will be accessible elsewhere in the site.
      $GLOBALS[$view->name.'_nids'] = implode('+',$nids);

    break;
  }
}

Comments

nevets’s picture

In the case where you are exclude based on node id (nid) you can also use Views Exclude Previous

bohz’s picture

Thanks nevets, I didn't know about this module. However, Views Exclude Previous is limited to list, grid, unformatted or table displays.
The method suggested by cwebster, I suppose, would work for any view, regardless their display.

@cwebster: would it be possible to output a different argument variable for each display within a particular view?

Ideally, it would be great if views could output an argument based on any of the filter criteria; something similar to exposed filters ("exposed arguments"?). For instance you could use the termID or the value of a CCK field...

Thank you for sharing!

MaxCross’s picture

This is exactly what I have been looking for. I try and reuse content on all my sites but the duplication issue is major problem. Any ideas why this might not working in my test on D7?

cwebster’s picture

Unfortunately, I haven't been able to dive into Drupal 7 yet... hopefully someone else can weigh in on why this might be happening. I think a key suspect might be differences in the templating system, possibly the preprocess function name has changed? Sorry I couldn't be more help.

bryancasler’s picture

Just wondering if you found a way to achieve this in D7

alan d.’s picture

Here is a generic approach to the same issue.

/**
 * Creates a global cache of view node nids for later use.
 *
 * To use, edit the view in question:
 * 1) Add a new "Node: nid" argument to the view.
 * 2) Select "Provide default argument" > "PHP Code"
 * 3) Enter the following line of code to exclude all view node results:
 *
 *    return empty($GLOBALS['view-node-nids'])
 *        ? 'all'
 *        : implode('+', $GLOBALS['view-node-nids']);
 *
 *    OR this one to exclude the current views node results
 *
 *    return empty($GLOBALS['view-node-'. $view->name .'-nids'])
 *        ? 'all'
 *        : implode('+', $GLOBALS['view-node-'. $view->name .'-nids']);
 *
 * 4) Check "Allow multiple terms per argument." & "Exclude the argument"
 * 5) As a sanity check, I also checked return all values if argument does not validate
 *
 * To test, deselect "Exclude the argument".
 */
function htsa_preprocess_views_view(&$variables) {
  if ($variables['view']->base_table == 'node') {
    $view_key = 'view-node-' . $variables['view']->name . '-nids';
    foreach($variables['view']->result as $key => $value){
      $GLOBALS[$view_key][$value->nid] = $value->nid;
      $GLOBALS['view-node-nids'][$value->nid] = $value->nid;
    }
  }
}

Alan Davison
siefca’s picture

The MYMODULE_preprocess_views_view() hook is not called for anonymous users. Data goes from some cache, but it's not even called once (assuming: when cached version is generated) and that's strange.

naero’s picture

@cwebster:

In D6, what if you want to do this with a view that contains two or more displays? Or, does this approach require you to use 2+ separate views?

naero’s picture

Also, for this to work, does the display type need to be anything in specific (page, block, attachment)?

cjtriplett’s picture

The code above, as placed in my template.php file does not seem to be called. It returns nothing. I've printed my query into the footer of the view I am attempting to filter. I've also placed the view that contains the filter nid's into the same page to verify it returns the correct nid's in the first place.

I placed an echo statement inside the function: e.g. echo 'Preprocess Views View Starting…'; It is never printed? I think I'm missing something very simple here and would appreciate anyone's help.

cjtriplett’s picture

So I figured out that I needed to clear the Views cache to get the preprocess code to run properly. The view calls it and I have the preprocess code write out the Global it creates. It looks correct. But it seems as if the string value is not passed inside the view itself even though I can verify it's generated.

cjtriplett’s picture

I have concluded this solution requires both views be displayed as blocks on the same page and subsequently this particular solution, although very useful, will not work for my situation.

MmMnRr’s picture

Hi,

If performance is not a huge issue here, you can use Drupal 7 and Views 3 in order to filter results of a view depending on another view.
What you need is to modify the view that will exclude the results from the other one:

1) Add the field that will be used to filter inside "Contextual filters" section.
2) In the area "When the filter value is NOT in the URL" select "Provide default value" and then "PHP Code".
3) Inside "PHP contextual filter code" you can dynamically generate the results from the other view and use them to filter:

    $args = arg();
    $nid  = $args[1];
    $result = views_get_view_result('your_view_name', 'your_view_display', $nid);
    foreach ($result as $key => $value) $uids[] = $value->uid;
    return empty($uids) ? 'all' : implode('+',$uids);

In my case I needed User ID's (uid) instead of Node ID's. But you can play with any result field that is included in your other view.
As you can see, you can provide arguments to your other view (if needed). In my case, the Node ID from the current URL.

4) Inside "More" area you have to check "Allow multiple values" and "Exclude" options.
5) Save your view and now results that appear in the other view will be excluded here.

drupaljunior’s picture

Hi MmMnRr,

I'm very interested to your approach, in particular to your statement "As you can see, you can provide arguments to your other view (if needed)".

SCENARIO:
- I have a main view that is called View1 that produces a list of values. View1 has a contextual filter that is a date, we call this field big_date.
- From the list generated by View1, I want to exclude the list of values that are the output of the Display1 of a view called View2.
- View2|Display1 has a contextual filter that is also a date, let's call this field other_date.

Can you help me with the php code that I have to use to pass the contextual filter that is input for big_date to the View2|Display1 to generate the list of values to be excluded based on other_date field = value passed?

I really hope you can help me, because I'm stuck since 1 month with this problem :(
Thank you in advance.

MmMnRr’s picture

Hi drupaljunior,

If I understand correctly, you have:

- A view called View1 with contextual filter called "big_date".
- A view called View2 with contextual filter called "other_date".

And you want:

- View2 will receive as argument for its contextual filter ( called "other_date" ) the list of DATES that are the OUTPUT of View1.
- View2 will show results for all rows that DO NOT have as "other_date" = list of DATES that are the OUTPUT of View1.

Is that right?
If so, I understand that you want the PHP code that would be included in View2 in order to retrieve the list of dates that are the output of View1. That PHP code would be included inside:

2) In the area "When the filter value is NOT in the URL" select "Provide default value" and then "PHP Code".

Is that right?

drupaljunior’s picture

Hi MmMnRr,

Sorry, it is a bit difficult to explain my scenario, I try again.

I have:
- A view called "View1" with contextual filter called "big_date"; the output of this view is a list of entities of type "Resources".
- A view called "View2" with contextual filter called "other_date"; the output of this view is a list of entities of the same type "Resources".

What I want to do:
- pass a date and get the list of "Resources" entities that are IN the output of "View1" and are NOT IN the output of "View2".

Solution I'm trying:
Looking at your proposed solution I think it can be achieved like follows: into the "View1" I can set up a contextual filter on the Entity ID and say "When the filter value is NOT in the URL" select "Provide default value" and then "PHP Code" to exclude the ones provided by "View2".

The problem is passing the date filters as input:
- I can pass it to "big_date" of "View1", because it is a simple contextual filter of the view
- I need the PHP Code into the Entity ID contextual filter to receive the same date value as a filter for the field "other_date" in "View2". I have no idea how to do this in PHP code.

I hope that this explanation is a bit more clear than previous I gave :)

Thank you for the help.

MmMnRr’s picture

If I understand, you need to:

- Add a contextual filter "Entity ID" inside View1.
- Select "When the filter value is NOT in the URL".
- Select "Provide default value".
- Select "PHP Code".
- Write a PHP code like the following:

  /* Get the date value */
  $date_value = ...
  /* Obtain the results of View2 by providing the filter "date_value" */
  $result = views_get_view_result('your_view2_name', 'your_view2_display', $date_value);
  /* Loop over the result of View2 */
  foreach ($result as $key => $value) {
    /* Get the Entity ID value of each View2 result row. */
    /* I wrote "entityIdFieldName" as an example. In order to know which is the field name, take advantage of function dsm(...); from Devel module. It will print the PHP object/array structure. For example: dsm($value); */
    $eids[] = $value->entityIdFieldName;
  }
  /* Finally, return the Entity IDs found. */
  return empty($eids) ? 'all' : implode('+',$eids);

- Inside "More" area of the contextual filter "Entity ID" you have to check "Allow multiple values" and "Exclude" options.

As you can see, I left $date_value = .... This is because I do not know where your date filter is available.
It is an argument from the URL?
If so, you would do:

  $args = arg();
  $date_value = $args[1]; // The argument 1 of the URL represents the date value.

If not, I need more information about how you retrieve the date value.

cruze72’s picture

I have 2 views.

The first view has a just one exposed filter for a taxonomy vocabulary. This returns a simple list of node titles.

The second view has a number of more detailed exposed filters for several other taxonomy vocabularies and again just drives out a list of node titles.

I want the results of the first view to be used as the default search results on the second view before using the additional exposed filters.

Can i achieve this by amending the above code?

MmMnRr’s picture

Taking into account that the "default search results" of the 2nd view can be obtained by setting the appropiate exposed filters of that view, there is no need to communicate 1st view with the 2nd view.
The point is that you should be able to reproduce the result of the 1st view by setting the appropiate filters inside the 2nd view.

If you want to set a "default search" you have to set default values for your exposed filters. You can achieve that by implementing hook_form_alter() and modifying the #default_value of the specific exposed filters you want.

nithinkolekar’s picture

getting argument by $args = arg(); will not work if contextual filter is passed using embed method. More details about what I am trying to get is at https://www.drupal.org/node/1871388/discuss.

sprite’s picture

Following for future reference.

spritefully yours
Technical assistance provided to the Drupal community on my own time ...
Thank yous appreciated ...