Treegrid expansion when I click the +

I am trying to get treeGrid going.

I have a somewhat functional callback program based on http://bdn.backbase.com/node/4062. It responds to the read request and looks up a mysql database to send back information.

The problem I have is that when I click on the little + sign to expand a node, the new data shows up at the same level on the tree rather than as expanded braches. See the attached picture.

When the tree is first created, it is created with no data and it calls the callback script which detects that there is no parent and sends the first record. OK so far.
This is what is sent to make the first screen

<?xml version="1.0" encoding="UTF-8"?>
<bom totalRecords='1'>
  <part id='157' parentId='' hasChildren='true'>
      <assembly_n>157</assembly_n>
      <part_n>157</part_n>
      <reference>default reference </reference>
      <comment>this is a comment</comment>
      <urn>3456</urn>
  </part>
</bom>

Then I click the + sign and it calls back and I send the new info for the new nodes
<?xml version="1.0" encoding="UTF-8"?>
<bom totalRecords='2'>
  <part id='158' parentID='157' hasChildren='true'>
    <assembly_n>157</assembly_n>
    <part_n>158</part_n>
    <quantity>1.000</quantity>
    <reference></reference>
    <comment></comment>
    <urn>661</urn>
  </part>
  <part id='353' parentID='157' hasChildren='true'>
    <assembly_n>157</assembly_n>
    <part_n>353</part_n>
    <quantity>3.000</quantity>
    <reference></reference>
    <comment></comment>
    <urn>662</urn>
  </part>
</bom>

bom means bill of material. A bill of material consists of a number of parts and each part may have sub parts and they can have sub parts and so on. Ideal thing to be displayed in a treegrid format.

So here we should see

157
  158
  353

ie part 158 and part 353 are sub parts of part number 157

So the question is, how should I format my XML so that parts 158 and 353 show up as sub parts of part 157.

Thanks.

ParentID

Hi,

Just struggled through a 4 level treeGrid using the examples.

In the examples the parent-id is referenced as '@parentId' and not '@parentID'.
Are you shure your dataSource references the correct name.

<part id='157' parentId='' hasChildren='true'>
<part id='158' parentID='157' hasChildren='true'>

Resolved - Thank you

Hi Joop,
You hit the nail on the head. It was the capitialisation.
ParentId is correct. ....ID is wrong.
Thankyou.

It seems that backbase is very case sensitive.
I can't use for example. Have to use .

FYI, here is my call back routine and the html bit that calls it.

<!---->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- This example is intnded to load the tree from a MYSQL DATABASE stored on the server
     and served from PHP
     References:
         Listgrid example "remode data"
         Tree grid example:
         http://localhost/backbase/Backbase_4_1_2/demos/explorer/indexChameleon.html#|examples/treeGrid.xml
         
         
         I dont understand why the <b:dataSource
       e:behavior="b:localData"
           is local data and yet with the datacontainer we are retrieving data from a remote source.
-->

<html>

<head>
        <title>BOM Tree Grid PHP MYSQL Data</title>
        <script type="text/javascript" src="../Backbase_4_1_2/engine/boot.js"></script></head>
<body>
        <!-- the script type="application/backbase+xml"  tag, tells the engine to start doing its job.
                this could be <a href="http://bdn.backbase.com/node/3959
