1

I would like to keep a count of how many times a node is displayed in a view. For example, in my home page I have a view that displays a list of 20 products. I can count how many times users click on it. I can also count full node views.

But what I would like to do is to count how many times this product is displayed in this view. I use a random system to show users different products each time the home page is reloaded.

I have searched a lot, with different statistics modules, for example radioactivity, node view count, and entity view count but I'm just not able to find something that do what I want.

The answer should work with caching of the view enabled as well.

6
  • There is a "new content" filter and sort handler that you can use in Views. Only for authenticated users. When the user visits the full page if that node, that nose is marked as the user has visited it.
    – AKS
    Jan 12, 2015 at 11:23
  • But what I need to do is to add a counter to see how much times a product is displayed in a view, I already know the times users click on it to see the entire node
    – Russo
    Jan 12, 2015 at 21:02
  • The caching requirement makes things trickier.
    – rooby
    Jan 15, 2015 at 5:02
  • Where do you want the data to go / what do you want to do with the data afterwards?
    – rooby
    Jan 15, 2015 at 5:05
  • Just curious - shouldn't you be able to determine the number of times a node is displayed based on the method you're using to generate the list in the first place? If you view is displaying a random set of 20 nodes out of a pool of, say, 100 nodes - given 10k views, couldn't we assume each node was displayed ~2k times? ([page-impressions] / ([nodes-in-view] / [nodes-in-pool])) Jan 21, 2015 at 17:24

5 Answers 5

3
+100

This can be done by tracking events with the help of Google Analytics Drupal provides module for it https://www.drupal.org/project/google_analytics

Events can be tracked by event tracking module https://www.drupal.org/project/google_analytics_et

Will need to write custom js for making this event tracking, see documentation of google analytics event tracking module

