JoshuaBoshi
JoshuaBoshi

Reputation: 1286

jsonRest complex structures with dojo

I am developing a web app based on dojo framework.

I decided to use dojox.data.JsonRestStore for communication with the server.

For example, I have this representation of Order (ecommerce) in Json:

{
id: int,
name: str,
date: str,
items: [
    {
        id: int,
        name: str,
        pcs: int,
        price: int,
        total: int

    }, ...
]
}

Order has some basic attributes (id, name, date) and it also contains array of ordered items.

I am not sure if this is good (REST) design and I am wondering if the ordered items should be in a separate resource (and so in separate jsonRestStore).

I think I may have trouble with the current object model when I want to display the Orders' basic attributes in dojo Form and the ordered items in dojo Datagrid.

So my question, is my current approach OK - in way of creating REST client app? Also, what is the correct way to implement my example form with datagrid in dojo?

Upvotes: 3

Views: 2011

Answers (2)

Eric LaForce
Eric LaForce

Reputation: 2151

While your design is RESTful and there is nothing in the REST architecture that requires it, I think most people would agree that keeping your resources separate is the best way to go. If you check out Restful Web Services they describe this as the Resource Oriented Architecture.

I have worked out an example that keeps the orders and items in separate JsonRestStores and will allow you to display the Order object via a Dojo form, and the order's items via a Dojo DataGrid. I think the code is straightforward and I also added some comments in the code to try and clear things up.

I created a simple ASP.NET MVC 3 backend to provide some dummy data for the example. I have posted that code as well, though you are really only interested in the "View' portion of the code, the backend code may also help you figure out or change any of the backend code you have.

View:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>JsonRestStore Datagrid Example</title>

<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/resources/dojo.css"/>
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dijit/themes/tundra/tundra.css"/>
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojox/grid/resources/Grid.css"/>
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojox/grid/resources/tundraGrid.css"/>

<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/dojo.xd.js' djConfig="parseOnLoad:true"></script>

<script type="text/javascript">

    dojo.require("dojox.data.JsonRestStore");
    dojo.require("dojox.grid.DataGrid");
    dojo.require("dijit.form.DateTextBox");
    dojo.require("dojox.grid.cells.dijit");
    dojo.require("dojo.date.locale");
    var item_structure = null;

    dojo.ready(function () {
        setItemTableStructure();
        //retrieve a single order
        //expects json to look like
        // {"Id":2,"Name":"Order Name 2","Date":"\/Date(1321135185260)\/","Items":{"Ref":"/Order/2/Items"}}
        order_store = new dojox.data.JsonRestStore({ target: "/Order/2", idAttribute: "Id" });

        order_store.fetch({
            onComplete: function (item) {
                //this method is called after an item is fetched into the store
                //item here is the order json object
                var orderId = new dijit.form.TextBox({ value: order_store.getValue(item, 'Id') }, 'orderId');
                var orderName = new dijit.form.TextBox({ value: order_store.getValue(item, 'Name') }, 'orderName');
                var orderDate = new dijit.form.DateTextBox({ value: parseJsonDate(order_store.getValue(item, 'Date')) }, 'orderDate');
                var items = order_store.getValue(item, 'Items').Ref;

                //make a call to retrieve the items that are contained within a particular order
                //expects a json object that looks like
                //[{"Ref":"/Item/1"},{"Ref":"/Item/2"},{"Ref":"/Item/3"},{"Ref":"/Item/4"},{"Ref":"/Item/5"}]
                var xhrArgs = {
                    url: items,
                    handleAs: "json",
                    load: loadOrder, //main method
                    error: function (error) {
                        console.log(error);
                    }
                }
                var deferred = dojo.xhrGet(xhrArgs);
            }
        });
    });

    //This is the main method
    function loadOrder(data) {
        var itemIds = "";
        dojo.forEach(data, function(item, i){
            itemIds += item.Ref.charAt(item.Ref.length-1);
            itemIds += ",";
        });
        itemIds = itemIds.substring(0, itemIds.length-1);

        //build the backend to accept a comma seperated list of item ids
        //like    /Item/1,2,3
        item_store = new dojox.data.JsonRestStore({target:"/Item/" + itemIds, idAttribute:"Id"});
        items = new dojox.grid.DataGrid({
            name: "items",
            formatter: function(date) {
                        if (date) return dojo.date.locale.format(parseJsonDate(date), {
                            selector: "Date"
                        })
                    },
            structure: item_structure,
            store: item_store
        }, dojo.byId('orderItems'));

        items.startup();
    }

    function setItemTableStructure() {
        item_structure = [
        { 
          field: 'Id'
        },
        {
          field: 'Name'
        },
        {
          field: 'Price'
        },
        {
            field: 'Date',
            type: dojox.grid.cells.DateTextBox,
            widgetProps: {
                selector: "Date"
            },
            formatter: function(v) {
                if (v) return dojo.date.locale.format(parseJsonDate(v), {
                    selector: 'Date'
                })
            }
        }];
    }

    function parseJsonDate(jsonDate) {
        var offset = new Date().getTimezoneOffset() * 60000;
        var parts = /\/Date\((-?\d+)([+-]\d{2})?(\d{2})?.*/.exec(jsonDate);

        if (parts[2] == undefined)
          parts[2] = 0;

        if (parts[3] == undefined)
          parts[3] = 0;

        return new Date(+parts[1] + offset + parts[2]*3600000 + parts[3]*60000);
    }
  </script>
  </head>
  <body>
    <h1>Json Rest Store with DataGrid Example</h1><br />

    <form style='margin: 10px' action='post'>
    <h2>Order</h2>
    <input id='orderId' />
    <input id='orderName' />
    <input id='orderDate' />
    </form>
    <h2>Items</h2>
    <div id='orderItems'></div>
    </body>
 </html>

Models:

using System;

namespace OrdersRestService.Models
{
    public class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public DateTime Date { get; set; }
    }
}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime Date { get; set; }
    public Reference Items { get; set; }
}