" title="http://bdn.backbase.com/node/3959
">
http://bdn.backbase.com/node/3959
</a>            -->
  <script type="application/backbase+xml"
          xmlns:b="http://www.backbase.com/2006/btl"
                  xmlns:bf="http://www.backbase.com/2007/forms"
          xmlns:c="http://www.backbase.com/2006/command"
          xmlns:d="http://www.backbase.com/2006/tdl"
          xmlns:e="http://www.backbase.com/2006/xel"
          xmlns:xi="http://www.w3.org/2001/XInclude"
                  xmlns:xs="http://www.w3.org/2001/XMLSchema"
                  xmlns:smil="http://www.w3.org/2005/SMIL21"
          xmlns="http://www.w3.org/1999/xhtml"
                  >

       
          <xi:include href="../Backbase_4_1_2/bindings/config.xml" />
         
        <div>
                <e:handler event="click" type="text/javascript">
                     alert('This is a working Backbase installation');
                </e:handler>
                Click here to check for proper installation...
        </div>
        <!--1 First we define the data source
              name = The value of this attribute will be used as a match to the dataObserver
                         or other data widget that wants to use this dataSource.
                  e:behavior=   "b:remoteData"  b means it is from btl
                                "b:localData"
                  dataType  =   "text/json"
                                "application/xml"
                  url       =   The name of the script that will be called to handle the data server side.
                 
                -->

               
        <b:dataSource  
                           name="remote"
                           e:behavior="b:remoteData"
                           dataType="application/xml"
                           url="bom-dataprovider-tree-mysql.php"
                           method="POST"
         >

         <!--2 we define the data schema
            The dataSchema and dataField elements let you easily
                update your query when the underlying data structure has changed.
                The dataSchema is a child of           
        -->

        <b:dataSchema>
                <b:dataField name="identifier" select="@id" />
                <b:dataField name="parent" select="@parentId" />
                <b:dataField name="expandable" select="@hasChildren" />
        </b:dataSchema>
        <e:handler event="error" type="application/javascript">
                alert(event.message)
    </e:handler>
    </b:dataSource>
       
        <!--3 Invoke the tree
             
        -->
   
        <b:treeGrid
                dataSource="remote"
                openQuery="@open"
                width="100%" height="100%"
                populate="always"
        >

        <!--4 Name the columns
              select= is the field that the system looks for in the xml data
                  label is the name printed at the top of the column
                  The xml data looks like this
                  <movie id="film-2" parentId="film-0">

                        <new></new>
                        <title>The Godfather</title>
                        <genre>Crime</genre>
                        <rating>91</rating>
                  </movie>
                 <part id="42" parentId="">
                  <assembly_n>42</assembly_n>
                  <part_n>123</part_n>
                  <reference>bla bla </reference>
                  <comment>this is a comment</comment>
                  <urn>3456</urn>
                 </part>
        -->    
        <b:treeGridCol select="assembly_n" label="Assembly_n" width="200px" />
        <b:treeGridCol select="part_n" label="part_n" width="100px" />
        <b:treeGridCol select="reference" label="reference" width="100px" />
        <b:treeGridCol select="comment" label="comment" width="100px" />
        <b:treeGridCol select="urn" label="urn" width="50px" />
        </b:treeGrid>
       
    </script>
 </body>
</html>

and the callback php.

<?php
// dataprovider-tree-mysql.php
//
// This is the server side file that processes requests from the treegrid
//
// REFERENCES
// 1 <a href="http://bdn.backbase.com/node/4062" title="http://bdn.backbase.com/node/4062">
http://bdn.backbase.com/node/4062</a>  --> dataProviderMysql.php
//   This script was given as an example for listgrid.
//   In the .chm help file, we are told that treegrid inherits from listGridBase
//   So it is probably directly usable.
//
// -------------------------------------------------------
// DEBUGGING FILE
// Open a file to write debug info to.
// Note $_REQUEST
//      An associative array consisting of the contents of $_GET, $_POST, and $_COOKIE.
//      This is a 'superglobal',
//$fp = fopen('frank-bacbase-debug.txt',"a+");  // append mode
$fp = fopen('frank-bacbase-debug.txt',"w");  // write new file
fwrite($fp, "\n opened file\n");
$request_information = print_r($_REQUEST, true);
$request_information = str_replace("\\\"","\"",$request_information);
fwrite($fp, $request_information);     
// -----------------------------------------------------------
// MYSQL
//--------- MySQL variables
        $host = "localhost";
        $login = "root";
        $password = "password";
        $dbname = "bomtreetest";
        $table = "boms";

   $db_link = mysql_pconnect( $host, $login, $password);
   if (!$db_link)
                {
                 // Could not connect to mysql
                 fwrite($fp, "***ERROR: can not connect to MYSQL");
                 $temp =          "mysql_errno".mysql_errno().": ".mysql_error() ;
                 fwrite($fp, $temp);
                }
        else
                {
                 // Successfully connected to mysql
                 //fwrite($fp, "Connected to mysql OK");
                };

        //global variable for database access
        $db_selected = mysql_select_db( $dbname, $db_link) ;
        if (!@mysql_select_db( $dbname, $db_link) )
                {
                 $temp = "\n***ERROR: Can not select table".$dbname."mysql_errno=".mysql_errno().": ".mysql_error() ;
                 fwrite($fp, $temp);
                }
        else
                {
                // Successfully opened the table
                 //$temp = "\nConnected to mysql table OK ".$dbname;
                 //fwrite($fp, $temp);
                } ;
