Welcome, guest ( Login )

WikiHome » dojo.data » DataStoreAPI

DataStoreAPI

Version 30, changed by chrism 09/12/2006.   Show version history

Here are some ideas for the Data Store API in the dojo.data package. The "Data Store API" has also been called the Data Access API or the Data Provider API -- we don't seem to have settled on a name yet.

For background and context, see


    //==========================================================
    // Some ideas for the data-store/data-provider/data-access API
    // in the dojo.data package.

    // ---------------------------------------------------------
    // (1) Create a simple JSON data source

    var states = [
        { 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 store = dojo.data.newStore({json: states});

    // ---------------------------------------------------------
    // (2) Create a new data item and use accessor methods

    var kermit = store.newItem({name: "Kermit"});    // Create a new data item
    store.set(kermit, "color", "green");     // Set an attribute value
    var green = store.get(kermit, "color");  // Get an attribute value
    dojo.debug("Kermit is " + green);

    // ---------------------------------------------------------
    // (3) Get data from simple file formats -- xml, csv, json, rdf, etc.

    var storeA = dojo.data.newStore({file: "foo/bar/states.json"});
    var storeB = dojo.data.newStore({file: "foo/bar/states.xml"});
    var storeC = dojo.data.newStore({file: "foo/bar/states.csv"});
    var storeD = dojo.data.newStore({file: "foo/bar/states.rdf"});

    // ---------------------------------------------------------
    // (4) Get data from an xml data island
    // <xml id="xml-data-island">
    //   <state><name>Washington</name><abbr>WA</abbr></state>
    //   <state><name>Wyoming</name><abbr>WY</abbr></state>
    // <xml>

    var storeE = dojo.data.newStore({islandId: "xml-data-island"});

    // ---------------------------------------------------------
    // (5) Get data from an html table
    // <table id="html-table">
    //   <thead><tr><th>Name</th><th>Abbr</th></tr></thead>
    //   <tbody><tr><td>Washington</td><td>WA</td></tr></tbody>
    // </table>

    var storeF = dojo.data.newStore({islandId: "html-table"});

    // ---------------------------------------------------------
    // (6) Get data from an html list
    // <ul id="html-list">
    //   <li>Washington</li>
    //   <li>Wyoming</li>
    // </ul>

    var storeG = dojo.data.newStore({islandId: "html-list"});

    // ---------------------------------------------------------
    // (7) Dojo can offer a variety of data stores.

    var sqlStore  = new dojo.data.store.Sql();
    var rdfStore  = new dojo.data.store.Rdf();
    var soapStore = new dojo.data.store.Soap();

    // ---------------------------------------------------------
    // (8) Third parties can implement their own data stores to access
    //     proprietary content sources.

    var jotStore        = new jot.data.Store();
    var googlebaseStore = new google.base.data.Store();
    var ningStore       = new ning.data.Store();
    var dabbleStore     = new dabble.data.Store();
    var openRecordStore = new orp.data.Store();
    var googlemailStore = new google.mail.data.Store();

    // ---------------------------------------------------------
    // (9) Different data stores can have different connection and
    //     initialization parameters, specific to a particular data source.

    var storeH = new dojo.data.store.Delicious({query: "hillary/medicaid"}); // del.ic.ious
    var storeI = new dojo.data.store.MySqlBookDb({user:"hillary", password:"d3fbq"});

    // ---------------------------------------------------------
    // (10) You can register a custom data store, and create an instance of a registered type

    dojo.data.registerStore("delicious", dojo.data.store.Delicious);
    var storeJ = dojo.data.newStore({type: "delicious", query: "hillary/medicaid"});

    // ---------------------------------------------------------
    // (11) You can loop through a data array, or use an iterator

    // *ISSUE:*
    // As of Sept 2006, we're still discussing what looping constructs
    // to support.  We're agreed that we want some sort of forEach()
    // method, and we may decide to only have forEach() and then get
    // rid of iterator access and array access.

    var store = new dojo.data.csvStore({file: "foo/bar/states.csv"});
//CCM: Why not just have a simpler operation name, fetchResult()
    var results = store.fetchResultList(); // synchronous
    results.forEach(
        function(item) {
            var name = store.get(item, "name");
            var population = store.get(item, "population");
            dojo.debug(name + " has " + population + " people");
        });

    // *ISSUE:* We may not support this fetchArra() style access...
    // Use a simple array to loop through a data set:
    var store = dojo.data.newStore({file: "foo/bar/states.csv"});
    var dataArray = store.fetchArray(); // synchronous
    for (var i in dataArray) {
        var state = dataArray[i];
        var name = store.get(state, "name");
        var population = store.get(state, "population");
        dojo.debug(name + " has " + population + " people");
    }

    // *ISSUE:* We probably won't support this iterator style access...
    // Or use an iterator to do the same sort of synchronous loop:
    var store = new dojo.data.store.LibraryOfCongressCatalog();
    var iterator = store.fetchIterator({query: "all books"}); // synchronous
    while(!iterator.atEnd()){
        var dataItem = iterator.get();
        var title = store.get(dataItem, "title");
        var author = store.get(dataItem, "author");
        dojo.debug(title + " by " + author);
    }

    // ---------------------------------------------------------
    // (12) You can load an entire list of data asynchronously
    //
    // *ISSUE*: See the note in part (11) above about forEach() and iterators.

//CCM: Why not just have a simpler operation name, fetchResult()
    var results = store.fetchResultList({query: "all books", async: true}); // asynchronous
    results.forEach(
        function(item) {
            var title = store.get(item, "title");
            var author = store.get(item, "author");
            dojo.debug(name + " has " + population + " people");
        });

    // In addition to using forEach() to call a function, we may
    // sometimes want to call a method on an object.  We could have
    // forEach() work like dojo.event.connect(), either taking just a
    // function or taking an object and a method name.
    results.forEach(someDisplayFunction);
    results.forEach(someControllerObject, "someDisplayMethod");

    // *ISSUE:* We probably won't support this iterator style access...
    var deferredIterator = store.fetchIterator({query: "all books", async: true}); // asynchronous
    deferredIterator.addFinalCallback(displayAllStates);
    displayAllStates = function(deferredIterator) {
        var iterator = deferredIterator;
        while(!iterator.atEnd()){
            var dataItem = iterator.get();
            var title = store.get(dataItem, "title");
            var author = store.get(dataItem, "author");
            dojo.debug(title + " by " + author);
        }
    }

    // ---------------------------------------------------------
    // (13) You can use an iterator to loop through a large data set without
    //      loading the entire data set into memory
    //
    // *ISSUE*: See the note in part (11) above about forEach() and iterators.

    // If we're loading the data asynchronously and displaying partial
    // data as it loads, then we may want to have some sort of progress
    // bar to give the user some sense of what percent has loaded.
    // To do that, we may want a few more features...

    var results = store.fetchResultList({query: "all books", async: true}); // asynchronous

    // Maybe some way to get the total number of items in the result list...
    var totalNumberOfItems = results.getLength();

    // Maybe some way to get a callback when the loop is finished...
    results.forEach(someDisplayFunction, {onCompletion: finishedFunction});

    // Maybe some way to cancel a forEach() loop that's in progress...
    if (thisIsTakingTooLong) {
        results.cancel();
    }

    // Maybe some way to loop through only a small portion of the result list...
    results.forEach(someFunction, {start: 11, end: 20}); // start/end, from/to, or first/last

    // *ISSUE:* We probably won't support this iterator style access...
    var deferredIterator = store.fetchIterator({query: "all books", async: true}); // asynchronous
    deferredIterator.addIncrementalCallback(displaySomeStates);
    deferredIterator.addFinalCallback(finishDisplayingStates);
    displaySomeStates = function(deferredIterator) {
        while(deferredIterator.hasDataAvailable()) { // this might trigger another server fetch
            var dataItem = deferredIterator.get();
            var title = store.get(dataItem, "title");
            var author = store.get(dataItem, "author");
            dojo.debug(title + " by " + author);
        }
        if(!deferredIterator.atEnd()) {
            dojo.debug("more data will arrive in a minute");
        }
    }
    finishDisplayingStates = function(deferredIterator) {
        var totalStates = deferredIterator.getLength();
        dojo.debug("finished displaying all " + totalStates + " states");
    }

    // ---------------------------------------------------------
    // (14) Each different data store implementation can use a completely
    //      different syntax for queries.  The Dojo Data Access API
    //      says nothing about what queries look like and knows nothing
    //      about actually evaluating queries.

    var deliciousStore = new dojo.data.store.Delicious();
    var libraryStore = new dojo.data.store.PublicLibraryCatalog();
    var iteratorOne = deliciousStore.fetchIterator({query: "hillary/medicaid"});
    var iteratorTwo = libraryStore.fetchIterator({query: "author=clinton"});

    // ---------------------------------------------------------
    // (15) You can get more than one result set from a single data store

    var store = new dojo.data.store.Delicious();
    var iteratorOne = store.fetchIterator({query: "hillary/medicaid"});
    var iteratorTwo = store.fetchIterator({query: "hillary/education"});

    // ---------------------------------------------------------
    // (16) You can get a single data object from a data store, by id

    var store = new dojo.data.store.Csv({file: "foo/bar/states.csv"});
    var alaska = store.byId(2);

    // *SKINNER ISSUE*:
    //   The name "byId" for the store.byId() method follows in the
    // footsteps of dojo.byId() and dojo.widget.byId().  Since we
    // already have two different byId() functions that do different
    // things, maybe introducing a third one is fine.  On the other
    // hand, having homonyms for dojo.byId() and dojo.widget.byId()
    // seems to cause a lot of confusion, so maybe we shouldn't be
    // following that pattern in dojo.data.

    // ---------------------------------------------------------
    // (17) Each different data store implementation can have a completely
    //      different notion of what an "id" is.

    var alaska = csvStore.byId(2);      // int
    var alaska = aaaStore.byId("AK");   // string
    var alaska = bbbStore.byId("uuid:2cc8fef0-1b82-11db-a98b-0800200c9a66");    // UUID
    var alaska = rdfStore.byId("http://www.daml.ri.cmu.edu/ont/State.daml#AK"); // URI

    // *SKINNER ISSUE*: What should we use as ids for records in SQL databases?
    //    It would be convenient if all ids were always strings, for any sort
    // data store.  But for identifying records in SQL databases, simple strings
    // may not be as good as simple JSON objects.  Here are some examples...

    // SELECT * FROM state_table WHERE ID=2
    var alaska = sqlStore.byId({table:"state_table", id:2);
    var alaska = sqlStore.byId("state_table:2");

    // SQL DB compound key
    var alaska = sqlStore.byId({
        table:"state_table",
        country_id:"US",
        state_id:2 });

    // Or, here's another possible approach for dealing with SQL DB compound keys.
    // This approach has the advantage that the "id" is represented as a simple
    // string, but the string is long, and with this approach we would need to be
    // careful to only ever use a single standard representation of each id string,
    // without variations in spacing and capitalization.
    var alaska = sqlStore.byId("FROM state_table WHERE country_id = 'US' AND state_id = 2");

    // You can ask a data store for the id of an item
    var id = store.getIdOf(alaska);

    // ---------------------------------------------------------
    // (18) Some data stores are read-only, but others do full
    //      read-write access (CRUD = Create, Read, Update, Delete)

    var kermit = store.newItem({name: "Kermit"});  // create
    store.set(kermit, "color", "green");           // update
    store.save();

    store.delete(kermit);                          // delete
    store.save();


    // *PW/CCM ISSUE*:  Some save operations can be time-consuming,
    // presumably because of a significant amount of data being
    // posted to a server.  There should be an asynchronous save
    // with a completion callback
    store.save(
		{async: true, onComplete: postSave}
	);
	function postSave() {
		//do something
	}

    // SKINNER: Good point.  I agree, we should offer an async
    // save() method.  What do you think about using a "deferred"
    // object for that, like this:
    var deferred = store.save({async: true});
    function saveCompletedSuccessfully() { /* do something */ };
    function errorDuringSave() { /* handle error */ };
    deferred.addCallbacks(saveCompletedSuccessfully, errorDuringSave);

    // ---------------------------------------------------------
    // (18.5) A CRUD data store knows about unsaved changes

    store.set(kermit, "color", "blue");
    (store.isDirty() == true);
    (store.isDirty(kermit) == true);

    store.save();
    (store.isDirty() == false);
    (store.isDirty(kermit) == false);

    var elmo = store.newItem("Elmo");
    (store.isDirty() == true);
    (store.isDirty(kermit) == false);

    // ---------------------------------------------------------
    // (19) Some data stores offer features like transactions,
    //      nested transactions, rollback, etc.  The transaction API is
    //      available on the base class that all data stores inherit from,
    //      but in the base class the beginTransaction/endTransaction pair
    //      is simply impelmented by calling "store.save()".

    store.beginTransaction();
    store.set(kermit, "color", "green");
    store.endTransaction();
    (store.isDirty() == false);

    // ---------------------------------------------------------
    // (20) Some stores may have data classes

    var arrayOfClasses = sqlStore.getAllDataClasses();
    var state = sqlStore.getDataClass("State");
    var country = sqlStore.getDataClass("Country");
    var state = sqlStore.getDataClassOf(alaska);

    // ---------------------------------------------------------
    // (21) In a store that has classes, you can create instances of classes

    var state = sqlStore.getDataClass("State");
    var texas = sqlStore.newItemOfClass(state);
    var texas = sqlStore.newItem();  // throws an exception

    // ---------------------------------------------------------
    // (22) Each data class is itself a data item, with attributes

    var stateDataClass = sqlStore.getDataClass("State");
    var name = store.get(stateDataClass, "table_name");
    var type = store.get(stateDataClass, "table_type");

    // ---------------------------------------------------------
    // (23) Data classes know what attributes they have

    var sqlStore = new dojo.data.sqlStore({connection: ...});
    var stateDataClass = sqlStore.getDataClass("State");
    var array          = sqlStore.getAttributesOf(stateDataClass);

    // *CCM ISSUE*: We've discussed having attributes maintained at the model/package
    // level for untyped data (data with no Class).
    // In the case of typed data, where attributes are owned by Classes in the metamodel,
    // will we want to make the qualified names of those attributes also available in the
    // model level, eg. getMetadata().getAttribute("pkgA.pkgb.Class1.attribute")?

    // SKINNER: Yup, in the getAttribute method we need to have some way to specify a
    // class to look for the attribute on.  Here are a few possibilities...
    //  #1 getAttribute("State:population");           // namespace style
    //  #2 getAttribute("State.population");           // path style
    //  #3 getAttribute(stateDataClass, "population"); // class object, not class-name string
    //
    // My intuition is that option #3 would end up causing more problems than options
    // #1 or #2, but I really don't know.  With options #1 or #2, we could just say
    // that any data store is required to provide some sort of unique key string as
    // an identifier for each attribute, and then it's up to the data store implementation
    // to decide how to parse that string and find the attribute.  So, as far as the
    // DataStoreAPI is concerned, the string is an opaque id.

    // SKINNER: In the example above, "pkgA.pkgb.Class1.attribute", it looks like
    // there's a notion that you can have a data class which doesn't belong directly
    // to the meta-model itself, but instead belongs to a sub-package of a sub-package
    // of the meta-model.  Do we need to have a notion of sub-packages, and if we do,
    // what other methods in the DataStoreAPI should be updated to allow for that?

    // ---------------------------------------------------------
    // (24) A data store may have attributes that aren't associated with a data class.

    var csvStore = dojo.data.newStore({file: "foo/bar/states.csv"});
    var abbr       = csvStore.getAttribute("abbr");
    var name       = csvStore.getAttribute("name");
    var population = csvStore.getAttribute("population");
    var array      = csvStore.getAllAttributes();

    // ---------------------------------------------------------
    // (25) A data store knows what attributes an item has.

    var array = store.getAttributesOf(utah);
    for (var i in array) {
        var attribute = array[i];
        var value = store.get(utah, attribute);
    }

    // A data store knows what attributes an item does and doesn't have.
    (store.hasAttribute(utah, population) == true);
    (store.hasAttribute(utah, stateFlag) == false);

    // ---------------------------------------------------------
    // (26) Each attribute is itself a data item.

    var population = sqlStore.getAttribute(stateDataClass, "population");
    var name = sqlStore.get(population, "name");   // "population"
    var type = sqlStore.get(population, "type");   // varchar
    var max  = sqlStore.get(population, "length"); // varchar(255)

    // ---------------------------------------------------------
    // (27) An attribute can have a data type and other descriptive info.

    // *CCM ISSUE*: Rather than having all metadata api's on the datastore itself, we
    // should consider providing an accessor, getMetadata() that gives you a facade into
    // the metadata information.
    //
    // SKINNER: Would it be okay to have the meta information available through both
    // the getMetadata() method and through the datastore itself, so that the caller
    // could use either of these two lines of code synonymously:
    //   var population = store.getMetadata().getAttribute("State:population");
    //   var population = store.getAttribute("State:population");
    // Some data sources (RDF, OpenRecord, etc.) have the feature that any data item
    // can serve as an attribute, and any attribute can be treated as a regular data
    // item, which makes it nice to have an API that doesn't make a sharp distinction.
    //
    // SKINNER: Would getMetaModel() be an okay name, instead of getMetadata()?  The
    // word "metadata" has already has a different meaning in the context of RDF and
    // the semantic web.

    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  = store.newAttribute({id:"area", type:NUMBER});
    var stateFlag = store.newAttribute({id:"flag", type:IMAGE});

    // In some data stores, like an RDF store, an attribute object can have
    // whatever 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 = store.newAttribute({
        id: "admission",
        name: "Admission into Union",
        summary: "This is when the state was addmitted into the US",
        foobar: {foo:23, bar:"iggy"},
        type: DATETIME });

    // ---------------------------------------------------------
    // (28) The get() and set() methods take attribute names or attribute items.

    var csvStore = dojo.data.newStore({file: "foo/bar/states.csv"});
    var abbr       = csvStore.getAttribute("abbr");
    var name       = csvStore.getAttribute("name");
    var population = csvStore.getAttribute("population");

    var kansas = csvStore.newItem({name: "Kansas"});
    csvStore.set(kansas,  abbr,  "KS");   // attribute data item
    csvStore.set(kansas, "abbr", "KS"); // attribute name

    var ks = csvStore.get(kansas,  abbr);   // attribute data item
    var ks = csvStore.get(kansas, "abbr"); // attribute name

    // ---------------------------------------------------------
    // (29) A "mapping" can be used by a general purpose widget controller to
    //      map data attributes to parts of the widget UI.

    var elephants = [ { weight: 5000, height: 3.0, age: 20, name: "Homer" },
                      { weight: 3000, height: 2.5, age: 35, name: "Marge" },
                      { weight: 4000, height: 2.8, age: 40, name: "Maggy" } ];
    var jsonStore = dojo.data.newStore({json: elephants});

    var tableGrid = dojo.widget.createWidget("dojo:TableGrid", {}, dojo.byId("divOne"));
    var tableGridController = new dojo.widget.TableGridController({
        widget: tableGrid,
        store: jsonStore,
        mapping: {
            columns: ["name", "weight", "height"],
            sortColumn: "height" }
        });

    var bubbleChart = dojo.widget.createWidget("dojo:BubbleChart", {}, dojo.byId("divTwo"));
    var bubbleChartController = new dojo.widget.BubbleChartController({
        widget: bubbleChart,
        store: jsonStore,
        mapping: {
            x: "weight",
            y: "age",
            size: "height",
            hoverText: "name" }
        });

    // Both widgets (table and chart) will automatically redisplay when a new item
    // is saved to the store, or when any other CRUD happens.
    var lisa = store.newItem({ weight: 4500, height: 2.9, age: 28, name: "Lisa" });
    store.save(); // triggers redisplay

    // ---------------------------------------------------------
    // (30) A store knows about the current selection, and widgets
    //      are notified when the selection changes.

    store.selectItem(lisa);     // triggers redisplay of table and chart

    store.addSelection(bart);   // allow for multiple selection
    store.clearSelection();     // deselect everything

    var no = store.isItemSelected(lisa);  // check to see what's selected
    var array = store.getSelectedItems();

    // An widget like an ItemInspector might only show the selected item(s)
    var itemInspector = dojo.widget.createWidget("dojo:ItemInspector", {}, dojo.byId("divThree"));
    var inspectorController = new dojo.widget.inspectorController({
        widget: itemInspector,
        store: jsonStore });

    // ---------------------------------------------------------
    // (30.5) In addition to selecting entire items, you can also
    // select just one attribute value of on an item.

    // I think there are cases in some UIs where you need to select
    // a single attribute value on an item, rather than selecting
    // the entire item.  Unfortunately that will complicate our API.

    store.selectItemAttribute(lisa, height);

    // We might also want to have a selectAttribute() method, which
    // you could use, for example, to select one column of a table.
    // Although, we don't really need a separate selectAttribute()
    // method, since you could always select an attribute by using
    // a the selectItem() method.

    store.selectAttribute(height); // same as store.selectItem(height);

    // ---------------------------------------------------------
    // (31) Some data stores may allow an attribute to be re-used
    //      across different data classes.

    // Provo and Utah can both have values for the population attribute,
    // even though Utah is a State, and Provo is a City.
    rdfStore.set(provo, population, 105166);
    rdfStore.set(utah, population, 2233169);
    (rdfStore.getDataClassOf(utah) != rdfStore.getDataClassOf(provo));

    // ---------------------------------------------------------
    // (32) Attributes can hold different types of values,
    //      including JavaScript types like Strings, Numbers, and Dates.
    //      Different data store implementations will try to map the
    //      JavaScript types onto data source types.

    var utah = store.newItem({name: "Utah"});
    store.set(utah, abbr, "UT");
    store.set(utah, population, 2233169);
    store.set(utah, admissionDate, new Date("January 4, 1896"));

    // ---------------------------------------------------------
    // (33) In some data stores attributes are strongly typed.

    var utah = sqlStore.newItem({name: "Utah"});
    sqlStore.set(utah, population, 2233169); // works
    sqlStore.set(utah, population, "lots!"); // throws an exception

    // ---------------------------------------------------------
    // (34) In some data stores attributes are loosely typed.
    //
    // The loosely typed API is necessary for apps like spreadsheets,
    // and for data stores that connect to semi-structured data sources,
    // like Google Base, the Ning Content Store, the JotSpot store, etc.

    var utah = rdfStore.newItem({name: "Utah"});
    rdfStore.set(utah, population, 2233169); // works
    rdfStore.set(utah, population, "lots!"); // also works

    // ---------------------------------------------------------
    // (35) In loosely typed stores, each value can have a type.

    store.set(idaho, stateFlower, new Date());
    var valueObject = store.get(idaho, stateFlower);
    var type = valueObject.getType(); // Date
    var string = valueObject.toString();
    dojo.debug("The state flower of Idaho is " + string);

    // All value objects implement toString(), so you can always write
    // simple code that ignores the fact that the values are not literals
    dojo.debug("The state flower of Idaho is " + store.get(idaho, stateFlower););

    // Some data stores only talk to structured data sources, like relational
    // databases.  A structured data store may not want to pay the performance
    // penalty for all this flexibility, so a structured data store may elect
    // to have store.get(idaho, stateFlower) 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 store returns.

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

    // ---------------------------------------------------------
    // (36) The dojo.data package may include some data type implementations.

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

    // Data type implementations could have features for sorting.
    (recent.compare(longAgo) == dojo.data.GREATER_THAN);
    (dojo.data.Date.compare(longAgo, recent) == dojo.data.LESS_THAN);

    // ---------------------------------------------------------
    // (37) A value can be an item-reference instead of a literal.

    var wyoming = store.newItem("Wyoming");
    store.set(wyoming, "capital", "Cheyenne"); // set a literal value

    var cheyenne = store.newItem("Cheyenne");
    store.set(wyoming, "capital", cheyenne);   // set a reference value

    // 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"));

    // ---------------------------------------------------------
    // (38) An item can have more than one value for a single attribute
    //     Multi-valued attributes are common in semi-structured data stores
    //    (RDF, Google Base, the Ning Content Store, OpenRecord, etc.)

    var states = [
        { abbr: "WA", 'name': "Washington", cities: "Seattle" },
        { abbr: "WY", 'name': "Wyoming", cities: ["Laramie", "Cheyenne"] } ];
    var store = dojo.data.newStore({json: states});
    var states = store.fetchArray();
    var washington = states[0];
    var wyoming    = states[1];

    // The getValues() method always returns an array
    var array = store.getValues(washington, "cities");
    var array = store.getValues(wyoming, "cities");

    // 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
    // store to support multi-valued attributes.  So, all the data access
    // methods should support single-valued attributes as the default, and
    // multi-valued attributes as the exception.  Hence the simple get()
    // method only returns a single data value

    // The get() method always returns a single value
    var abbr    = store.get(wyoming, "abbr");
    var laramie = store.get(wyoming, "cities");  // just returns the first value

    // The set() method can be used to set a single value
    store.set(washington, "cities", "Seattle");
    var yes = store.hasAttributeValue(washington, "cities", "Seattle");

    // The set() method can be used to set an array of values
    store.set(washington, "cities", ["Seattle", "Tacoma"]);
    var yes = store.hasAttributeValue(washington, "cities", "Seattle");
    var yes = store.hasAttributeValue(washington, "cities", "Tacoma");

    // The addValue() method can be used to add an additional value
    store.addValue(washington, "cities", "Bellevue");
    var yes = store.hasAttributeValue(washington, "cities", "Seattle");
    var yes = store.hasAttributeValue(washington, "cities", "Tacoma");
    var yes = store.hasAttributeValue(washington, "cities", "Bellevue");

    // ---------------------------------------------------------
    // (39) Spreadsheets can use formulas, as well as literal values.

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

    // You can ask an item for the value of a derived attribute.
    var value = store.get(utah, "density");

    // *PW/CCM ISSUE*:  With derived attributes, we'll run into the issue
    // of binding between attributes referenced in the formula and the value
    // of the derived attribute.  An observer of the derived attribute needs
    // to be notified of when one of the dependent attributes changes and
    // affects the value.

    // SKINNER: Agreed.  An observer of the derived attribute should be
    // notified whenever the derived value changes, which may happen whenever
    // any of the attributes used in the formula changes.

    // ---------------------------------------------------------
    // (40) Some data stores may allow bi-directional associations
    //      between items.  This is a feature found in Dabble DB,
    //      the Ning Content Store, OpenRecord, etc.

    var author = store.newAttribute({
          id: "author",
          name: "Author",
          inverseAttribute: "booksAuthored" });
    var booksAuthored = new dojo.data.Attribute({
          id: "booksAuthored",
          name: "Books Authored",
          inverseAttribute: "author" });

    var jrrTolkien = store.newItem("JRR Tolkien");
    var theHobbit  = store.newItem("The Hobbit");
    store.set(jrrTolkien, booksAuthored) = theHobbit;

    // Now theHobbit knows who its author is, even though we didn't set that
    // explicitly.
    (store.get(theHobbit, author) == jrrTolkien);

    // ---------------------------------------------------------
    // (41) The Data Store base class knows how to get string representations
    //      of items in a couple basic formats.

    var jsonAboutUtah = store.getJsonString(utah);
    dojo.debug("Everything we know about Utah: \n" + jsonAboutUtah);

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

    // ---------------------------------------------------------
    // (42) The Data Store base class knows how to sort items.

    var vermont = store.newItem({name: "Vermont", abbr: "VT", population: 608827 });
    var utah    = store.newItem({name: "Utah",    abbr: "UT", population: 2233169 });

    var populationResult, nameResult;
    (store.compare("name", utah, vermont) == dojo.data.LESS_THAN);
    (store.compare("population", utah, vermont) == dojo.data.GREATER_THAN);

    // *PW/CCM ISSUE*: the base class should also be able to generate methods
    // that can be used in the array sort method to sort an array of items.
    // For example, the following should be easy to do if the compare method
    // is implemented.
    var states = store.fetchArray();
    var sortMethod = dojo.data.getSortMethod( {direction: dojo.data.ASCENDING, attribute: "population"});
    states.sort(sortMethod);

    // SKINNER: Sounds good.  But maybe we should call it "getSortFunction"
    // instead of "getSortMethod"?  And probably getSortFunction() should be
    // a method on the data store itself, like this:
    var states = store.fetchArray();
    var sortFunction = store.getSortFunction({
        direction: dojo.data.ASCENDING,
        attribute: "population"});
    states.sort(sortFunction);

    // ---------------------------------------------------------
    // (43) Data items are opaque
    //
    // Different data store implementations will use different data structures
    // to represent data items.  An XML store may use XML DOM nodes to represent
    // data items, while a JSON store may use anonymous objects.
    //
    // The data store API exposes "handles" to the data items, like the
    // "broccoli" handles below.  The handles are intended to be opaque.
    // The client of the data store API should never message directly to
    // the broccoli.  The broccoli is only ever to be used as a parameter
    // passed into a method on the store.

    var broccoli = sqlStore.newItem({name: "Broccoli"});
    var broccoli = xmlStore.newItem({name: "Broccoli"});
    var broccoli = rdfStore.newItem({name: "Broccoli"});
    var broccoli = jsonStore.newItem({name: "Broccoli"});

    // A data store can tell you if something is a data item.

    var yes = store.isItem(broccoli);
    var no  = store.isItem(333);

    // ---------------------------------------------------------
    // (44) We might want to have the get() method optionally
    //      work for not just a single attribute but a "path"
    //      of attributes.  Here's one way that might look...

    // the long way...
    var edith = store.get(kermit, "mother"); // returns a single value
    var blue  = store.get(edith, "color");   // returns a single value

    // the short way...
    var blue  = store.get(kermit, ["mother", "color"]); // returns a single value
	
    // attributes are represented by attribute items
    var color = store.getAttribute("color");
    var mom   = store.getAttribute("mother");
	
    // you can use attribute items instead of attribute names
    var blue  = store.get(kermit, [mom, color]); // returns a single value

    // get() always returns a single value
    // getValues() always returns an array
    var array = store.getValues(kermit, [uncles, mom, color]);
	
    // The array syntax,
    //    [uncles, mom, color]
    // is an alternative to a text path sytnax like
    //    "uncles/mother/color" -- XPath style
    // or
    //    "uncles.mother.color" -- JavaScript style
    //
    // The array syntax has the advantage that it works with attribute items
    // as well as attribute names.  And the possible advantage that it doesn't
    // imply it will work exactly like XPath or JavaScript would (for example,
    // when dealing with single-valued or multi-valued attributes.

    // *** BEGIN PW/CCM ISSUE ***
    // For nested data objects (especially XML and JSON), it would be useful to
    // support more complex queries, eg.
    store.get("states/cities") <- all cities in all states
    store.get("states[abbr='AL']/cities") <- all cities in Alabama
    store.get("states[0]/cities") <- cities in the first state of the datastore
    store.get("states[contains(./cities/name, 'Springfield')]") <-All states containing a city named Springfield
    // Perhaps we could allow custom functions to be added to the query:
    function hasPanhandle(store, item) {		//DataStore will always be passed to a custom function
	var statesWithPanhandle = ["Alaska", "Connecticut", "Florida", "Idaha", "Maryland",
              "Nebraska", "Oklahoma", "Texas", "West Virgina"];
	var stateName = store.get(item, "name");
	for (var i in statesWithPanhandle) {
		if (stateName == statesWithPanhandle[i])
			return true;
	}
	return false;
    }
    store.get("states[hasPanhandle(.)]/cities") <-cities in states with panhandles
    // We like the idea of using an Array syntax both because it distinguishes
    // itself from XPath and because it's faster to parse than an XPath expression.
    // However, it seems difficult to intuitively represent complex queries
    // in an array syntax.

    // Here's an attempt to try to represent a more complex query as a JSON object...
    // Rules:
    // 1) There are three types: paths, operations, and literals
    // 2) An object represents an operation where the member's name is the operation
    //    name and the member's value is an array of operands
    // 3) Non-array values within the operand array represent literals, and strings
    //    within nested arrays represent paths.  Example operations:
    //	{plus: [1, 2]} <- results in 3
    //	{uppercase: ['stringToConvert']} results in 'STRINGTOCONVERT'
    //	{divide: [['.', 'population'], 1000]} <- Divide the populate attribute of the current object by 1000
    //	{not: [{equal: [13, 14]}]} <-nested operations
    // So using this notation for the previous queries:
    // ['states', {hasPanhandle: [['.']]}, 'cities']
    //    ^path       ^operation    ^path    ^path
    // ['states', {equals: [['.', 'name'], 'Alaska']}, 'cities']
    //    ^path    ^operation ^path ^path    ^literal      ^path
    // ['states', {equals: [uppercase: [['.', 'name']], 'ALASKA']}, 'cities']
    //    ^path     ^operation ^operation ^path  ^path    ^literal     ^path
    // *** END PW/CCM ISSUE ***

    // SKINNER:
    // In some of the suggested complex query examples here in section
    // 44, the calls to get() do not have an item as the first argument.
    // For example:
    //   store.get("states/cities");
    // In contrast, all the other get() examples prior to section 44
    // have an item as a first argument, like this:
    //   store.get(iowa, "cities");
    //
    // If get() has an item an attribute as required arguments, then
    // something like store.get(iowa, "cities") is just a conventional
    // accessor method, just like saying iowa.getCities().  Once you
    // make the first argument optional, then get() becomes more of a
    // general query method.
    //
    // I think it might be good to have two separate methods,
    // one named "get()" and one named something like "evalQuery()".
    // (Hopefully we can come up with a better name than evalQuery,
    // but I'll use that for now, for lack of a better idea.)
    //
    // The basic get() method could be very simple -- the arguments
    // would always be just a single item and a single attribute, and
    // the result would always just be single value, like this:
    //   var edith = store.get(kermit, "mother");
    // Different datastores might have different implementations for the
    // the get() method -- a datastore that stores values in DOM nodes
    // would implement get() by doing a DOM call, whereas a datastore
    // that stores data in anonymous JSON objects would implement
    // get(kermit, "color") by just doing something as simple as:
    //   return kermit["color"]
    //
    // The evalQuery() method would offer complete support for paths
    // operations, custom functions, etc.  The implementation of the
    // evalQuery() method could be built on top of calls to the simple
    // get() method.  We could have a single, standard, well-tested
    // implementation of evalQuery(), and different datastores could
    // include that implementation as a mixin, even though the datastores
    // might each have different implementations for the basic get()
    // method.
    //
    // I understand how powerful and useful that sort of evalQuery()
    // method could be, but it makes me nervous.  I'm not crazy about
    // the idea of having an evalQuery() method that operates in-memory
    // on the client, and then also having other parts of the API that
    // allow you to send queries to be executed on the server.  That
    // seems complicated.  If we do end up having both client-side
    // queries and server-side queries, maybe we should figure out some
    // new terminology, so that the word "query" isn't ambiguous.

    // ---------------------------------------------------------
    // (45) Changes to the data store (and items in the data store)
    //      trigger updates in controller and binder objects

    // FIXME:
    //   Need to figure out the best way for controller and binder
    //   objects to get notified about data changes.
    //
    //   Different types of changes:
    //      change in what items are selected (add, remove, clear)
    //      change to a single item that a binder is watching
    //      change to a single item property a binder is watching
    //      change to an item in a set being watched by a controller
    //      addition/removal of items in a set
    //      etc.
    //
    //   Different possible mechanisms for notification:
    //      dojo.event.connect() to store methods like store.set()
    //      dojo.event.topic -- publish and subscribe
    //      observer/observable pattern
    //      etc.

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

Attachments (0)

  File By Size Attached Ver.