public class Reference
{
    public string Ref { get; set; }
}

Controllers:

namespace OrdersRestService.Controllers
{
    public class OrderController : Controller
    {
        public ActionResult Index(string id)
        {
            Order order = new Order
                              {
                                  Id = Convert.ToInt32(id),
                                  Name = "Order Name " + id,
                                  Date = DateTime.Now,
                                  Items = new Reference
                                                {
                                                    Ref = "/Order/" + id + "/Items"
                                                }
                              };

            return Json(order, JsonRequestBehavior.AllowGet);
        }

        public ActionResult Items()
        {
            List<Reference> items = new List<Reference>();

            for (int i = 1; i <= 5; i++)
            {
                Reference r = new Reference();
                r.Ref = "/Item/" + i;
                items.Add(r);
            }

            return Json(items, JsonRequestBehavior.AllowGet);
        }
    }

    public class ItemController : Controller
    {
        public ActionResult Index(string id)
        {
            List<Item> items = new List<Item>();
            foreach (string itemid in id.Split(','))
            {
                Item item = new Item 
                                  {
                                      Id = Convert.ToInt32(itemid),
                                      Name = "Item Name " + itemid,
                                      Date = DateTime.Now,
                                  };
                items.Add(item);
            }

            return Json(items, JsonRequestBehavior.AllowGet);
        }
    }

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

Global.aspx.cs

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "DefaultWithAction", // Route name
            "{controller}/{id}/{action}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );
    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }
}

Upvotes: 3

Richard Ayotte
Richard Ayotte

Reputation: 5080

Your approach is fine. The REST client shouldn't need to know how the data is stored whether it's in a relational database or a flat text file. You definitely can include order items when posting to the orders endpoint. This design is faster and has fewer server requests. Instead of posting once to create an order, waiting for the order id and then posting all the items afterwards, you post once with everything.

REST is about using the existing HTTP verbs such as POST, DELETE, GET, PUT and others to simplify your endpoints. The idea is to use the HTTP protocol as part of your application interface. It doesn't dictate how your model should be designed.

Upvotes: 1

Related Questions