Hi!
I am new to Drupal (only 6 months) and I am writing my first module (code name: ActiveField). The module is fully documented on my blog:
http://www.sageofcode.com/?p=59
This module takes the values entered for pre-defined “source” fields and dynamically sets the values of predefined “target” fields via jQuery and AJAX. The “source” and “target” fields can be of any field type; i.e., they are not limited to drop down menu fields.
I think this module may be useful to the Drupal community as a whole (I welcome you feedback on this).
If so, I would like to submit it as a Contributed Module and updated and move the documentation into the drupal.org site proper.
I hope that's enough background.
Using the AJAX example in "Pro Drupal Development" [1st ed.] as a starting point, I have build the module.
But, unfortunately, it's not working. The module activates. But, when I look into the menu table, I do no see an evidence that the menu item was actually created. According to VanDkyk and Westgate (p. 47):
"The menu item type, such as MENU_CALLBACK or DEFAULT_LOCAL_TASK, is represented in the database by its decimal equivalent."
I do not suspect that I am missing any critical module (c.f. http://www.sageofcode.com/?p=59 ) and so I do not think it's an environmental issue.
I now have 6 books on Drupal and 2 on jQuery on my book shelf, several videos, and articles on the subject.
But, unfortunately, I am now losing gray hairs over this issue. I have gone through ever line and character in my code. I simply cannot slay this bug. I will continue reaching a higher level of ignorance, but if anyone could help me out, I would greatly appreciate it.
CODE
Here is the code for each file. I did not create a .install file because I am not creating or deleting any database tables based in the drupal database schema.
FILE: ActiveField.info
/** ActiveField
*
* This file registeres the ActiveField module with Drupal.
* The module can hence be enabled or disabled via admin interface
*
*/
name = ActiveField
description = "This module dynamically sets the value of a field based on another field(s) values."
version = 5.x-1.9.1
File: ActiveField.js
/** ActiveField.js
*
* JavaScript file with jQuery
* Used by ActiveField.module
*
*/
//$Id$
if(Drupal.jsEnabled)
{
$(document).ready(function()
{
// Parent field & respective event
// Adding change event to source Field
$('#edit-field-location-key').change(function(event)
{
// Call back function for AJAX call
var frmDrupal = function(data)
{
// convert the value from Drupal to JSON
var result = Drupal.parseJson(data);
// Set the child field(s)' value(s)
// setting the text for the "test" text field
$('#edit-field-capital-value').text(result);
}
//AJAX call
// URL: add/field-trip/ - maps to a Drupal function
// Parameters: null for now.
// Call back function: "fromDrupal"
$.get(add/field-trip, null, frmDrupal);
// preventing entire page from reloading
return false;
});
});
}
FILE: ActiveField.module
<?php
//$id$
/**
* @file
* A dynamic parent-to-child field module via Drupal, Ajax, and jQuery
*
*/
/**
*
* Implementation of hook_perm().
* Adding the "Use ActiveField" permission to Drupal's role-based access control page.
* Prevents Anonymous use.
*
*/
function ActiveField_perm(){
return array('Use ActiveField');
}
/**
* Implementation of hook_menu()
* Allows for the mapping for jQuery intercepted URL to a Drupal PHP function
* The Drupal PHP function will return the child field's value(s) to jQuery in JSON
*/
function ActiveField_menu($may_cache){
$items = array();
if($may_cache)
{
$items[] = array(
'path' => 'add/fishing-trip',
'callback' => 'ActiveField_getTargets',
'type' => MENU_CALLBACK,
'access' => user_access('Use ActiveField360')
);
}
return $items;
}
/**
*
* Theming function
*
*/
function theme_ActiveField()
{
// Loading the jQuery .js and CSS files
drupal_add_js(drupal_get_path('module', 'ActiveField'),'/ActiveField.js');
drupal_add_css(drupal_get_path('theme', 'garland'), '/style.css');
}
/**
*
* Called by jQuery
* Submits the parent field value and returns the child field's value(s) as a JSON
*/
function ActiveField_getTargets()
{
// NOTE: GET PARENT FIELD VALUE FROM jQuery TO BUILD sQL STRING
// this particular query is just for testing.
$sql="SELECT title FROM {node} WHERE type='location'";
$result = db_result(db_query($sql));
print drupal_to_js($result);
exit();
}
[Edited to add code tags: nevets]
Comments
To start I see the following
To start I see the following issues
theme_ActiveField: 1) I do not see where it is invoked. 2) No reason to include the theme style sheet
ActiveField.js vs ActiveField.module: ActiveField.js uses 'add/field-trip' for the path, ActiveField.module uses 'add/fishing-trip'.
ActiveField.js: add/field-trip needs quotes around it and should probably have a leading slash ie '/add/field-trip'.
Thank you. But, still not working :-(
nevets,
Thank for for the feedback. I have made the code changes that you suggested, but the module still does not work.
Is there anything else I can do?
I trying to debug this, I came up with some questions...but I am not sure how to answer them:
1. Is the AJAX call being made to Drupal?
2. Does the _getTargets() method get called?
3. Does the SQL query get executed?
4. Does the call back function in the AJAX function get called?
Is there a way to "step through" the code in "debug" mode and see what the values of certain variables?
Is there a way to see a "stack trace" of all the functions that get called?
Thank you again!! Much appreciated.
Take care.
www.sageofcode.com
There are two parts to your
There are two parts to your puzzle, the client/js side and the server/php side. There are debuggers for PHP that allow you to step through the code though I have never bothered to set one up. On the client (browser) side if you use Firefox the firebug extension is really useful for tasks like this, It includes a debugger that lets you place breakpoints in you javascript.
As nevets mentioned, firebug
As nevets mentioned, firebug is your best friend here to determine whether the ajax callback is being invoked, and to examine what is being returned (use the 'console' tab in firebug).
The cheap and easy way to debug the rest (ie: PHP side) of it is to place 'print "DEBUG: here"; exit;' in code locations that you are curious about.
Also try a "DELETE FROM cache_menu" on your database.
Minor point the 'print
Minor point the 'print "DEBUG: here"; exit;' does not work very well for callbacks that are expected to return javascript.
Also over 'print' I prefer to use drupal_set_message(), something like
drupal_set_message("FunctionName: entered");
as it does not interfere with the normal output from the page.Agreed, as mentioned the
Agreed, as mentioned the "print" statements should be used for the 'PHP proper' portions, while those that are expected to return javascript are best monitored via firebug (although you could, if you wished, monitor the printed output through firebug as well, and in realtime).
I prefer the quick and dirty "show me what I want to know and terminate immediately", but nevets makes a good point that this method may be jarring and does prevent the normal rendering of the page, which could be desirable or undesirable depending on what you are attempting to accomplish, and what you need to see.
Update...
Thank you for the debugging info. Very helpful.
Starting with client side debugging via FireBug, I noticed that no posts show up in the "console" tab as I go through the use case.
That is, after I select a "date" and a "location", the change event on the "location" field should fire an Ajax call--- which it does not.
Therefore, it looks like the URL is not being mapped to the Drupal function and the Javascript file is not being loaded?
Is my understanding correct? Any suggestions?
Aside:
The current version of the jQuery library that I am running is , does this version support AJAX? Should I update to version ?
Also, what should I look for in the menu table to verify that the menu item that for the URL was created? I looked but I did not "see" any evidence that a row was created.
File: ActiveField.info
File: ActiveField.js
File: ActiveField.module
Hmm, ok, let's debug this
Hmm, ok, let's debug this ..
First thing to do is to clear your cache, particularly the cache_menu table (via MySQL, the command line, or devel, whichever suits you)
Then, check via firebug that ActiveField.js is actually being included when you visit add/field-trip. You can do this by clicking the js tab in firebug and going through the list of files. You should see ActiveField.js .. if so, select it and make sure the contents of your js file are show up.
To test the path simply
To test the path simply enter it in your browser URL bar, so if your site is at www.example.com, use http://www.example.com/add/field-trip (assumes use have clean urls, otherwise add 'add/field-trip' after 'q='). If the path does not work visit the menu administration page and try again.
Something I notice is the only place you show you add ActiveField.js is in ActiveField_getTargets() which is the wrong place since that function is simply the callback for the ajax call. At some point you need to add ActiveField.js for the page that displays the form.
Another suggestion is to use $.ajax instead of $.get, while a little harder to use it allows you define a callback for failures (can make debugging easier).
Also firebug can be useful by looking at the net tab.
Ah, nevets caught it. He's
Ah, nevets caught it. He's right -- you aren't including your javascript which has the AJAX callback anywhere except inside of the callback itself, so it will never get added.
I am finally breaking stuff...
nevets and drawk, once again, great feedback. THANK YOU!
I did some digging, thinking, and hacking...
In summary:
1. hook_nodeapi appears to be the appropriate place to load the .js file because it allow one to modify a node as it's being created and updated. I hence implemented the hook (although, most likely improperly).
2. After reactivating the new version of the module, I cleared my cache via Devel.
3A. I set the site up for clean URLs. I have been meaning to do it; just never got to it. The following path works:
node/add/location (this is for the first content type in my example: http://www.sageofcode.com/?p=59 ). But, now the path node/add/field-trip (which is the path that is mapped the Drupal function) doe NOT work.
I get the following error message:
"Palo Alto"
Queries taking longer than 5 ms and queries executed more than once, are highlighted.
ms # where query
It almost looks like Drupal is having a hard time rendering the "field-trip" page.
3B. No post showed up in the Firebug console tab.
4. I just finished watching the Lullabot DVD on Understanding Drupal. I feel like I am at the "I suck" inflection point of the Drupal learning curve. :-)
Once again, thank you(!) in advance for your feedback.
Here is the updated code:
File: ActiveField.js
File: ActiveField.module
www.sageofcode.com
Your use of hook_nodeapi is
Your use of hook_nodeapi is node going to get you where you want as the cases for $op being 'insert' or 'update' happen after the form is submitted. It appears that the case where $op is 'prepare' should work though hook_form_alter is generally considers more appropriate.
Using 'node/add/field-trip' as your callback is probably risky especially if 'field-trip' is a content type. In general the path node/add /{content -type} is used by the node module to add a new node of the type specified by {content-type}, ex node/add/page.
Your callback should be unique to your module and used only for handling the ajax part of your task.
Wrong approach?
Nevets,
I used the 'prepare' option and the result it the same.
I also did some investigation into hook_form_alter, which requires a form id. Based on my investigation, it looks like all CCK content types have the same generic form id: "node-form". For example:
Given this non-unique form id, I am not sure that hook_form_alter will work with CCK content types. If the form id is non-unique, how do I differentiate between different content types? I could very well be wrong. If so, please correct me.
In addition, the use of "node" in the URL paths in the AJAX $get and hook_menu() functions causes issues. When I use it, I get a white page with the error:
...and the .js file does not appear to be loaded because no posts show up in the console tab.
Moreover, when I do not use clean URLs, then all content types will be created via ?q=node/add/. When I use ?q=node/add/field-trip or 'add/field-trip' or '/add/field-trip', the content type is displayed properly, but the .js file does not appear to be loaded because no posts show up in the console tab.
I thought I was 95% of the way there, but now I am starting to think that the approach identified in "Pro Drupal Development" 1st ed. for adding AJAX to Drupal modules via jQuery (Chapter 17) may not work for CCK content types. If so, are there alternative approaches?
I have been working on this module for a good amount of time with incremental progress and would love to just get it working.
I welcome any feedback.
Take care. :-)
www.sageofcode.com
The "Queries taking longer
The "Queries taking longer than 5 ms and queries executed more than once, are highlighted.
ms # where query" you are seeing is from Devel module. Try disabling Devel or add $GLOBALS['devel_shutdown'] = FALSE to your function.
Quick Update: 1 of 2
drawk, I will try this latter today.
After some more hacking and investigation, I stand corrected!
Each form in Drupal has a unique form ID, including CCK based content types:
"Modifying Forms in Drupal 5 and 6"
http://www.lullabot.com/articles/modifying-forms-5-and-6
Lullabot rocks!
I will exploit the form ids and do some hacking with hook_form_alter() an post my results in "Quick Update: 2 of 2".
www.sageofcode.com
Quick Update 2 of 2
Based on nevets' feedback, (1) I abandoned the use of the hook_node-api() and implemented the hook_form_alter() and (2) I gave the callback a unique path to just handle the AJAX call (i.e. '/ActiveField/field-trip'). The updated code is as follows below. But, the module is still not working.
If I understand the sequence of events correctly...
1. When I got to "node/add/field-trip", the field-trip form is built and the ActiveField_form_alter() function is called and the .js file is added.
2. When I click the NET tab in Firebug, followed by clicking the "JS" tab, I should see the ActiveField.js file.
Note: Unfortunately, I do not see the ActiveField.js file.
3. When the drop down menu is opened and a value is selected, the change event will trigger the AJAX call and the CONSOLE tab of FireBugs should display the AJAX call.
Note: Unfortunately, I do not see the AJAX call.
4. Drupal will receive the Ajax and return a JSON
4. The "target" field is updated.
Aside: What I find interesting is that even with deleting the cache and a unique path for the AJAX call, the menu table still does not have a row for this menu item.
I have also updated the "ActiveField Specification" article (http://www.sageofcode.com/?p=59). How I have the module working, I will work on getting it on the contributed modules list and add documentation on the drupal.org site itself. My thought is that this module can be generalized for wider use. I welcome feedback on this idea also.
ANY help or feedback will be much, much, much, appreciated.
Here is the updated code.
www.sageofcode.com
A couple things to try,
A couple things to try, first in ActiveField_form_alter() before the 'if' statement add
drupal_set_message("ActiveField_form_alter($form_id, ...)");
to make sure that a) the function is being called and b) you have the correct form id. Visit node/add/field-trip to see what prints in the message area.Also, after visiting the menu admin page (it will force the menus to rebuild) visit ActiveField/field-trip to see what happens.
Quick update 3
Nevets,
Thank you for the feedback on the "printf" statement (sorry, just had a C moment).
Ok, after some debugging, here are the results:
1. I added
drupal_set_message("ActiveField_form_alter($form_id, ...)");
as you mentioned, and when I went tonode/add/field-trip
I got:"ActiveField_form_alter(field_trip_node_form, ...)"
, which is the same form ID as in the IF statement of
ActiveField_form_alter($form_id, &$form)
This also validates that function is being called. But, when I went to to the NET -> JS tab in FireBug, I did not see ActiveField.js.
When I went to
ActiveField/field-trip
, I got:"Page not found"
2. Feeling creative, I added
drupal_set_message("ActiveField_menu($may_cache,...)");
just before the if statement in
ActiveField_menu($may_cache)
And after I activated the updated module, I got the following messages on the Admin page:
When I went to:
node/add/field-trip
I got:When I went to the menu table, I still to not see an entry for the call back path.
I do see two entries for the two content types used in this module:
Could the if logic in
ActiveField_menu($may_cache)
be the issue?www.sageofcode.com
Quick Update 4
Eureka!
Ok. After some hacking, I got the .js file to load.
In short, here are the modification that I made to the code:
1. Removed the "/" from the paths in the following functions:
2. Replaced "," with "." in the function:
This is what was causing the .js file from not being loaded.
But, alas I am still stumped. The final bug appears to be in the MENU_CALLBACK path.
(1). The .js file is loaded and the following error is displayed in the Console tab of FireBug:
I click the "Load Response" button and I get this error:
When I got to the http:///ActiveField/field-trip , I get
What I find interesting is that when I go to the Menu callback of the "plus1" module example in chapter 17 of "Pro Drupal Devlopment" 1st Ed., I get a blank white page. The "plus1" module works just fine on my site. I have compared every character in the ActiveField_menu() function, and I cannot see an issue. I have confirmed that the code is executed. In fact, the access privilege is created!
Another interesting observation is that the menu table does not have a row for the MENU_CALLBACK for the plus1 module; at least as far as I can see.
The issue appears to be with the actual, path. But, I am out of ideas on how to debug this.
I welcome any feedback.
Thank you in advance.
Aside: once I have the module working, I am going to create a lesson for the Drupal Dojo.
www.sageofcode.com
Subscribing, greetings,
Subscribing, greetings, Martijn
Quick Update 5
After some more debugging and hacking, I got the module working further.
The reason the callback was not working was because there is a bug in the SQL code in the
ActiveField_getTargets()
function. So, for now, I am just returning a string to get the module "walking", so to speak.The string is returned, as can be seen in the Console tab of FireBug and by going to
ActiveField/field-trip
However, the actual field in the page is not updated. Using Firebug, the following code updates the field on the page:
But, the same code in call back function in the ActiveField.js file does not update the field.
If anyone has any ideas on how to debug or fix this, I would greatly appreciate it.
Here is the updated code:
www.sageofcode.com
Quick Update 6
I realize this thread is getting long, but for those following this...here is another quick update.
After some additional debugging, the code inside:
..does not appear to be getting executed.
I am not sure why this function is not being entered.
I determined this by putting code to hide and show the target field, and nothing happened.
I tested this code via FireBug, and it worked.
I am truly stumped.
Any feedback welcome.
www.sageofcode.com
I know I'm late to the
I know I'm late to the party... by a couple years... but this is EXACTLY the functionality I need for a project I am working on. Was this ever completed? Is there another module that can do this?
looking also for tat module
looking also for tat module or any similar ones.
Thanks for letting me know where to get a hand on it .
Sincerely,
Will