Dojo JsonRest store and dijit.Tree

8.8k views Asked by At

I'm having a some problem making JSonRest store and dijit.Tree with ForestModel. I've tried some combination of JsonRestStore and json data format following many tips on the web, with no success.

At the end, taking example form here http://blog.respondify.se/2011/09/using-dijit-tree-with-the-new-dojo-object-store/

I've made up this simple page (I'm using dojotolkit 1.7.2)

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Tree Model Explorer</title>

<script type="text/javascript">
djConfig = {
parseOnLoad : true,
isDebug : true,
}
</script>
<script type="text/javascript" djConfig="parseOnLoad: true"
src="lib/dojo/dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.Tree");
dojo.require("dojo.store.JsonRest");
dojo.require("dojo.data.ObjectStore");
dojo.require("dijit.tree.ForestStoreModel");

dojo.addOnLoad(function() {

var objectStore = new dojo.store.JsonRest({
target : "test.json",
labelAttribute : "name",
idAttribute: "id"
});

var dataStore = new dojo.data.ObjectStore({

objectStore : objectStore
});

var treeModel = new dijit.tree.ForestStoreModel({
store : dataStore,
deferItemLoadingUntilExpand : true,
rootLabel : "Subjects",
query : {
"id" : "*"
},
childrenAttrs : [ "children" ]
});

var tree = new dijit.Tree({
model : treeModel
}, 'treeNode');
tree.startup();
});
</script>
</head>
<body>

<div id="treeNode"></div>
</body>
</html>

My rest service responds the following json

{
data: [
{
"id": "PippoId",
"name": "Pippo",
"children": []
},
{
"id": "PlutoId",
"name": "Pluto",
"children": []
},
{
"id": "PaperinoId",
"name": "Paperino",
"children": []
}
]}

I've tried also with the following response (actually my final intention n is to use lazy loading for the tree)

{ data: [
 {
  "id": "PippoId",
  "name": "Pippo",
  "$ref": "author0",
  "children": true
 },
 {
  "id": "PlutoId",
  "name": "Pluto",
  "$ref": "author1",
  "children": true
 },
 {
  "id": "PaperinoId",
  "name": "Paperino",
  "$ref": "author2",
  "children": true
 }
]}

Neither of the two works. I see no error message in firebug. I simply see the root "Subject" on the page. Thanks to anybody could help in some way.

1

There are 1 answers

1
mschr On

From a quick glance;

your serverside returns the wrong data. Here's the quickstart jsonrest from dojo reference guide, follow the GET part.

There is a difference, since the manner of how your REST request looks (GET from browser), the serverside should 1) return an array of items or 2) return an item.

Try remove the data key as such:

[
  {
    "id": "PippoId",
    "name": "Pippo",
    "children": []
  }, {
    "id": "PlutoId",
    "name": "Pluto",
    "children": []
  }, {
    "id": "PaperinoId",
    "name": "Paperino",
    "children": []
  }
]

So this will not bring lazy loading capeabilities just yet? This must be because the model has quite a complex setup in your sample of code, first the REST store then an OBJECT another store, then a ForestTree model and last a Tree view. It is fairly simple to implement what the model has to offer our store, and lets skip the double-store definition. Otherwise the objectstore.query will call reststore.query - which im not entirely certain will work.

Missing logic in the RestStore

The Tree requires five model methods to render data as a tree:

  1. getIdentity(object) - Already provided by the store, and doesn't usually need to be reimplemented.
  2. mayHaveChildren(object) - Indicates whether or not an object may have children (prior to actually loading the children). In this example, we will treat the presence of a "children" property as the indication of having children.
  3. getChildren(parent, onComplete, onError) - Called to retrieve the children. This may execute asynchronously and should call the onComplete callback when finished. In this example, we will do a get() to retrieve the full representation of the parent object to get the children. Once the parent is fully loaded, we return the "children" array from the parent.
  4. getRoot(onItem) - Called to retrieve the root node. The onItem callback should be called with the root object. In this example, we get() the object with the id/URL of "root" for the root object.
  5. getLabel(object) - Returns the label for the object (this is the text that is displayed next to the node in the tree). In this example, the label is just the "name" property of the object.

How could this be done then? Lets make a couple of definitions:
1) server sets for jsonrest.target 'base', the ID 'root' and 2) server returns 'children' key allways, true if there are any

var reststore = JsonRest({
    target:"data/",         // << adapt URL
    mayHaveChildren: function(object){
        // if true, we might be missing the data, false and nothing should be done
        return "children" in object;
    },
    getChildren: function(object, onComplete, onError){
        // this.get calls 'mayHaveChildren' and if this returns true, it will load whats needed, overwriting the 'true' into '{ item }'
        this.get(object.id).then(function(fullObject){
            // copy to the original object so it has the children array as well.
            object.children = fullObject.children;
            // now that full object, we should have an array of children
            onComplete(fullObject.children);
        }, function(error){
            // an error occurred, log it, and indicate no children
            console.error(error);
            onComplete([]);
        });
    },
    getRoot: function(onItem, onError){
        // get the root object, we will do a get() and callback the result
        this.get("root").then(onItem, onError);
    },
    getLabel: function(object){
        // just get the name (note some models makes use of 'labelAttr' as opposed to simply returning the key 'name')
        return object.name;
    }
});

