2

In my previous question How to mark a Message as 'viewed' when a user sees it in a View?, I explained that I'd like to flag a Message when it's hovered or clicked in a View. @Geoff suggested to create an AJAX callback in his answer, but I've no idea how to accomplish that.

How can this be done? Is this the easiest approach?

6
+50

The entire process may seem a little complicated at first, but it's easy once we break it down into smaller parts and understand how it all comes together.

Basically we have two parts which we need to address. Firstly the "backend" logic where we will be implementing hook_menu, allowing us to receive an AJAX request via a custom menu route and then flag our Message as seen. Then secondly the "frontend" logic where we'll be implementing a little bit of jQuery to trigger our AJAX calls (using the proper Drupal.behaviours way of course) :)

With this example I'm assuming you have created a Message entity and have added a boolen field to it. We'll use this to store our flag to indicate whether the Message has been "seen" or not.

Once you've done that, go ahead and create a simple custom module and implement hook_menu using the following example:

/**
* Implements hook_menu().
*/
function my_example_menu() {
  $items['my_example/message/seen/%'] = array(  
  'page callback' => 'my_example_flag_message',
  'page arguments' => array(3),
  'access callback' => 'my_example_message_access',
  'access arguments' => array(3),
  'type' => MENU_CALLBACK,
  );
  return $items;
}

Now before we start writing the page callback function which will flag our message as seen, we need to make sure that we implement proper access rights. This ensures the user accessing our custom menu route actually "owns" the message, and isn't some malicious hacker trying to annoy us and mark all the messages on your system as "seen" as some kind of lame prank haha! See below:

/**
 *  Access callback to ensure user has the right to message entity
 */
function my_example_message_access($msg_id) {

  global $user;

  $access = TRUE;

  //if anonymous user then throw access denied
  if(!$user->uid) {
    return FALSE;
  }

  //load our message entity and check the uid
  $msg = entity_load('message', array($msg_id));

  if($msg[$msg_id]->uid != $user->uid) {
    return FALSE;
  }

  return $access;
}

You can test the access callback by visiting http://example.com/my_example/message/seen/[MESSAGE_ID] and trying out different message id's logged in as different users. If a message doesn't belong to you, Drupal should be giving you the old "Access Denied" message.

Now that we know our AJAX route is protected by Drupal access control, we can go ahead and write the page callback function which will flag our Message entity as "seen":

//page callback function
function my_example_flag_message($msg_id){

  $response = array('success' => FALSE);

  //first check that the message id argument is present
  if(!isset($msg_id) || $msg_id == 0){
    return drupal_json_output($response);
    drupal_exit();
  }

  //load our message entity
  $msg = entity_load('message', array($msg_id));

  //set the flag to TRUE
  $msg[$msg_id]->field_YOUR_FIELD_MACHINE_NAME[LANGUAGE_NONE][0]['value'] = 1;

  //save our message entity
  if(entity_save('message', $msg[$msg_id])){
    $response['success'] = TRUE;
  }else{
    $response['success'] = FALSE;
  }

  //return the status of our attempt at saving our entity back to the front end
  return drupal_json_output($response);
  drupal_exit();
} 

Great so if you made it this far and didn't fall asleep reading that wall of text, then you should now have a working route which accepts a message id, and marks that message as "seen" (as long as it belongs to you). :)

Then our final piece of the puzzle is attaching a Drupal behaviour to each View row, enabled as to fire an AJAX call to our Drupal route and trigger our backend logic. I'm assuming you know some basic jQuery, but here's the example script you'll need to add to your page using the drupal_add_js() function or something similar. We'll call it my_example.js:

(function ($) {
  Drupal.behaviors.my_example = {
    attach: function(context, settings) {
      //you can adjust this jQuery selector to anything you like
      $('.view-row').click(function(e){
        //I'm assuming the message ID is in view row markup as a data attribe (eg: data-message-id="[MID]")
        var msgID = $(this).data('message-id');
        //trigger our jQuery AJAX call
        $.ajax({
          type: 'POST',
          url: '/my_example/message/seen/' + msgID,
          dataType: 'json',
          data: 'seen=1', //added some arb param here as an example
          beforeSend: function(){
            //if you want to do anything fancy before the AJAX call do it here
          },
          success: function(response){
            //if we successfully updated the message            
            if(response.success == true){           
              //do something fancy like display a message if you like
            }else{
              //if the update failed display an error message
            }
          },
          error: function(jqXHR, textStatus, errorThrown) {
            //if there was some kind of javascript / jquery error you can debug it here
          }
        });
      });
    }
  }
})(jQuery);

And hey presto, we now have some jQuery attaching our Drupal behaviour to each View row which when clicked, will fire off our AJAX trigger and then get Drupal to update your message.

This design pattern opened up a whole host of other ideas (think AngularJS and other exciting geeky tech) for my use of Drupal. I hope it helps you too :)

Obviously if there's anyone else out there with a better implementation please let me know!

8
  • 1
    great tutorial and it'll likely work great so long as he's not using something silly like Workbench or the Revisions module (just saying). Seriously nice writeup. – tenken Sep 9 '14 at 21:05
  • thanks tenken! good point regarding those modules, I didn't even know about Workbench until your comment thank you. It looks pretty cool. – Alex Kirsten Sep 9 '14 at 21:42
  • Wow, thanks a lot for this long answer! I'm tempted to upvote it just because you took the effort to type all this. :) So, I started following your tutorial, but faced a problem halfway. I've used your MY_EXAMPLE_menu and MY_EXAMPLE_message_access functions, but when I visit MYSITE/MY_EXAMPLE/message/seen/MID, I get 'Page not found' instead of 'Access denied' or something else. Is there a mistake in your code or did I miss something? By the way @tenken: no, I don't use Workbench or Revisions... :) – Jeroen Sep 10 '14 at 23:48
  • 1
    @Jeroen Those errors are coming from entity_load function I think. As Message module provides a message entity, you can change hook_menu and its calbacks a litle bit to work them properly: $items['my_example/message/seen/%'] should be $items['my_example/message/seen/%message'], $msg_id in function my_example_message_access($msg_id) and function my_example_flag_message($msg_id) should be $message, after that you can use $message entity without calling entity_load function in callbacks.. – xurshid29 Sep 11 '14 at 18:52
  • 1
    @Jeroen Another thing you need to change is entity_save receives an object, not an array, in this example $msg is an array (entity_load function returns an array of entity objects), you should use some loop structure to access single entity.. – xurshid29 Sep 11 '14 at 19:02

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.