Pages

Monday, August 20, 2012

Jquery/Javascript method to handle real-time user input in a textfield


I recently ran across a problem where I needed to handle user input in a textfield in real-time. What I needed to do in my situation was to perform an AJAX call to my Drupal site as the user was typing into a textfield (to see if that value was available in the database). The main problem was that there were a few easy options for doing this in JQuery, but most of them were not good enough. Here are two of the options I tried.

The change or blur approach

The first and easiest option was to just perform the action using the Jquery .change() or .blur() method. This would basically run the AJAX request after the text-field loses focus. This would mean that after a user stopped typing they would need to click out of the text-field in order for the AJAX to run. This was not an acceptable solution as that is not the typical user behavior.

The keypress/keyup/keydown or input approach

I eventually stumbled on a way to run the AJAX after every key the user pressed. I could use something like the following line to accomplish this:
$('#edit-my-textfield', context).bind('input', function() {
  //my code goes here
});
This however runs the AJAX call after every single keypress. Because of the various validation I also needed to do on the text-field, this often caused unexpected and buggy results (the results of the AJAX calls did not always come back in the exact order they were executed if many keys were pressed rapidly). Again, this method was much closer, but it was to buggy and unreliable.

The "near" real-time solution

It took me awhile, but I finally came across a solution I can live with. There may be many other options out there to do this (if you know of any, please let me know), but this one seems to work fairly well.
I built all of this inside a simple Drupal 6 module. Here is and example of the related form information:
$form['my_fieldset'] = array(
  '#type' => 'fieldset', 
  '#title' => t('My Fieldset'), 
  '#weight' => 5, 
  '#collapsible' => FALSE, 
  '#collapsed' => FALSE,
);
$form['my_fieldset']['my_textfield'] = array(
  '#type' => 'textfield',
  '#title' => '',
  '#default_value' => "",
  '#size' => 30,
  '#maxlength' => 30,
  '#required' => TRUE,
  '#prefix' => "<div id='selection_wrapper'>",
  '#suffix' => "<div id='display_results'></div></div>",
);
I also used the hook_init() function to include the necessary Javascript and CSS files:
/**
 * Implements hook_init().
 */
function MYMODULE_init() {
  drupal_add_js(drupal_get_path('module', 'MYMODULE') .'/MYMODULE.js');
  drupal_add_css(drupal_get_path('module', 'MYMODULE') .'/MYMODULE.css');
}
Here is an example of the Javascript I used:
Drupal.behaviors.MYMODULE = function(context) {
  var myTimer = undefined;
  var delayTime = 1500; // should be in miliseconds.
 
  //should insert the throbber image and hide it
  $('#edit-my-textfield', context).bind('input', function() {
    $('#display_results').html('');
    $('#display_results').addClass('display-throbber');
 
    if (myTimer) {
        clearTimeout(myTimer);
    }
    my_val = $(this).val();
    myTimer = setTimeout('myfunction(my_val);', delayTime);
 
    return false;
  });
}
 
function myfunction(my_val) {
  //do whatever you want here
  //I used this function for my logic and AJAX calls
  $('#display_results').removeClass('display-throbber');
  $('#display_results').html('It works!, the textfield says: ' + my_val);
}
I also added some basic CSS to display the results. I added a simple loading/throbber image that is displayed during the AJAX requests. I created the animated GIF image athttp://ajaxload.info/. In the JavaScript above, I add and remove a class called "display-throbber" that will trigger the display of the loading image.
The above code uses a simple timer (set to 1.5 seconds) that will call a function (called "myfunction") if it expires. However, if a user types another character in the text-field, the timer gets reset. This allows the user to type in the text-field and only runs the code in the "myfunction" function when at least 1.5 seconds have elapsed. For the simple example above it may not seem necessary, however when making AJAX calls this provides reliable results and removes the need of unneccesary AJAX calls to the server. The only minor negative is the short delay before the "myfunction" code is run.
Note: In my original code I actually removed the "display-throbber" class in my AJAX response function, but since the above code is just a simple example, I added it to the "myfunction" function that is called after the timer elapses.
Here is the example CSS that goes along with the Drupal form and Javascript. It will probably need to be modified to fit your scenario but should provide a good starting point.
#selection-wrapper {
  overflow: auto;
}
#edit-my-textfield-wrapper {
  float:left;
}
#display_results {
  float: left;
  min-height: 15px;
  min-width: 100px;
  padding: 10px 0 0 10px;
  max-width: 280px;
}
#display_results.display-throbber {
  background: transparent url('./images/ajax-loader.gif') no-repeat center bottom;
}
So go ahead and provide some real-time features for your users when they enter data into text-fields. Meanwhile you can sleep soundly knowing that you are avoiding unnecessary AJAX requests, and providing a great user experience. In the future I will try to post a more detailed example of the AJAX requests interacting with the Drupal database.
One more thing, if you have not signed up for the newsletter yet... do it now! There will be some big happenings in the near future and if you are interested in learning more about Drupal and enjoy video formats, you will be glad you signed up.