Creating Tree with a customized RestStore as model

We us the variable reststore, defined above - and simply set this as the model of the tree construct parameters

var tree = new dijit.Tree({
  model : treeModel
}, 'treeNode');
tree.startup();

Serverside lazyloading JSON data setup

Leaving out most of the data in our children arrays, the payload sent can be reduced and lazyloading can be taken advantage of. For every One item received (e.g. /data/parents/number1parent) a full representation of the item itself must be filled in. If he/she has children, we need to 1) name these for getting labels in the view - we use 'name' key, see getLabel method, 2) supply a unique ID and 3) indicate whether they may have children or not.

The root JSON

{
    "name": "Papparazis",
    "id": "root",
    "children": [
  {
    "id": "PippoId",
    "name": "Pippo",
    // may have children - makes dijit.Tree create expandoNode and events
    "children": true
  }, {
    // example leaf, children == undefined
    "id": "PlutoId",
    "name": "Pluto" 
  }, {
    "id": "PaperinoId",
    "name": "Paperino",
    "children": true
  }
]
}

With this, we will show
ROOT
+ Pippo
* Pluto
+ Paperino

Any item JSON

Should we feel like clicking Pippo to see his child objects, lets regress; Tree requests target treeNode.item.children which by design in the RestStore translates to a request for the object.id==PippoId - and a URL will be compiled, using the this.target/object.parent-relations/object.id shcema. In this case, server will respond with a full representation of the ultimate URL data/PippoID via GET.

{
    "name": "Pippo",
    "id": "PippoId",
    "surname" : "foo",
    "shoosize" : "bar",
    "children": [
        {
            "name": "Baz",
            "id": "BazId"
        }, {
            "name": "Godfather",
            "id": "GodfatherId",
            "children": true
        }
    ]
}

Note, that the 'full representation object' - the top level - has extra entities to it, examplewise shoosize and surname. However the children are still a 'short-form representaion object'.

Serverside PHP Rest implementation

So, for the sake of argument, heres a representation of how one could implement the rest-capeabilities with PHP. We will go under the assumption, that papparazies is a table in sql-db and that a getPaparazi($id) along with getPapasKids($id) function is implemented to retrieve data columns.

First off, we need to tell apache how to handle this by a .htaccess modification. If youre new at this, check some of these resources:

Put this in /.htaccess where '/' is your DocumentRoot - and modify RewriteBase as nescessary

<IfModule mod_rewrite.c>
  RewriteEngine on
  # If your site is running in a VirtualDocumentRoot at http://example.com/,
  # uncomment the following line:
  # RewriteBase /
  # If no file nor a directory exists for the request url,
  # rewrite URLs of the form 'data/object' to the form 'index.php?rest=data/object'.

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_URI} !=/favicon.ico
  RewriteRule ^(.*)$ index.php?rest=$1 [L,QSA]

</IfModule>

From the PHP perspective in index.php (rewritten handler for all non-existing file requests) we could simply create following:

<?php

 if(isset($_REQUEST['rest'])) {

   // IMPORTANT; validate the request, e.g. check for referrer or similar

   $ids = explode("/", $_REQUEST['rest']);
   $pathCount = count($ids);
   $table = array_shift($ids); // as string, 'data/' by example
   if(!$table || $table == "") exit; //  ... validate more
   if($pathCount > 1) {
      $objectRequested = array_pop($ids); // as string '' for root
   }
   if($pathCount > 2) {
      $mid = $ids; // an array, holding rest of the path (middle relatives)
   }
   // with this in hand, we should be able to get the object. 
   // by example we're serving store data for paparazies which 
   // has the 'target' parameter set to 'data/'
   if($table == "data") {
      $fields = getPaparazi($objectRequested);
      $obj = new stdobject();
      $obj->name = $fields['name'];
      $obj->id = $objectRequested;
      $obj->shoosize = $fields['shoosize'];
      $obj->children = array();
      if( ( $children = getPapasKids($objectRequested) ) ) {
          foreach($children as $child) {
             $c_obj = new stdobject();
             $c_obj->name = $child['name'];
             $c_obj->id = $child['id'];
             if($child['hasChildren']) 
                $c_obj->children = true;
             $obj->children[] = $c_obj;
          }
      }
      header("Content-Type: application/x-json; charset=XXXXX");
      print json_encode($obj);
      flush();
      exit;
   }
 }
?>

See a SitePen blog for compiled info on reststores and trees