Gmane
From: Brian Douglas Skinner <openrecord <at> gumption.org>
Subject: Re: Data grid data provider discussion
Newsgroups: gmane.comp.web.dojo.devel
Date: 2005-12-11 06:15:45 GMT (2 years, 38 weeks, 4 days, 14 hours and 4 minutes ago)
Dustin Machi wrote:
> http://staff.vbi.vt.edu/dmachi/dojoDataObjectexample.html

Inspired by Dustin's example code, here's another batch of example code 
with some more ideas for what a Dojo API might look like for things like 
data-providers, data-items, data-types, etc.  The code below focuses 
mostly on the data model layer, rather than the grid widget itself, or 
the data provider layer.  Let me know what you think...

Cheers,
   Brian

//==========================================================
// Some ideas for what a Dojo API might look like for things
// like data-providers, data-items, data-types, etc.

// ---------------------------------------------------------
// Part 1 -- It should be easy to create simple data items.

var kermit = new dojo.data.Item("Kermit");
kermit.set("color", "green");
dojo.debug("Kermit is " + kermit.get("color"));

var stateArray = [
   { abbr: "WA", population: 5894121, name: "Washington" },
   { abbr: "WV", population: 1808344, name: "West Virginia" },
   { abbr: "WI", population: 5453896, name: "Wisconsin" },
   { abbr: "WY", population:  493782, name: "Wyoming" } ];

var stateProvider = new dojo.data.provider.Json(stateArray);
var allStates = stateProvider.fetch();

// You ask an item for the value of each of its attributes.
for (var i in allStates) {
   var state = allStates[i];
   dojo.debug(state.get("name") + " has " +
              state.get("population") + " people");
}

var stateXML = '';
stateXML.push('<state>');
stateXML.push('  <abbr>WA</abbr>');
stateXML.push('  <name>Washington</name>');
stateXML.push('</state>');

var xmlStateProvider = new dojo.data.provider.Xml(stateXML);

// ---------------------------------------------------------
// Part 2 -- It should be easy to bind data items to a Grid widget.

var grid = new dojo.widget.Grid(stateProvider);

var column, row;
var numRows    = grid.getRowCount();
var numColumns = grid.getColumnCount();
var columns    = grid.getColumns();

// You can access the item in a row, and the attributes of that item.
for (row=0; row<numRows; row++) {
   var state = grid.getItemInRow(row);
   dojo.debug(state.get("name") + " has " +
              state.get("population") + " people");
}

// You can access values in the grid based on row and column.
var valueAtColumn1Row3 = grid.getValueAt(column = 1, row = 3);
dojo.debug("grid(1, 3) == " + valueAtColumn1Row3);

// Here are some operations that would change what the grid shows,
// without changing any of the actual content data.
grid.sort("abbr");
grid.reorderColumns(["name", "abbr", "population"]);
grid.sort("population", dojo.widget.Grid.ASCENDING);
grid.reorderColumns(["abbr", "population"]);
grid.hideColumn("population");
grid.showRowForTotals();
grid.selectCellAt(column = 1, row = 3);
// Question: Do we need to explicitly call some method like
// grid.redisplay() at this point, or will the grid get
// redrawn automatically?

// If you get value at a specific row and column position,
// the value will be different after you have sorted the
// grid or changed the column order.
var oldValueAtColumn1Row3 = valueAtColumn1Row3;
var valueAtColumn1Row3 = grid.getValueAt(column = 1, row = 3);
dojo.lang.assert(valueAtColumn1Row3 != oldValueAtColumn1Row3);

// ---------------------------------------------------------
// Part 3 -- Attributes are described by attribute objects.

var abbr      = stateProvider.getAttribute("abbr");
var name      = stateProvider.getAttribute("name");
var numPeople = stateProvider.getAttribute("population");

var allAttributes = stateProvider.getAttributes();

for (var i in allAttributes) {
   var attribute = allAttributes[j];
   dojo.debug(attribute.getToken() + " is of type " attribute.getType());
}