// Now we have successfully connected to mysql
// --------------------------------------------------------------------------------------
// Have a look at the data passed to this script.  
// So that we can handle getting the data in either $_POST or $_GET,
// we use $_REQUEST
//
// 1 SEND HEADERS
//   We check the stuff that was sent from the treegrid and send the appropriate header
//   if it is not defined we assume 'json' type
//     if n is negative then the returned string starts n characters from the end.
// Ref 1 does it like this
//      $dataType = isset($_REQUEST['dataType']) && strtolower( substr(urldecode($_GET['dataType']), -3) == 'xml') ? 'xml' : 'json';
        if  (isset($_REQUEST['dataType']))
                {
                $dataType = substr(urldecode($_REQUEST['dataType']), -3) ; // get the last 3 characters
                $dataType = strtolower($dataType);
                if ($dataType <> 'xml')
                        {
                        $dataType = 'json';
                        };
                }
        else
                {
                // dataType not specified.  This is where we set the data type we will default to
                // For now we will go with xml
                // Note that this is different to reference 1 which defaults to json.
                $dataType == 'xml';
                };
        //$temp = "\nData Type ".$dataType; fwrite($fp, $temp);

        if( $dataType == 'xml')
                {
                header('Content-type: application/xml');
                }
        else
                {
                header('Content-type: application/jsonrequest');
                };
        // This is a bit that is not in dataProviderMysql.php
        // When it is removed, we get an error.  Dont know why yet!
        echo '<?xml version="1.0" encoding="UTF-8"?>' ;

// ---------------------------------------------------------------------------
// 2 Determine REQUEST TYPE
//   Ref 1 does it like this
//   $requestType = strtolower( substr(urldecode($_GET['requestType']), -3) == 'xml') ? 'xml' : 'json';
//   This is a bit too tricky for me so will will do it more step by step
        if  (isset($_REQUEST['requestType']))
                {
                $requestType = substr(urldecode($_REQUEST['requestType']), -3) ; // get the last 3 characters
                $requestType = strtolower($requestType);
                if ($requestType <> 'xml')
                        {
                        $requestType = 'json';
                        };
                }
        else
                {
                // requestType not specified.
                // Default to making it the same as the dataType
                // Note that this is Same as reference 1.
                $requestType = $dataType;
                };
        //$temp = "\nRequest Type ".$requestType; fwrite($fp, $temp);
// ----------------------------------------------------------------------------
// 3 PROCESS REQUESTS
//   For now we are going to assume that the requests only come in json form.  They look like
//   [request] => {"read":{"attributes":{"select":{"parent":""},"page":"1","rows":""}}}
$Requests = decodeRequest( $requestType);
//   and when they have decoded, they look like
//Decoded Request = Array
//(
//    [read] => Array
//        (
//            [attributes] => Array
//                (
//                    [select] => Array
//                        (
//                            [parent] =>
//                        )
//
//                    [page] => 1
//                    [rows] =>
//                )
//
//        )
//
//)
if(isset($_REQUEST['request']))
        {
         // Yes there is a request
         //$temp = $_REQUEST['request'] ;
         //$temp = urldecode($_REQUEST['request']);
         //$temp = str_replace("\\\"","\"",$temp);
         //$temp = "\nGot a request ".$temp; fwrite($fp, $temp);
        }
else
        {
         $temp = "\n???? No request "; fwrite($fp, $temp);
        }
//$temp = print_r($Requests, true);
//$temp = "\nDecoded Request = ".$temp; fwrite($fp, $temp);

