601 lines
16 KiB
JavaScript
601 lines
16 KiB
JavaScript
//>>built
|
|
define("dojox/data/CdfStore", ["dojo", "dojox", "dojo/data/util/sorter"], function(dojo, dojox) {
|
|
|
|
dojox.data.ASYNC_MODE = 0;
|
|
dojox.data.SYNC_MODE = 1;
|
|
|
|
dojo.declare("dojox.data.CdfStore", null, {
|
|
// summary:
|
|
// IMPORTANT: The CDF Store is designed to work with Tibco GI, and references Tibco's
|
|
// JSX3 JavaScript library and will not work without it.
|
|
//
|
|
// The CDF Store implements dojo.data.Read, Write, and Identity api's. It is a local
|
|
// (in memory) store that handles XML documents formatted according to the
|
|
// Common Data Format (CDF) spec:
|
|
// http://www.tibco.com/devnet/resources/gi/3_1/tips_and_techniques/CommonDataFormatCDF.pdf
|
|
//
|
|
// The purpose of this store is to provide a glue between a jsx3 CDF file and a Dijit.
|
|
//
|
|
// While a CDF document is an XML file, other than the initial input, all data returned
|
|
// from and written to this store should be in object format.
|
|
//
|
|
// identity: [const] String
|
|
// The unique identifier for each item. Defaults to "jsxid" which is standard for a CDF
|
|
// document. Should not be changed.
|
|
identity: "jsxid",
|
|
//
|
|
// url : String
|
|
// The location from which to fetch the XML (CDF) document.
|
|
url: "",
|
|
//
|
|
// xmlStr: String
|
|
// A string that can be parsed into an XML document and should be formatted according
|
|
// to the CDF spec.
|
|
// example:
|
|
// | '<data jsxid="jsxroot"><record jsxtext="A"/><record jsxtext="B" jsxid="2" jsxid="2"/></data>'
|
|
xmlStr:"",
|
|
//
|
|
// data: Object
|
|
// A object that will be converted into the xmlStr property, and then parsed into a CDF.
|
|
data:null,
|
|
//
|
|
// label: String
|
|
// The property within each item used to define the item.
|
|
label: "",
|
|
//
|
|
// mode [const]: dojox.data.ASYNC_MODE | dojox.data.SYNC_MODE
|
|
// This store supports syncronous fetches if this property is set to dojox.data.SYNC_MODE.
|
|
mode:dojox.data.ASYNC_MODE,
|
|
|
|
constructor: function(/* Object */ args){
|
|
// summary:
|
|
// Constructor for the CDF store. Instantiate a new CdfStore.
|
|
//
|
|
if(args){
|
|
this.url = args.url;
|
|
this.xmlStr = args.xmlStr || args.str;
|
|
if(args.data){
|
|
this.xmlStr = this._makeXmlString(args.data);
|
|
}
|
|
this.identity = args.identity || this.identity;
|
|
this.label = args.label || this.label;
|
|
this.mode = args.mode !== undefined ? args.mode : this.mode;
|
|
}
|
|
this._modifiedItems = {};
|
|
|
|
this.byId = this.fetchItemByIdentity;
|
|
},
|
|
|
|
/* dojo.data.api.Read */
|
|
|
|
getValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* value? */ defaultValue){
|
|
// summary:
|
|
// Return an property value of an item
|
|
//
|
|
return item.getAttribute(property) || defaultValue; // anything
|
|
},
|
|
|
|
getValues: function(/* jsx3.xml.Entity */ item, /* String */ property){
|
|
// summary:
|
|
// Return an array of values
|
|
//
|
|
// TODO!!! Can't find an example of an array in any CDF files
|
|
//
|
|
var v = this.getValue(item, property, []);
|
|
return dojo.isArray(v) ? v : [v];
|
|
},
|
|
|
|
getAttributes: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// Return an array of property names
|
|
//
|
|
return item.getAttributeNames(); // Array
|
|
},
|
|
|
|
hasAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
|
|
// summary:
|
|
// Check whether an item has a property
|
|
//
|
|
return (this.getValue(item, property) !== undefined); // Boolean
|
|
},
|
|
|
|
hasProperty: function(/* jsx3.xml.Entity */ item, /* String */ property){
|
|
// summary:
|
|
// Alias for hasAttribute
|
|
return this.hasAttribute(item, property);
|
|
},
|
|
|
|
containsValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* anything */ value){
|
|
// summary:
|
|
// Check whether an item contains a value
|
|
//
|
|
var values = this.getValues(item, property);
|
|
for(var i = 0; i < values.length; i++){
|
|
if(values[i] === null){ continue; }
|
|
if((typeof value === "string")){
|
|
if(values[i].toString && values[i].toString() === value){
|
|
return true;
|
|
}
|
|
}else if(values[i] === value){
|
|
return true; //boolean
|
|
}
|
|
}
|
|
return false;//boolean
|
|
},
|
|
|
|
isItem: function(/* anything */ something){
|
|
// summary:
|
|
// Check whether the object is an item (jsx3.xml.Entity)
|
|
//
|
|
if(something.getClass && something.getClass().equals(jsx3.xml.Entity.jsxclass)){
|
|
return true; //boolean
|
|
}
|
|
return false; //boolran
|
|
},
|
|
|
|
isItemLoaded: function(/* anything */ something){
|
|
// summary:
|
|
// Check whether the object is a jsx3.xml.Entity object and loaded
|
|
//
|
|
return this.isItem(something); // Boolean
|
|
},
|
|
|
|
loadItem: function(/* object */ keywordArgs){
|
|
// summary:
|
|
// Load an item
|
|
// description:
|
|
// The store always loads all items, so if it's an item, then it's loaded.
|
|
},
|
|
|
|
getFeatures: function(){
|
|
// summary:
|
|
// Return supported data APIs
|
|
//
|
|
return {
|
|
"dojo.data.api.Read": true,
|
|
"dojo.data.api.Write": true,
|
|
"dojo.data.api.Identity":true
|
|
}; // Object
|
|
},
|
|
|
|
getLabel: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// See dojo.data.api.Read.getLabel()
|
|
//
|
|
if((this.label !== "") && this.isItem(item)){
|
|
var label = this.getValue(item,this.label);
|
|
if(label){
|
|
return label.toString();
|
|
}
|
|
}
|
|
return undefined; //undefined
|
|
},
|
|
|
|
getLabelAttributes: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// returns an array of what properties of the item that were used
|
|
// to generate its label
|
|
// See dojo.data.api.Read.getLabelAttributes()
|
|
//
|
|
if(this.label !== ""){
|
|
return [this.label]; //array
|
|
}
|
|
return null; //null
|
|
},
|
|
|
|
|
|
fetch: function(/* Object? */ request){
|
|
// summary:
|
|
// Returns an Array of items based on the request arguments.
|
|
// description:
|
|
// Returns an Array of items based on the request arguments.
|
|
// If the store is in ASYNC mode, the items should be expected in an onComplete
|
|
// method passed in the request object. If store is in SYNC mode, the items will
|
|
// be return directly as well as within the onComplete method.
|
|
// note:
|
|
// The mode can be set on store initialization or during a fetch as one of the
|
|
// parameters.
|
|
//
|
|
// query: String
|
|
// The items in the store are treated as objects, but this is reading an XML
|
|
// document. Further, the actual querying of the items takes place in Tibco GI's
|
|
// jsx3.xml.Entity. Therefore, we are using their syntax which is xpath.
|
|
// Note:
|
|
// As conforming to a CDF document, most, if not all nodes are considered "records"
|
|
// and their tagNames are as such. The root node is named "data".
|
|
//
|
|
// examples:
|
|
// All items:
|
|
// | store.fetch({query:"*"});
|
|
// Item with a jsxid attribute equal to "1" (note you could use byId for this)
|
|
// | store.fetch({query:"//record[@jsxid='1']"});
|
|
// All items with any jsxid attribute:
|
|
// | "//record[@jsxid='*']"
|
|
// The items with a jsxid of '1' or '4':
|
|
// | "//record[@jsxid='4' or @jsxid='1']"
|
|
// All children within a "group" node (could be multiple group nodes):
|
|
// "//group/record"
|
|
// All children within a specific group node:
|
|
// "//group[@name='mySecondGroup']/record"
|
|
// Any record, anywhere in the document:
|
|
// | "//record"
|
|
// Only the records beneath the root (data) node:
|
|
// | "//data/record"
|
|
//
|
|
// See:
|
|
// http://www.tibco.com/devnet/resources/gi/3_7/api/html/jsx3/xml/Entity.html#method:selectNodes
|
|
// http://www.w3.org/TR/xpath
|
|
// http://msdn.microsoft.com/en-us/library/ms256086.aspx
|
|
//
|
|
// See dojo.data.Read.fetch():
|
|
// onBegin
|
|
// onComplete
|
|
// onItem
|
|
// onError
|
|
// scope
|
|
// start
|
|
// count
|
|
// sort
|
|
//
|
|
request = request || {};
|
|
if(!request.store){
|
|
request.store = this;
|
|
}
|
|
if(request.mode !== undefined){
|
|
this.mode = request.mode;
|
|
}
|
|
var self = this;
|
|
|
|
var errorHandler = function(errorData){
|
|
if(request.onError){
|
|
var scope = request.scope || dojo.global;
|
|
request.onError.call(scope, errorData, request);
|
|
}else{
|
|
console.error("cdfStore Error:", errorData);
|
|
}
|
|
};
|
|
|
|
var fetchHandler = function(items, requestObject){
|
|
requestObject = requestObject || request;
|
|
var oldAbortFunction = requestObject.abort || null;
|
|
var aborted = false;
|
|
|
|
var startIndex = requestObject.start?requestObject.start:0;
|
|
var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
|
|
|
|
requestObject.abort = function(){
|
|
aborted = true;
|
|
if(oldAbortFunction){
|
|
oldAbortFunction.call(requestObject);
|
|
}
|
|
};
|
|
|
|
var scope = requestObject.scope || dojo.global;
|
|
if(!requestObject.store){
|
|
requestObject.store = self;
|
|
}
|
|
if(requestObject.onBegin){
|
|
requestObject.onBegin.call(scope, items.length, requestObject);
|
|
}
|
|
if(requestObject.sort){
|
|
items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
|
|
}
|
|
|
|
if(requestObject.onItem){
|
|
for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
|
|
var item = items[i];
|
|
if(!aborted){
|
|
requestObject.onItem.call(scope, item, requestObject);
|
|
}
|
|
}
|
|
}
|
|
if(requestObject.onComplete && !aborted){
|
|
if(!requestObject.onItem){
|
|
items = items.slice(startIndex, endIndex);
|
|
if(requestObject.byId){
|
|
items = items[0];
|
|
}
|
|
}
|
|
requestObject.onComplete.call(scope, items, requestObject);
|
|
}else{
|
|
items = items.slice(startIndex, endIndex);
|
|
if(requestObject.byId){
|
|
items = items[0];
|
|
}
|
|
}
|
|
return items;
|
|
};
|
|
|
|
if(!this.url && !this.data && !this.xmlStr){
|
|
errorHandler(new Error("No URL or data specified."));
|
|
return false;
|
|
}
|
|
var localRequest = request || "*"; // use request for _getItems()
|
|
|
|
if(this.mode == dojox.data.SYNC_MODE){
|
|
// sync mode. items returned directly
|
|
var res = this._loadCDF();
|
|
if(res instanceof Error){
|
|
if(request.onError){
|
|
request.onError.call(request.scope || dojo.global, res, request);
|
|
}else{
|
|
console.error("CdfStore Error:", res);
|
|
}
|
|
return res;
|
|
}
|
|
this.cdfDoc = res;
|
|
|
|
var items = this._getItems(this.cdfDoc, localRequest);
|
|
if(items && items.length > 0){
|
|
items = fetchHandler(items, request);
|
|
}else{
|
|
items = fetchHandler([], request);
|
|
}
|
|
return items;
|
|
|
|
}else{
|
|
|
|
// async mode. Return a Deferred.
|
|
var dfd = this._loadCDF();
|
|
dfd.addCallbacks(dojo.hitch(this, function(cdfDoc){
|
|
var items = this._getItems(this.cdfDoc, localRequest);
|
|
if(items && items.length > 0){
|
|
fetchHandler(items, request);
|
|
}else{
|
|
fetchHandler([], request);
|
|
}
|
|
}),
|
|
dojo.hitch(this, function(err){
|
|
errorHandler(err, request);
|
|
}));
|
|
|
|
return dfd; // Object
|
|
}
|
|
},
|
|
|
|
|
|
_loadCDF: function(){
|
|
// summary:
|
|
// Internal method.
|
|
// If a cdfDoc exists, return it. Otherwise, get one from JSX3,
|
|
// load the data or url, and return the doc or a deferred.
|
|
var dfd = new dojo.Deferred();
|
|
if(this.cdfDoc){
|
|
if(this.mode == dojox.data.SYNC_MODE){
|
|
return this.cdfDoc; // jsx3.xml.CDF
|
|
}else{
|
|
setTimeout(dojo.hitch(this, function(){
|
|
dfd.callback(this.cdfDoc);
|
|
}), 0);
|
|
return dfd; // dojo.Deferred
|
|
}
|
|
}
|
|
|
|
this.cdfDoc = jsx3.xml.CDF.Document.newDocument();
|
|
this.cdfDoc.subscribe("response", this, function(evt){
|
|
dfd.callback(this.cdfDoc);
|
|
});
|
|
this.cdfDoc.subscribe("error", this, function(err){
|
|
dfd.errback(err);
|
|
});
|
|
|
|
this.cdfDoc.setAsync(!this.mode);
|
|
if(this.url){
|
|
this.cdfDoc.load(this.url);
|
|
}else if(this.xmlStr){
|
|
this.cdfDoc.loadXML(this.xmlStr);
|
|
if(this.cdfDoc.getError().code){
|
|
return new Error(this.cdfDoc.getError().description); // Error
|
|
}
|
|
}
|
|
|
|
if(this.mode == dojox.data.SYNC_MODE){
|
|
return this.cdfDoc; // jsx3.xml.CDF
|
|
}else{
|
|
return dfd; // dojo.Deferred
|
|
}
|
|
},
|
|
|
|
_getItems: function(/* jsx3.xml.Entity */cdfDoc, /* Object */request){
|
|
// summary:
|
|
// Internal method.
|
|
// Requests the items from jsx3.xml.Entity with an xpath query.
|
|
//
|
|
var itr = cdfDoc.selectNodes(request.query, false, 1);
|
|
var items = [];
|
|
while(itr.hasNext()){
|
|
items.push(itr.next());
|
|
}
|
|
return items;
|
|
},
|
|
|
|
close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
|
|
// summary:
|
|
// See dojo.data.api.Read.close()
|
|
},
|
|
|
|
/* dojo.data.api.Write */
|
|
|
|
newItem: function(/* object? */ keywordArgs, /* object? || String? */parentInfo){
|
|
// summary:
|
|
// Creates a jsx3.xml.Entity item and inserts it either inside the
|
|
// parent or appends it to the root
|
|
//
|
|
keywordArgs = (keywordArgs || {});
|
|
if(keywordArgs.tagName){
|
|
// record tagName is automatic and this would add it
|
|
// as a property
|
|
if(keywordArgs.tagName!="record"){
|
|
// TODO: How about some sort of group?
|
|
console.warn("Only record inserts are supported at this time");
|
|
}
|
|
delete keywordArgs.tagName;
|
|
}
|
|
keywordArgs.jsxid = keywordArgs.jsxid || this.cdfDoc.getKey();
|
|
if(this.isItem(parentInfo)){
|
|
parentInfo = this.getIdentity(parentInfo);
|
|
}
|
|
var item = this.cdfDoc.insertRecord(keywordArgs, parentInfo);
|
|
|
|
this._makeDirty(item);
|
|
|
|
return item; // jsx3.xml.Entity
|
|
},
|
|
|
|
deleteItem: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// Delete an jsx3.xml.Entity (wrapper to a XML element).
|
|
//
|
|
this.cdfDoc.deleteRecord(this.getIdentity(item));
|
|
this._makeDirty(item);
|
|
return true; //boolean
|
|
},
|
|
|
|
setValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* almost anything */ value){
|
|
// summary:
|
|
// Set an property value
|
|
//
|
|
this._makeDirty(item);
|
|
item.setAttribute(property, value);
|
|
return true; // Boolean
|
|
},
|
|
|
|
setValues: function(/* jsx3.xml.Entity */ item, /* String */ property, /*array*/ values){
|
|
// summary:
|
|
// Set property values
|
|
// TODO: Needs to be fully implemented.
|
|
//
|
|
this._makeDirty(item);
|
|
console.warn("cdfStore.setValues only partially implemented.");
|
|
return item.setAttribute(property, values);
|
|
|
|
},
|
|
|
|
unsetAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
|
|
// summary:
|
|
// Remove an property
|
|
//
|
|
this._makeDirty(item);
|
|
item.removeAttribute(property);
|
|
return true; // Boolean
|
|
},
|
|
|
|
revert: function(){
|
|
// summary:
|
|
// Invalidate changes (new and/or modified elements)
|
|
// Resets data by simply deleting the reference to the cdfDoc.
|
|
// Subsequent fetches will load the new data.
|
|
// Note:
|
|
// Any items outside the store will no longer be valid and may cause errors.
|
|
//
|
|
delete this.cdfDoc;
|
|
this._modifiedItems = {};
|
|
return true; //boolean
|
|
},
|
|
|
|
isDirty: function(/* jsx3.xml.Entity ? */ item){
|
|
// summary:
|
|
// Check whether an item is new, modified or deleted.
|
|
// If no item is passed, checks if anything in the store has changed.
|
|
//
|
|
if(item){
|
|
return !!this._modifiedItems[this.getIdentity(item)]; // Boolean
|
|
}else{
|
|
var _dirty = false;
|
|
for(var nm in this._modifiedItems){ _dirty = true; break; }
|
|
return _dirty; // Boolean
|
|
}
|
|
},
|
|
|
|
|
|
|
|
/* internal API */
|
|
|
|
_makeDirty: function(item){
|
|
// summary:
|
|
// Internal method.
|
|
// Marks items as modified, deleted or new.
|
|
var id = this.getIdentity(item);
|
|
this._modifiedItems[id] = item;
|
|
},
|
|
|
|
|
|
_makeXmlString: function(obj){
|
|
// summary:
|
|
// Internal method.
|
|
// Converts an object into an XML string.
|
|
//
|
|
var parseObj = function(obj, name){
|
|
var xmlStr = "";
|
|
var nm;
|
|
if(dojo.isArray(obj)){
|
|
for(var i=0;i<obj.length;i++){
|
|
xmlStr += parseObj(obj[i], name);
|
|
}
|
|
}else if(dojo.isObject(obj)){
|
|
xmlStr += '<'+name+' ';
|
|
for(nm in obj){
|
|
if(!dojo.isObject(obj[nm])){
|
|
xmlStr += nm+'="'+obj[nm]+'" ';
|
|
}
|
|
}
|
|
xmlStr +='>';
|
|
for(nm in obj){
|
|
if(dojo.isObject(obj[nm])){
|
|
xmlStr += parseObj(obj[nm], nm);
|
|
}
|
|
}
|
|
xmlStr += '</'+name+'>';
|
|
}
|
|
return xmlStr;
|
|
};
|
|
return parseObj(obj, "data");
|
|
},
|
|
|
|
/*************************************
|
|
* Dojo.data Identity implementation *
|
|
*************************************/
|
|
getIdentity: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// Returns the identifier for an item.
|
|
//
|
|
return this.getValue(item, this.identity); // String
|
|
},
|
|
|
|
getIdentityAttributes: function(/* jsx3.xml.Entity */ item){
|
|
// summary:
|
|
// Returns the property used for the identity.
|
|
//
|
|
return [this.identity]; // Array
|
|
},
|
|
|
|
|
|
fetchItemByIdentity: function(/* Object || String */ args){
|
|
// summary:
|
|
// See dojo.data.api.Identity.fetchItemByIdentity(keywordArgs)
|
|
//
|
|
// Note:
|
|
// This method can be synchronous if mode is set.
|
|
// Also, there is a more finger friendly alias of this method, byId();
|
|
if(dojo.isString(args)){
|
|
var id = args;
|
|
args = {query:"//record[@jsxid='"+id+"']", mode: dojox.data.SYNC_MODE};
|
|
}else{
|
|
if(args){
|
|
args.query = "//record[@jsxid='"+args.identity+"']";
|
|
}
|
|
if(!args.mode){args.mode = this.mode;}
|
|
}
|
|
args.byId = true;
|
|
return this.fetch(args); // dojo.Deferred || Array
|
|
},
|
|
byId: function(/* Object || String */ args){
|
|
// stub. See fetchItemByIdentity
|
|
}
|
|
|
|
});
|
|
|
|
return dojox.data.CdfStore;
|
|
});
|
|
|