I'm adding a code sample for tracking click on social media share icons:

    $('.facebook_popup', context).click(function (e) {
    // First register an event in Google Analytics
    if (typeof ga != "undefined") {
      if (jQuery(e.target).closest('.node-social-media').length) {
        ga('send', 'event', 'social-side-bar', 'fb-share', window.location.pathname);
      }
      else if (jQuery(e.target).closest('#block-menu-menu-share').length) {
        ga('send', 'event', 'social-shares-top', 'fb-share', window.location.pathname);
      }
    }

This might help you creating google analytics events

7
  • This is a decent idea but it depends what the intended use of the data is (which the OP has not stated). If they need the data stored in Drupal to use for other things then this isn't ideal. It is also reliant on Javascript, which isn't ideal either.
    – rooby
    Jan 21, 2015 at 21:42
  • @rooby I agree with you that this is relient on JS. But you can't track events without using javascript as per my knowledge. Though you can call a function in html_preprocess for tracking but we only can get the HTTP_REFERRER not the exact section from the previous page. I mean we can't decide if the link was clicked from a view or some other section. Jan 22, 2015 at 7:17
  • Also @rooby if you really need to store the data fo GA into drupal, you can import it though API. There are modules ie. drupal.org/project/ga_stats. Check if any other better module meeting the requirements. Jan 22, 2015 at 7:19
  • We're not tracking clicks though, we're recording number of views.
    – rooby
    Jan 22, 2015 at 7:57
  • answered for "For example, in my home page I have a view that displays a list of 20 products. I can count how many times users click on it. I can also count full node views." Jan 22, 2015 at 8:34
1

Add the node id as a hidden field to the result set. Add a jQuery script that collates all the node ids into one array (frequency summarised or raw), and then makes an AJAX call to the server, passing the node ids along. The server can then update any tables as necessary.

This will work with caching enabled as well.

In more detail:

VIEW:

Give the hidden nid field a class in views.

jQuery:

Add your jQuery in preprocess_page (check for correct view url). Your jQuery will then have a selector looking for that class. It will loop over the selector result, passing each item to an inline function. The function will add the nid as a key to an array (if it does not already exist), and increment the value of that key by 1. This will build up the array of frequencies with the keys of the nodes.

Server:

When this is done, you call the drupal AJAX path, passing along the JSONified array. Your server will then convert the JSON param back to an array. It will be faster to just load the field for all the nodes in one db_select (using IN) directly from the field's table (instead of loading nodes). You then loop over the result and do your additions, and finally write the field back for each node using db_update/db_merge.

If you have n different nodes that need updating, then it is n+1 database queries. Also look at optimising the SQL update/merge by perhaps using INSERT ... ON DUPLICATE KEY UPDATE. Perhaps best would be to ask another question on how best to do multiple inserts in one query in Drupal.

7
  • By 'primary identifier' of the product you mean the node id ?
    – Russo
    Jan 12, 2015 at 6:53
  • If it is nodes, then yes. Note that if you had 3 identical products in the view, now you will only have 1 with a count of 3 next to it. If that is not what you wanted let me know. Jan 12, 2015 at 6:58
  • In fact the agregation was already activated, what I would like to do is to increment (a field?) each time this views block is charged. But I need to do it for each product (node) that is displayed in the views result, not when a user click on it (this is already made)
    – Russo
    Jan 12, 2015 at 7:01
  • I do not think there is a module for this. You could do it via Google Analytics. Alternatively you will need to implement a module for this. You cannot do it on the server as caching the view or page will prevent accurate counting. The best would be to do it via jQuery and AJAX. It's going to be a mission. Let's hope someone else can get a better solution. If in two days you have not received an answer I will put a bounty on this for you if you wish. Jan 12, 2015 at 7:12
  • Thanks a lot for your help ! yes it seems really challenging. I was working in a custom module with a hook in views and a field wich gets incremented in each "reload" I was wondering if the cache would make it difficult and it seems true. I'll let you know if I was able to find the solution, thanks again !
    – Russo
    Jan 12, 2015 at 7:20
0

If you just want to display the number of items listed in the view, there are two possibilities, depending on your needs. I think that these are both part of Views itself.

There is a Global: Result summary item you can add to either a Header, Footer or No Results Behavior. The output is for display only and cannot be used as input to any other fields (it seems). See the image below for the @tokens that can be displayed (@total is what you're looking for):

There is also a Global: View result counter field that, when added to your view's list of fields, adds a column that displays a running count of displayed records in that column. Unfortunately, if you use a Global: Text area in a Header to output that field, you can only output the value from the first row.

If you need to save the value, let me know and I'll think about it some more.

enter image description here

1
  • They want to store the data, not just display it.
    – rooby
    Jan 21, 2015 at 21:43
0

Simple but you have to code here is a simple code for you

     function yourmodule_name_views_pre_render(&$view){
             if ($view->name == 'your_view_name') {
                if ($view->current_display == 'your_block_name') {
//to get the field in view pre render
     $fieldname = isset($view->result->_field_data['uid']['entity']->field_fieldname) && !empty($row->_field_data['uid']['entity']->field_fieldname['und'][0]['value']) ? $row->_field_data['uid']['entity']->field_fieldname['und'][0]['value'] : "";
                 //use your code create a custom table with count filed and update the count field by +1  so here when ever the view_pre_render is called this so you can select from  there your count 
                }
          }

        }

in this you can select what node field you want and add that condition here and if the node is there update the count

you can print $views->result to take any field from that view.

if you want the node type in result add content type in the views field will give you that field it will help you to find node of that type

1
  • hook_views_pre_render() will not run if the view output is cached.
    – rooby
    Jan 21, 2015 at 21:19
0

I was interested to see what kind of non-js solution I coul come up with that still works with caching so...

The main caching we have to deal with is:

  • Views results cache.
  • Views output cache.
  • Drupal page cache.

The main hooks we have available are:

  • hook_views_pre_execute() is too early to know about results so it's out.
  • hook_views_post_execute() happens after we have results and it runs on both cached and non-cached results so it is a viable choice.
  • hook_views_pre_render() doesn't run if we are displaying cached output so it's out.
  • hook_views_post_render() happens after we have results and runs on both cached and non-cached output so it is a viable choice.

So you could use hook_views_post_execute() or hook_views_post_render() to handle your main logic. It's really up to you which to use. I would probably use hook_views_post_execute() since I'm dealing more with results than display but if you wanted to run after other hook_views_post_render() implementations or something the post_render would be better.
For example if you had custom code that modifies the view output so that not all results are actually shown to the user then you might want to put your logic in post render so that you can account for that.

For this example I will use post_execute.

/**
 * Implements hook_views_post_execute().
 */
function MYMODULE_views_post_execute(&$view) {
  if ($view->name == 'VIEWNAME' && $view->current_display == 'DISPLAYNAME') {
    // Since we're dealing with a single view & display we don't need to work
    // out the entity type but there are ways you could make this more generic.


    // Loop through the results and log the views.
    if ($view->result) {
      foreach ($view->result as $row) {
        // The view I'm using is a node view using teaser as display.
        // You may need to adjust how entity ID is obtained in other cases.
        MYMODULE_log_view($view, 'node', $row->nid);
      }
    }
  }
}

Then have something like this to write to the table (you can work out the schema I was playing with from this):

/**
 * Log an user's viewing of an entity in a view's results.
 */
function MYMODULE_log_view($view, $entity_type, $entity_id) {
  $count = db_query("SELECT *
                       FROM {TABLENAME}
                      WHERE view_name = :view_name
                        AND view_display = :view_display
                        AND entity_type = :entity_type
                        AND entity_id = :entity_id",
    array(
      ':view_name' => $view->name,
      ':view_display' => $view->current_display,
      ':entity_type' => $entity_type,
      ':entity_id' => $entity_id,
    ))->fetchObject();

  // If we didn't get a count create a new one, otherwise increament.
  $primary_keys = array();
  if (!$count) {
    $count = new stdClass();
    $count->view_name = $view->name;
    $count->view_display = $view->current_display;
    $count->entity_type = $entity_type;
    $count->entity_id = $entity_id;
    $count->view_count = 1;
  }
  else {
    $count->view_count++;
    $primary_keys[] = 'id';
  }

  // Write the data.
  drupal_write_record('TABLENAME', $count, $primary_keys);

  // @todo: Save to a cache table based on path so we can load it again when
  // drupal serves a cached page.

  return $count;
}

That successfully logs how many times each node is displayed.
That works with views result & output caching on.

Then you have to deal with the views page cache.
As you can see I have left a todo in the above code for that to store a record of the nodes displayed on this page.
Along with that you could need some code to read our cache and log the views for cached pages. Something like this:

/**
 * Implements hook_boot().
 */
function MYMODULE_boot() {
  // Check to see if we are loading from the cache.
  if (drupal_page_get_cache(TRUE)) {
    // @todo: Get our cache entries for this page and log the views.
  }
}

I'm pretty confident that would all work out to handle the drupal page cache.

After that you have external caches like varnish.
Then you're in trouble and you'll need to go to an ajax request to log the views, which could basically pass in the page url and save like the hook_boot() example above.

I have no time now but I will flesh this out some more a little later and finish the @todos.
I may also make a sandbox module and see how generic I can get it, for supporting other entities, integrating with views UI, etc.

Anyway, it works :)

Then you could add views support for the custom table and build reports etc.
Also notable is that this only stores a total count per entity. If you wanted to go really hard core you could adapt it to save each view separately with a timestamp and then you could do reporting based on date ranges too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.