// Loop through each request
//
$foreach_counter = 0;
foreach($Requests as $request => $value)
        {
        $foreach_counter = $foreach_counter + 1;
        $temp = "\n $foreach_counter Decoded Request key=".$request." value=".$value; fwrite($fp, $temp);
        if ($request == 'read')
                {
                // Now read comes with an array telling us about what to read
                //Decoded Request = Array
                //(
                //    [read] => Array
                //        (
                //            [attributes] => Array
                //                (
                //                    [select] => Array
                //                        (
                //                            [parent] =>       or [parent] => film-0
                //                        )
                //
                //                    [page] => 1
                //                    [rows] =>
                //                )
                //
                //        )
                //
                //)
                //$temp = print_r($value, true);
                //$temp = "\nRead Value = ".$temp; fwrite($fp, $temp);

                 // The query that we want is like
                 // $query = SELECT * FROM $table WHERE fieldname = parent bit
                 // however if the parent bit is blank, then what.  Select all parents?
                 //$query = "SELECT * FROM $table $whereChunk $sortChunk $limitChunk";
                 $parent = $value['attributes']['select']['parent'];
                 $temp = "\n $foreach_counter parent = ".$parent; fwrite($fp, $temp);
                 if ($parent == '')
                        {
                          // We will send back a dummy response
                          // Becuase this is the root node
                          $temp = "\n $foreach_counter No parent.  Sending default parent root node "; fwrite($fp, $temp);
                          $response_string = "\n<bom totalRecords='1'>";
                          $response_string = $response_string.
                          "
                          <part id='157' parentId='' hasChildren='true'>
                                        <assembly_n>157</assembly_n>
                                        <part_n>157</part_n>
                                        <reference>default reference </reference>
                                        <comment>this is a comment</comment>
                                        <urn>3456</urn>
                                </part>
                                </bom>
                          ";
                         //$temp = "\n response_string = ".$response_string; fwrite($fp, $temp);
                         //echo "\n$response_string";                    
                        }
                else //$parent <> '' ie we have a parent.  so we are looking for children
                        {
                         // The xml response looks like
                         //1 <bom totalRecords='10' >
                         //2   <part id="42" parentId='$parent' hasChildren="true">
                         //3     <assembly_n>42</assembly_n>
                         //      <part_n>123</part_n>
                         //      <reference>bla bla </reference>
                         //      <comment>this is a comment</comment>
                         //      <urn>3456</urn>
                         //4   </part>
                         //....   ... more parts here
                         //5 </bom>
                         $query = "SELECT * FROM boms WHERE assembly_n = $parent";
                         $temp = "\n $foreach_counter  query = ".$query; fwrite($fp, $temp);
                         $result = mysql_query($query);
                         if (!($result))
                                {
                                 $temp = "\n***ERROR: can not select data from boms mysql_errno=".mysql_errno().": ".mysql_error() ;
                                 fwrite($fp, $temp);
                                };
                         $num_results = mysql_num_rows($result);
                         // --------------
                         // 1 Start the response
                         $response_string = "<bom totalRecords='".$num_results."'>";
               
                         for ($i=1; $i<=$num_results; $i++)
                                {
                                 $row = mysql_fetch_array($result, MYSQL_ASSOC);
                                 $part_n = $row['part_n'];
                                 //$temp = print_r($row, true);
                                 //$temp = "\nRead row = ".$temp; fwrite($fp, $temp);
                                 //2   <part id="42" parentId='$parent' hasChildren="true">

                                 $response_string = $response_string."\n  <part id='$part_n' parentId='$parent' hasChildren='true'>";
                                 foreach ($row as $field => $data)
                                        {
                                         //$temp = "\nfield = ".$field." data=".$data ; fwrite($fp, $temp);
                                         // 3 Build the XML for each part in this form <part_n>123</part_n>
                                         $response_string = $response_string."\n    <$field>$data</$field>";
                                        };
                                 //4   </part>
                                 $response_string = $response_string."\n  </part>";
                                 
               
                                } //for ($i=1; $i<=$num_results; $i++)
                         //5 </bom>

                         $response_string = $response_string."\n</bom>";
                        }; //else //$parent <> ''
                }; //if  ($request == 'read')
       
        };
