Module with AJAX -- best way to lay out directories?

jsm174 - January 17, 2007 - 02:42

Hello.

A while ago I wrote some code to search a directory for images and dynamically create a date selector and thumbnail container. It consisted of a PHP file and some Javascript. When the date selector is changed, it uses AJAX (to the PHP file) to fetch the image numbers for the selected day. The Javascript then dynamically modifies the DOM to display the correct thumbnails.

I'm new at Drupal, and I figured I'd start learning how to writing a module. From looking at the core modules, I can't really find anything to point me in the right direction. I'm having a problem laying out my directories.

Here is the layout of my server

www/myserver/drupal - base install
www/myserver/drupal/media/photos/155W - photos directory

www/myserver/drupal/modules/mediaselector/mediaselector.css
www/myserver/drupal/modules/mediaselector/mediaselector.js
www/myserver/drupal/modules/mediaselector/mediaselector.module
www/myserver/drupal/modules/mediaselector/mediaselector.php
www/myserver/drupal/modules/mediaselector/mediaselector.inc -- common functions to module and php

In my page I reference the module passing in the directory.

mediaselector_load( "media/photos/155W" );

When the page is referenced, the current working directory for the php file is www/myserver/drupal.
This works great and my images are found correctly.

The problem I'm having is in the AJAX call in the javascript code. When I do a GET of mediaselector.php,
the current working directory is now www/myserver/drupal/modules/mediaselector. So the images are not found because it's looking at www/myserver/drupal/modules/mediaselector/media/photos/155W

To get around this, I could hack in a "../../" but I'd rather do it a more standard/best practice sort of way.

Also, is it not a good idea to have a php file directly in a module directory?

Any ideas/comments would be greatly appreciated.

Thanks,
-- Jason

Incorporate mediaselector.php into your module

nedjo - January 17, 2007 - 05:27

as a menu callback would be the Drupal way of doing this. See the example of upload_js() in upload.module. (Not the specific code, but the way a menu item leads to that function--see the first item declared in upload_menu()). That way, the base url remains the same as the rest of the Drupal site.

Use internal Drupal files instead of the PHP Script

dwees - January 17, 2007 - 05:37

I actually Ajax as well to call some PHP to update the database, but the principle is the same:

What I do is define a new variable which is the path to my module since I want to be able to add a CSS file and a Javascript file. Then I create a MENU_CALLBACK so that when users go to a particular URL, they get to see a list of all of the book pages in order and showing the parent/child relationships. This same MENU_CALLBACK is also used for the PHP return function of the Ajax script, and all I do is test for the existance of the $_POST['whatever'] variable to do the switch.

After that it becomes easier to determine the paths. Heck you could define them at the beginning and use absolute paths.

<?php
// $Id: bookgui.module,v 1.0 2007/01/07 20:02:48 dwees Exp $

/**
* @file
* Enables users to modify the structure of a Drupal book using a Graphical User Interface.
*/

define('BOOKGUI_PATH', drupal_get_path('module', 'bookgui'));

/**
* Implementation of hook_help().
*/
function bookgui_help($section) {
    switch (
$section) {
        case
'admin/help#bookgui':
           
$output = '<p>'. t('This module allows authorized users to modify the tree structure of a Drupal book. ') .'</p>';
            return
$output;
        case
'admin/modules#description':
            return
t('Allows an authorized user to modify the page structure of a book');
        case
'admin/book/gui':
            return
t('This page allows you to modify the structure of a book by clicking and dragging the title of each book to the appropriate location.  If you drag a book page onto another book page\'s handle, the dragged book should be inserted before the 2nd book.  If you drag a book page onto the right side of another book page, it should become a child of that book page.  <s>You can also drag the orphaned book pages below to a location in the current book.</s><em> edit: not yet</em>');
    }
}

/**
* Implementation of hook_perm().
*/
function bookgui_perm() {
    return array(
'modify book structure.');
}

/**
* Implementation of hook_access().
*/
function bookgui_access($op, $node) {
    global
$user;
   
   
/*if ($op == 'view') {
        if (user_access('modify book structure.')) {
            return TRUE;
        }
    }*/
   
return TRUE;
}

/**
* Implementation of hook_menu().
*/
function bookgui_menu($may_cache) {
   
$items = array();

    if (
$may_cache) {
       
$items[] = array(
           
'path' => 'admin/book/gui',
           
'title' => t('Book Management'),
           
'callback' => 'bookgui_all',
           
'access' => user_access('modify book structure.'),
           
'type' => MENU_NORMAL_ITEM
       
);
    }
   
    return
$items;
}

