I have created a node using CCK and need to modify it to display charts. The issue I am having is that this content type shows up on about 20 different pages within my site; I am using sort within views to rank these nodes on different data. I would like to have a different type of chart within each page (so one distinct chart type for each of the 20 pages), and the actual chart on each node will display data associated with that node.
I have looked into a number of different ways to do this and none of which seem feasible:
1. Using views_charts will not work because my view type is node, so I need to modify the actual nodes being displayed.
2. Thought about modifying node.tpl.php; however, I've read that putting php code directly into the body of your node through this method could cause security holes.
3. Putting PHP snippets into my actual content - this just seems insane and time consuming.
My next thought is to create a module with hook_form_alter() to alter the node prior to rendering and insert the chart. This "seems" like the best method, but my concern is that the code will need to check what node is being rendered and the URL of the page being rendered to determine what data to pull from SQL and what type of chart to display, I don't even know if this is possible / efficient.
Any thoughts on whether this is possible, a better way to go about this, or if I am just completely off base would be much appreciated.
Comments
You should use
You should use hook_nodeapi(). You load your data when $op == 'load', and you display it when $op == 'view'.
Full-time freelancer, always looking for work.
jaypan.com (my portfolio)
Appreciate the help...
So if I am understanding correctly; essentially at a high level it would look like this (need to clean up, but want to make sure it is accurate in theory):
<?php
function hook_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'load':
$sql = [Query for data on all nodes] ;
$db_result = db_query($sql) ;
case 'view':
if (code here to determine the node it is being rendered on page type 1) {
//insert code for chart display type 1 using $db_result and $node ;
}
else if (code here to determine the node it is being rendered on page type 2) {
//insert code for chart display type 2 using $db_result and $node] ;
}
break;
}
}
?>
I guess two questions 1. Are nodes being rendered one at a time where I would use something like if ($node->nid) or is all of the data being loaded for all of the nodes on the page (this is why I am currently querying all node data at once) and 2. Is it possible to add in condition statement I have above in brackets to determine the page the node is being rendered on in order to change the chart type?
Almost. You would do it like
Almost. You would do it like this:
<?phpfunction hook_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
switch ($op) {
case 'load':
// Load the custom data for the specific node that has been loaded (you can get the NID from $node->nid). In your case, this is the chart for that node. Attach the data to the $node object
break;
case 'view':
// Format the attached data, and add it to the $node->content array.
break;
}
}
?>
Full-time freelancer, always looking for work.
jaypan.com (my portfolio)
I was so close...
So it seems like all the data and the chart needs to be pulled in the case 'load' so I would perform: the SQL query for the data I need on that node, the if statement to determine the right type of chart, and then inserting the array into the correct chart code. Then in case 'view', I format as necessary and attach to the $node object.
This all makes sense, my only issue is that my chart code will look like a modified form of this (just a simple sample chart; I am working to modify for my needs).
<?php
function charts_graphs_test() {
$canvas = chart_graphs_get_graph('amcharts');
$canvas->title = "AmCharts Chart";
$canvas->type = "line";
$canvas->y_legend = "Y Legend";
$canvas->colour = '#808000';
$canvas->theme = 'keynote';
$canvas->series = array(
'Some Value' => array(9,6,7,9,5,7,6,9,7),
'Page Views' => array(6,7,9,5,7,6,9,7,3),
);
$canvas->x_labels = array('one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine');
$out = $canvas->get_chart();
return $out;
}
?>
Will it still be possible to attach it to the $node object, format it correctly, and add it to $node->content array to have it display in the node correctly?
Sure. You can add whatever
Sure. You can add whatever data you want to the $node object in the load function.
For example:
<?php
function get_object($nid)
{
$object = new StdClass;
$data = object_load($nid);
$object->something = $data->something;
$object->else = $data->else;
return $object;
}
function mymodule_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL)
{
if($op == 'load')
{
$node += get_object($node->nid);
}
elseif($op == 'view')
{
$node->content['something'] = theme('something', $node->something);
$node->content['else'] = theme('else', $node->else);
}
}
?>
You can see that an object is added to the node object in the load function, and the data that was added is formatted using theme functions in the view function.
The above is pure example, but you can add as much complexity as necessary, though of course you will want to try to keep it as slim as possible.
Full-time freelancer, always looking for work.
jaypan.com (my portfolio)
Thank You
Jay, I appreciate all of the help. I will spend the weekend wrapping my head around all of this and trying to implement. Take care!
Quick Additional Question...
Jay, thanks so much for this information. Couple follow-up questions. In your function get_object($nid) code above, this is where I would be performing the SQL query for the data I need for my charts. My thought is that I can pass on the current NID through to my SQL query as I have done in the below, would this work? My other questions are:
1. If these nodes are passed through views onto a page, is it possible to pass arg[x] of the URL through the module in order to render the right type of chart for that particular page on each of the nodes? So when the arg[x] changes, the chart would change to a different type. **UPDATED** So after more research it looks like I can use arg(1) in order to identify the view being displayed and have that along with $node->nid drive the correct SQL query and the correct type of chart. So please disregard.2. In the sample code you provided, you are passing content['something'] through to the theme during 'view.' In what I am working through below, I am actually trying to pass "return array('custom_node_chart_pie')" which is the chart array. Is this possible? In this case it looks like it would be $node->content['custom_node_chart_pie'] = theme ('custom_node_chart_pie', $node->'custom_node_chart_pie'). **UPDATED** Well now I am sort of flummoxed and have been working on this for almost a week now. I can call the code below, (removing the hook_nodeapi) in the body of a node with the following:
<?phpprint open_flash_chart_api_render('custom_node_chart_pie');
?>
but I can't figure out how to pass this information into the hook_nodeapi() and render it. The solution may be that I need to add the information to the node during load and then use node.tpl.php to print open_flash_chart_api_render('custom_node_chart_pie'). This would make sense; however, I am still unable to get the information into the node using node +=. Keep getting the white screen of death.
Any thoughts would be appreciated.
<?php
// Generate the correct chart based on the NID and insert the data from the SQL query
function custom_node_chart_pie() {
// grab the data on the node
$content_id = $node->nid ;
$sql = "SELECT value AS total, content_id, function AS category ";
$sql .= "FROM {votingapi_cache} ";
$sql .= "WHERE content_id = %d ";
$sql .= "GROUP BY value, function, content_id ";
$result = db_query(db_rewrite_sql($sql), $content_id);
// create new chart
$chart = ofc_api_chart();
// create pie chart element
$pie = ofc_api_element('pie');
// set some effects and colors
$pie->set('alpha', 0.8);
$fade = ofc_api_element('fade');
$bounce = ofc_api_element('bounce');
$bounce->set('distance', 5);
$pie->set('animate', array(
$fade,
$bounce,
));
$pie->set('colours', ofc_api_color_theme('retro-spanky'));
// set label and value for each pie slice
while ($data = db_fetch_object($result)) {
$slice = ofc_api_element();
$slice->set('label', 'Level ' . $data->category);
$slice->set('value', $data->total);
$pie->add('value', $slice);
}
// add pie element to the chart
$chart->add('element', $pie);
// render the chart
return $chart;
}
function custom_node_chart_open_flash_chart_api_data() {
return array('custom_node_chart_pie');
}
// 'Load' the chart and attach it to the $node object
function custom_node_chart_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if($op == 'load') {
$node += custom_node_chart_open_flash_chart_api_data();
}
// 'View' the chart and add it to $node->content array
elseif($op == 'view') {
$node->content['something'] = theme('something', $node->something);
$node->content['else'] = theme('else', $node->else);
}
}
?>
I don't know anything about
I don't know anything about open flash chart module, but looking at your code I see a few problems:
1) In this code:
<?phpreturn array('custom_node_chart_pie');
?>
You are returning an array with the name of a function. You aren't actually calling that function. What you need to do is call the function and return it like this:
<?phpreturn custom_node_chart_pie();
?>
2) The problem with the above is that in custom_node_chart_pie, you are trying to use
$node->nid, but you haven't passed the node object to the function, so the value will be empty.3) In the code I showed you in #1, you are returning an array, but you are trying to add that to the $node object in your hook_nodeapi(). Arrays cannot be added to objects. You can fix the above three problems by doing this:
<?php
function custom_node_chart_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if($op == 'load') {
$node->chart_data = custom_node_chart_pie($node);
}
}
?>
This calls the function directly, passes it the node object, and returns an object, added to the $node object, though not directly, but as a branch. This keeps it all contained, since that data isn't actually relative to the node, but is one contained set of data.
4) You copied my 'view' in hook_nodeapi, but you will have to adjust this to your own code, mine was just an example:
<?phpfunction custom_node_chart_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
// 'View' the chart and add it to $node->content array
elseif($op == 'view') {
$node->content['chart'] = theme([THEME_FUNCTION_NAME], $node->chart_data); // here we are passing the chart data to a theming function. You will have to replace [THEME_FUNCTION_NAME] with the actual name of the theming function that generates the chart from the data.
}
}
?>
Full-time freelancer, always looking for work.
jaypan.com (my portfolio)
Got it Working
Jay, thanks again for all of you help. I finally got it working, but in a slightly different manner. I was having a lot of trouble with the $op == 'view' portion of making this work. After creating my tpl.php file to create the chart from the data, I was getting operand fatal errors when trying to pass $node->content['my_chart'] = theme('my_chart_theme', $node->$chart_data). Instead I used the following code in my module:
<?php
//Generate the correct chart based on the NID and insert the data from the SQL query
function custom_node_chart_pie($nid) {
// grab the data on the node
$sql = "SELECT value AS total, content_id, function AS category ";
$sql .= "FROM {votingapi_cache} ";
$sql .= "WHERE content_id = %d AND function IN ('Male_percent_day', 'Female_percent_day') ";
$sql .= "GROUP BY value, function, content_id ";
$result = db_query(db_rewrite_sql($sql), $nid);
// create new chart
$chart = ofc_api_chart();
// create pie chart element
$pie = ofc_api_element('pie');
// set some effects and colors
$pie->set('alpha', 0.8);
$fade = ofc_api_element('fade');
$bounce = ofc_api_element('bounce');
$bounce->set('distance', 5);
$pie->set('animate', array(
$fade,
$bounce,
));
$pie->set('colours', ofc_api_color_theme('retro-spanky'));
// set label and value for each pie slice
while ($data = db_fetch_object($result)) {
$slice = ofc_api_element();
$slice->set('label', 'Level ' . $data->category);
$slice->set('value', $data->total);
$pie->add('value', $slice);
}
// add pie element to the chart
$chart->add('element', $pie);
// render the chart
return $chart;
}
// 'Load' the chart and attach it to the $node object
function custom_node_chart_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if($op == 'load') {
$node->chart_data = custom_node_chart_pie($node->nid);
}
}
?>
Then I used the dynamic content module to create a new php CCK field and put the following code in there:
<?php$return = custom_node_chart_open_flash_chart_render($node->chart_data)
?>
This is obviously not the best way to go about this given I am using a php field as a content type, but I figured I had bugged you enough.
Thanks so much for the help.
Ahh, sorry that was a typo on
Ahh, sorry that was a typo on my part. I needed to see the error to realize it. This:
<?php$node->content['chart'] = theme([THEME_FUNCTION_NAME], $node->chart_data);
?>
should have been this:
<?php$node->content['chart'] = array
(
'#value' => theme([THEME_FUNCTION_NAME], $node->chart_data),
);
?>
Full-time freelancer, always looking for work.
jaypan.com (my portfolio)