// Send the response to the users browser
echo "$response_string";
$temp = "\n Response_string = ".$response_string; fwrite($fp, $temp);

// -------- the end

//=============================================== getNumberOfRecords =====================
// returns number of records in the table with optional conditions
function getNumberOfRecords( $whereChunk=''){
        global $db_link, $table;
        $res = mysql_query( "SELECT count(*) from $table $whereChunk", $db_link);
        if($res && (mysql_num_rows($res) > 0) && ($arr = mysql_fetch_array($res, MYSQL_NUM)))
                return $arr[0];
        return 0;
}

//=============================================== prepareFieldName =====================
//checks if the field name is safe to use in sql request and put it in quotes
//it should be changed according to the requirements of database you use
function prepareFieldName( $fieldName){
        $matches = array();
        if( preg_match('/^(\w+)$/', $fieldName, $matches)){
                return '`'.$matches[1].'`';
        } elseif( preg_match('/^(\.|\w)+$/', $fieldName, $matches)){
                $parts = explode($fieldName, '.');
                foreach( $parts as $field_part)
                        if( strlen($field_part))
                                $field_part = '`'.$fieldName.'`';
                return implode($parts, '.');
        }
        dieError( "Bad field name: $fieldName");
}
//=============================================== prepareFieldValue =====================
//encodes a field value for safe using in sql request
//it should be changed according to the requirements of database you use

