CVS edit link for maximpodorov

I would like to introduce and maintain new module named ajaxblocks. It's purpose is to give site administrator the ability to choose which blocks are to be loaded by additional AJAX request after loading the whole cached page itself. The module is suitable for sites which are mostly static, and the page caching for anonymous users is a great benefit, but there are some pieces of information that have to be dynamic.

The classical example is Ubercart sites which has to show the cart content in a block. Current solutions are incompatible with the page caching. The other example is showing random banners (some solutions are provided by Ad module, but it's hard to use these solutions when you need to show just some view output which randomizes data).

There are some discussions on drupal.org regarding this theme:
http://groups.drupal.org/node/24825
http://drupal.org/node/441312
http://drupal.org/node/657826
and maybe others. These discussions provide solutions to the problem, but they are very specific. My module provides the general solution which can be used out of the box.

The module itself is a fork of existing module:
http://drupal.org/project/ajaxify_regions
I created the separate module because the purpose of ajaxify_regions is to support AJAX loading on pages cached for authenticated users (by authcache or similar modules), and there is no solution for anonymous caching case.

In the future, I plan to add 'rapid' mode of serving AJAX requests which will not require full bootstrap process. This mode will rely on provided PHP code which will construct the block content without using theming functions, views, etc., and the response will be really fast.

I hope to serve the community by providing this module.

Comments

maximpodorov’s picture

StatusFileSize
new2.34 KB

The module itself is attached.

maximpodorov’s picture

Status: Postponed (maintainer needs more info) » Needs review

Some more words.
While being a fork of axaxify_regions module, my module provides additional functionality:
1. per block setting of loading or not via AJAX
2. providing current page address to AJAX handler (so it doesn't depend on HTTP_REFERER variable existence)
3. ability to specify JavaScript code which will be executed after loading the block besides attachment of Drupal behaviors
4. using json_encode() function if it's available (in PHP 5.2 or later) for faster response

avpaderno’s picture

Status: Needs review » Needs work
Issue tags: +Module review

Hello, and thanks for applying for a CVS account. I am adding the review tags, and some volunteers will review your code, pointing out what needs to be changed.

As per http://drupal.org/cvs-application/requirements, the motivation message should be expanded to contain more details about the features of the proposed module, and it should include also a comparison with the existing solutions.

maximpodorov’s picture

Thank you for the response.

The proposed module provides new settings for every block, which allow to choose the loading method for this block content if the cached page is to be displayed for anonymous users:
1. within the page - the usual way for Drupal.
2. by additional AJAX request after loading the cached page.

If the first option is selected for all blocks on a page, the module does not interfere this page loading.
If the second option is selected for some blocks on a page, these blocks are replaced by empty placeholders during page preprocessing, and the resulting page is cached and is retrieved by user's browser. After page loading, the browser initiates one AJAX request which contains the information needed to evaluate contents of these blocks on the server side (the information includes block IDs and current page path). AJAX handler gets the actual (non-cached) block contents for this page and returns the result as JSON. This can require less resources than generating the whole page.

So, blocks with dynamic content (cart, random banners, list of logged in users, etc.) can be viewed on the cached pages, and the page loading time can be significantly decreased comparing to the case of disabling this page caching. Moreover, search engine bots (and even DoS attacks) will produce less load to the server.

I couldn't find the generalized solution of exactly this problem on Drupal.org.
The discussions mentioned above provide very specific solutions, although the method is the same (AJAX request), they suggest to write PHP code (which calculates block content) by hand and put it into a separate PHP file. This can't be convenient for further maintenance. Contrary, this module doesn't require complicated actions, just to select different option for the dynamic blocks.
The work is based on existing http://drupal.org/project/ajaxify_regions module, but solves the different problem. While ajaxify_regions is designed to work with authcache or similar solutions (page caching for logged in users), doesn't allow to change per module settings, and just don't work in some cases, my module is for anonymous caching only which is more used. It can be used together with http://drupal.org/project/boost or similar solutions.

avpaderno’s picture

Status: Needs work » Needs review

Thanks for the explanation; I think it makes clear what is the purpose of the module, and the difference with existing projects.

maximpodorov’s picture

Excuse me, but can I do something to help to review this module?

zzolo’s picture

Status: Needs review » Needs work

Hi @maximpodorov, my apologies for the delay; this is all volunteer work. Please note that this could be a lengthy back and forth process reviewing your module (depending on a lot of things), but the goal for all of us is to get you a CVS account and make sure you are a responsible contributor. So, be patient. :)

Review, not in any specific order. Please note that some points are just examples that may apply in other places.

  1. Having a basic README.txt is a good practice.
  2. Your uninstall hook is good to delete variables, but you should be utilizing variable_del(). See http://zzolo.org/thoughts/tip-managing-variables-drupal-module
  3. Files should have docblocks, as well as all functions. Overall, make sure you have read and are following coding standards. Note that these standards are for more than just PHP. http://drupal.org/coding-standards
  4. Since you are using a variable amount of variables and depends on the number of block. It is suggested to store your own values in a separate table so you do not overuse the variabels table.
  5. You should implement hook_help() for a minimal help section in the admin section.
  6. You JS should not be inline. You should pass data to the JS side, then process it in the JS file.
  7. You need to utilize Drupal.behaviors in your javascript. These get called on page load and should be called on any AJAX events. http://drupal.org/node/205296 (Just added better practical example)
  8. Good use of attachBehaviors, but you should pass along the DOM element you are processing as context.
  9. What is the goal of allowing arbitrary JS execution? This is a huge performance hit and a big security risk, and I don't see the use-case. I would suggest utilizing jQuery's event system to create an even point that someone can plug into.

Overall, you do a pretty good job of writing good Drupal code, but there are some key structures that need to change, specifically on the JS side.

maximpodorov’s picture

StatusFileSize
new3.95 KB

Thank you for your review, @zzolo. Let me provide the new version of the module.
The changes:

  1. No more inline JS code. Drupal.settings are used to pass the needed parameters in JS code residing in the separate file.
  2. Correct context is passed to attachBehaviors.
  3. If Drupal.settings are changed by block content calculation process in the server AJAX handler, they are updated at the client side.
  4. Arbitrary JS code execution is removed. Updating of Drupal.settings and reattaching behaviors can be enough.
  5. Cached page includes original block contents wrapped by <noscript> tags for the situations when JavaScript isn't supported.
  6. Inline documentation is updated, hook_help() is implemented, README.txt is added.
  7. Variable deletion code in uninstall hook is changed.
  8. Now, additional variables are created for ajaxified blocks only, not for all blocks, so I decided not to use separate DB table, but rely on variables and their caching, since there will be just some additional variables in most cases.
  9. Block contents are not modified in POST requests (i.e. other AJAX requests).
avpaderno’s picture

Assigned: Unassigned » avpaderno
Status: Needs work » Needs review

Remember to change the status, when you upload new code.

Thanks for your patience. I am assigning this report to me because I will review the code tomorrow morning (which means between 18 hours from now).

avpaderno’s picture

Status: Needs review » Needs work
  1. /**
     * Implementation of hook_uninstall().
     */
    function ajaxblocks_uninstall() {
      // Get global variable array.
      global $conf;
      foreach (array_keys($conf) as $key) {
        // Find and remove variables that have the module prefix.
        if (strpos($key, 'ajaxblocks_') === 0) {
          variable_del($key);
        }
      }
    }
    

    The code could remove variables used by another module with a name starting with ajaxblocks_ (somebody could create a module named ajaxblocks_extension, or ajaxblocks_views, in example). Also, the code is scanning all the variables to get its own variables, when those variables should be well known once the code has been completed.

  2. See http://drupal.org/coding-standards to understand how a module should be written. In particular, see how the code should be formatted, and how constants should be written.
  3. /**
     * Additional submit handler for block settings form. Saves AJAX settings for the block.
     */
    function ajaxblocks_save_settings($form, &$form_state) {
      $param_name = 'ajaxblocks_' . $form_state['values']['module'] . '-' . $form_state['values']['delta'];
      $old_value = variable_get($param_name, '0');
      $new_value = $form_state['values'][$param_name];
      if ($old_value == $new_value) return;
      // Remove variable if its value is default (not using AJAX).
      if ($new_value == '0') variable_del($param_name);
      else variable_set($param_name, $new_value);
    }
    

    It's not clear to me the purpose of that code, which is not implemented in other modules. Why isn't the form using system_settings_form()?

  4.     // Set 'q' parameter in order to make arg() work correctly.
        $_GET['q'] = $_GET['path'];
        // Block content goes through theme functions.
        init_theme();
    

    It's not clear to me why that is required to make arg() work, and why the code is not restoring the original value of $_GET['q']; some other modules could need to use arg(), and they would get a value that doesn't match the path of the page currently viewed.

  5. /**
     * Implements hook_preprocess().
     */
    function ajaxblocks_preprocess(&$vars, $type) {
    }
    

    It would be better to implement hook_preprocess_page(), and hook_preprocess_block().

maximpodorov’s picture

Status: Needs work » Needs review
StatusFileSize
new4.33 KB

Thank you for your review, @kiamlaluno. I modified the module according to your remarks.

  1. Module settings are stored in separate DB table now, additionally, they are cached as one record in the system cache ('cache' table or memcache, etc.).
  2. Conditional operator formatting is fixed.
  3. Preprocess function is split into ajaxblocks_preprocess_page() and ajaxblocks_preprocess_block().

As for your questions, ajaxblocks_save_settings() was written this way in order to store only non-default (non-zero) values in variables. Now, it's rewritten for storing settings in DB. The module uses it's own settings saving code, since it's better to add AJAX configuration of the block to the system block configuration form.

In this code:

    // Set 'q' parameter in order to make arg() work correctly.
    $_GET['q'] = $_GET['path'];
    // Block content goes through theme functions.
    init_theme();

the module sets $_GET['q'] intentionally, in order to make any module or theme think the user is visiting the page for which we have to return the blocks. It has to set $_GET['q'], since arg() and drupal_is_front_page() functions use only this variable, and calling these two functions is a usual way in Drupal to decide what to show on the page.

avpaderno’s picture

Status: Needs review » Fixed

the module sets $_GET['q'] intentionally, in order to make any module or theme think the user is visiting the page for which we have to return the blocks. It has to set $_GET['q'], since arg() and drupal_is_front_page() functions use only this variable, and calling these two functions is a usual way in Drupal to decide what to show on the page.

As the module is using that trick for its own menu callback, I think it is more acceptable.

Thank you for your contribution! I am going to update your account.
These are some recommended readings to help with excellent maintainership:

You can find more contributors chatting on the IRC #drupal-contribute channel. So, come hang out and stay involved.
Thank you, also, for your patience with the review process.
Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.

I thank all the dedicated reviewers as well.

Status: Fixed » Closed (fixed)
Issue tags: -Module review

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

avpaderno’s picture

Component: Miscellaneous » new project application
Issue summary: View changes