for (var i in allStates) {
   var state = allStates[i];
   // The argument to the item.get() method can be either
   // a string token or an attribute object.
   dojo.debug(state.get("name") + " has " +
              state.get("population") + " people");
   dojo.debug(state.get(name)   + " has " +
              state.get(numPeople)    + " people");
}

// ---------------------------------------------------------
// Part 4 -- An Attribute can have a data type and other meta-data.

var DATETIME = dojo.data.Types.DATETIME;
var NUMBER   = dojo.data.Types.NUMBER;
var IMAGE    = dojo.data.Types.IMAGE;
var TEXT     = dojo.data.Types.TEXT;

// An attribute object can have an associated data type.
var landArea  = new dojo.data.Attribute("area", NUMBER);
var stateFlag = new dojo.data.Attribute("flag", IMAGE);

// An Attribute object can not only know its data type, but can also
// have whatever other meta-data you want to add.  For example, an
// attribute might have a summary description, which could be used to
// show a tooltip when the mouse hovers over the column of a grid.
var admissionDate = new dojo.data.Attribute({
       token: "admission",
       name: "Addmission into Union",
       type: DATETIME,
       summary: "This is when the state was addmitted into the US",
       foobar: new Date() });

var longAgo = new dojo.data.Date("1849");
var recent  = new dojo.data.Date("July 2004");

var result;
result = dojo.data.Date.compare(longAgo, recent);
dojo.lang.assert(result == dojo.data.LESS_THAN);

result = recent.compare(longAgo);
dojo.lang.assert(result == dojo.data.GREATER_THAN);

// ---------------------------------------------------------
// Part 5 -- You can work with items individually.

// An Item can be created programmatically, and its attributes
// can be set.
var utah = new dojo.data.Item("Utah");
utah.set("abbr", "UT");  // an alternative to: utah.set(abbr, "UT");
utah.set(numPeople, 2233169);
utah.set(admissionDate, new Date("January 4, 1896"));

// An Item knows what attributes it has.
var attributes = utah.getAttributes();
for (var i in attributes) {
   var attribute = attributes[i];
   var value = utah.get(attribute);
   dojo.debug(attribute + ": " + value);
}

// An Item knows what attributes it does and doesn't have.
dojo.lang.assert(utah.hasAttribute(numPeople) == true);
dojo.lang.assert(utah.hasAttribute(stateFlag) == false);

// Items know how to describe themselves in simple formats.
var jsonAboutUtah = utah.getJsonString();
dojo.debug("Everything we know about Utah: \n" + jsonAboutUtah);

var xmlDescriptionOfUtah = utah.getXmlString();
dojo.debug("Everything we know about Utah: \n" + xmlDescriptionOfUtah);

var vermont = new dojo.data.Item({
   name: "Vermont",
   abbr: "VT",
   population: 608827 });

var populationResult, nameResult;
nameResult       = dojo.data.Item.compare(name, utah, vermont);
populationResult = dojo.data.Item.compare(population, utah, vermont);
dojo.lang.assert(nameResult == dojo.data.LESS_THAN);
dojo.lang.assert(populationResult == dojo.data.GREATER_THAN);

nameResult       = utah.compare(name, vermont);
populationResult = utah.compare(population, vermont);
dojo.lang.assert(nameResult == dojo.data.LESS_THAN);
dojo.lang.assert(populationResult == dojo.data.GREATER_THAN);

// ---------------------------------------------------------
// Part 6 -- Attributes are loosely typed, and values have types.

// An attribute value can be set to a value object, rather than just a
// simple string literal or number literal.
var idaho = new dojo.data.Item("Idaho");
idaho.set(abbr, "ID");
idaho.set(landArea, new dojo.data.Quantity(216632, "sq km"));
idaho.set(admissionDate, new dojo.data.Date("1890"));