function prepareFieldValue( $fieldValue, $fieldName=null){
        global $numberFields, $db_link;

        if( $fieldName && isset($numberFields[$fieldName]) && $numberFields[$fieldName]) //check and prepare number
                if(is_numeric($fieldValue))
                        return $fieldValue;
                else
                        dieError( "Bad number: $fieldValue");
        else //prepare string
                return "'".mysql_real_escape_string( $fieldValue, $db_link)."'";
}
//=============================================== decodeFieldValue =====================
//decodes a field value
//it should be changed according to the requirements of database you use
function decodeFieldValue( $fieldValue){
        return $fieldValue;
}
//=============================================== prepareRecordId =====================
//checks if the record id is safe to use in sql request
//it should be changed according to the requirements of database you use
function prepareRecordId( $recordId){
        global $id_field;
        return prepareFieldValue($recordId, $id_field);
}
//=============================================== dieError =====================
//sends a response with an error message
function dieError( $errorMessage, $detailedMessage=''){
        global $debug, $allErrors;    //see the top of the script about security reason to disable detailed error reporting
        global $dataType, $db_link;

        if( $debug)
                $errorMessage = "$allErrors \n $errorMessage $detailedMessage";

        if($db_link) mysql_close($db_link);
        die( encodeResponse( array( '_actions_'=>'true', 'attributes' => array( 'error' => $errorMessage))));
}
//=============================================== encodeResponse =====================
//encodes a response according to requested data format
function encodeResponse( $Response){
        global $dataType, $table;

        if( $dataType == 'json')
                return json_encode($Response);

        elseif( $dataType == 'xml'){
                // SimpleXML PHP extension is used for converting into XML.
                // This extension is enabled by default in PHP5
                $xml = new SimpleXMLElement("<?xml version='1.0'?><$table></$table>");
                foreach($Response as $name => $responseItem){
                        if( $name == "attributes"){
                                foreach($responseItem as $attrName=>$attrValue)
                                        $xml->addAttribute( $attrName, $attrValue);
                        }
                        elseif( is_array($responseItem)){ //create an action node
                                $actionNode = $xml->addChild($name); //action name

                                //create list of records or create record nodes
                                if( isset($responseItem['records'])){ //count($responseItem['records'])
                                        // Detect if it is array of simple values or array of objects
                                        // Check the first element if it is an array.
                                        // This means that 'records' contains full records
                                        // otherwise it contains list of record id.
                                        $simpleList = !is_array( reset( $responseItem['records']));
                                        if( $simpleList){
                                                foreach($responseItem['records'] as $recordId){
                                                        $movie = $actionNode->addChild('movie');

                                                        //save record id as an attribute instead of field value
                                                        $movie->addAttribute( 'id', $recordId);
                                                }
                                        } else {//create a node for every record
                                                foreach($responseItem['records'] as $recordId=>$record){
                                                        $movie = $actionNode->addChild('movie');

                                                        //save record id as an attribute instead of field value
                                                        $movie->addAttribute( 'id', $record['id']);
                                                        //delete it from the record
                                                        unset($record['id']);

                                                        //save fields
                                                        //create a node for every record field
                                                        foreach($record as $fieldName=>$fieldValue)
                                                                $movie->addChild($fieldName, $fieldValue);
                                                }
                                        }
                                        unset($responseItem['records']);
                                }//if 'records'

                                //and pass the rest as attributes
                                foreach( $responseItem as $actionItemName => $actionItem)
                                        if( is_array($actionItem)) //convert array into a string
                                                $actionNode->addAttribute( $actionItemName, implode(',', $actionItem));
                                        else
                                                $actionNode->addAttribute( $actionItemName, $actionItem);
                        } else // create an attribute
                                $xml->addAttribute( $name, $responseItem);
                }
                return $xml->asXML();
        }//elseif xml
}
//=============================================== decodeRequest =====================
//convert xml node into PHP array
function xml2array( &$aNodes, $bAttributes){
        $arr = array();
        if( is_array( $aNodes))
                foreach( $aNodes as $NodeName=>$Node){
                        if($bAttributes)
                                foreach($Node->attributes() as $attrName => $attrValue)
                                        $arr[ $attrName] = (string)$attrValue;
                        $arr[ $NodeName] = xml2array( $Node, $bAttributes);
                }
        else if( $aNodes){
                if($bAttributes)
                        foreach($aNodes->attributes() as $attrName => $attrValue)
                                $arr[ $attrName] = (string)$attrValue;
                foreach($aNodes->children() as $childName => $child)
                        $arr[ $childName] = count($child->children()) ? xml2array( $child) : (string) $child;
        }
        return $arr;
}
//decode data from the specified data type
function decodeRequest( $requestType){

        if( $requestType == 'json'){
                $encodedRequest = stripslashes(isset($_POST['request']) ? $_POST['request'] : $_GET['request']);
                $requests = json_decode( $encodedRequest, true);
                return $requests;

        } elseif( $requestType == 'xml'){
                $request = array();
                $content = file_get_contents( 'php://input');

                if( !strlen($content) && isset($_GET['request'])) //
                        $content = stripslashes($_GET['request']);

                if( !strlen($content))
                        return $request; //can't proceed empty string

                $xml = new SimpleXMLElement(rawurldecode( $content));
                foreach ($xml->children() as $actionName => $action) {

                        $arr = array();
                        foreach($action->attributes() as $attrName => $attrValue) //copy attributes from the XML node
                                        $arr[$attrName] = (string)$attrValue;

                        $records = array();
                        foreach($action->children() as $childName => $child)
                                if( $childName != "records") //load attributes passed as children, 'filter' for example
                                        $arr[ $childName] = xml2array( $child, true);
                                else //records
                                        foreach($child->children() as $recordId => $record)
                                                if(isset($record['id']))
                                                        $records[ (string)$record['id']] = xml2array( $record, false);
                                                else
                                                        $records[] = xml2array( $record, false);

                        $request[ $actionName] = array('attributes' => $arr);

                        if( count($records))
                                $request[ $actionName] = array('records' => $records);
                        elseif( isset($request[ $actionName]['attributes']['records'])){ //list of records passed as attribute
                                //list of records passed as attribute, for 'delete' action for example
                                $request[ $actionName]['records'] = explode( ',', $request[ $actionName]['attributes']['records']);
                                unset( $request[ $actionName]['attributes']['records']); //remove it from attributes
                        }
                }
                return $request;
        }
        return null;
}
//=============================================== errorHandler =====================
function errorHandler($errno, $errstr, $errfile, $errline){
global $allErrors;

  switch ($errno){
          case E_WARNING:
                  $allErrors .= "Warning: $errstr in $errfile on line $errline";
                  break;
          case E_NOTICE:
                  $allErrors .= "Notice: $errstr in $errfile on line $errline";
                  break;
      default:
                  dieError("Error: ", "$errstr in $errfile on line $errline");
  }
  return true;
}
?>