/*
* implementation of Drupal's theme functions
*/
function bookgui_all() {
   
    if (isset(
$_POST['struct'])) {
   
        echo
bookgui_database_all();   

    } else {
           
       
theme_add_style(BOOKGUI_PATH .'/bookgui.css');
       
drupal_add_js(BOOKGUI_PATH.'/bookgui.js');
       
       
$book_top_page = 0;
       
$levels_deep = 5;
       
$emulate_book_block = true;
       
       
$current_lineage = array();
       
       
$result = db_query(db_rewrite_sql('SELECT n.nid, n.title, b.parent, b.weight FROM {node} n INNER JOIN {book} b ON n.nid = b.nid WHERE n.status = 1 ORDER BY b.weight, n.title'));
       
        while (
$node = db_fetch_object($result)) {
          if (!
$children[$node->parent]) {
           
$children[$node->parent] = array();
          }
         
array_push($children[$node->parent], $node);
        
          if (
arg(0) == 'node' && is_numeric(arg(1)) && arg(1) == $node->nid) {
           
$_temp = book_location($node);
            foreach (
$_temp as $key => $val){
             
$current_lineage[] = $val->nid;
            }
           
$current_lineage[] = arg(1);
          }
        }
       
       
$output = '<br /><div id="bookwrap">'."\n";
       
$output .= book_struct_recurse($book_top_page, $levels_deep, $children, $current_lineage, $emulate_book_block);
       
$output .= '</div><br /><div id="notices" style="display:none"></div>'."\n";
       
$output .= '<br /><form id="bookForm" action="javascript:void(0)">'."\n";
       
$output .= '<label>Undo all moves since the last database update</label><br />'."\n";
       
$output .= '<input id="undoMove" type="button" value="Undo Moves" onclick="undoAllMoves();" /><br />'."\n";
       
$output .= '<label>Update the database with the current book structure.  This cannot be undone!</label><br />'."\n";
       
$output .= '<input id="bookFormButton" type="button" value="Update Book" class="update" onclick="this.disabled = true; updateParents();"/>'."\n";
       
$output .= '</form>'."\n";
       
$output .= '<script type="text/javascript">
                        var li = document.getElementById("bookwrap").getElementsByTagName("li");
                        for (var i = 0; i < li.length; i++) {
                            if (/sortable/.test(li[i].parentNode.className)) {
                                var span = li[i].getElementsByTagName("span")[0];
                                Drag.init(span, li[i]);
                            }
                        }
                        oldPosition = document.getElementById("bookwrap").innerHTML;               
                    </script>'
."\n";
       
        return
$output;
    }
}

function
book_struct_recurse($nid, $levels_deep, $children, $current_lineage = array(), $emulate_book_block = true) {
   
$struct = '';
    if (
$children[$nid] && ($levels_deep > 0 || ($emulate_book_block && in_array($nid, $current_lineage)))) {
     
$struct = '<ul class="sortable boxy">'."\n";
      foreach (
$children[$nid] as $key => $node) {
        if (
$tree = book_struct_recurse($node->nid, $levels_deep - 1, $children, $current_lineage, $emulate_book_block)) {
         
$struct .= '<li id="item'.$node->nid.'">';
         
$struct .= '<span id="handle'.$node->nid.'" class="handle">&nbsp;</span>';
         
$struct .= $node->title;
         
$struct .= $tree;
         
$struct .= "</li>\n";
        }
        else {
          if (
$children[$node->nid]){
           
$struct .= '<li id="item'.$node->nid.'">';
           
$struct .= '<span id="handle'.$node->nid.'" class="handle">&nbsp;</span>';
           
$struct .= $node->title;
           
$struct .= "</li>\n";
          }
          else {
           
$struct .= '<li id="item'.$node->nid.'">';
           
$struct .= '<span id="handle'.$node->nid.'" class="handle">&nbsp;</span>';
           
$struct .= $node->title;
           
$struct .= "</li>\n";
          }
        }
      }
     
$struct .= "</ul>\n";
      return
$struct;
    }
}

function
bookgui_database_all() {
   
$output = "";
    if (isset(
$_POST['struct'])) {
       
$tmp = $_POST['struct'];
       
$tmp = explode(":", $tmp);
        for (
$i = 0; $i < count($tmp) - 1; $i++) {
           
$data[$i] = explode(";", $tmp[$i]);
           
$result = db_query(db_rewrite_sql('UPDATE {book} SET parent = '.$data[$i][0].' WHERE nid = '.$data[$i][1] ));
        }
        if (
$result) {
           
$output .= "true";
        } else {
           
$output .= "false";
        }       
    } else {
       
$output .= "false";
    }
    return
$output;
}
?>

Basically the MENU_CALLBACK allows you to define a regular Drupal path

Awesome. Got it to work!

jsm174 - January 25, 2007 - 13:30

Sorry for taking such a long time to respond. I finally had a chance to understand your post and implement menu callbacks in my module. It works awesome! Thank you very much!

The more I play with drupal, the more I'm enjoying it! The module system seems to be very well thought out.

I did notice that I had to remove the $may_cache if block, because the callbacks wouldn't be recogonized.

if ($may_cache) {
}

Also I have another callback that 1) loads or 2) generates and loads thumbnail images. This seems to be a little bit slower, so I'm not sure of the any extra overhead before the callback is actually reached. I'm not sure if I just have to play with my apache cache settings, etc.

Thanks,
-- Jason

 
 

Drupal is a registered trademark of Dries Buytaert.