// Attributes can be loosely typed.  An attribute value does not have
// to match the type of of the column.
var texas = new dojo.data.Item("Texas");
texas.set(abbr, "TX");
texas.set(landArea, "really really big");
texas.set(admissionDate, "don't know -- still have to look this up");
var valueObject = texas.get(landArea);
var expectedType = landArea.getType();
var actualType = valueObject.getType();
dojo.lang.assert(expectedType == NUMBER);
dojo.lang.assert(actualType   == TEXT);
dojo.lang.assert(expectedType != actualType);
// This sort of loosely typed API is necessary for apps like
// spreadsheets, and for data providers that connect to semi-structured
// data stores, like Google Base, the Ning Content Store, the JotSpot
// content store, etc.
// However, some data providers only talk to structured data stores,
// like relational databases.  A structured data provider may not want
// to pay the performance penalty for all this flexibility, so a
// structured data provider may elect to have texas.get(landArea) return
// a simple literal, rather than value object.  Widgets with simple data
// bindings will not care about the distinction.  Widgets with
// full-featured data bindings will have to check to see what sort of
// values a provider returns.

// Because all value objects implement toString(), you can always write
// simple code that ignores the fact that the values are not literals
dojo.lang.assert(valueObject.toString() == "really really big");
dojo.debug("The land area of Texas is: \n" + texas.get(landArea));

// ---------------------------------------------------------
// Part 7 -- The same data can be bound to all sorts of widgets.

var gridA = new dojo.widget.Grid(stateProvider);
var gridB = new dojo.widget.Grid({
                  dataProvider: stateProvider,
                  attributes: ["abbr", "name", "area", "population"] });

// match on Name
var comboBoxA = new dojo.widget.ComboBox(stateProvider, "name");

// uses "name" by default
var comboBoxB = new dojo.widget.ComboBox(stateProvider);

var tree = new dojo.widget.Tree({
                  dataProvider: stateProvider,
                  displayAttribute: "name",
                  childAttribute: "cities" });

var chart = new dojo.widget.Chart({
                  plotType: "bar",
                  dataProvider: stateProvider,
                  xValue: "area",
                  yAxis: "name" });

var inspector = new dojo.widget.ItemInspector(utah);

// ---------------------------------------------------------
// Part 8 -- Different data providers can talk to different data stores.

// Dojo can offer a variety of simple data providers.
var xmlProvider  = new dojo.data.provider.Xml();
var jsonProvider = new dojo.data.provider.Json();
var sqlProvider  = new dojo.data.provider.Sql();
var soapProvider = new dojo.data.provider.Soap();

// Third parties can implement their own data providers, for access
// to proprietary content stores that hold structured data or
// semi-structured data.
var jotProvider        = new jot.data.Provider();
var googlebaseProvider = new google.base.data.Provider();
var ningProvider       = new ning.data.Provider();
var dabbleProvider     = new dabble.data.Provider();
var openRecordProvider = new orp.data.Provider();
var googlemailProvider = new google.mail.data.Provider();

var availableProviders = [xmlProvider,
                           jsonProvider,
                           sqlProvider,
                           soapProvider,
                           jotProvider,
                           googlebaseProvider,
                           ningProvider,
                           dabbleProvider,
                           openRecordProvider,
                           googlemailProvider ];

// All data providers implement a standard interface, so any of these
// data providers can be used by any widget.  We should be able to pick
// a provider at random, and the widget won't know the difference.
var provider = dojo.random.pickOneAtRandom(availableProviders);

// ---------------------------------------------------------
// Part 9 -- Providers don't just read, they do full Database CRUD
//           (Create, Read, Update, Delete)

// Some simple providers will be re-only, but full-featured providers
// can implement a complete read/write interface.
provider.beginTransaction();

provider.delete(texas);
provider.insert(utah);

utah.set(name, "Utah!");
dojo.lang.assert(utah.isDirty() == true);

provider.endTransaction();
dojo.lang.assert(utah.isDirty() == false);

// ---------------------------------------------------------
// Part 10 -- Spreadsheets can use formulas, as well as literal values.

// An attribute can have a formula, which acts like a derivation rule.
var peoplePerSquareKm = new dojo.data.Attribute({
       token: "density",
       name: "Population Density",
       type: NUMBER,
       formula: "population / area"
       summary: "Population density is number of people per square km" });

// The derived attribute can be applied uniformly to all the items
// in a grid.
var grid = new dojo.widget.Grid(stateProvider);
grid.showColumns(["name", "population", "area", "density"]);
grid.sort("density");

