"openils.FlattenerStore", null, {
"_last_fetch": null, /* used internally */
+ "_flattener_url": "/opac/extras/flattener",
/* Everything between here and the constructor can be specified in
* the constructor's args object. */
},
"_prepare_flattener_params": function(req) {
- var limit = (!isNaN(req.count) && req.count != Infinity) ?
- req.count : this.limit;
- var offset = (!isNaN(req.start) && req.start != Infinity) ?
- req.start : this.offset;
-
var content = {
"hint": this.fmClass,
- "ses": openils.User.authtoken,
- "where": dojo.toJson(req.query),
- "slo": dojo.toJson({
- "sort": this._prepare_sort(req.sort),
- "limit": limit,
- "offset": offset
- })
+ "ses": openils.User.authtoken
};
+ /* If we're asked for a specific identity, we don't use
+ * any query or sort/count/start (sort/limit/offset). */
+ if ("identity" in req) {
+ var where = {};
+ where[this.fmIdentifier] = req.identity;
+
+ content.where = dojo.toJson(where);
+ } else {
+ var limit = (!isNaN(req.count) && req.count != Infinity) ?
+ req.count : this.limit;
+ var offset = (!isNaN(req.start) && req.start != Infinity) ?
+ req.start : this.offset;
+
+ dojo.mixin(
+ content, {
+ "where": dojo.toJson(req.query),
+ "slo": dojo.toJson({
+ "sort": this._prepare_sort(req.sort),
+ "limit": limit,
+ "offset": offset
+ })
+ }
+ );
+ }
+
if (this.mapKey) { /* XXX TODO, get a map key */
content.key = this.mapKey;
} else {
"_display_attributes": function() {
var self = this;
- /* XXX normalize map clause like ML does,
- * so we can use short form */
return openils.Util.objectProperties(this.mapClause).filter(
function(key) { return self.mapClause[key].display; }
);
if (typeof something != "object" || something === null)
return false;
- var fields = this._display_attributes()
+ var fields = this._display_attributes();
for (var i = 0; i < fields.length; i++) {
var cur = fields[i];
- if (typeof something[cur] == "undefined")
+ if (!(cur in something))
return false;
}
return true;
},
"isItemLoaded": function(/* anything */ something) {
- console.warn("[unfinished] isItemLoaded(" + something + ")");
+ //console.log("isItemLoaded(" + something + ")");
- /* This is assuming items are always loaded, which is probably not right for this particular store.*/
- return this.isItem(something);
+ /* XXX if 'something' is not an item at all, are we just supposed
+ * to return false or throw an exception? */
+ return this.isItem(something) && (
+ something[this.fmIdentifier] in this._current_items
+ );
},
"close": function(/* object */ request) { /* no-op */ return; },
if (!this.isItem(keywordArgs.item))
throw new FlattenerStoreError("not an item; can't load it");
- keywordArgs.identity = this.getIdentity(item);
+ keywordArgs.identity = this.getIdentity(keywordArgs.item);
return this.fetchItemByIdentity(keywordArgs);
},
console.info("fetch(" + dojo.toJson(req) + ")");
- /* XXX We're clearing this cache upon every fetch? Do we need/want
- * to do that? Not sure. */
- this._current_items = {};
+ // this._current_items={}; /* I'm pretty sure we don't want this */
var callback_scope = req.scope || dojo.global;
var post_params = this._prepare_flattener_params(req);
};
req.abort = function() {
- alert("The 'abort' operation is not supported");
+ throw new FlattenerStoreError(
+ "The 'abort' operation is not supported"
+ );
};
var fetch_time = this._last_fetch = (new Date().getTime());
dojo.xhrPost({
- "url": "/opac/extras/flattener",
+ "url": this._flattener_url,
"content": post_params,
"handleAs": "json",
"sync": false,
/* *** Begin dojo.data.api.Identity methods *** */
"getIdentity": function(/* object */ item) {
- //console.log("getIdentity(" + item + ")");
if (!this.isItem(item))
throw new FlattenerStoreError("not an item");
return [this.fmIdentifier];
},
+ /* not officially part of dojo.data.api.Read, and mostly probably
+ * what fetchItemByIdentity should do? */
+ "refreshItem": function(/* object */ item) {
+ console.log("refreshItem(" + dojo.toJson(item) + ")");
+ var post_params = this._prepare_flattener_params({"identity": this.getIdentity(item)});
+
+ var self = this;
+ var process_fetch_one = function(obj, when) {
+ if (when < self._last_fetch) /* Stale response. Discard. */
+ return;
+
+ /* don't call onBegin for this, even */
+ if (dojo.isArray(obj)) {
+ if (obj.length == 1) {
+ obj = obj[0];
+ for (var prop in obj) {
+ self.setValue(item, prop, obj[prop]);
+ }
+ } else if (obj.length > 1) {
+ throw new FlattenerStoreError("too many results");
+ } else {
+ /* no results; not really an error from the
+ * store's point of view I suppose? */
+ }
+ } else {
+ throw new FlattenerStoreError("bad response");
+ }
+ };
+
+ console.log("post_params: " + dojo.toJson(post_params));
+
+ var fetch_time = this._last_fetch = (new Date().getTime());
+
+ dojo.xhrPost({
+ "url": this._flattener_url,
+ "content": post_params,
+ "handleAs": "json",
+ "sync": false,
+ "preventCache": true,
+ "headers": {"Accept": "application/json"},
+ "load": function(obj){ process_fetch_one(obj, fetch_time); }
+ });
+ },
+
"fetchItemByIdentity": function(/* object */ keywordArgs) {
+ console.warning("[unimplemented] fetchItemByIdentity() unneeded?");
+ },
- /* XXX This almost certainly needs attention (unless DataGrid
- * doesn't use it (or loadItem)?), and needs to be able to call
- * fetch() or to talk to the web service directly. */
+ /* dojo.data.api.Write */
- console.warn(
- "[unfinished] fetchItemByIdentity(" +
- dojo.toJson(keywordArgs) + ")"
- );
- if (keywordArgs.identity == undefined)
- return null; // Identity API spec unclear whether error callback
- // would need to be run, so we won't.
- var callback_scope = keywordArgs.scope || dojo.global;
+/* to add:
+ * ------
+ * newItem -> call onNew(newitem)
+ * deleteItem -> call onDelete(deleteditem)
+ * setValue -> call onSet(item, attr, oldval, newval)
+ * setValues -> ditto
+ */
- var item;
- if (item = this._current_items[keywordArgs.identity]) {
- if (typeof keywordArgs.onItem == "function")
- keywordArgs.onItem.call(callback_scope, item);
+ "newItem": function(keywordArgs, parentInfo) {
+ if (parentInfo)
+ throw new FlattenerStoreError("not a hierarchical datastore");
- return item;
- } else {
- if (typeof keywordArgs.onError == "function")
- keywordArgs.onError.call(callback_scope, E);
- return null;
- }
+ /* we need to get new item by calling a fetch or something, because
+ * the new fmobj as result of createpane or something is not enough
+ * (we need the data the flattener would return about it)
+ *
+ * finish when editing is figured out */
+ //this.onNew(myNewItem, parentInfo)
+ },
+
+ "setValue": function(item, attribute, value) {
+ if (attribute == this.fmIdentifier)
+ throw new FlattenerStoreError("can't change item identifier");
+
+ var old_value = dojo.clone(item[attribute]);
+
+ item[attribute] = dojo.clone(value);
+ this.onSet(item, attribute, old_value, value);
+ },
+
+ "setValues": function(item, attribute, values) {
+ console.warn("[unimplemented] setValues(); unneeded or TODO?");
},
+ "deleteItem": function(item) {
+ console.warn("[unimplemented] deleteItem() XXX TODO");
+ },
+
+ "unsetAttribute": function() {
+ console.warn("[unimplemented] unsetAttribute()");
+ },
+
+ "save": function() {
+ console.warn("[unimplemented] save()");
+ },
+
+ "revert": function() {
+ console.warn("[unimplemented] revert()");
+ },
+
+ "isDirty": function() { /* I /think/ this will be ok for our purposes */
+ console.info("[stub] isDirty() will always return false");
+
+ return false;
+ },
+
+ /* dojo.data.api.Notification */
+
+ "onNew" : function(item) { /* no-op, but keep */ },
+ "onDelete" : function(item) { /* no-op, keep */ },
+ "onSet": function(item, attr, oldval, newval) { /* no-op, but keep */ },
+
/* *** Classes implementing any Dojo APIs do this to list which
* APIs they're implementing. *** */
"getFeatures": function() {
return {
"dojo.data.api.Read": true,
- "dojo.data.api.Identity": true
+ "dojo.data.api.Identity": true,
+ "dojo.data.api.Notification": true,
+ "dojo.data.api.Write": true /* well, only partly */
};
}
});
dojo.declare(
"openils.widget.FlattenerGrid",
[dojox.grid.DataGrid], {
- /* These potential constructor arguments are useful to
- * lattenerGrid in their own right */
+ /* These potential constructor arguments are useful to
+ * FlattenerGrid in their own right */
"columnReordering": true,
"columnPersistKey": null,
/* These potential constructor arguments maybe useful to
* FlattenerGrid in their own right, and are passed to
* FlattenerStore. */
+ "editable": true,
"fmClass": null,
"fmIdentifier": null,
"mapExtras": null,
"startup": function() {
if (!this.store) {
- this.store = new openils.FlattenerStore({
- "fmClass": this.fmClass,
- "fmIdentifier": this.fmIdentifier,
- "mapClause": (this.mapClause || this._generate_map()),
- "baseSort": this.baseSort,
- "defaultSort": this.defaultSort
- });
+ this._setStore( /* this exact method chosen intentionally */
+ new openils.FlattenerStore({
+ "fmClass": this.fmClass,
+ "fmIdentifier": this.fmIdentifier,
+ "mapClause":
+ (this.mapClause || this._generate_map()),
+ "baseSort": this.baseSort,
+ "defaultSort": this.defaultSort
+ })
+ );
}
if (!this.columnPicker) {
"_makeEditPane": function(storeItem, rowIndex, onPostSubmit, onCancel) {
var grid = this;
var fmObject = (new openils.PermaCrud()).retrieve(
- this.fmClass,
+ this.fmClass,
this.store.getIdentity(storeItem)
);
"requiredFields": this.requiredFields,
"suppressFields": this.suppressEditFields,
"onPostSubmit": function() {
- console.info("onPostSubmit");
- console.warn("[unfinished] would update store with fmObject values here");
- // for(var i in fmObject._fields) {
- // var field = fmObject._fields[i];
- // if(idents.filter(function(j){return (j == field)})[0])
- // continue; // don't try to edit an identifier field
- // grid.store.setValue(storeItem, field, fmObject[field]());
- // }
+ console.log("onPostSubmit");
+ /* ask the store to call flattener specially to get
+ * the flat row related to only this fmobj */
+ grid.store.refreshItem(storeItem);
+
if (grid.onPostUpdate)
grid.onPostUpdate(storeItem, rowIndex);
+
setTimeout(
function() {
try {