Drupal 7 Deleting Organic Group content


Ran into a situation today where I had to delete content from a group programmatically. Basically I have a setup where a group can only have one of a specific type of content posted to their group at any one time. I have it set up so a user can control which one is posted into the group but it needs to restrict them to only allow one.
Here is how I was able to delete all posts of a specific content type in a specific group:
  $content_type = 'my_content_type';
  $group_id = 25;
 
  $query = db_select('og_membership', 'ogm');
 
  $query
    ->condition('ogm.group_type', 'node', '=')
    ->condition('ogm.entity_type', 'node', '=')
    ->condition('ogm.gid', $group_id, '=')
    ->fields('ogm', array('id'))
    ->join('node', 'n', "ogm.etid = n.nid AND n.type = :ctype", array(':ctype' => $content_type));
 
  $results = $query->execute();
 
  $ids = array();
  foreach ($results AS $result) {
    $ids[] = $result->id;
  }
 
  if (!empty($ids)) {
    og_membership_delete_multiple($ids);
  }

Drupal 7 Views Exposed Filters in Blocks (and panels)


Ran into a views and panels issue today that was very simple to fix, but difficult to track down. Here is my situation in case you run into a similar views/panels issue.
I have one Drupal 7 panel page that does a lot of visibility rule filtering based on the path of the panel. Depending on the path, I have different pieces of content that show up. In some situations, this may be a view (othertimes it is static content, custom coded blocks, etc).
I wanted some of my views that are displayed in the this panel to have an exposed filter to allow easy searching. As I normally would with a view, I simply turned on the exposed filter on the views admin interface and went to make sure it was working... no luck. No exposed filter was being displayed.
I began searching for a solution and it turns out to be very simple. I have my views set up as block displays. In order for exposed filters to work with block views, you need to have Ajax enabled in the view. All I had to do to get this working, was go to the views admin and switch "use AJAX" over to "YES". After that, my exposed filters began showing up in my panel page.
A few things to note. This isn't really an issue with panels, it is only related to views being displayed as blocks, however in my situation, panels is what led me to encounter the problem. Any views block will need AJAX set to YES in order to see the exposed filters.
I also know that it is possible to set up Views Content Panes to use in my panels pages, instead of using Views Blocks. However a year or so ago, back in Drupal 6 I ran into a lot of restrictions with how Views Content Panes handled arguments and panels context, so I stopped using them. I have not used them recently so I can't say if those issues have been fixed or not yet. If anyone has arguments or opinions on why using Views Content Panes are better than using Views Blocks for displaying inside a Panel, I would be happy to hear you out.
Happy Drupaling.

Adding Javascript to your Drupal 7 Module


It starts with adding the following line to your modules .info file (rename MYMODULE to your module name or the name you want to give to your javascript file):

scripts[] = MYMODULE.js
The next step is to create the MYMODULE.js file. The general template of the javascript file looks like:
(function ($) {
  Drupal.behaviors.MYMODULE = {
    attach: function (context, settings) {
      // Your Javascript code goes here
 
    }
  };
}(jQuery));
You can pass variables from your PHP to your Javascript file by adding the following code somewhere in your module.
drupal_add_js(array('MYMODULE' => array('tax_rate' => '0.06')), 'setting');
You can now access this variable in your JavaScript:
(function ($) {
  Drupal.behaviors.MYMODULE = {
    attach: function (context, settings) {
      // You can access the variable by using Drupal.settings.MYMODULE.tax_rate
      alert(Drupal.settings.MYMODULE.tax_rate);
 
    }
  };
}(jQuery));