// You can ask a single item for the value of a derived attribute.
dojo.debug("The density of Idaho is: \n" + idaho.get("density"));

// ---------------------------------------------------------
// Part 11 -- A value can be an item-reference instead of a literal.

var saltLakeCity = new dojo.data.Item("Salt Lake City");

// We can set the capital attribute of the utah item to point to
// another item.
utah.set("capital", saltLakeCity);
dojo.debug("The capital of Utah is: \n" + utah.get("capital"));
dojo.debug("The capital of Utah is: \n" + utah.get("capital").get("name"));

// We can set up bi-directional connections between items, like the
// bi-directional connects available in different content stores:
// Dabble DB, OpenRecord, the Ning Content Store, etc.
var author = new dojo.data.Attribute({
       token: "author",
       name: "Author",
       type: ITEM,
       inverseAttribute: "booksAuthored" });
var booksAuthored = new dojo.data.Attribute({
       token: "booksAuthored",
       name: "Books Authored",
       type: ITEM,
       inverseAttribute: "author" });

var jrrTolkien = new dojo.data.Item("JRR Tolkien");
var theHobbit  = new dojo.data.Item("The Hobbit");
jrrTolkien.set(booksAuthored) = theHobbit;

// Now theHobbit knows who its author is, even though we didn't set that
// explicitly.
dojo.debug('The author of "The Hobbit" is: \n' + theHobbit.get(author));

// ---------------------------------------------------------
// Part 12 -- An attribute can be used by different kinds of items.

// Provo and Utah can both have values for the population attributes,
// even though Utah is a State, and Provo is a City.
provo.set(numPeople, 105166);
utah.set(numPeople, 2233169);

// ---------------------------------------------------------
// Part 13 -- An item can have more than one value for a single
// attribute.

var coloradoFlag = new dojo.data.Item("Colorado Flag");
coloradoFlag.set("color", ["red", "yellow", "white", "blue"]);

var kansas  = new dojo.data.Item("Kansas");
var topeka  = new dojo.data.Item("Topeka");
var wichita = new dojo.data.Item("Wichita");
kansas.set(cities, [topeka, wichita, "Dodge City"]);
kansas.addValue(cities, "Lawrence");

// Multi-valued attributes are common in semi-structured data stores
// (Google Base, the Ning Content Store, OpenRecord, etc.), so it's
// useful to have the feature available in the dojo data model.
// However, many widgets may be written to assume single-valued
// attributes, and widget authors may not want to have to deal with
// multi-valued attributes.  Likewise, for a structured content store
// like a relational database, it doesn't make sense for the data
// provider to support multi-valued attributes.  So, all the data model
// methods should support single-valued attributes as the default, and
// multi-valued attributes as the exception.  Hence. kermit.get("color")
// only returns kermit's first color, not all colors...

var kermit = new dojo.data.Item("Kermit");
kermit.set("color", ["green", "blue", "aqua"]);

dojo.lang.assert(kermit.get("color") == "green");
dojo.debug("Kermit is " + kermit.get("color"));

dojo.lang.assert(kermit.hasAttributeValue("color", "green") == true);
dojo.lang.assert(kermit.hasAttributeValue("color", "blue") == true);
dojo.lang.assert(kermit.hasAttributeValue("color", "red") == false);

var arrayOfValues = kermit.getValues("color"));
for (var i in arrayOfValues) {
   var value = arrayOfValues[i];
   dojo.debug("Kermit is " + value);
}

// ---------------------------------------------------------
// Part 14 -- A data provider can fetch items and execute at least
// simple queries.

var alaska = provider.fetchItem("id", "564625");
var kermit = provider.fetchItem("name", "Kermit");
var arrayOfGreenItems = provider.fetchSet("color", "green");

for (var i in arrayOfGreenItems) {
   var item = arrayOfGreenItems[i];
   dojo.debug("Name: " + item.get("name"));
}

// ---------------------------------------------------------
// Copyright rights relinquished under the Creative Commons
// Public Domain Dedication:
//   http://creativecommons.org/licenses/publicdomain/