diff --git a/centrallix-os/sys/js/htdrv_fileupload.js b/centrallix-os/sys/js/htdrv_fileupload.js old mode 100755 new mode 100644 diff --git a/centrallix-os/sys/js/htdrv_form.js b/centrallix-os/sys/js/htdrv_form.js index bf9a6bd14..bd9a3e577 100644 --- a/centrallix-os/sys/js/htdrv_form.js +++ b/centrallix-os/sys/js/htdrv_form.js @@ -264,36 +264,37 @@ function form_cb_is_discard_ready() function form_load_fields(data, no_clear, modify, onefield) { - var name_to_id = []; - if (!data) { + // No data supplied -- load hints based on previous information, if available. for(var i in this.elements) + { if (this.last_hints[this.elements[i].fieldname]) cx_set_hints(this.elements[i], this.last_hints[this.elements[i].fieldname], 'data'); else cx_set_hints(this.elements[i], '', 'data'); - //return; - } - else - { - for(var j in data) - { - if (data[j].oid) - name_to_id[data[j].oid] = j; } } + // Loop through the form elements to load the data. for(var i in this.elements) { - if (onefield && onefield != this.elements[i].fieldname) continue; + var field = this.elements[i].fieldname; + + // are we loading just one field instead of all of them? + if (onefield && onefield != field) + continue; - if (!this.elements[i]._form_type && - (typeof name_to_id[this.elements[i].fieldname]) != 'undefined' && - (typeof data[name_to_id[this.elements[i].fieldname]].type) != 'undefined') - this.elements[i]._form_type = data[name_to_id[this.elements[i].fieldname]].type; + // Make a note of the data type. + if (!this.elements[i]._form_type && data) + { + var t = data.getAttrType(field); + if (t !== null) + this.elements[i]._form_type = t; + } - if (this.elements[i].fieldname == '__position') + // Synthetic "position" form element? + if (field == '__position') { var txt = ""; if (this.mode == "New") @@ -313,18 +314,24 @@ function form_load_fields(data, no_clear, modify, onefield) } this.elements[i].setvalue(txt); } - else if ((typeof name_to_id[this.elements[i].fieldname]) != 'undefined' && (typeof data[name_to_id[this.elements[i].fieldname]].value) != 'undefined') - { - var id = name_to_id[this.elements[i].fieldname]; - this.elements[i].setvalue(data[id].value); - if (modify) - this.elements[i]._form_IsChanged = true; - cx_set_hints(this.elements[i], data[id].hints, 'data'); - this.last_hints[this.elements[i].fieldname] = data[id].hints; - } - else if (!no_clear) + else { - this.elements[i].clearvalue(); + // Normal field -- check to see if it exists in the data. + var attr = null; + if (data && (attr = data.getAttr(field))) + { + // It does exist -- set its value and presentation hints + this.elements[i].setvalue(attr.get()); + if (modify) + this.elements[i]._form_IsChanged = true; + cx_set_hints(this.elements[i], attr.getHints(), 'data'); + this.last_hints[field] = attr.getHints(); + } + else if (!no_clear) + { + // It does not exist - clear it. + this.elements[i].clearvalue(); + } } } } @@ -375,19 +382,20 @@ function form_cb_object_available(data, osrc, why) if (data) { this.ClearAll(true); - for(var j in data) + var alist = data.getAttrList(); + for(var aname in alist) { - if (!this.ifcProbe(ifValue).Exists(data[j].oid, true)) + if (!this.ifcProbe(ifValue).Exists(aname, true)) { - this.ifcProbe(ifValue).Add(data[j].oid, form_cb_getvalue); - this.valuelist.push(data[j].oid); + this.ifcProbe(ifValue).Add(aname, form_cb_getvalue); + this.valuelist.push(aname); } - this.ifcProbe(ifValue).Changing(data[j].oid, data[j].value, true); + this.ifcProbe(ifValue).Changing(aname, data.getAttrValue(aname), true); } this.data=data; - if (data.__osrc_is_last || (this.didsearch && data.id == this.recid)) - this.lastrecid = data.id; - this.recid = data.id; + if (data.__osrc_is_last || (this.didsearch && data.getID() == this.recid)) + this.lastrecid = data.getID(); + this.recid = data.getID(); this.LoadFields(this.data); @@ -1130,17 +1138,20 @@ function form_action_queryexec() { if(!htr_boolean(wgtrGetServerProperty(this,'allow_query',1))) {alert('Query not allowed');return 0;} if(!(this.mode=='Query')) {alert("You can't execute a query if you're not in Query mode.");return 0;} -/** build an query object to give the osrc **/ - var query=new Array(); - query.oid=null; - query.joinstring='AND'; + + // build an query object to give the osrc + var query = new $CX.Types.osrcObject(); + query.setID(null); + query.setJoin('AND'); + this.recid = 1; this.lastrecid = null; - + + // Set status for user for(var i in this.statuswidgets) - { this.statuswidgets[i].setvalue('QueryExec'); - } + + // Add the criteria to the query object for(var i in this.elements) { if(this.elements[i]._form_IsChanged) @@ -1148,49 +1159,45 @@ function form_action_queryexec() var v = this.elements[i].getvalue(); if (v != null && v != '') { - var t=new Object(); - t.oid=this.elements[i].fieldname; - t.value=v; - if (typeof this.elements[i]._form_type == 'undefined') - t.type='undefined'; - else - t.type=this.elements[i]._form_type; - if(isArray(v)) - t.type+='array'; - query.push(t); + // Determine data type + var type = 'undefined'; + if (typeof this.elements[i]._form_type != 'undefined') + type = this.elements[i]._form_type; + if (isArray(v)) + type += 'array'; + + // Add the criteria + query.newAttr(this.elements[i].fieldname, v, type); } } } -/** Done with the query -- YEAH **/ - //if(confirm('Send to "'+this.osrc.name+'"(osrc):'+query)) - { - this.Pending=true; - this.IsUnsaved=false; - this.is_savable = false; - //this.cb['DataAvailable'].add(this,new Function('this.osrc.ifcProbe(ifAction).Invoke("First", this)')); - this.osrc.ifcProbe(ifAction).Invoke("QueryObject", {query:query, client:this, ro:this.readonly}); - } - delete query; + + this.Pending=true; + this.IsUnsaved=false; + this.is_savable = false; + this.osrc.ifcProbe(ifAction).Invoke("QueryObject", {query:query, client:this, ro:this.readonly}); } function form_build_dataobj() { - var dataobj=new Array(); + // Get a new object + var dataobj=new $CX.Types.osrcObject(); + + // For each changed element, add an attribute to the data object we're creating. for(var i in this.elements) { if(this.elements[i]._form_IsChanged) { - var t=new Object(); - t.oid=this.elements[i].fieldname; - t.value=this.elements[i].getvalue(); - t.type=this.elements[i]._form_type; - dataobj.push(t); + dataobj.newAttr(this.elements[i].fieldname, this.elements[i].getvalue(), this.elements[i]._form_type); } } + + // Set the record ID if (this.mode == "New" || !this.data) - dataobj.oid = 0; + dataobj.setID(0); else - dataobj.oid=this.data.oid; + dataobj.setID(this.data.getID()); + return dataobj; } diff --git a/centrallix-os/sys/js/htdrv_map.js b/centrallix-os/sys/js/htdrv_map.js old mode 100755 new mode 100644 diff --git a/centrallix-os/sys/js/htdrv_osrc.js b/centrallix-os/sys/js/htdrv_osrc.js index 49eb9f160..1b83f3a2d 100644 --- a/centrallix-os/sys/js/htdrv_osrc.js +++ b/centrallix-os/sys/js/htdrv_osrc.js @@ -8,6 +8,309 @@ // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. +// + +// Merge in our object types +$.extend($CX.Types, + { + DateTime: function(d) + { + if (d && d.year) + { + Object.assign(this, d); + this.dateobj = new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second); + } + else if (typeof d == 'object' && Date.prototype.isPrototypeOf(d)) + { + this.dateobj = d; + this.year = d.getFullYear(); + this.month = d.getMonth() + 1; + this.day = d.getDate(); + this.hour = d.getHours(); + this.minute = d.getMinutes(); + this.second = d.getSeconds(); + } + else + { + d = this.dateobj = new Date(); + this.year = d.getFullYear(); + this.month = d.getMonth() + 1; + this.day = d.getDate(); + this.hour = d.getHours(); + this.minute = d.getMinutes(); + this.second = d.getSeconds(); + } + }, + Money: function() + { + }, + osrcObject: function(obj) + { + if (obj) this.copyFrom(obj); + }, + osrcAttr: function(attr) + { + if (typeof attr == 'object' && $CX.Types.osrcAttr.prototype.isPrototypeOf(attr)) + { + // Type osrcAttr + this.v = attr.v; + this.t = attr.t; + this.h = attr.h; + this.s = attr.s; + this.y = attr.y; + this.a = attr.a; + this.__cx_filter = Object.assign({}, attr.__cx_filter); + } + else + { + // Generic value + this.v = attr; + } + } + }); + + +// Money methods +// === money format: === +// I = a leading 'I' is not printed but indicates the currency is in "international format" with commas and periods reversed. +// Z = a leading 'Z' is not printed but means zeros should be printed as "-0-" +// z = a leading 'z' is not printed but means zeros should be printed as "0" +// B = a leading 'B' is not printed but means zeros should be printed as "" (blank) +// # = optional digit unless after the '.' or first before '.' +// 0 = mandatory digit, no zero suppression +// , = insert a comma (or a period, if in international format) +// . = decimal point (only one allowed) (prints a comma if in international format) +// $ = dollar sign +// + = mandatory sign, whether + or - (if 0, +) +// - = optional sign, space if + or 0 +// ^ = if last digit, round it (trunc is default). Otherwise, like '0'. NOT YET IMPL. +// () = surround # with () if it is negative. +// [] = surround # with () if it is positive. +// = (space) optional digit, but put space in its place if suppressing 0's. +// * = (asterisk) optional digit, put asterisk in its place if suppressing 0s. +// +$CX.Types.Money.prototype.format = function(f) + { + var ch; + var is_intl=false; + var is_acczero=false; + var is_plainzero=false; + var is_blankzero=false; + var str = ''; + + for(var i=0; i<=f.length; i++) + { + ch = (i= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) + item += ch; + else + { + if (item.length > 0) + { + switch(item) + { + case 'dd': str += ((this.day < 10)?'0':'') + this.day; break; + case 'MMM': str += (new Intl.DateTimeFormat([], {month: 'short'})).format(this.dateobj); break; + case 'yyyy': str += this.year; break; + case 'HH': str += ((this.hour < 10)?'0':'') + this.hour; break; + case 'mm': str += ((this.minute < 10)?'0':'') + this.minute; break; + } + } + str += ch; + item = ''; + } + } + return str; + } + +$CX.Types.DateTime.prototype.toString = function() + { + return this.format($CX.Globals.dateFormat); + } + +// +// osrcObject methods +// +$CX.Types.osrcObject.prototype.getAttrCount = function() + { + var acnt = 0; + for(var aname in this) + if (typeof this[aname] == 'object' && $CX.Types.osrcAttr.prototype.isPrototypeOf(this[aname])) + acnt++; + return acnt; + } + +$CX.Types.osrcObject.prototype.getAttrList = function() + { + var alist = {}; + for(var aname in this) + if (typeof this[aname] == 'object' && $CX.Types.osrcAttr.prototype.isPrototypeOf(this[aname])) + alist[aname] = true; + return alist; + } + +$CX.Types.osrcObject.prototype.getID = function() + { + return this.__cx_id; + } + +$CX.Types.osrcObject.prototype.setID = function(id) + { + return this.__cx_id = id; + } + +$CX.Types.osrcObject.prototype.setJoin = function(join) + { + return this.__cx_joinstring = join; + } + +$CX.Types.osrcObject.prototype.getJoin = function() + { + return this.__cx_joinstring; + } + +$CX.Types.osrcObject.prototype.getAttr = function(a) + { + if (a != '__cx_joinstring' && a != '__cx_id' && a != '__cx_handle' && a != '__proto__' && typeof this[a] == 'object') + { + if (!this[a].a) this[a].a = a; + return this[a]; + } + return null; + } + +$CX.Types.osrcObject.prototype.getAttrValue = function(a) + { + var attr = this.getAttr(a); + return attr?attr.get():null; + } + +$CX.Types.osrcObject.prototype.getAttrType = function(a) + { + var attr = this.getAttr(a); + return attr?attr.getType():null; + } + +$CX.Types.osrcObject.prototype.copyFrom = function(o) + { + if (typeof o == 'object') + { + if ($CX.Types.osrcObject.prototype.isPrototypeOf(o)) + { + // osrcObject type + var attrlist = o.getAttrList(); + for(var a in attrlist) + { + this[a] = new $CX.Types.osrcAttr(o.getAttr(a)); + } + } + else + { + // Generic object -- just copy property names and values + for(var p in o) + { + this[p] = new $CX.Types.osrcAttr(o[p]); + } + } + } + } + +// Either invoke with newAttr(attrname, value, type) or as newAttr(attrname, attrobj) +$CX.Types.osrcObject.prototype.newAttr = function(a,v,t) + { + if (a) + { + this[a] = new $CX.Types.osrcAttr(v); + if (t) this[a].t = t; + return this[a]; + } + } + +$CX.Types.osrcObject.prototype.removeAttr = function(a) + { + var a = this.getAttr(a); + if (a) + { + delete this[a]; + } + return a; + } + +// +// osrcAttr methods +// +$CX.Types.osrcAttr.prototype.get = function() + { + return this.v; + } + +$CX.Types.osrcAttr.prototype.set = function(v) + { + return this.v = v; + } + +$CX.Types.osrcAttr.prototype.getSystem = function() + { + return this.y; + } + +$CX.Types.osrcAttr.prototype.getType = function() + { + return this.t; + } + +$CX.Types.osrcAttr.prototype.setType = function(t) + { + return this.t = t; + } + +$CX.Types.osrcAttr.prototype.getHints = function() + { + return this.h; + } + +$CX.Types.osrcAttr.prototype.getFilter = function() + { + if (!this.__cx_filter) + this.__cx_filter = {}; + return this.__cx_filter; + } + function osrc_init_query() { @@ -27,23 +330,27 @@ function osrc_action_order_object(aparam) //order) this.ifcProbe(ifAction).Invoke("QueryObject", {query:this.queryobject, client:null, ro:this.readonly}); } + function osrc_criteria_from_aparam(aparam) { - var qo = []; - var t; - var v; - qo.joinstring = 'AND'; + var qo = new $CX.Types.osrcObject(); + qo.setJoin('AND'); + + // Add a criteria item for each aparam item for (var i in aparam) { - if (i == 'cx__enable_lists') continue; - if (i == 'cx__case_insensitive') continue; + var v = aparam[i]; + var t = 'undefined'; + + if (i == 'cx__enable_lists' || i == 'cx__case_insensitive' || i == '_Origin' || i == '_EventName') + continue; if (i == 'joinstring' && (new String(aparam[i])).toLowerCase() == 'or') { - qo.joinstring = 'OR'; + qo.setJoin('OR'); continue; } - v = aparam[i]; - if (i == '_Origin' || i == '_EventName') continue; + + // Determine type if (typeof v == 'string' && (new String(v)).indexOf(',') > 0 && aparam.cx__enable_lists) { t = 'stringarray'; @@ -57,11 +364,15 @@ function osrc_criteria_from_aparam(aparam) t = 'integer'; else t = 'string'; - qo.push({oid:i, value:v, type:t}); + + // Add the attribute to the query object + qo.newAttr(i, v, t); } + return qo; } + function osrc_action_query_param(aparam) { this.init = true; @@ -93,19 +404,14 @@ function osrc_action_refresh(aparam) this.doing_refresh = true; // Keep track of current object by name + this.refresh_objname = null; if (this.replica[this.CurrentRecord]) { - for(var j=0; j 0) { var filt = this.MakeFilter(filter); if (filt) { - if(firstone) - statement+=sep; + if (firstone) + statement += sep; else - statement+=' '+q.joinstring+' '; + statement += ' ' + q.getJoin() + ' '; statement+=filt; } } @@ -406,17 +723,20 @@ function osrc_query_object_handler(aparam) function osrc_make_filter_colref(col) { - return (col.obj?(':"' + col.obj + '"'):'') + ':"' + col.oid + '"'; + var filt = col.getFilter(); + return (filt.obj?(':"' + filt.obj + '"'):'') + ':"' + col.a + '"'; } -function osrc_make_filter_integer(col,val) +function osrc_make_filter_integer(col, val) { - if (val == null && (typeof col.nullisvalue == 'undefined' || col.nullisvalue == true)) + var filt = col.getFilter(); + + if (val == null && (typeof filt.nullisvalue == 'undefined' || filt.nullisvalue == true)) return this.MFCol(col) + ' is null '; else if (val == null) return this.MFCol(col) + ' = null '; - else if (!col.plainsearch && typeof val != 'number' && (new String(val)).search(/-/)>=0) + else if (!filt.plainsearch && typeof val != 'number' && (new String(val)).search(/-/)>=0) { var parts = (new String(val)).split(/-/); return '(' + this.MFCol(col) + ' >=' + parts[0] + ' AND ' + this.MFCol(col) + ' <=' + parts[1] + ')'; @@ -431,13 +751,15 @@ function osrc_make_filter_string(col, val, icase) var str = ''; var ifunc = ''; var colref = this.MFCol(col); + var filt = col.getFilter(); + if (icase) ifunc = 'upper'; - if (val == null && (typeof col.nullisvalue == 'undefined' || col.nullisvalue == true)) + if (val == null && (typeof filt.nullisvalue == 'undefined' || filt.nullisvalue == true)) str=colref + ' is null '; else if (val == null) str=colref + ' = null '; - else if (col.plainsearch) + else if (filt.plainsearch) str=ifunc + '(' + colref + ')='+ifunc+'("'+val+'")'; else if (val.search(/^\*.+\*$/)>=0) @@ -465,6 +787,7 @@ function osrc_make_filter_string(col, val, icase) } else str=ifunc + '(' + colref + ')='+ifunc+'("'+val+'")'; + return str; } @@ -474,194 +797,193 @@ function osrc_make_filter(q) var firstone=true; var statement=''; var isnot; - for(var i in q) + var al = q.getAttrList(); + for(var a in al) { + var col = q.getAttr(a); + var filt = col.getFilter(); isnot = false; - if(i!='oid' && i!='joinstring') + var str; + if (filt.force_empty) { - var str; - if (q[i].force_empty) + str = " (" + this.MFCol(col) + " = null and 1 == 0) "; + } + else + { + var val=col.get(); + + if (typeof val == 'string') + val = new String(val); + + if (val && val.substring && val.substring(0,1) == '~') { - //str = '1 == 0'; - str = " (" + this.MFCol(q[i]) + " = null and 1 == 0) "; + val = val.substring(1); + isnot = true; } - else if(q[i].joinstring) + else if (val && val.length && val[0] && val[0].substring && val[0].substring(0,1) == '~') { - str=this.MakeFilter(q[i]); + val[0] = val[0].substring(1); + isnot = true; } - else - { - var val=q[i].value; - //if (val == null) continue; - if (typeof val == 'string') - val = new String(val); + // Use a "remembered" type for this attribute, if not supplied? + if (typeof col.getType() == "undefined" && this.type_list[a]) + col.setType(this.type_list[a]); - if (val && val.substring && val.substring(0,1) == '~') - { - val = val.substring(1); - isnot = true; - } - else if (val && val.length && val[0] && val[0].substring && val[0].substring(0,1) == '~') - { - val[0] = val[0].substring(1); - isnot = true; - } + var colref = this.MFCol(col); - if (typeof q[i].type == "undefined" && this.type_list[q[i].oid]) - q[i].type = this.type_list[q[i].oid]; + switch(col.getType()) + { + case 'criteria': + str = this.MakeFilter(val); + break; - var colref = this.MFCol(q[i]); + case 'integer': + str = this.MakeFilterInteger(col, val); + break; - switch(q[i].type) - { - case 'integer': - str = this.MakeFilterInteger(q[i], val); - break; - - case 'integerarray': - if (val == null && (typeof col.nullisvalue == 'undefined' || col.nullisvalue == true)) - str = colref + 'is null '; - else if (val == null) - str = colref + ' = null '; - else if (val.length) + case 'integerarray': + if (val == null && (typeof filt.nullisvalue == 'undefined' || filt.nullisvalue == true)) + str = colref + 'is null '; + else if (val == null) + str = colref + ' = null '; + else if (val.length) + { + str = "("; + for(var j=0;j=') - str+=' >= \"'+val[j].substring(2)+'\"'; - else if(val[j].substring(0,2)=='<=') - str+=' <= \"'+val[j].substring(2)+'\"'; - else if(val[j].substring(0,2)=='=>') - str+=' >= \"'+val[j].substring(2)+'\"'; - else if(val[j].substring(0,2)=='=<') - str+=' <= \"'+val[j].substring(2)+'\"'; - else if(val[j].substring(0,1)=='>') - str+=' > \"'+val[j].substring(1)+'\"'; - else if(val[j].substring(0,1)=='<') - str+=' < \"'+val[j].substring(1)+'\"'; - else if(val[j].substring(0,1)=='=') - str+=' = \"'+val[j].substring(1)+'\"'; - } - str+=')'; - break; - - case 'string': - case 'istring': - str = this.MakeFilterString(q[i], val, q[i].type == 'istring'); - break; - - default: - //htr_alert(val, 1); - if(!val || typeof val.substring == 'undefined') // assume integer - str = this.MakeFilterInteger(q[i], val); - else if(val.substring(0,2)=='>=') - str=colref + ' >= '+val.substring(2); - else if(val.substring(0,2)=='<=') - str=colref + ' <= '+val.substring(2); - else if(val.substring(0,2)=='=>') - str=colref + ' >= '+val.substring(2); - else if(val.substring(0,2)=='=<') - str=colref + ' <= '+val.substring(2); - else if(val.substring(0,1)=='>') - str=colref + ' > '+val.substring(1); - else if(val.substring(0,1)=='<') - str=colref + ' < '+val.substring(1); - else if(val.indexOf('-')>=0) - { - //assume integer range in string - var ind = val.indexOf('-'); - var val1 = val.substring(0,ind); - var val2 = val.substring(ind+1); - str='(' + colref + ' >='+val1+' AND ' + colref + ' <='+val2+')'; - } - else - { - str = this.MakeFilterString(q[i], val); - } - break; - } - } - if (isnot) - str = "not (" + str + ")"; - if(firstone) - { - statement+=' ('+str+')'; - } - else - { - statement+=' '+q.joinstring+' ('+str+')'; + str += ")"; + } + break; + case 'datetimearray': + str='(' + colref; + var dtfirst=true; + for(var j in val) + { + if(!dtfirst) str+= ' AND ' + colref; + dtfirst=false; + if(val[j].substring(0,2)=='>=') + str+=' >= \"'+val[j].substring(2)+'\"'; + else if(val[j].substring(0,2)=='<=') + str+=' <= \"'+val[j].substring(2)+'\"'; + else if(val[j].substring(0,2)=='=>') + str+=' >= \"'+val[j].substring(2)+'\"'; + else if(val[j].substring(0,2)=='=<') + str+=' <= \"'+val[j].substring(2)+'\"'; + else if(val[j].substring(0,1)=='>') + str+=' > \"'+val[j].substring(1)+'\"'; + else if(val[j].substring(0,1)=='<') + str+=' < \"'+val[j].substring(1)+'\"'; + else if(val[j].substring(0,1)=='=') + str+=' = \"'+val[j].substring(1)+'\"'; + } + str+=')'; + break; + + case 'string': + case 'istring': + str = this.MakeFilterString(col, val, col.getType() == 'istring'); + break; + + default: + //htr_alert(val, 1); + if(!val || typeof val.substring == 'undefined') // assume integer + str = this.MakeFilterInteger(col, val); + else if(val.substring(0,2)=='>=') + str=colref + ' >= '+val.substring(2); + else if(val.substring(0,2)=='<=') + str=colref + ' <= '+val.substring(2); + else if(val.substring(0,2)=='=>') + str=colref + ' >= '+val.substring(2); + else if(val.substring(0,2)=='=<') + str=colref + ' <= '+val.substring(2); + else if(val.substring(0,1)=='>') + str=colref + ' > '+val.substring(1); + else if(val.substring(0,1)=='<') + str=colref + ' < '+val.substring(1); + else if(val.indexOf('-')>=0) + { + //assume integer range in string + var ind = val.indexOf('-'); + var val1 = val.substring(0,ind); + var val2 = val.substring(ind+1); + str='(' + colref + ' >='+val1+' AND ' + colref + ' <='+val2+')'; + } + else + { + str = this.MakeFilterString(col, val); + } + break; } - firstone=false; } + + if (isnot) + str = "not (" + str + ")"; + + if (firstone) + statement += ' (' + str + ')'; + else + statement += ' ' + q.getJoin() + ' (' + str + ')'; + + firstone=false; } + return statement; } @@ -762,13 +1084,11 @@ function osrc_action_delete(aparam) //up,initiating_client) var up = aparam.data; var initiating_client = aparam.client; - //Delete an object through OSML - //var src = this.baseobj + '?cx__akey='+akey+'&ls__mode=osml&ls__req=delete&ls__sid=' + this.sid + '&ls__oid=' + up.oid; + // Delete an object through OSML this.initiating_client = initiating_client; this.deleteddata=up; this.DoRequest('delete', this.baseobj, {ls__oid:up.oid}, osrc_action_delete_cb); - //this.initiating_client.ObjectDeleted(); - //this.initiating_client.OperationComplete(); + return 0; } @@ -789,7 +1109,7 @@ function osrc_action_delete_cb() for(var i=recnum; i max_j) max_j = parseInt(j); - } - if (!found) - { - max_j++; - cr[max_j] = {}; - cr[max_j].oid = server_rec[i].oid; - cr[max_j].value = server_rec[i].value; - cr[max_j].type = server_rec[i].type; - cr[max_j].id = max_j; - cr[max_j].hints = server_rec[i].hints; - } + cr.copyFrom(data.resultset[0]); + cr.__cx_handle = data.resultset[0].__cx_handle; } - //alert(this.replica[this.CurrentRecord].oid); + // Notify clients of the newly created object. this.in_create = false; this.SyncID = osrc_syncid++; - if (this.initiating_client) this.initiating_client.OperationComplete(true, this); - pg_serialized_load(this, 'about:blank', null, true); + if (this.initiating_client) + this.initiating_client.OperationComplete(true, this); for(var i in this.child) this.child[i].ObjectCreated(recnum, this); this.GiveAllCurrentRecord('create'); @@ -961,69 +1254,83 @@ function osrc_action_create_cb() } else { + // Did not succeed - let the calling client know. this.in_create = false; - if (this.initiating_client) this.initiating_client.OperationComplete(false, this); + if (this.initiating_client) + this.initiating_client.OperationComplete(false, this); } + this.initiating_client=null; delete this.createddata; } + function osrc_action_refresh_object(aparam) { this.QueueRequest({Request:'RefreshObject', Param:aparam}); this.Dispatch(); } + function osrc_refresh_object_handler(aparam) { // Need a "last query" and a valid current record to proceed - if (!this.lastquery || !this.CurrentRecord || !this.replica || !this.replica[this.CurrentRecord]) return false; + if (!this.lastquery || !this.CurrentRecord || !this.replica || !this.replica[this.CurrentRecord]) + return false; // Build list of primary keys var row = this.replica[this.CurrentRecord]; - var keys = {}; var keycnt = 0; - var namecol = null; - for(var c in row) + var nameattr = null; + var filter = new $CX.Types.osrcObject(); + var attrlist = row.getAttrList(); + for(var a in attrlist) { - if (c == 'oid') continue; - var col = row[c]; - var ph = cx_parse_hints(col.hints); + var attr = row.getAttr(a); + var ph = cx_parse_hints(attr.getHints()); if (ph.Style & cx_hints_style.key) { - keys[col.oid] = col; - keycnt ++; - } - if (col.oid == 'name') - { - namecol = col; + filter.newAttr(a, attr); + keycnt++; } + if (a == 'name') + nameattr = attr; } if (!keycnt) { - keys['name'] = namecol; - keycnt ++; + if (!nameattr) + return false; + else + filter.newAttr('name', nameattr); } // Start with the lastquery SQL. var sql = this.lastquery; // Append logic to search for just this row - var first = true; - for(var k in keys) + if (this.use_having) + sql += " HAVING "; + else + sql += " WHERE "; + sql += this.MakeFilter(filter); + /*if (keycnt > 0) { - if (first) + var first = true; + for(var k in keys) { - if (this.use_having) - sql += " HAVING "; - else - sql += " WHERE "; + if (first) + { + if (this.use_having) + sql += " HAVING "; + else + sql += " WHERE "; + } + else + sql += " AND "; + sql += this.MakeFilter([keys[k]]); + first = false; } - else - sql += " AND "; - sql += this.MakeFilter([keys[k]]); - first = false; - } + }*/ sql += " LIMIT 1"; // Now issue the query @@ -1037,31 +1344,36 @@ function osrc_refresh_object_handler(aparam) ls__notify:this.request_updates, ls__sqlparam:this.EncodeParams() }, osrc_refresh_object_cb); + return true; } -function osrc_refresh_object_cb() + +function osrc_refresh_object_cb(data) { - var links = pg_links(this); - var success = links && links[0] && (links[0].target != 'ERR'); - if(success && links.length > 1) + // Do we have a valid result from the refresh? + if (data && data.status == 'OK' && data.resultset.length > 0) { // Check new/corrected data provided by server - var cr=this.replica[this.CurrentRecord]; - var server_rec = this.ParseOneRow(links, 1); - var diff = 0; - for(var i in server_rec) - for(var j in cr) + var cr = this.replica[this.CurrentRecord]; + var server_rec = data.resultset[0]; + var diff = false; + var attrlist = server_rec.getAttrList(); + for(var a in attrlist) + { + var attr = server_rec.getAttr(a); + var cattr = cr.getAttr(a); + if (!cattr) + cattr = cr[a] = new $CX.Types.osrcAttr(); + if (cattr.get() != attr.get()) { - if (cr[j].oid == server_rec[i].oid && cr[j].value != server_rec[i].value) - { - cr[j].value = server_rec[i].value; - cr[j].type = server_rec[i].type; - diff = 1; - } + cattr.v = attr.get(); + cattr.t = attr.getType(); + diff = true; } + } - // if any changes, display them + // if any changes, tell our clients about them. if (diff) { this.SyncID = osrc_syncid++; @@ -1070,7 +1382,8 @@ function osrc_refresh_object_cb() } } -function osrc_action_modify(aparam) //up,initiating_client) + +function osrc_action_modify(aparam) //up,initiating_client { this.doing_refresh = false; if (aparam) @@ -1400,8 +1713,7 @@ function osrc_open_session(cb) //alert('open'); if(this.sid || cb == osrc_open_query) { - this.__osrc_cb = cb; - this.__osrc_cb(); + cb.call(this, null); } else { @@ -1412,13 +1724,6 @@ function osrc_open_session(cb) function osrc_open_query() { //Open Query - /*if(!this.sid) - { - var lnks = pg_links(this); - if (!lnks || !lnks[0] || !lnks[0].target) - return false; - this.sid=pg_links(this)[0].target; - }*/ if(this.qid && this.sid) { this.DoRequest('queryclose', '/', {ls__qid:this.qid}, osrc_open_query); @@ -1434,33 +1739,32 @@ function osrc_open_query() this.querysize = this.replicasize; } -function osrc_get_qid() +function osrc_get_qid(data) { - //return; - var lnk = pg_links(this); - this.data_start = 1; - if (!this.sid && lnk && lnk[0] && lnk[0].target) + // Check for handles + if (data) { - this.sid = lnk[0].target; - this.data_start = 2; - } + // Get session handle + if (!this.sid && data.session) + this.sid = data.session; - if (lnk && lnk[this.data_start-1]) - this.qid=lnk[this.data_start-1].target; - else - this.qid = null; + // Get query handle + if (data.queryclosed) + this.qid = null; + else + this.qid = data.query; + } - //confirm(this.baseobj + " ==> " + this.qid); - if (!this.qid) + // No valid query run? Bail out if so. + if (!data || !data.query) { - /*this.pending=false;*/ this.move_target = null; this.GiveAllCurrentRecord('get_qid'); this.SetPending(false); - /*this.Dispatch();*/ } else { + // Valid query this.query_delay = pg_timestamp() - this.request_start_ts; for(var i in this.child) this.child[i].DataAvailable(this, this.doing_refresh?'refresh':'query'); @@ -1469,23 +1773,22 @@ function osrc_get_qid() else var tgt = 1; this.move_target = null; - if (lnk.length > 1) + if (data.resultset.length > 0) { // did an autofetch - we have the data already - if (!this.do_append) this.ClearReplica(); + if (!this.do_append) + this.ClearReplica(); this.TargetRecord = [tgt,tgt]; this.CurrentRecord = tgt; this.moveop = true; - this.FetchNext(); + this.FetchNext(data); } else { // start the ball rolling for the fetch - //this.ifcProbe(ifAction).Invoke("First", {from_internal:true}); this.ifcProbe(ifAction).Invoke("FindObject", {ID:tgt, from_internal:true}); } } - /** normally don't actually load the data...just let children know that the data is available **/ } function osrc_parse_one_attr(lnk) @@ -1504,14 +1807,15 @@ function osrc_parse_one_attr(lnk) function osrc_new_replica_object(id, oid) { - var obj = []; - obj.oid=oid; - obj.id = id; + var obj = new $CX.Types.osrcObject(); + obj.__cx_handle = oid; + obj.__cx_id = id; return obj; } function osrc_prune_replica(most_recent_id) { + // Remove records from beginning of replica? if(this.LastRecord < most_recent_id) { this.LastRecord = most_recent_id; @@ -1531,11 +1835,16 @@ function osrc_prune_replica(most_recent_id) if (found) break; // clean up replica - this.oldoids.push(this.replica[this.FirstRecord].oid); - delete this.replica[this.FirstRecord]; + if (this.replica[this.FirstRecord]) + { + this.oldoids.push(this.replica[this.FirstRecord].__cx_handle); + delete this.replica[this.FirstRecord]; + } this.FirstRecord++; } } + + // Remove records from end of replica? if(this.FirstRecord > most_recent_id) { this.FirstRecord = most_recent_id; @@ -1557,7 +1866,7 @@ function osrc_prune_replica(most_recent_id) // clean up replica if (this.replica[this.LastRecord]) { - this.oldoids.push(this.replica[this.LastRecord].oid); + this.oldoids.push(this.replica[this.LastRecord].__cx_handle); delete this.replica[this.LastRecord]; } this.LastRecord--; @@ -1575,16 +1884,15 @@ function osrc_action_clear(aparam) function osrc_clear_replica() { - this.TargetRecord = [1,1];/* the record we're aiming for -- go until we get it*/ - this.CurrentRecord=1;/* the current record */ - this.OSMLRecord=0;/* the last record we got from the OSML */ + this.TargetRecord = [1,1]; // the record we're aiming for -- go until we get it + this.CurrentRecord=1; // the current record + this.OSMLRecord=0; // the last record we got from the OSML - /** Clear replica **/ + // Clear replica contents if(this.replica) for(var i in this.replica) - this.oldoids.push(this.replica[i].oid); + this.oldoids.push(this.replica[i].__cx_handle); - if(this.replica) delete this.replica; this.replica = []; this.LastRecord=0; this.FinalRecord=null; @@ -1639,10 +1947,10 @@ function osrc_query_timeout() function osrc_end_query() { - //this.initiating_client.OperationComplete(); /* don't need this...I think....*/ var qid=this.qid this.qid=null; - /* return the last record as the current one if it was our target otherwise, don't */ + + // If we retrieved any data at all, mark the last one as the Final Record. if (this.LastRecord >= this.FirstRecord && this.replica[this.LastRecord]) { this.replica[this.LastRecord].__osrc_is_last = true; @@ -1686,7 +1994,6 @@ function osrc_found_record() this.GiveAllCurrentRecord('change'); else this.TellAllReplicaMoved(); - /*this.pending=false;*/ this.SetPending(false); this.osrc_oldoid_cleanup(); this.ifcProbe(ifEvent).Activate("Results", {FinalRecord:this.FinalRecord, LastRecord:this.LastRecord, FirstRecord:this.FirstRecord, CurrentRecord:this.CurrentRecord}); @@ -1704,79 +2011,72 @@ function osrc_found_record() } } -function osrc_fetch_next() +function osrc_fetch_next(data) { - pg_debug(this.id + ": FetchNext() ==> " + pg_links(this).length + "\n"); - //alert('fetching....'); - if(!this.qid) + // No data supplied? + if (!data || !data.query) { - //if (pg_diag) confirm("ERR: " + this.baseobj + " ==> " + this.qid); if (pg_diag) confirm("fetch_next: error - no qid. first/last/cur/osml: " + this.FirstRecord + "/" + this.LastRecord + "/" + this.CurrentRecord + "/" + this.OSMLRecord + "\n"); - //alert('something is wrong...'); - //alert(this.src); - } - var lnk=pg_links(this); - var lc=lnk.length; - //confirm(this.baseobj + " ==> " + lc + " links"); - if(lc <= this.data_start) - { // query over + return 0; + } + + // query over? + if (data.resultset.length == 0) + { this.EndQuery(); return 0; } - var colnum=0; - var i = this.data_start; + + // Records skipped? + if (data.skipped > 0) + { + this.OSMLRecord += data.skipped; + this.querysize++; + } + + // Import the data var rowcnt = 0; - while (i < lc) + for(var i=0; i 0) + while (!this.replica[this.LastRecord] && this.LastRecord > 0) this.LastRecord--; - if(this.LastRecord= this.querysize) + if ((this.LastRecord-this.FirstRecord+1) < this.replicasize && rowcnt >= this.querysize) { - // make sure we have a full replica if possible + // Replica is not full and more data is likely available: + // Make sure we have a full replica if possible. this.DoFetch(this.replicasize - (this.LastRecord - this.FirstRecord + 1), false); } else { if (rowcnt < this.querysize) + { + // Replica is full and no more data is available this.EndQuery(); + } else + { + // Replica is full but more data is likely available this.FoundRecord(); + } } } } @@ -1808,7 +2115,6 @@ function osrc_oldoid_cleanup() if(this.oldoids && this.oldoids[0]) { this.SetPending(true); - /*this.pending=true;*/ var src=''; for(var i in this.oldoids) src+=this.oldoids[i]; @@ -1828,7 +2134,6 @@ function osrc_oldoid_cleanup_cb() { /*this.pending=false;*/ //alert('cb recieved'); - delete this.oldoids; this.oldoids = []; this.SetPending(false); pg_serialized_load(this, 'about:blank', null, true); @@ -1932,7 +2237,7 @@ function osrc_move_first(aparam) function osrc_change_current_record() { - var newprevcurrent = []; + var newprevcurrent = new $CX.Types.osrcObject(); // first, build the list of fields we're working with. We look both in the // replica and in prevcurrent, since field lists can be irregular (different @@ -1940,13 +2245,15 @@ function osrc_change_current_record() var fieldlist = {}; if (this.prevcurrent) { - for(var i=0; i= this.FirstRecord && this.replica[this.LastRecord] && this.replica[this.LastRecord].__osrc_is_last) { @@ -2042,16 +2309,13 @@ function osrc_give_all_current_record(why) this.GiveOneCurrentRecord(i, why); this.ifcProbe(ifEvent).Activate("DataFocusChanged", {}); this.doing_refresh = false; - //confirm('give_all_current_record done'); } function osrc_tell_all_replica_moved() { - //confirm('tell_all_replica_moved start'); for(var i in this.child) if(this.child[i].ReplicaMoved) this.child[i].ReplicaMoved(this); - //confirm('tell_all_replica_moved done'); } @@ -2070,49 +2334,24 @@ function osrc_move_to_record_handler(param) var from_internal = param.from_internal; if(recnum<1) { - //alert("Can't move past beginning."); return 0; } if(this.pending) { - //alert('you got ahead'); return 0; } this.SetPending(true); - //this.pending=true; - //var someunsaved=false; this.RecordToMoveTo=recnum; if (!from_internal) this.SyncID = osrc_syncid++; this.GoNogo(osrc_cb_query_continue_2, osrc_cb_query_cancel_2, null); - /*for(var i in this.child) - { - if(this.child[i].IsUnsaved) - { - //alert('child: '+i+' : '+this.child[i].IsUnsaved+' isn\\'t saved...IsDiscardReady'); - this.child[i]._osrc_ready=false; - this.child[i].IsDiscardReady(); - someunsaved=true; - } - else - { - this.child[i]._osrc_ready=true; - } - }*/ - //if someunsaved is false, there were no unsaved forms, so no callbacks - // we can just continue - /*if(someunsaved) return 0; - this.MoveToRecordCB(recnum);*/ } function osrc_move_to_record_cb(recnum) { - pg_debug(this.id + ": MoveTo(" + recnum + ")\n"); - //confirm(recnum); this.moveop=true; if(recnum<1) { - //alert("Can't move past beginning."); return 0; } this.RecordToMoveTo=recnum; @@ -2120,25 +2359,22 @@ function osrc_move_to_record_cb(recnum) { if(this.child[i].IsUnsaved) { - //confirm('child: '+i+' : '+this.child[i].IsUnsaved+' isn\\'t saved...'); return 0; } } -/* If we're here, we're ready to go */ this.TargetRecord = [recnum, recnum]; this.CurrentRecord = recnum; if(this.CurrentRecord <= this.LastRecord && this.CurrentRecord >= this.FirstRecord) { this.GiveAllCurrentRecord('change'); this.SetPending(false); - /*this.pending=false; - this.Dispatch();*/ return 1; } else { if(this.CurrentRecord < this.FirstRecord) - { /* data is further back, need new query */ + { + // data is further back, need new query if(this.FirstRecord-this.CurrentRecord0?(this.FirstRecord-this.readahead):1; @@ -2158,12 +2394,13 @@ function osrc_move_to_record_cb(recnum) return 0; } else - { /* data is farther on, act normal */ + { + // data is farther on, act normal if(this.qid) { if(this.CurrentRecord == Number.MAX_VALUE) { - /* rowcount defaults to a really high number if not set */ + // rowcount defaults to a really high number if not set this.DoFetch(this.replicasize, true); } else if (recnum == 1) @@ -2190,11 +2427,9 @@ function osrc_move_to_record_cb(recnum) } else { - //this.pending=false; this.CurrentRecord=this.LastRecord; this.GiveAllCurrentRecord('change'); this.SetPending(false); - //this.Dispatch(); } return 0; } @@ -2221,33 +2456,43 @@ function osrc_open_query_startat() this.DoRequest('multiquery', '/', {ls__startat:this.startat, ls__autoclose_sr:1, ls__autofetch:1, ls__objmode:0, ls__notify:this.request_updates, ls__rowcount:this.querysize, ls__sql:this.query, ls__sqlparam:this.EncodeParams()}, osrc_get_qid_startat); } -function osrc_get_qid_startat() +function osrc_get_qid_startat(data) { - var lnk = pg_links(this); - this.qid=lnk[0].target; - if (!this.qid) + // Check for handles + if (data) + { + // Get session handle + if (!this.sid && data.session) + this.sid = data.session; + + // Get query handle + if (data.queryclosed) + this.qid = null; + else + this.qid = data.query; + } + + // No query performed? + if (!data || !data.query) { this.startat = null; - //this.pending=false; this.GiveAllCurrentRecord('get_qid'); this.SetPending(false); - //this.Dispatch(); return; } + this.OSMLRecord=(this.startat)?(this.startat-1):0; - //this.FirstRecord=this.startat; - /*if(this.startat-this.TargetRecord+1 1) + + // Do we have result set objects? + if (data.resultset.length > 0) { // did an autofetch - we have the data already this.query_delay = pg_timestamp() - this.request_start_ts; - this.FetchNext(); + this.FetchNext(data); } else { + // Did not do autofetch -- grab the records. if(this.FirstRecord - this.startat < this.replicasize) { this.DoFetch(this.FirstRecord - this.startat, false); @@ -2257,6 +2502,7 @@ function osrc_get_qid_startat() this.DoFetch(this.replicasize, false); } } + this.startat=null; } @@ -2442,56 +2688,47 @@ function osrc_action_sync(param) this.SyncID = this.parentosrc.SyncID; // Compile the list of criteria - var query = []; - query.oid=null; - query.joinstring='AND'; - var p=this.parentosrc.CurrentRecord; + var query = new $CX.Types.osrcObject(); + query.setJoin('AND'); + var parentobj = this.parentosrc.getObject(); var force_empty = false; for(var i=1;i<10;i++) { - //this.ParentKey[i]=eval('param.ParentKey'+i); - //this.ChildKey[i]=eval('param.ChildKey'+i); this.ParentKey[i]=param['ParentKey'+i]; this.ChildKey[i]=param['ChildKey'+i]; if(this.ParentKey[i]) { - if (!this.parentosrc.replica[p]) + var parentcol = null; + if (parentobj) + parentcol = parentobj.getAttr(this.ParentKey[i]); + if (!parentobj || !parentcol) { - var t = new Object(); - t.plainsearch = true; - t.oid = this.ChildKey[i]; - t.value = null; - t.type = 'integer'; // type doesn't matter if it is null. + // No current record, or if so, it has no attribute. + // Type doesn't matter if it is null. + var col = query.newAttr(this.ChildKey[i], null, 'integer'); + var filt = col.getFilter(); + filt.plainsearch = true; if (on_norecs == 'nullisvalue') - t.nullisvalue = true; + filt.nullisvalue = true; else if (on_norecs == 'norecs') - force_empty = t.force_empty = true; + force_empty = filt.force_empty = true; else - t.nullisvalue = false; - query.push(t); + filt.nullisvalue = false; } else { - for(var j in this.parentosrc.replica[p]) + // Current record with a valid attribute. + var col = query.newAttr(this.ChildKey[i], parentcol); + var filt = col.getFilter(); + filt.plainsearch = true; + if (col.get() === null) { - if(this.parentosrc.replica[p][j].oid==this.ParentKey[i]) - { - var t = new Object(); - t.plainsearch = true; - t.oid=this.ChildKey[i]; - t.value=this.parentosrc.replica[p][j].value; - t.type=this.parentosrc.replica[p][j].type; - if (t.value === null) - { - if (on_null == 'nullisvalue') - t.nullisvalue = true; - else if (on_null == 'norecs') - force_empty = t.force_empty = true; - else - t.nullisvalue = false; - } - query.push(t); - } + if (on_null == 'nullisvalue') + filt.nullisvalue = true; + else if (on_null == 'norecs') + force_empty = filt.force_empty = true; + else + filt.nullisvalue = false; } } } @@ -2520,27 +2757,36 @@ function osrc_action_sync(param) } this.was_forced_empty = force_empty; - // Did it change from last time? + // Did it change from last time? Get a merged list of the current and previous + // properties, and compare the values to see if anything is different. if (!this.lastSync) - this.lastSync = []; + this.lastSync = new $CX.Types.osrcObject(); var changed = false; - for(var i=0;i cur_seq)) && - (prevseq === null || ((direction == 'backward' && parseInt(field.value) > prevseq) || (direction == 'forward' && parseInt(field.value) < prevseq)))) + var attrval = parseInt(attr.get()); + if (((direction == 'backward' && attrval < cur_seq) || (direction == 'forward' && attrval > cur_seq)) && + (prevseq === null || ((direction == 'backward' && attrval > prevseq) || (direction == 'forward' && attrval < prevseq)))) { previtem = idx; - prevseq = parseInt(field.value); + prevseq = attrval; } - }); + }; }); // Didn't find anything? Nothing to do then. @@ -2827,7 +3050,7 @@ function osrc_seq(direction) if (!doneprev) { reqparam[seqfield] = cur_seq; - reqparam.ls__oid = this.replica[previtem].oid; + reqparam.ls__oid = this.replica[previtem].getID(); this.SetValue(seqfield, cur_seq, previtem); this.DoRequest('setattrs', '/', reqparam, seqproc); doneprev = true; @@ -2838,7 +3061,7 @@ function osrc_seq(direction) if (!donecurr) { reqparam[seqfield] = prevseq; - reqparam.ls__oid = this.replica[this.CurrentRecord].oid; + reqparam.ls__oid = this.replica[this.CurrentRecord].getID(); this.SetValue(seqfield, prevseq); this.DoRequest('setattrs', '/', reqparam, seqproc); donecurr = true; @@ -2881,29 +3104,26 @@ function osrc_apply_sequence(obj) var maxval = -1; this.replica.forEach(function(item) { - item.forEach(function(field) + if (typeof item[rl.field] == 'object') { - if (field.oid == rl.field) - { - var ckval = parseInt(field.value); - if (ckval > maxval) - maxval = ckval; - } - }); + var ckval = parseInt(item.getAttrValue(rl.field)); + if (ckval > maxval) + maxval = ckval; + } }); // got maximum value in the osrc's replica. Assign it now. - var found=false; - obj.forEach(function(field) + if (typeof obj[rl.field] == 'object') + obj[rl.field].v = '' + (maxval + 1); + else { - if (field.oid == rl.field) - { - found = true; - field.value = '' + (maxval + 1); - } - }); - if (!found) - obj.push({hints:"", oid:rl.field, system:false, type:"integer", value:'' + (maxval + 1)}); + obj[rl.field] = new $CX.Types.osrcAttr(); + obj[rl.field].h = ''; + obj[rl.field].y = false; + obj[rl.field].t = 'integer'; + obj[rl.field].v = '' + (maxval + 1); + //obj.push({hints:"", oid:rl.field, system:false, type:"integer", value:'' + (maxval + 1)}); + } } } } @@ -2965,9 +3185,6 @@ function osrc_apply_keys(obj) function osrc_apply_rel(obj, in_create, in_modify) { - var cnt = 0; - while(typeof obj[cnt] != 'undefined') cnt++; - // First, check for relationships that might imply key values for(var i=0; i + { + if (act) + pg_spinner_dec(); + data = JSON.parse(data, (key, value) => + { + // We revive prototypes for the json via duck-typing. + if (typeof value === 'object') + { + // null + if (value == null) + return value; + // attribute + if (typeof value.v !== 'undefined' && value.t && typeof value.__cx_handle === 'undefined') + value.__proto__ = $CX.Types.osrcAttr.prototype; + // object (i.e., row or tuple) + else if (value.__cx_handle) + value.__proto__ = $CX.Types.osrcObject.prototype; + // money + else if (typeof value.wholepart !== 'undefined' && typeof value.fractionpart !== 'undefined') + value.__proto__ = $CX.Types.Money.prototype; + // date/time + else if (typeof value.year !== 'undefined' && typeof value.month !== 'undefined') + { + value.__proto__ = $CX.Types.DateTime.prototype; + value.adjFromServer(); + } + } + return value; + }); + cb.call(this, data); + }) + .fail( (xhr, stat, err) => + { + if (act) + pg_spinner_dec(); + }); + //pg_serialized_load(target, url, cb, !this.ind_act || !this.req_ind_act); this.req_ind_act = true; - //this.onload = cb; - //target.src = url; } @@ -3833,55 +4110,7 @@ function osrc_api_get_object(id) id = this.osrcCurrentObjectID; if (!id || !this.replica[id]) return null; - var obj = this.replica[id]; - var jobj = {__cx_handle:obj.oid, id:id}; - for(var i=0; i"); + moveToAbsolute(pg_waitlyr, (pg_width-100)/2, (pg_height-24)/2); + htr_setzindex(pg_waitlyr, 99999); + } + if (pg_waitlyr_id) pg_delsched(pg_waitlyr_id); + pg_waitlyr_id = null; + pg_waitlyr.vis = true; + + htr_setvisibility(pg_waitlyr, "inherit"); + } + } + + +// This function terminates the busy spinner if everyone is done with it. +function pg_spinner_dec() + { + $CX.Globals.spinnerCount--; + + if ($CX.Globals.spinnerCount <= 0) + { + pg_clear_waitlyr(); + } + } + // pg_loadqueue_additem() - adds an item to the load queue, sorted by 'level' function pg_loadqueue_additem(item) @@ -2100,7 +2173,7 @@ function pg_serialized_write(l, text, cb) { //pg_debug('pg_serialized_write: ' + pg_loadqueue.length + ': ' + l.name + ' loads "' + text.substring(0,100) + '"\n'); //pg_loadqueue.push({lyr:l, text:text, cb:cb}); - pg_loadqueue_additem({level:1, type:'write', lyr:l, text:text, cb:cb, retry_cnt:0}); + pg_loadqueue_additem({level:1, type:'write', lyr:l, text:text, cb:cb, retry_cnt:0, silent:true}); //pg_debug('pg_serialized_write: ' + pg_loadqueue.length + '\n'); pg_serialized_load_doone(); } @@ -2111,7 +2184,7 @@ function pg_serialized_write(l, text, cb) // complete (even if scheduled later) before it runs. function pg_serialized_func(level, obj, func, params) { - pg_loadqueue_additem({level:level, type:'func', lyr:obj, cb:func, params:params, retry_cnt:0}); + pg_loadqueue_additem({level:level, type:'func', lyr:obj, cb:func, params:params, retry_cnt:0, silent:true}); //pg_serialized_load_doone(); pg_loadqueue_check(); } @@ -2122,24 +2195,11 @@ function pg_serialized_func(level, obj, func, params) // manner that keeps things serialized so server loads don't overlap. function pg_serialized_load(l, newsrc, cb, silent) { - // pg_waitlyr says if the 'wait layer' should be used (the 'wait layer' is the layer that takes focus and says "please wait...") - if (!silent && (!pg_waitlyr || !pg_waitlyr.vis)) - { - if (!pg_waitlyr) - { - pg_waitlyr = htr_new_layer(96); - htr_write_content(pg_waitlyr, "
"); - moveToAbsolute(pg_waitlyr, (pg_width-100)/2, (pg_height-24)/2); - htr_setzindex(pg_waitlyr, 99999); - } - if (pg_waitlyr_id) pg_delsched(pg_waitlyr_id); - pg_waitlyr_id = null; - pg_waitlyr.vis = true; + if (!silent) + pg_spinner_inc(); - htr_setvisibility(pg_waitlyr, "inherit"); - } pg_debug('pg_serialized_load: ' + pg_loadqueue.length + ': ' + l.name + ' loads ' + newsrc + '\n'); - pg_loadqueue_additem({level:1, type:'src', lyr:l, src:newsrc, cb:cb, retry_cnt:0}); + pg_loadqueue_additem({level:1, type:'src', lyr:l, src:newsrc, cb:cb, retry_cnt:0, silent:silent}); pg_debug('pg_serialized_load: ' + pg_loadqueue.length + '\n'); pg_serialized_load_doone(); } @@ -2153,7 +2213,7 @@ function pg_serialized_load_doone() if (pg_loadqueue.length == 0) { //pg_loadqueue_busy = 0; - pg_clear_waitlyr(); + //pg_clear_waitlyr(); return; } @@ -2178,8 +2238,18 @@ function pg_serialized_load_doone() switch(one_item.type) { case 'src': - one_item.lyr.onload = pg_serialized_load_cb; - one_item.lyr.onerror = pg_serialized_load_error_cb; + one_item.lyr.onload = () => + { + if (!one_item.silent) + pg_spinner_dec(); + pg_serialized_load_cb.call(one_item.lyr); + }; + one_item.lyr.onerror = () => + { + if (!one_item.silent) + pg_spinner_dec(); + pg_serialized_load_error_cb.call(one_item.lyr); + }; pg_set(one_item.lyr, 'src', one_item.src); break; @@ -2199,6 +2269,9 @@ function pg_serialized_load_doone() one_item.lyr.__load_busy = false; pg_loadqueue_busy--; } + + if (!one_item.silent) + pg_spinner_dec(); pg_loadqueue_check(); break; @@ -2206,6 +2279,8 @@ function pg_serialized_load_doone() one_item.cb.apply(one_item.lyr, one_item.params); one_item.lyr.__load_busy = false; pg_loadqueue_busy--; + if (!one_item.silent) + pg_spinner_dec(); pg_loadqueue_check(); break; } @@ -2239,13 +2314,13 @@ function pg_loadqueue_check() { if (pg_loadqueue.length > 0) pg_addsched_fn(window, 'pg_serialized_load_doone', [], 0); - else - pg_clear_waitlyr(); + //else +// pg_clear_waitlyr(); } function pg_clear_waitlyr() { - if (pg_waitlyr && !pg_loadqueue_busy) + if (pg_waitlyr) { if (pg_waitlyr_id) pg_delsched(pg_waitlyr_id); pg_waitlyr.vis = false; diff --git a/centrallix-os/sys/js/htdrv_table.js b/centrallix-os/sys/js/htdrv_table.js index eb991dfcd..1b5c8e8b0 100644 --- a/centrallix-os/sys/js/htdrv_table.js +++ b/centrallix-os/sys/js/htdrv_table.js @@ -199,9 +199,9 @@ function tbld_format_cell(cell, color) // function tbld_attr_cmp(a, b) { - if (a.oid > b.oid) + if (a.a > b.a) return 1; - else if (a.oid < b.oid) + else if (a.a < b.a) return -1; else return 0; @@ -241,16 +241,17 @@ function tbld_redraw_all(dataobj, force_datafetch) // Presentation mode -- rows or propsheet? if (this.datamode == 1) { - if (!dataobj && this.osrc.CurrentRecord && this.osrc.replica[this.osrc.CurrentRecord]) - dataobj = this.osrc.replica[this.osrc.CurrentRecord]; + if (!dataobj && this.osrc.osrcCurrentObjectID) + dataobj = this.osrc.getObject(); + // Propsheet mode - build the attr list + var al = dataobj.getAttrList(); this.attrlist = []; - for(var j in dataobj) + for(var a in al) { - if (dataobj[j].oid && !dataobj[j].system) - { - this.attrlist.push(dataobj[j]); - } + var attr = dataobj.getAttr(a); + if (!attr.getSystem()) + this.attrlist.push({a:a, v:attr.get(), t:attr.getType()}); } this.attrlist.sort(tbld_attr_cmp); this.rows.lastosrc = this.attrlist.length; @@ -416,14 +417,24 @@ function tbld_check_bottom() function tbld_find_osrc_value(rowslot, attrname) { var txt = ''; - if (this.osrc.LastRecord >= rowslot && this.osrc.FirstRecord <= rowslot) + if (this.osrc.osrcLastObjectID >= rowslot && this.osrc.osrcFirstObjectID <= rowslot) { - for(var k in this.osrc.replica[rowslot]) + var obj = this.osrc.getObject(rowslot); + if (obj) { - if (this.osrc.replica[rowslot][k].oid == attrname) + var attr = obj.getAttr(attrname); + if (attr) { - txt = this.osrc.replica[rowslot][k].value; - break; + var type = attr.getType(); + txt = attr.get(); + if (type == 'money') + { + txt = attr.get().toString(); + } + else if (type == 'datetime') + { + txt = attr.get().toString(); + } } } if (txt == null || typeof txt == 'undefined') @@ -459,9 +470,9 @@ function tbld_setup_row_data(rowslot, is_new) var txt = ''; switch(this.cols[j].fieldname) { - case 'name': txt = this.attrlist[attrid].oid; break; - case 'value': txt = htutil_obscure(this.attrlist[attrid].value); break; - case 'type': txt = this.attrlist[attrid].type; break; + case 'name': txt = this.attrlist[attrid].a; break; + case 'value': txt = htutil_obscure(this.attrlist[attrid].v); break; + case 'type': txt = this.attrlist[attrid].t; break; default: txt = ''; } if(txt == null || typeof txt == 'undefined') @@ -2335,6 +2346,7 @@ function tbld_keydown(e) return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } + function tbld_contextmenu(e) { var ly = e.layer; @@ -2357,14 +2369,16 @@ function tbld_contextmenu(e) event.data = new Object(); event.X = e.pageX; event.Y = e.pageY; - var rec=ly.table.osrc.replica[ly.rownum]; - if(rec) + var rec=ly.table.osrc.getObject(ly.rownum); + if (rec) { - for(var i in rec) + var al = rec.getAttrList(); + for(var a in al) { - event.data[rec[i].oid]=rec[i].value; - if (rec[i].oid != 'data' && rec[i].oid != 'Caller' && rec[i].oid != 'recnum' && rec[i].oid != 'X' && rec[i].oid != 'Y') - event[rec[i].oid] = rec[i].value; + var oneattr = rec.getAttr(a); + event.data[a] = oneattr.get(); + if (typeof event[a] == 'undefined') + event[a] = oneattr.get(); } } ly.table.dta=event.data; @@ -2440,15 +2454,17 @@ function tbld_mousedown(e) } event.Caller = ly.table; event.recnum = ly.rownum; - event.data = new Object(); - var rec=ly.table.osrc.replica[ly.rownum]; - if(rec) + event.data = {}; + var rec=ly.table.osrc.getObject(ly.rownum); + if (rec) { - for(var i in rec) + var al = rec.getAttrList(); + for(var a in al) { - event.data[rec[i].oid]=rec[i].value; - if (rec[i].oid != 'data' && rec[i].oid != 'Caller' && rec[i].oid != 'recnum') - event[rec[i].oid] = rec[i].value; + var oneattr = rec.getAttr(a); + event.data[a] = oneattr.get(); + if (typeof event[a] == 'undefined') + event[a] = oneattr.get(); } } ly.table.dta=event.data; @@ -2476,14 +2492,16 @@ function tbld_mousedown(e) event.Caller = ly.table; event.recnum = ly.rownum; event.data = new Object(); - var rec=ly.table.osrc.replica[ly.rownum]; - if(rec) + var rec=ly.table.osrc.getObject(ly.rownum); + if (rec) { - for(var i in rec) + var al = rec.getAttrList(); + for(var a in al) { - event.data[rec[i].oid]=rec[i].value; - if (rec[i].oid != 'data' && rec[i].oid != 'Caller' && rec[i].oid != 'recnum') - event[rec[i].oid] = rec[i].value; + var oneattr = rec.getAttr(a); + event.data[a] = oneattr.get(); + if (typeof event[a] == 'undefined') + event[a] = oneattr.get(); } } ly.table.dta=event.data; diff --git a/centrallix-sysdoc/JSON-REST.txt b/centrallix-sysdoc/JSON-REST.txt index cc5cf9590..d474fbe1c 100644 --- a/centrallix-sysdoc/JSON-REST.txt +++ b/centrallix-sysdoc/JSON-REST.txt @@ -141,6 +141,10 @@ OSML JSON DATA FORMAT... h = presentation hints, as encoded by hntEncodeHints() and as decoded by cx_parse_hints in JavaScript. If no hints are available, the h property may be omitted entirely. + y = whether the attribute is a "system" attribute that ordinarily + is hidden from the user (boolean: true/false). + s = the sequence order of the attribute, giving that natural order + the attributes would ordinarily be viewed in The 'a' property above will be omitted if the name has already been specified outside of the full format attribute, for instance if diff --git a/centrallix-sysdoc/OSRC-JSON-Data.txt b/centrallix-sysdoc/OSRC-JSON-Data.txt new file mode 100644 index 000000000..c55217fe7 --- /dev/null +++ b/centrallix-sysdoc/OSRC-JSON-Data.txt @@ -0,0 +1,53 @@ +Document: JSON data format for OSML-over-HTTP for OSRC widget +Author: Greg Beeley (GRB) +Date: 13-Feb-2018 +------------------------------------------------------------------------------- + +The new data format to be used for OSML-over-HTTP shall be identical to the +"full" format used for JSON-REST: + + Full Format + + The Full Format will be used to transfer metadata about each + attribute in addition to the attribute's name and value: + + { "a":"age", "e":null, "v":32, "t":"integer", "h":"d=0" } + + In the above representation, the property names are abbreviated for + space savings, and mean the following: + + a = name of attribute + e = error status, null (or not present) if no error. If set, it + will either be the string "error" or a string containing an + error message. + v = attribute's value (same representation as in the Basic Format) + t = data type (integer, double, string, money, datetime) + h = presentation hints, as encoded by hntEncodeHints() and as + decoded by cx_parse_hints in JavaScript. If no hints are + available, the h property may be omitted entirely. + + The 'a' property above will be omitted if the name has already been + specified outside of the full format attribute, for instance if + a list of attributes is contained in a JSON Object. + + Full: + + { + "@id":"/people/001?cx__mode=rest&cx__res_format=attrs&cx__res_attrs=full", + "first_name": { "v":"John", "t":"string", "h":"l=64" } + "last_name": { "v":"Smith", "t":"string", "h":"l=64" } + } + +Since the ordering of the objects can be important (as with a SQL order by +clause), the list of such objects will be numbered, rather than associative: + + { + "status": null, + "session": "X ...", + "query": "X ...", + "resultset": + [ + { "@id": ... }, + { "@id": ... }, + ] + } diff --git a/centrallix/include/net_http.h b/centrallix/include/net_http.h index f038911f3..beccb4ebc 100755 --- a/centrallix/include/net_http.h +++ b/centrallix/include/net_http.h @@ -309,6 +309,7 @@ typedef struct int NoCache:1; int UsingTLS:1; int UsingChunkedEncoding:1; + int nObjectsSent; int StrictSameSite:1; char ResponseContentType[128]; int ResponseContentLength; @@ -411,10 +412,18 @@ char* nht_i_GetHeader(pXArray hdrlist, char* hdrname); int nht_i_FreeHeaders(pXArray hdrlist); int nht_i_CheckAccessLog(); +/*** Some enums to control how we're responding to the request ***/ +typedef enum { ResTypeCollection, ResTypeElement, ResTypeBoth } nhtResType_t; +typedef enum { ResFormatAttrs, ResFormatAuto, ResFormatContent, ResFormatBoth } nhtResFormat_t; +typedef enum { ResAttrsBasic, ResAttrsFull, ResAttrsNone } nhtResAttrs_t; + /*** REST implementation ***/ int nht_i_RestGet(pNhtConn conn, pStruct url_inf, pObject obj); int nht_i_RestPatch(pNhtConn conn, pStruct url_inf, pObject obj, struct json_object*); int nht_i_RestPost(pNhtConn conn, pStruct url_inf, int size, char* content); int nht_i_RestDelete(pNhtConn conn, pStruct url_inf, pObject obj); +int nht_i_RestGetElement(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, char* mime_type); +int nht_i_RestWriteAttrSet(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs); + #endif diff --git a/centrallix/netdrivers/net_http_osml.c b/centrallix/netdrivers/net_http_osml.c index e0b25ea27..7962841db 100644 --- a/centrallix/netdrivers/net_http_osml.c +++ b/centrallix/netdrivers/net_http_osml.c @@ -225,6 +225,109 @@ nht_i_WriteOneAttr(pObject obj, pNhtConn conn, handle_t tgt, char* attrname) } +/*** nht_i_JsonWriteResponse - start a JSON-formatted response. + ***/ +int +nht_i_JsonWriteResponse(pNhtConn conn, handle_t h_sess, handle_t h_query, char* status) + { + char sess_str[20]; + char query_str[20]; + pXString err_str = NULL; + + /** Mark this as a JSON response document **/ + strtcpy(conn->ResponseContentType, "application/json", sizeof(conn->ResponseContentType)); + nht_i_WriteResponse(conn, 200, "OK", NULL); + + /** Error condition? try to get the details. **/ + if (!strcmp(status, "ERR")) + { + err_str = xsNew(); + if (err_str) + { + mssUserError(err_str); + if (xsLength(err_str) > 0) + status = xsString(err_str); + } + } + + /** Write the start of the JSON response **/ + snprintf(sess_str, sizeof(sess_str), "X" XHN_HANDLE_PRT, h_sess); + snprintf(query_str, sizeof(query_str), "X" XHN_HANDLE_PRT, h_query); + nht_i_QPrintfConn(conn, 0, "{ \"status\": %[null%]\"%[%STR&JSONSTR%]\"%[, \"session\": \"%STR&JSONSTR\"%]%[, \"query\": \"%STR&JSONSTR\"%], \"resultset\": [", + status == NULL, + status != NULL, + status, + h_sess && h_sess != XHN_INVALID_HANDLE, + sess_str, + h_query && h_query != XHN_INVALID_HANDLE, + query_str + ); + conn->nObjectsSent = 0; + + if (err_str) + xsFree(err_str); + + return 0; + } + + +/*** nht_i_JsonWriteAttrs - send one attribute-value list. + ***/ +int +nht_i_JsonWriteAttrs(pNhtConn conn, pObject obj, handle_t tgt) + { + char obj_str[20]; + + /** Output the start **/ + if (conn->nObjectsSent > 0) + nht_i_WriteConn(conn, ",\n{ ", 4, 0); + else + nht_i_WriteConn(conn, "\n{ ", 3, 0); + + /** Output the attribute set **/ + nht_i_RestWriteAttrSet(conn, obj, ResFormatAttrs, ResAttrsFull); + + /** Output the handle ID and closing. We do this last, so that if there is + ** already an attribute __cx_handle in the object, our trailing attr will + ** take precedence. + **/ + snprintf(obj_str, sizeof(obj_str), "X" XHN_HANDLE_PRT, tgt); + nht_i_QPrintfConn(conn, 0, ", \"__cx_handle\":\"%STR&JSONSTR\" }", obj_str); + conn->nObjectsSent++; + + return 0; + } + + +/*** nht_i_JsonWriteResponseEnd - end the json-based response + ***/ +int +nht_i_JsonWriteResponseEnd(pNhtConn conn, int is_closed, int skipped) + { + + nht_i_QPrintfConn(conn, 0, "], \"queryclosed\": %STR, \"skipped\": %INT }\n", + is_closed?"true":"false", + skipped + ); + + return 0; + } + + +/*** nht_i_JsonWriteEntireResponse - write a JSON-formatted response, both beginning + *** and ending, but without any result set. + ***/ +int +nht_i_JsonWriteEntireResponse(pNhtConn conn, handle_t h_sess, handle_t h_query, char* status) + { + + nht_i_JsonWriteResponse(conn, h_sess, h_query, status); + nht_i_JsonWriteResponseEnd(conn, h_query == XHN_INVALID_HANDLE, 0); + + return 0; + } + + /*** nht_i_WriteAttrs - write an HTML-encoded attribute list for the *** object to the connection, given an object and a connection. ***/ @@ -444,11 +547,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN pObjQuery qy = NULL; char* sid = NULL; int auto_session = 0; - char sbuf[256]; - char hexbuf[3]; int mode,mask; char* usrtype; - int i,t,n,o,cnt,start,flags,len,rval; + int i,t,n,start,len,rval; pStruct subinf, find_inf; MoneyType m; DateTime dt; @@ -468,11 +569,10 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN pNhtQuery nht_query; char* strval; XArray tail_buffer; - int n_skipped; - - handle_t session_handle; - handle_t query_handle; - handle_t obj_handle; + int n_skipped = 0; + handle_t session_handle = XHN_INVALID_HANDLE; + handle_t query_handle = XHN_INVALID_HANDLE; + handle_t obj_handle = XHN_INVALID_HANDLE; if (DEBUG_OSML) stPrint_ne(req_inf); @@ -492,8 +592,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (app) xaAddItem(&app->AppOSMLSessions, objsess); session_handle = xhnAllocHandle(&(sess->Hctx), objsess); } - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, session_handle); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, session_handle); if (DEBUG_OSML) printf("ls__mode=opensession X" XHN_HANDLE_PRT "\n", session_handle); } else @@ -508,8 +609,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN { if (!auto_session) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Session ID required for OSML request '%s'",request); + nht_i_JsonWriteEntireResponse(conn, XHN_INVALID_HANDLE, XHN_INVALID_HANDLE, "ERR"); return -1; } else @@ -517,10 +619,11 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN objsess = objOpenSession(req_inf->StrVal); if (!objsess) { - nht_i_WriteResponse(conn, 200, "OK", NULL); session_handle = XHN_INVALID_HANDLE; - nht_i_WriteHandle(conn, session_handle); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, session_handle); mssError(1,"NHT","Failed to open new OSML session"); + nht_i_JsonWriteEntireResponse(conn, XHN_INVALID_HANDLE, XHN_INVALID_HANDLE, "ERR"); return -1; } else @@ -547,8 +650,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (!objsess || !ISMAGIC(objsess, MGK_OBJSESSION)) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Invalid Session ID in OSML request"); + nht_i_JsonWriteEntireResponse(conn, XHN_INVALID_HANDLE, XHN_INVALID_HANDLE, "ERR"); return -1; } @@ -565,8 +669,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN obj = (pObject)xhnHandlePtr(&(sess->Hctx), obj_handle); if (!obj || !ISMAGIC(obj, MGK_OBJECT)) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Invalid Object ID in OSML request"); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "ERR"); return -1; } } @@ -585,8 +690,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN qy = (pObjQuery)xhnHandlePtr(&(sess->Hctx), query_handle); if (!qy || !ISMAGIC(qy, MGK_OBJQUERY)) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Invalid Query ID in OSML request"); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "ERR"); return -1; } } @@ -596,16 +702,18 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN !strcmp(request,"read") || !strcmp(request,"write") || !strcmp(request,"attrs") || !strcmp(request, "setattrs") || !strcmp(request,"delete"))) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Object ID required for OSML '%s' request", request); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "ERR"); return -1; } /** Does this request require a query handle? **/ if (query_handle == XHN_INVALID_HANDLE && (!strcmp(request,"queryfetch") || !strcmp(request,"queryclose"))) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Query ID required for OSML '%s' request", request); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "ERR"); return -1; } @@ -614,16 +722,18 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN { if (session_handle == XHN_INVALID_HANDLE) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); mssError(1,"NHT","Illegal attempt to close the default OSML session."); + nht_i_JsonWriteEntireResponse(conn, XHN_INVALID_HANDLE, XHN_INVALID_HANDLE, "ERR"); return -1; } xhnFreeHandle(&(sess->Hctx), session_handle); if (app) xaRemoveItem(&app->AppOSMLSessions, xaFindItem(&app->AppOSMLSessions, objsess)); objCloseSession(objsess); - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, (handle_t)0); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, (handle_t)0); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); } else if (!strcmp(request,"open")) { @@ -638,15 +748,18 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN obj_handle = XHN_INVALID_HANDLE; else obj_handle = xhnAllocHandle(&(sess->Hctx), obj); - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, obj_handle); if (DEBUG_OSML) printf("ls__mode=open X" XHN_HANDLE_PRT "\n", obj_handle); if (obj && stAttrValue_ne(stLookup_ne(req_inf,"ls__notify"),&ptr) >= 0 && !strcmp(ptr,"1")) objRequestNotify(obj, nht_i_UpdateNotify, sess, OBJ_RN_F_ATTRIB); /** Include an attribute listing **/ - nht_i_WriteAttrs(obj,conn,obj_handle,1); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, obj_handle); + //nht_i_WriteAttrs(obj,conn,obj_handle,1); + nht_i_JsonWriteResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); + nht_i_JsonWriteAttrs(conn, obj, obj_handle); + nht_i_JsonWriteResponseEnd(conn, 1, 0); } else if (!strcmp(request,"close")) { @@ -669,8 +782,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN xhnFreeHandle(&(sess->Hctx), obj_handle); objClose(obj); } - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, (handle_t)0); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, (handle_t)0); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); } else if (!strcmp(request,"objquery")) { @@ -683,19 +797,22 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN query_handle = XHN_INVALID_HANDLE; else query_handle = xhnAllocHandle(&(sess->Hctx), qy); - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, query_handle); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, query_handle); + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "OK"); if (DEBUG_OSML) printf("ls__mode=objquery X" XHN_HANDLE_PRT "\n", query_handle); } else if (!strcmp(request,"queryfetch") || !strcmp(request,"multiquery")) { - nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteResponse(conn, 200, "OK", NULL); nht_query = NULL; + + /** If an initial multiquery request, set up the query. **/ if (!strcmp(request,"multiquery")) { if (auto_session) { - nht_i_WriteHandle(conn, session_handle); + //nht_i_WriteHandle(conn, session_handle); } qy = NULL; @@ -722,7 +839,7 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN qy = NULL; query_handle = XHN_INVALID_HANDLE; } - nht_i_WriteHandle(conn, autoclose?XHN_INVALID_HANDLE:query_handle); + //nht_i_WriteHandle(conn, autoclose?XHN_INVALID_HANDLE:query_handle); if (DEBUG_OSML) printf("ls__mode=multiquery X" XHN_HANDLE_PRT "\n", query_handle); if (!qy && nht_query) { @@ -733,6 +850,13 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN xaAddItem(&sess->OsmlQueryList, nht_query); } } + + /** Send the response header to the client. **/ + nht_i_JsonWriteResponse(conn, session_handle, query_handle, qy?"OK":"ERR"); + + /** If we're returning data -- either because of a queryfetch or because of a multiquery + ** with autofetch enabled, retrieve the objects. + **/ if (!strcmp(request,"queryfetch") || (qy && stAttrValue_ne(stLookup_ne(req_inf,"ls__autofetch"),&ptr) == 0 && strtol(ptr,NULL,0))) { if (!nht_query) @@ -759,7 +883,7 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (start < 0) start = 0; if (!strcmp(request,"queryfetch")) { - nht_i_WriteHandle(conn, (handle_t)0); + //nht_i_WriteHandle(conn, (handle_t)0); } /** Skip over objects at the beginning? **/ @@ -776,7 +900,6 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (stAttrValue_ne(stLookup_ne(req_inf, "ls__tail"), &ptr) == 0 && strtol(ptr,NULL,0)) { xaInit(&tail_buffer, n); - n_skipped = 0; /** Get the object listing **/ while((obj = objQueryFetch(qy, mode)) != NULL) @@ -791,7 +914,7 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN } /** Send the data to the client **/ - nht_i_QPrintfConn(conn, 0, "%INT\r\n", n_skipped); + //FIXME nht_i_QPrintfConn(conn, 0, "%INT\r\n", n_skipped); for(i=0; i= 0 && !strcmp(ptr,"1")) objRequestNotify(obj, nht_i_UpdateNotify, sess, OBJ_RN_F_ATTRIB); - nht_i_WriteAttrs(obj,conn,obj_handle,1); + //nht_i_WriteAttrs(obj,conn,obj_handle,1); + nht_i_JsonWriteAttrs(conn, obj, obj_handle); n--; if (autoclose) objClose(obj); } @@ -825,7 +949,8 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (DEBUG_OSML) printf("ls__mode=queryfetch X" XHN_HANDLE_PRT "\n", obj_handle); if (stAttrValue_ne(stLookup_ne(req_inf,"ls__notify"),&ptr) >= 0 && !strcmp(ptr,"1")) objRequestNotify(obj, nht_i_UpdateNotify, sess, OBJ_RN_F_ATTRIB); - nht_i_WriteAttrs(obj,conn,obj_handle,1); + //nht_i_WriteAttrs(obj,conn,obj_handle,1); + nht_i_JsonWriteAttrs(conn, obj, obj_handle); n--; if (autoclose) objClose(obj); } @@ -847,7 +972,7 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN } objQueryClose(qy); qy = NULL; - nht_i_WriteConn(conn, " \r\n", -1, 0); + //nht_i_WriteConn(conn, " \r\n", -1, 0); } else if (autoclose) { @@ -866,6 +991,7 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN qy = NULL; } } + nht_i_JsonWriteResponseEnd(conn, qy == NULL, n_skipped); } else if (!strcmp(request,"queryclose")) { @@ -881,11 +1007,13 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN } } objQueryClose(qy); - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, (handle_t)0); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, (handle_t)0); } else if (!strcmp(request,"read")) { +#if 00 if (stAttrValue_ne(stLookup_ne(req_inf,"ls__bytecount"),&ptr) < 0) n = 0x7FFFFFFF; else @@ -899,10 +1027,12 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN else flags = strtoi(ptr,NULL,0); start = 1; + while(n > 0 && (cnt=objRead(obj,sbuf,(256>n)?n:256,(o != -1)?o:0,(o != -1)?flags|OBJ_U_SEEK:flags)) > 0) { if(start) { + nht_i_JsonWriteResponse(conn, session_handle, XHN_INVALID_HANDLE, "OK"); nht_i_WriteResponse(conn, 200, "OK", NULL); nht_i_WriteHandle(conn, (handle_t)0); start = 0; @@ -922,15 +1052,21 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN start = 0; } nht_i_WriteConn(conn, "\r\n", 6,0); +#endif + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "OSML:read() - not yet implemented"); } else if (!strcmp(request,"write")) { + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "OSML:write() - not yet implemented"); } else if (!strcmp(request,"attrs")) { - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, (handle_t)0); - nht_i_WriteAttrs(obj,conn,obj_handle,1); + nht_i_JsonWriteResponse(conn, session_handle, query_handle, "OK"); + nht_i_JsonWriteAttrs(conn, obj, obj_handle); + nht_i_JsonWriteResponseEnd(conn, query_handle == XHN_INVALID_HANDLE, 0); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, (handle_t)0); + //nht_i_WriteAttrs(obj,conn,obj_handle,1); } else if (!strcmp(request,"setattrs") || !strcmp(request,"create")) { @@ -947,8 +1083,9 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN obj = objOpen(objsess, req_inf->StrVal, OBJ_O_AUTONAME | O_CREAT | O_RDWR, 0600, "system/object"); if (!obj) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); - mssError(0,"NHT","Could not create object"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + mssError(0, "NHT", "Could not create object"); + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "ERR"); return -1; } else @@ -1054,7 +1191,8 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (retval < 0) { mssError(0, "NHT", "Failed to set attribute <%s> on object <%s>", subinf->Name, obj->Pathname->Pathbuf + 1); - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "ERR"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); if (!strcmp(request, "create")) { xhnFreeHandle(&(sess->Hctx), obj_handle); @@ -1070,7 +1208,8 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN rval = objCommitObject(obj); if (rval < 0) { - nht_i_WriteResponse(conn, 200, "OK", " \r\n"); + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "ERR"); + //nht_i_WriteResponse(conn, 200, "OK", " \r\n"); objClose(obj); } else @@ -1154,25 +1293,33 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN if (!strcmp(request,"create")) { if (DEBUG_OSML) printf("ls__mode=create X" XHN_HANDLE_PRT "\n", obj_handle); - nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteResponse(conn, 200, "OK", NULL); if (obj) { - nht_i_WriteHandle(conn, obj_handle); + nht_i_JsonWriteResponse(conn, session_handle, query_handle, "OK"); + //nht_i_WriteHandle(conn, obj_handle); } else { - nht_i_WriteConn(conn, " \r\n", -1, 0); + nht_i_JsonWriteEntireResponse(conn, session_handle, query_handle, "ERR"); + //nht_i_WriteConn(conn, " \r\n", -1, 0); } } else { - nht_i_WriteResponse(conn, 200, "OK", NULL); - nht_i_WriteHandle(conn, (handle_t)0); + nht_i_JsonWriteResponse(conn, session_handle, query_handle, "OK"); + //nht_i_WriteResponse(conn, 200, "OK", NULL); + //nht_i_WriteHandle(conn, (handle_t)0); } /** Write the (possibly updated) attrs to the connection **/ if (obj) - nht_i_WriteAttrs(obj,conn,obj_handle,1); + { + //nht_i_WriteAttrs(obj,conn,obj_handle,1); + nht_i_JsonWriteAttrs(conn, obj, obj_handle); + } + + nht_i_JsonWriteResponseEnd(conn, query_handle == XHN_INVALID_HANDLE, 0); } if (nht_query) nht_i_FreeQuery(nht_query); @@ -1199,7 +1346,8 @@ nht_i_OSML(pNhtConn conn, pObject target_obj, char* request, pStruct req_inf, pN rval = objDeleteObj(obj); if (rval < 0) break; } - nht_i_WriteResponse(conn, 200, "OK", (rval==0)?" \r\n":" \r\n"); + //nht_i_WriteResponse(conn, 200, "OK", (rval==0)?" \r\n":" \r\n"); + nht_i_JsonWriteEntireResponse(conn, session_handle, XHN_INVALID_HANDLE, (rval == 0)?"OK":"ERR"); } } diff --git a/centrallix/netdrivers/net_http_rest.c b/centrallix/netdrivers/net_http_rest.c index c236c4da2..5480318b1 100644 --- a/centrallix/netdrivers/net_http_rest.c +++ b/centrallix/netdrivers/net_http_rest.c @@ -31,13 +31,7 @@ /************************************************************************/ -/*** Some enums to control how we're responding to the request ***/ -typedef enum { ResTypeCollection, ResTypeElement, ResTypeBoth } nhtResType_t; -typedef enum { ResFormatAttrs, ResFormatAuto, ResFormatContent, ResFormatBoth } nhtResFormat_t; -typedef enum { ResAttrsBasic, ResAttrsFull, ResAttrsNone } nhtResAttrs_t; - int nht_i_RestGetElementContent(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, char* mime_type); -int nht_i_RestGetElement(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, char* mime_type); int nht_i_RestGetCollection(pNhtConn conn, pObject obj, nhtResType_t res_type, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, int levels); int nht_i_RestGetBoth(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, int res_levels, char* mime_type); @@ -135,7 +129,7 @@ nht_i_RestWriteAttrValue(pNhtConn conn, pObject obj, char* attrname, int data_ty *** entry here, res_attrs will be either ResAttrsBasic or ResAttrsFull. ***/ int -nht_i_RestWriteAttr(pNhtConn conn, pObject obj, char* attrname, nhtResAttrs_t res_attrs, int do_comma) +nht_i_RestWriteAttr(pNhtConn conn, pObject obj, char* attrname, nhtResAttrs_t res_attrs, int do_comma, int sequence, int is_system) { int data_type; int err; @@ -179,7 +173,7 @@ nht_i_RestWriteAttr(pNhtConn conn, pObject obj, char* attrname, nhtResAttrs_t re xsDeInit(&hints_str); objFreeHints(hints); } - nht_i_WriteConn(conn, " }", 2, 0); + nht_i_QPrintfConn(conn, 0, ", \"y\":%STR%[, \"s\":%INT%] }", is_system?"true":"false", sequence > 0, sequence); } return 0; @@ -187,35 +181,18 @@ nht_i_RestWriteAttr(pNhtConn conn, pObject obj, char* attrname, nhtResAttrs_t re -/*** nht_i_RestGetElement() - get a REST element, which will be - *** the list of its attributes, possibly including an objcontent attribute. - *** - *** On entry here, res_format will be ResFormatAttrs or ResFormatBoth. - *** res_attrs could be ResAttrsBasic, ResAttrsFull, or ResAttrsNone. +/*** nht_i_RestWriteAttrSet() - send the list of attributes of an object/element ***/ int -nht_i_RestGetElement(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs, char* mime_type) +nht_i_RestWriteAttrSet(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtResAttrs_t res_attrs) { - char* path; - char pathbuf[OBJSYS_MAX_PATH]; + int i, seq; + int is_system; char* attr; char xfer_buf[256]; int rcnt; char* sys_attrs[] = { "name", "annotation", "inner_type", "outer_type" }; char sent_sys_attrs[sizeof(sys_attrs)/sizeof(char*)]; - int i; - - /** We begin our object with just a { **/ - nht_i_WriteConn(conn, "{\r\n", -1, 0); - - /** First thing we need to output is the URI of the element **/ - path = objGetPathname(obj); - strtcpy(pathbuf, path, sizeof(pathbuf)); - nht_i_QPrintfConn(conn, 0, "\"@id\":\"%STR&JSONSTR?cx__mode=rest&cx__res_format=%STR&JSONSTR%[&cx__res_attrs=full%]\"", - pathbuf, - (res_format == ResFormatAttrs)?"attrs":"both", - (res_attrs == ResAttrsFull) - ); /** Write any attrs? **/ if (res_attrs != ResAttrsNone) @@ -224,24 +201,31 @@ nht_i_RestGetElement(pNhtConn conn, pObject obj, nhtResFormat_t res_format, nhtR memset(sent_sys_attrs, 0, sizeof(sent_sys_attrs)); /** We loop through the main attributes that are iterable **/ + seq = 1; for(attr=objGetFirstAttr(obj); attr; attr=objGetNextAttr(obj)) { for(i=0; i