2
0
Эх сурвалжийг харах

* Add Data Abstract classes and demo (read-only for the moment)

michael 6 жил өмнө
parent
commit
b8e3bcccaf

+ 1947 - 0
demo/dataabstract/DataAbstract.js

@@ -0,0 +1,1947 @@
+RemObjects.UTIL.toBase64 = function (aValue) {
+    if (typeof (btoa) != 'undefined') {
+        return btoa(aValue);
+    } else {
+        throw (new Error("Base64 encoding is not supported by your browser."));
+        //return $.base64Encode(aValue);
+    };
+};
+
+RemObjects.UTIL.fromBase64 = function (aValue) {
+    if (typeof (atob) != 'undefined') {
+        return atob(aValue.replace(/(\n|\r)+/g, ""));
+    } else {
+        throw (new Error("Base64 decoding is not supported by your browser."));
+        //      return $.base64Decode(aValue);
+    };
+
+};
+
+RemObjects.DataAbstract = {
+    Enum: {},
+
+    Util: {},
+
+    Views: {},
+
+    Scripting: {},
+
+    DADataType: ["datUnknown", "datString", "datDateTime", "datFloat", "datCurrency",
+        "datAutoInc", "datInteger", "datLargeInt", "datBoolean", "datMemo",
+        "datBlob", "datWideString", "datWideMemo", "datLargeAutoInc", "datByte",
+        "datShortInt", "datWord", "datSmallInt", "datCardinal", "datLargeUInt",
+        "datGuid", "datXml", "datDecimal", "datSingleFloat", "datFixedChar", "datFixedWideChar", "datCursor"],
+
+    //    RemoteDataAdapter : function RemoteDataAdapter(aDataService, aLoginService, aStreamerClass) { //old api
+    RemoteDataAdapter: function RemoteDataAdapter(aURL, aDataServiceName, aLoginServiceName, aStreamerClass) {
+        var that = this;
+
+        this.fService = new RemObjects.DataAbstract.Server.DataAbstractService(arguments[0]);
+        this.fLoginService = new RemObjects.SDK.RemoteService(this.fService.fChannel, this.fService.fMessage, "LoginService");
+        if (arguments.length == 0)
+            this.fService.fChannel = { onLoginNeeded: null }; //hack for docGen
+        if (!(arguments[0] instanceof RemObjects.SDK.RemoteService)) this.fService.fServiceName = "DataService";
+        this.fStreamerClass = RemObjects.DataAbstract.Bin2DataStreamer;
+
+        if (RemObjects.UTIL.checkArgumentTypes(arguments, [RemObjects.SDK.RemoteService, RemObjects.SDK.RemoteService])) {
+            this.fLoginService = arguments[1];
+            this.fStreamerClass = arguments[2] || RemObjects.DataAbstract.Bin2DataStreamer;
+        } else if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string", "string", "string"])) { //URL, DataServiceName, LoginServiceName
+            this.fService.fServiceName = arguments[1];
+            this.fLoginService.fServiceName = arguments[2];
+            if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string", "string", "string", Function])) {
+                this.fStreamerClass = arguments[3];
+            };
+        } else if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string", "string"])) { //URL, DataServiceName
+            this.fService.fServiceName = arguments[1];
+            if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string", "string", Function])) {
+                this.fStreamerClass = arguments[2];
+            };
+        } else if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string"])) { //URL
+            if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string", Function])) {
+                this.fStreamerClass = arguments[1];
+            };
+        };
+
+        this.fSavedOnLoginNeeded = this.fService.fChannel.onLoginNeeded;
+        this.fService.fChannel.onLoginNeeded = function (aCallback) { that.intOnLoginNeeded(aCallback) };
+        this.fSendReducedDelta = false;
+        this.fAutoGetScripts = false;
+        //methods:
+        //setSendReducedDelta
+        //buildDelta
+        //createStreamer
+        //onLoginNeeded
+        //login
+
+        //asynchronous methods (pass callback function):
+        //getSchema
+        //createTableFromSchema
+        //getData
+        //applyUpdates
+        //getDataService
+        //getLoginService
+        //executeCommand
+
+
+    },
+
+    Deltas: function Deltas() {
+        this.deltas = [];
+        //methods
+        //findByName
+
+    },
+
+    Delta: function Delta() {
+        this.name = "";
+        this.loggedfields = [];
+        this.keyfields = [];
+        this.data = [];
+    },
+
+    Change: function Change() {
+        this.recid = 0;
+        this.changetype = "";
+        this.status = "";
+        this.message = "";
+        this["old"] = [];
+        this["new"] = [];
+
+    },
+
+    DataTable: function DataTable(aName) {
+        this.name = aName || "";
+        this.fields = [];
+        this.keyfields = [];
+        this.rows = [];
+        this.deletedRows = [];
+        this.fRecordBuffer = [];
+        this.fNextRecId = 0;
+        this.fIndex = -1;
+        this.bofFlag = true;
+        this.eofFlag = true;
+        this.dynamicWhere = null;
+        this.onNewRecord = null;
+        this.onBeforeDelete = null;
+        this.onAfterDelete = null;
+        this.onBeforeScroll = null;
+        this.onAfterScroll = null;
+        //methods
+        //appendRow
+        //deleteRow
+        //markDeleted
+        //currentRow
+        //first
+        //next
+        //prev
+        //last
+        //findId
+        //intFindDeleted
+        //eof
+        //bof
+        //getFieldValue
+        //setFieldValue
+        //getFieldAsString
+        //setFieldAsString
+        //addLookupField
+        //fieldNumByName
+        //fieldByName
+        //locate
+
+    },
+
+    Field: function Field(aName, aType) {
+        this.name = aName || "";
+        this.type = aType || "";
+        this.logChanges = true;
+    },
+
+    LookupField: function LookupField(aName, aType) {
+        RemObjects.DataAbstract.Field.call(this, aName, aType);
+        this.logChanges = false;
+        this.sourceField = "";
+        this.lookupTable = null;
+        this.lookupKeyField = "";
+        this.lookupResultField = "";
+    },
+
+    DataTableRow: function DataTableRow() {
+        this.recId = 0;
+        this.state = RemObjects.DataAbstract.Enum.RowStates.rsUnchanged;
+        this.__oldValues = [];
+        this.__newValues = [];
+    },
+
+    DataStreamer: function DataStreamer() {
+
+    },
+
+    JSONDataStreamer: function JSONDataStreamer() {
+        //methods:
+        //setStream
+        //getStream
+        //initializeRead
+        //initializeWrite
+    },
+
+    Bin2DataStreamer: function Bin2DataStreamer() {
+        this.fStream = "";
+        this.fStreamPos = 0;
+        this.fStreamInfoPos = 0;
+        this.fParser = new BinaryParser();
+        //methods:
+        //setStream
+        //getStream
+        //initializeRead
+        //initializeWrite
+        //readByte
+        //readInteger
+        //readAnsiStringWithLength
+        //readDataSet
+        //readField
+        //writeDelta
+    },
+
+    DynamicWhere: function DynamicWhere(anExpression) {
+        this.fExpression = anExpression;
+        //toXML
+    },
+
+    ConstantExpression: function ConstantExpression(aType, aValue, aNull) {
+        this.fType = aType;
+        this.fValue = aValue;
+        this.fNull = aNull;
+        //toXML
+    },
+
+    NullExpression: function NullExpression() {
+
+    },
+
+    FieldExpression: function FieldExpression(aName) {
+        this.fName = aName;
+    },
+
+    MacroExpression: function MacroExpression(aName) {
+        this.fName = aName;
+    },
+
+    UnaryExpression: function UnaryExpression(aNode, anOperator) {
+        this.fNode = aNode;
+        this.fOperator = anOperator;
+    },
+
+    BinaryExpression: function BinaryExpression(aNode1, aNode2, anOperator) {
+        this.fNode1 = aNode1;
+        this.fNode2 = aNode2;
+        this.fOperator = anOperator;
+    },
+
+    BetweenExpression: function BetweenExpression(aNode1, aNode2, aNode3) {
+        this.fNode1 = aNode1;
+        this.fNode2 = aNode2;
+        this.fNode3 = aNode3;
+    },
+
+    ParameterExpression: function ParameterExpression(aName, aType, aSize) {
+        this.fName = aName;
+        this.fType = aType;
+        this.fSize = aSize;
+    },
+
+    ListExpression: function ListExpression(anItems) {
+        this.fItems = anItems;
+    }
+
+};
+
+RemObjects.DataAbstract.Enum.RowStates = {
+    //DataTableRow states
+    rsUnchanged: 0,
+    rsModified: 1,
+    rsNew: 2,
+    rsDeleted: 3
+};
+
+RemObjects.DataAbstract.Enum.ChangeType = { ctInsert: 0, ctUpdate: 1, ctDelete: 2 };
+RemObjects.DataAbstract.Enum.ChangeTypeNames = ["insert", "update", "delete"];
+RemObjects.DataAbstract.Enum.ChangeStatus = { csPending: 0, csResolved: 1, csFailed: 2 };
+RemObjects.DataAbstract.Enum.ChangeStatusNames = ["pending", "resolved", "failed"];
+
+
+RemObjects.DataAbstract.Util.createDataParameter = function createDataParameter(aName, aValue) {
+    var result = new RemObjects.DataAbstract.Server.DataParameter();
+    result.Name.value = aName;
+    result.Value.value = aValue;
+    return result;
+};
+
+
+RemObjects.DataAbstract.Util.createRequestInfo = function createRequestInfo(includeSchema, maxRecords, userFilter, parameters) {
+    var result = new RemObjects.DataAbstract.Server.TableRequestInfo();
+    if (arguments.length == 0) {
+        result.IncludeSchema.value = true;
+        result.MaxRecords.value = -1;
+        result.UserFilter.value = "";
+        result.Parameters.value = new RemObjects.DataAbstract.Server.DataParameterArray();
+        result.Parameters.value.items = [];
+    } else {
+        result.IncludeSchema.value = includeSchema;
+        result.MaxRecords.value = maxRecords;
+        result.UserFilter.value = userFilter;
+        result.Parameters.value = new RemObjects.DataAbstract.Server.DataParameterArray();
+        result.Parameters.value.items = parameters;
+    };
+    return result;
+};
+
+RemObjects.DataAbstract.Util.createRequestInfoV5 = function createRequestInfoV5(includeSchema, maxRecords, userFilter, parameters) {
+    var ri = RemObjects.DataAbstract.Util.createRequestInfo.apply(this, arguments);
+    var result = new RemObjects.DataAbstract.Server.TableRequestInfoV5();
+    result.IncludeSchema = ri.IncludeSchema;
+    result.MaxRecords = ri.MaxRecords;
+    result.UserFilter = ri.UserFilter;
+    result.Parameters = ri.Parameters;
+
+    result.WhereClause = { dataType: "Xml", value: null };
+    result.DynamicSelectFieldNames = { dataType: "StringArray", value: new RemObjects.DataAbstract.Server.StringArray() };
+    result.DynamicSelectFieldNames.value.items = [];
+    result.Sorting = { dataType: "ColumnSorting", value: new RemObjects.DataAbstract.Server.ColumnSorting() };
+    result.Sorting.value.fromObject({ FieldName: "", SortDirection: "Ascending" });
+    return result;
+};
+
+
+RemObjects.DataAbstract.Util.createRequestInfoV6 = function createRequestInfoV6(sql, maxRecords, userFilter, parameters) {
+    var result = new RemObjects.DataAbstract.Server.TableRequestInfoV6();
+    if (arguments.length == 1) {
+        result.Sql.value = sql;
+        result.IncludeSchema.value = true;
+        result.MaxRecords.value = -1;
+        result.UserFilter.value = "";
+        result.Parameters.value = new RemObjects.DataAbstract.Server.DataParameterArray();
+        result.Parameters.value.items = [];
+    } else {
+        result.Sql.value = sql;
+        result.IncludeSchema.value = true;
+        result.MaxRecords.value = maxRecords;
+        result.UserFilter.value = userFilter;
+        result.Parameters.value = new RemObjects.DataAbstract.Server.DataParameterArray();
+        result.Parameters.value.items = parameters;
+    };
+    return result;
+};
+
+RemObjects.DataAbstract.Util.setupScriptingCallbacks = function setupScriptingCallbacks() {
+    for (var p in RemObjects.DataAbstract.Scripting) {
+        eval("var " + p + " = RemObjects.DataAbstract.Scripting." + p);
+        console.log("var " + p + " = RemObjects.DataAbstract.Scripting." + p);
+    };
+};
+
+RemObjects.DataAbstract.Scripting.log = function log(str) {
+    console.log(str);
+};
+
+RemObjects.DataAbstract.Scripting.fail = function fail(str) {
+    throw new Error(str);
+};
+
+RemObjects.DataAbstract.Scripting.newGuidString = function newGuidString() {
+    return RemObjects.UTIL.NewGuid();
+};
+
+//RO.DA implementation
+
+
+
+RemObjects.DataAbstract.Deltas.prototype.findByName = function findByName(aName) {
+    for (var i = 0; i < this.deltas.length; i++) {
+        if (this.deltas[i].name.toUpperCase() == aName.toUpperCase()) {
+            return this.deltas[i];
+        };
+    };
+    return null;
+};
+
+
+RemObjects.DataAbstract.Delta.prototype.intFindId = function intFindId(anId) {
+    for (var i = 0; i < this.data.length; i++) {
+        if (this.data[i].recid == anId) {
+            return i;
+        };
+    };
+    return null;
+};
+
+
+RemObjects.DataAbstract.Field.prototype.checkReadOnly = function checkReadOnly() {
+    if (this instanceof RemObjects.DataAbstract.LookupField) {
+        throw new Error(this.name + ": lookup fields are read only");
+    };
+    if (this.readOnly == "True") {
+        throw new Error(this.name + " is read only.");
+    };
+};
+
+RemObjects.DataAbstract.LookupField.prototype = new RemObjects.DataAbstract.Field();
+
+RemObjects.DataAbstract.DataTable.prototype.checkRequired = function checkRequired() {
+    if (this.fIndex < 0) return;
+    for (var i = 0; i < this.fields.length; i++) {
+        if (this.fields[i].required == "True" && (this.fRecordBuffer[i] == null || this.fRecordBuffer[i] == undefined)) {
+            throw new Error("Field " + this.fields[i].name + " is required.");
+        };
+    };
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.locate = function locate(aName, aValue) {
+    this.post();
+    var result = false;
+    var fieldNum = this.fieldNumByName(aName);
+    for (var i = 0; i < this.rows.length; i++) {
+        if (this.rows[i].__newValues[fieldNum] == aValue) {
+            this.fIndex = i;
+            result = true;
+            break;
+        };
+    };
+    return result;
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.addLookupField = function addLookupField(aName, aSourceField, aLookupTable, aLookupKeyField, aLookupResultField) {
+    var f = new RemObjects.DataAbstract.LookupField(aName);
+    f.type = aLookupTable.fieldByName(aLookupResultField).type;
+    f.lookupTable = aLookupTable;
+    f.sourceField = aSourceField;
+    f.lookupKeyField = aLookupKeyField;
+    f.lookupResultField = aLookupResultField;
+    this.fields.push(f);
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.getNextId = function getNextId() {
+    return this.fNextRecId++;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.intSetupProperties = function intSetupProperties(aRow) {
+    if (!Object.defineProperty) return;
+    var that = this;
+    for (var j = 0; j < this.fields.length; j++) {
+        (function (fieldNum) {
+
+            Object.defineProperty(aRow, that.fields[fieldNum].name, {
+                get: function () {
+                    return this.__newValues[fieldNum];
+                },
+                set: function (aValue) {
+                    if (this.__oldValues.length == 0)
+                        this.__oldValues = this.__newValues.slice();
+                    this.__oldValues[fieldNum] = this.__newValues[fieldNum];
+                    this.__newValues[fieldNum] = aValue;
+                    if (this.state == RemObjects.DataAbstract.Enum.RowStates.rsUnchanged)
+                        this.state = RemObjects.DataAbstract.Enum.RowStates.rsModified;
+                }
+            });
+
+
+        })(j);
+    };
+
+};
+
+RemObjects.DataAbstract.DataTable.prototype.intAppendRow = function intAppendRow() {
+    var row = new RemObjects.DataAbstract.DataTableRow();
+    row.recId = this.getNextId();
+    row.state = RemObjects.DataAbstract.Enum.RowStates.rsNew;
+    row.__newValues = new Array(this.fields.length);
+    this.rows.push(row);
+    this.fIndex = this.rows.length - 1;
+    this.eofFlag = false;
+    this.bofFlag = false;
+
+    this.intSetupProperties(row);
+
+    return row;
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.appendRow = function appendRow() {
+    //this.checkRequired();
+    this.post();
+    var row = this.intAppendRow();
+    for (var i = 0; i < this.fields.length; i++) {
+        if (typeof (this.fields[i].fAutoIncSequence) != 'undefined') {
+            row.__newValues[i] = this.fields[i].fAutoIncSequence--;
+        } else if (this.fields[i].defaultValue != "") {
+            row.__newValues[i] = this.fields[i].defaultValue;
+        };
+    };
+
+    if (this.__onNewRow) {
+        var lRow = {};
+        for (var i = 0; i < this.fields.length; i++) {
+            lRow[this.fields[i].name] = row.__newValues[i];
+        };
+        this.__onNewRow(lRow);
+        for (var i = 0; i < this.fields.length; i++) {
+            row.__newValues[i] = lRow[this.fields[i].name];
+        };
+    };
+
+    this.fRecordBuffer = row.__newValues.slice();
+    if (this.onNewRecord) this.onNewRecord(row);
+    return row;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.deleteRow = function deleteRow() {
+    if (this.rows.length == 0)
+        throw new Error("DataTable.deleteRow: table is empty");
+
+    if (this.__beforeDelete) {
+        var row = this.currentRow();
+        var lRow = {};
+        for (var i = 0; i < this.fields.length; i++) {
+            lRow[this.fields[i].name] = row.__newValues[i];
+        };
+        this.__beforeDelete(lRow);
+    };
+
+    if (this.onBeforeDelete) this.onBeforeDelete(this.currentRow());
+
+    this.markDeleted();
+    this.deletedRows.push(this.rows[this.fIndex]);
+    this.rows.splice(this.fIndex, 1);
+    if (this.fIndex == this.rows.length)
+        this.fIndex--;
+    this.eofFlag = (this.rows.length == 0);
+    this.bofFlag = (this.rows.length == 0);
+    this.fRecordBuffer = [];
+
+    if (this.onAfterDelete && this.rows.length) this.onAfterDelete(this.currentRow());
+};
+
+RemObjects.DataAbstract.DataTable.prototype.markDeleted = function markDeleted() {
+    if (this.rows[this.fIndex].__oldValues.length == 0)
+        this.rows[this.fIndex].__oldValues = this.rows[this.fIndex].__newValues.slice();
+    this.rows[this.fIndex].state = RemObjects.DataAbstract.Enum.RowStates.rsDeleted;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.fieldNumByName = function fieldNumByName(aName) {
+    var fieldNum = -1;
+    for (var i = 0; i < this.fields.length; i++) {
+        if (this.fields[i].name.toUpperCase() == aName.toUpperCase()) {
+            fieldNum = i;
+            break;
+        };
+    };
+    if (fieldNum == -1)
+        throw new Error("DataTable.fieldNumByName: no such field name - " + aName);
+    return fieldNum;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.fieldByName = function fieldByName(aName) {
+    return this.fields[this.fieldNumByName(aName)];
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.setFieldValue = function setFieldValue(aField, aValue) {
+    if (this.rows.length == 0)
+        throw new Error("DataTable.setFieldValue: table is empty");
+    var fieldNum = this.fieldNumByName(aField);
+    var f = this.fields[fieldNum];
+    f.checkReadOnly();
+    if (this.rows[this.fIndex].__oldValues.length == 0)
+        this.rows[this.fIndex].__oldValues = this.rows[this.fIndex].__newValues.slice();
+    if (this.rows[this.fIndex].state == RemObjects.DataAbstract.Enum.RowStates.rsUnchanged)
+        this.rows[this.fIndex].state = RemObjects.DataAbstract.Enum.RowStates.rsModified;
+    if (this.fRecordBuffer.length == 0)
+        this.fRecordBuffer = this.rows[this.fIndex].__newValues.slice();
+
+    this.fRecordBuffer[fieldNum] = aValue;
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.getFieldValue = function getFieldValue(aField) {
+    if (this.rows.length == 0)
+        throw new Error("DataTable.getFieldValue: table is empty");
+    var fieldNum = this.fieldNumByName(aField);
+    var f = this.fields[fieldNum];
+    if (f instanceof RemObjects.DataAbstract.LookupField) {
+        if (f.lookupTable.locate(f.lookupKeyField, this.getFieldValue(f.sourceField))) {
+            return f.lookupTable.getFieldValue(f.lookupResultField);
+        } else {
+            return null;
+        };
+    } else if (this.fRecordBuffer.length == 0) {
+        return this.rows[this.fIndex].__newValues[fieldNum];
+    } else {
+        return this.fRecordBuffer[fieldNum];
+    };
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.setFieldAsString = function setFieldAsString(aField, aValue) {
+    if (this.rows.length == 0)
+        throw new Error("DataTable.setFieldAsString: table is empty");
+    var tempValue;
+    var fieldNum = this.fieldNumByName(aField);
+    var f = this.fields[fieldNum];
+    f.checkReadOnly();
+    if (this.rows[this.fIndex].__oldValues.length == 0)
+        this.rows[this.fIndex].__oldValues = this.rows[this.fIndex].__newValues.slice();
+    if (this.rows[this.fIndex].state == RemObjects.DataAbstract.Enum.RowStates.rsUnchanged)
+        this.rows[this.fIndex].state = RemObjects.DataAbstract.Enum.RowStates.rsModified;
+    if (this.fRecordBuffer.length == 0)
+        this.fRecordBuffer = this.rows[this.fIndex].__newValues.slice();
+
+    if (aValue == "") {
+        if (this.rows[this.fIndex].__oldValues[fieldNum] == null)
+            tempValue = null;
+    } else {
+        switch (this.fields[fieldNum].type) {
+            //case "datBlob": return;
+            case "datInteger":
+            case "datAutoInc":
+            case "datSmallInt":
+            case "datByte":
+            case "datLargeInt":
+            case "datLargeAutoInc":
+            case "datLargeUInt":
+            case "datShortInt":
+            case "datWord":
+            case "datCardinal":
+                tempValue = parseInt(aValue);
+                break;
+            case "datFloat":
+            case "datCurrency":
+            case "datSingleFloat":
+            case "datDecimal":
+                tempValue = parseFloat(aValue);
+                break;
+            case "datDateTime":
+                tempValue = new Date(aValue);
+                break;
+            case "datBoolean":
+                tempValue = (aValue.toUpperCase() == "TRUE");
+            default:
+                tempValue = aValue;
+            //todo
+        };
+    };
+    this.fRecordBuffer[fieldNum] = tempValue;
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.getFieldAsString = function getFieldAsString(aField) {
+    var fieldNum = this.fieldNumByName(aField);
+    var value = this.getFieldValue(aField);
+    return (this.fields[fieldNum].type == "datBlob" ? "(Blob)" : (
+        (value != undefined && value != null)
+            ? value : "").toString());
+
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.currentRow = function currentRow() {
+    return (this.rows.length ? this.rows[this.fIndex] : null);
+};
+
+RemObjects.DataAbstract.DataTable.prototype.intGoToIndex = function intGoToIndex(anIndex) {
+    if (this.onBeforeScroll) this.onBeforeScroll(this.currentRow());
+    this.fIndex = anIndex;
+    if (this.onAfterScroll) this.onAfterScroll(this.currentRow());
+};
+
+RemObjects.DataAbstract.DataTable.prototype.first = function first() {
+    this.post();
+    if (this.rows.length > 0)
+        this.intGoToIndex(0);
+    this.bofFlag = true;
+    this.eofFlag = false;
+    return this.currentRow();
+};
+
+RemObjects.DataAbstract.DataTable.prototype.last = function last() {
+    this.post();
+    if (this.rows.length > 0)
+        this.intGoToIndex(this.rows.length - 1);
+    this.eofFlag = true;
+    this.bofFlag = false;
+    return this.currentRow();
+};
+
+RemObjects.DataAbstract.DataTable.prototype.next = function next() {
+    this.post();
+    if (this.fIndex < this.rows.length - 1) {
+        this.intGoToIndex(this.fIndex + 1);
+        this.bofFlag = false;
+        return this.currentRow();
+    } else {
+        this.eofFlag = true;
+        return null;
+    };
+};
+
+RemObjects.DataAbstract.DataTable.prototype.prev = function prev() {
+    this.post();
+    if (this.fIndex > 0) {
+        this.intGoToIndex(this.fIndex - 1);
+        this.eofFlag = false;
+        return this.currentRow();
+    } else {
+        this.bofFlag = true;
+        return null;
+    };
+};
+
+RemObjects.DataAbstract.DataTable.prototype.findId = function findId(anId) {
+    this.post();
+    for (var i = 0; i < this.rows.length; i++) {
+        if (this.rows[i].recId == anId) {
+            this.intGoToIndex(i);
+            return this.currentRow();
+        };
+    };
+    return null;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.intFindDeleted = function intFindDeleted(anId) {
+    for (var i = 0; i < this.deletedRows.length; i++) {
+        if (this.deletedRows[i].recId == anId) {
+            return i;
+        };
+    };
+    return null;
+};
+
+
+RemObjects.DataAbstract.DataTable.prototype.eof = function eof() {
+    return this.eofFlag;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.bof = function bof() {
+    return this.bofFlag;
+};
+
+RemObjects.DataAbstract.DataTable.prototype.post = function post() {
+    var bufferNotSet = true;
+    for (var i = 0; i < this.fRecordBuffer.length; i++) {
+        if (typeof this.fRecordBuffer[i] != 'undefined')
+        {
+            bufferNotSet = false;
+            break;
+        }
+    }
+
+    if (bufferNotSet) {
+        return;
+    }
+
+
+    if (this.__beforePost) {
+        var lRow = {};
+        for (var i = 0; i < this.fields.length; i++) {
+            lRow[this.fields[i].name] = this.fRecordBuffer[i];
+        };
+        try {
+            this.__beforePost(lRow);
+            for (var i = 0; i < this.fields.length; i++) {
+                this.fRecordBuffer[i] = lRow[this.fields[i].name];
+            };
+        } catch (e) { throw e };
+    };
+
+    this.checkRequired();
+    this.rows[this.fIndex].__newValues = this.fRecordBuffer;
+    this.fRecordBuffer = [];
+};
+
+RemObjects.DataAbstract.DataTable.prototype.cancel = function cancel() {
+    this.fRecordBuffer = [];
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getDataService = function getDataService() {
+    return this.fService;
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getLoginService = function getLoginService() {
+    return this.fLoginService;
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.intOnLoginNeeded = function intOnLoginNeeded(aCallback) {
+    if (this.onLoginNeeded) {
+        this.onLoginNeeded(aCallback)
+    } else {
+        this.fSavedOnLoginNeeded(aCallback);
+    };
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.onLoginNeeded = function onLoginNeeded(aCallback) {
+    RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign remoteDataAdapter.onLoginNeeded and call aCallback there after successful login");
+    //example:
+    //    this.login("John", "password", function(result) {
+    //        if (aCallback)
+    //            aCallback();
+    //    }, function(msg, e) {
+    //        if (e)
+    //            throw e
+    //        else
+    //            throw new Error(msg.getErrorMessage());
+    //    });
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.login = function login(aUserID, aPassword, aConnectionName, onSuccessFunction, onErrorFunction) {
+
+    if (!this.fLoginService)
+        throw new Error("RemoteDataAdapter.login: login service not assigned");
+
+    var svc;
+    switch (arguments.length - 2) {
+        case 1: //loginString
+            svc = new RemObjects.DataAbstract.Server.BaseLoginService(this.fLoginService);
+            svc.LoginEx(arguments[0], arguments[1], arguments[2]);
+            break;
+        case 2: //userID, password
+            svc = new RemObjects.DataAbstract.Server.SimpleLoginService(this.fLoginService);
+            svc.Login(arguments[0], arguments[1], arguments[2], arguments[3]);
+            break;
+        case 3: //userID, password, connection
+            svc = new RemObjects.DataAbstract.Server.MultiDbLoginService(this.fLoginService);
+            svc.Login(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
+            break;
+        default:
+            throw new Error("RemoteDataAdapter.login: incorrect number of arguments");
+    };
+
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.logout = function logout(onSuccessFunction, onErrorFunction) {
+    if (!this.fLoginService)
+        throw new Error("RemoteDataAdapter.logout: login service not assigned");
+
+    var svc = new RemObjects.DataAbstract.Server.BaseLoginService(this.fLoginService);
+    svc.Logout(onSuccessFunction, onErrorFunction);
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getSchema = function getSchema(aFilter, aCallback, onFailure) {
+    var that = this;
+    this.fService.GetSchema(aFilter, aCallback, onFailure || RemObjects.UTIL.showError);
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.createStreamer = function createStreamer() {
+    return new this.fStreamerClass();
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.setSendReducedDelta = function setSendReducedDelta(aValue) {
+    this.fSendReducedDelta = aValue;
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.buildDelta = function buildDelta(aTable) {
+
+    function processRows(aRows, aDelta) {
+        for (var i = 0; i < aRows.length; i++) {
+
+            var c = new RemObjects.DataAbstract.Change();
+            c.status = "pending";
+            c.recid = aRows[i].recId;
+            if (aRows[i].state == RemObjects.DataAbstract.Enum.RowStates.rsNew) {
+                c.old = aRows[i].__newValues.slice();
+            } else {
+                c.old = aRows[i].__oldValues.slice();
+            };
+            c["new"] = aRows[i].__newValues.slice();
+            excludeItems(c.old);
+            excludeItems(c["new"]);
+
+            switch (aRows[i].state) {
+                case RemObjects.DataAbstract.Enum.RowStates.rsModified: c.changetype = "update"; break;
+                case RemObjects.DataAbstract.Enum.RowStates.rsNew: c.changetype = "insert"; break;
+                case RemObjects.DataAbstract.Enum.RowStates.rsDeleted: c.changetype = "delete"; break;
+            };
+
+            aDelta.data.push(c);
+        };
+    };
+
+    function excludeItems(anArray) {
+        for (var i = excludedFields.length - 1; i >= 0; i--) {
+            anArray.splice(excludedFields[i], 1);
+        };
+    };
+
+    var excludedFields = [];
+    for (var i = 0; i < aTable.fields.length; i++) {
+        if (!aTable.fields[i].logChanges) {
+            excludedFields.push(i);
+        };
+    };
+    var changedRows = [];
+    for (var i = 0; i < aTable.rows.length; i++) {
+        if (aTable.rows[i].state != RemObjects.DataAbstract.Enum.RowStates.rsUnchanged) changedRows.push(aTable.rows[i]);
+    };
+
+    if ((changedRows.length > 0) || (aTable.deletedRows.length > 0)) {
+
+        d = new RemObjects.DataAbstract.Delta();
+        d.name = aTable.name;
+        d.keyfields = aTable.keyfields.slice();
+        d.loggedfields = aTable.fields.slice();
+        excludeItems(d.loggedfields);
+
+        processRows(changedRows, d);
+        processRows(aTable.deletedRows, d);
+        return d;
+    } else {
+        return null;
+    };
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.createTableFromSchema = function createTableFromSchema(aTableName, aTable, aCallback) {
+    var that = this;
+    var tablesArray = new RemObjects.DataAbstract.Server.StringArray();
+    tablesArray.items = [aTableName];
+    this.fService.GetTableSchema(tablesArray, function (result) {
+        var xmlDoc;
+        if (typeof (DOMParser) != 'undefined') {
+            var parser = new DOMParser();
+            xmlDoc = parser.parseFromString(result, "text/xml");
+        } else // Internet Explorer
+        {
+            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
+            xmlDoc.async = "false";
+            xmlDoc.loadXML(result);
+        };
+
+        var fieldsNode = xmlDoc.getElementsByTagName("Fields")[0];
+        for (i = 0; i < fieldsNode.childElementCount; i++) {
+            var f = new RemObjects.DataAbstract.Field();
+            f.name = fieldsNode.childNodes[i].getElementsByTagName('Name')[0].textContent;
+            f.type = fieldsNode.childNodes[i].getElementsByTagName('DataType')[0].textContent;
+            f.logChanges = (fieldsNode.childNodes[i].getElementsByTagName('LogChanges')[0].textContent == "True");
+            if (fieldsNode.childNodes[i].getElementsByTagName('InPrimaryKey')[0].textContent == "True")
+                aTable.keyfields.push(f.name);
+
+            aTable.fields.push(f);
+
+        };
+        if (aCallback)
+            aCallback();
+    }, RemObjects.UTIL.showError);
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.executeCommand = function executeCommand(aName, aParameters, onSuccess, onError) {
+    if (aParameters.constructor != Array) {
+        aParameters = [aParameters];
+    };
+    var params = new RemObjects.DataAbstract.Server.DataParameterArray();
+    params.items = aParameters;
+    this.fService.ExecuteCommand(aName, params, onSuccess, onError)
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getSQLData = function getSQLData(aTable, aSQL, onSuccess, onError) {
+    var ria = new RemObjects.DataAbstract.Server.TableRequestInfoArray();
+    ria.elementType = "TableRequestInfoV6";
+
+    if (aTable.constructor != Array) {
+        aTable = [aTable];
+    };
+
+    switch (aSQL.constructor) {
+        case RemObjects.DataAbstract.Server.TableRequestInfoV6:
+            ria.items.push(aSQL);
+            break;
+        case Array:
+            for (var i = 0; i < aTable.length; i++) {
+                switch (aSQL[i].constructor) {
+                    case RemObjects.DataAbstract.Server.TableRequestInfoV6:
+                        ria.items.push(aSQL[i]);
+                        break;
+                    case String:
+                        ria.items.push(RemObjects.DataAbstract.Util.createRequestInfoV6(aSQL[i]));
+                        break;
+                    default:
+                        throw new Error("rda.getSQLData: incorrect aRequestInfo array item");
+                };
+            };
+            break;
+        case String:
+            ria.items.push(RemObjects.DataAbstract.Util.createRequestInfoV6(aSQL));
+            break;
+        default:
+            throw new Error("rda.getSQLData: incorrect aSQL");
+    };
+
+    this.getData(aTable, ria, onSuccess, onError);
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getAutoGetScripts = function getAutoGetScripts() {
+    return this.fAutoGetScripts;
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.setAutoGetScripts = function setAutoGetScripts(aValue) {
+    this.fAutoGetScripts = aValue;
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.intGetScripts = function intGetScripts(aTable, onSuccess, onError) {
+    var tableNames = [];
+    if (aTable.constructor != Array) {
+        aTable = [aTable];
+    };
+    for (var i = 0; i < aTable.length; tableNames.push(aTable[i++].name));
+    this.fService.GetDatasetScripts(tableNames.join(","), function (aScript) {
+        for (var t = 0; t < aTable.length; t++) {
+            var __tbl = aTable[t];
+            __tbl.__onNewRow = null;
+            __tbl.__beforeDelete = null;
+            __tbl.__beforePost = null;
+            var r = new RegExp('<' + aTable[t].name + ' Language="(.*?)"><!\\[CDATA\\[([\\s\\S]*?)\\]\\]', "mi");
+            var m = r.exec(aScript);
+            var s = (m ? m[2] : "");
+            var m1 = s.match(/function (.*?)\(/g);
+            if (m1) for (var i = 0; i < m1.length; i++) {
+                s = s.replace(m1[i], m1[i].replace(/function (.*?)\(/, "__tbl.__$1 = ") + m1[i]);
+            }
+            var m1 = s.match(/function (.*?)\{/g);
+            if (m1) for (var i = 0; i < m1.length; i++) {
+                s = s.replace(m1[i], m1[i] + '\nfor (var p in RemObjects.DataAbstract.Scripting) {\n' +
+                    'eval("var " + p + " = RemObjects.DataAbstract.Scripting." + p);\n' +
+                    '};\n');
+            }
+            eval(s);
+        };
+        if (onSuccess) onSuccess();
+    }, onError);
+};
+
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.getData = function getData(aTable, aRequestInfo, onSuccess, onError) {
+    var tableNames = new RemObjects.DataAbstract.Server.StringArray();
+    if (aTable.constructor != Array) {
+        aTable = [aTable];
+    };
+    for (var i = 0; i < aTable.length; tableNames.items.push(aTable[i++].name));
+
+    var tableOptions = new RemObjects.DataAbstract.Server.TableRequestInfoArray();
+
+    switch (aRequestInfo.constructor) {
+        case RemObjects.DataAbstract.Server.TableRequestInfoArray:
+            tableOptions = aRequestInfo;
+            break;
+        case RemObjects.DataAbstract.Server.TableRequestInfo:
+        case RemObjects.DataAbstract.Server.TableRequestInfoV5:
+        case RemObjects.DataAbstract.Server.TableRequestInfoV6:
+            tableOptions.items.push(aRequestInfo);
+            break;
+        case Array:
+            tableOptions.items = aRequestInfo;
+            break;
+        default:
+            var requestInfo = RemObjects.DataAbstract.Util.createRequestInfo();
+            for (var i = 0; i < aTable.length; i++) {
+                tableOptions.items.push(requestInfo);
+            };
+    };
+
+    for (var i = 0; i < aTable.length; i++) {
+        if (aTable[i].dynamicWhere) {
+            var requestInfoV5 = RemObjects.DataAbstract.Util.createRequestInfoV5(tableOptions.items[i].IncludeSchema.value,
+                tableOptions.items[i].MaxRecords.value, tableOptions.items[i].UserFilter.value, tableOptions.items[i].Parameters.value.items);
+            requestInfoV5.WhereClause.value = aTable[i].dynamicWhere.toXML();
+            tableOptions.items[i] = requestInfoV5;
+        };
+    };
+
+
+    var __onError, __onSuccess;
+    if (arguments.length < 4) {
+        __onError = arguments[arguments.length - 1];
+        __onSuccess = arguments[arguments.length - 2];
+    } else {
+        __onError = onError;
+        __onSuccess = onSuccess;
+    };
+
+    var that = this;
+    this.fService.GetData(tableNames, tableOptions,
+        function (result) {
+            var streamer = that.createStreamer();
+            var tmp = (that.fService.fMessage instanceof RemObjects.SDK.JSONMessage) ? RemObjects.UTIL.fromBase64(result) : result;
+            streamer.setStream(tmp);
+            streamer.initializeRead();
+
+            for (var i = 0; i < aTable.length; i++) {
+                streamer.readDataset(aTable[i]);
+            };
+
+            if (that.fAutoGetScripts) {
+                that.intGetScripts(aTable, __onSuccess, __onError);
+            } else if (__onSuccess) __onSuccess();
+        }, __onError);
+
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.onChangeFail = function onChangeFail(aData) {
+    RemObjects.UTIL.showMessage("Update failed for:\n" + RemObjects.UTIL.toJSON(aData));
+};
+
+RemObjects.DataAbstract.RemoteDataAdapter.prototype.applyUpdates = function applyUpdates(aTable, onSuccess, onError) {
+    if (aTable.constructor != Array) {
+        aTable = [aTable];
+    };
+    var __deltas = new RemObjects.DataAbstract.Deltas();
+    for (var i = 0; i < aTable.length; i++) {
+        aTable[i].post();
+        var d = this.buildDelta(aTable[i]);
+        if (d) __deltas.deltas.push(d);
+    };
+    if (__deltas.deltas.length > 0) {
+        var that = this;
+        var streamer = this.createStreamer();
+        streamer.initializeWrite();
+        streamer.writeDelta(__deltas, this.fSendReducedDelta);
+        streamer.finalizeWrite();
+
+        var tmp = streamer.getStream();
+
+        this.fService.UpdateData(tmp, function (result) {
+            var tmp = (that.fService.fMessage instanceof RemObjects.SDK.JSONMessage) ? RemObjects.UTIL.fromBase64(result) : result;
+
+            var streamer = that.createStreamer();
+            streamer.setStream(tmp);
+            streamer.initializeRead();
+            var __res = streamer.readDelta();
+
+            for (var i = 0; i < __deltas.deltas.length; i++) {
+                var processedTable = null;
+                for (var t = 0; t < aTable.length; t++) {
+                    if (aTable[t].name == __deltas.deltas[i].name) {
+                        processedTable = aTable[t];
+                        break;
+                    };
+                };
+
+                var baseDelta = __deltas.deltas[i];
+                var processedDelta = __res.findByName(__deltas.deltas[i].name);
+                if (processedDelta) {
+                    for (var j = 0; j < processedDelta.data.length; j++) {
+
+                        if (processedTable) {
+                            switch (processedDelta.data[j].status) {
+                                case "failed":
+                                    that.onChangeFail(processedDelta.data[j]);
+                                    //remove from baseDelta
+                                    baseDelta.data.splice(baseDelta.intFindId(processedDelta.data[j].recid), 1);
+                                    break;
+                                case "resolved":
+                                    if (processedTable.findId(processedDelta.data[j].recid)) {
+                                        for (var k = 0; k < processedDelta.loggedfields.length; k++) {
+                                            if (processedDelta.data[j]["new"][k] != processedDelta.data[j]["old"][k]) {
+                                                var fieldNum = processedTable.fieldNumByName(processedDelta.loggedfields[k].name);
+                                                processedTable.currentRow().__newValues[fieldNum] = processedDelta.data[j]["new"][k];
+                                                processedTable.currentRow().__oldValues[fieldNum] = processedDelta.data[j]["new"][k];
+                                            };
+                                        };
+                                        processedTable.currentRow().state = RemObjects.DataAbstract.Enum.RowStates.rsUnchanged;
+                                        processedTable.currentRow().__oldValues = processedTable.currentRow().__newValues.slice();
+                                    } else {
+                                        var deleted = processedTable.intFindDeleted(processedDelta.data[j].recid);
+                                        if (deleted != null) {
+                                            processedTable.deletedRows.splice(deleted, 1);
+                                        } else {
+                                            RemObjects.UTIL.showMessage("Incorrect recid in resolved change:\n" + RemObjects.UTIL.toJSON(processedDelta.data[j]));
+                                        };
+                                    };
+                                    break;
+                            };
+                        } else {
+                            RemObjects.UTIL.showMessage("Incorrect table name in result delta:\n" + RemObjects.UTIL.toJSON(processedDelta));
+                        };
+                    };
+
+
+                };
+
+                for (var j = 0; j < baseDelta.data.length; j++) {
+                    if (processedTable.findId(baseDelta.data[j].recid)) {
+                        processedTable.currentRow().state = RemObjects.DataAbstract.Enum.RowStates.rsUnchanged;
+                        processedTable.currentRow().__oldValues = [];
+                    };
+                    var deleted = processedTable.intFindDeleted(baseDelta.data[j].recid);
+                    if (deleted != null) {
+                        processedTable.deletedRows.splice(deleted, 1);
+                    };
+
+                };
+            };
+
+            if (onSuccess) onSuccess(__res);
+        }, onError);
+    };
+};
+
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.setStream = function setStream(aStream) {
+    this.fStream = aStream;
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.getStream = function getStream() {
+    return this.fStream;
+};
+
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.initializeRead = function () {
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.initializeWrite = function () {
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.finalizeWrite = function () {
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.readDataset = function readDataset(dataset) {
+    dataset.rows = [];
+    dataset.fNextRecId = 0;
+    var rows = null;
+    var datasets = RemObjects.UTIL.parseJSON(this.fStream).datasets;
+    for (var i = 0; i < datasets.length; i++) {
+        if (datasets[i].schema) {
+            dataset.fields.length = 0;
+            dataset.keyfields.length = 0;
+            for (var j = 0; j < datasets[i].schema.fields.length; j++) {
+                var schemaField = datasets[i].schema.fields[j];
+                var datasetField = new RemObjects.DataAbstract.Field();
+                datasetField.name = schemaField.Name;
+                datasetField.type = schemaField.DataType;
+                datasetField.logChanges = schemaField.LogChanges;
+
+                if (schemaField.InPrimaryKey == true) {
+                    datasetField.inPrimaryKey = true;
+                    dataset.keyfields.push(schemaField.Name);
+                };
+
+                dataset.fields.push(datasetField);
+            };
+        };
+        if (datasets[i].name.toUpperCase() == dataset.name.toUpperCase()) {
+            rows = datasets[i].data.rows;
+            break;
+        };
+    };
+
+    if (!rows) throw new Error("readDataset: dataset not found - " + dataset.name);
+
+    for (var i = 0; i < rows.length; i++) {
+        var r = dataset.intAppendRow();
+        r.state = RemObjects.DataAbstract.Enum.RowStates.rsUnchanged;
+        r.__newValues = rows[i];
+    };
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.writeDelta = function writeDelta(aDelta) {
+    this.fStream = RemObjects.UTIL.toJSON(aDelta);
+};
+
+RemObjects.DataAbstract.JSONDataStreamer.prototype.readDelta = function readDelta() {
+    var result = new RemObjects.DataAbstract.Deltas();
+    var remoteDelta = RemObjects.UTIL.parseJSON(this.fStream);
+
+    if (remoteDelta.deltas) {
+        for (var i = 0; i < remoteDelta.deltas.length; i++) {
+            result.deltas.push(remoteDelta.deltas[i]);
+        };
+    };
+
+    return result;
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.setStream = function setStream(aStream) {
+    this.fStream = aStream;
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.getStream = function getStream() {
+    return this.fStream;
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.initializeRead = function initializeRead() {
+    if (this.fStream.substr(0, 8) != "DABIN200")
+        throw new Error("Bin2DataStreamer.initializeRead: incorrect stream header");
+    this.fStreamPos = 8;
+    this.fStreamInfoPos = this.readInteger();
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.initializeWrite = function initializeWrite() {
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.finalizeWrite = function finalizeWrite() {
+    this.fStream = "DABIN200" + this.fParser.encodeInt(this.fStreamInfoPos + 12, 32, false) + this.fStream;
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readByte = function readByte() {
+    var result = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 1), 8, false);
+    this.fStreamPos += 1;
+    return result;
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readInteger = function readInteger() {
+    var result = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+    this.fStreamPos += 4;
+    return result;
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readAnsiStringWithLength = function readAnsiStringWithLength() {
+    var len = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+    this.fStreamPos += 4;
+    var result = this.fStream.substr(this.fStreamPos, len);
+    this.fStreamPos += len;
+    return result;
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readUtf8StringWithLength = function readUtf8StringWithLength() {
+    var len = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, true);
+    this.fStreamPos += 4;
+    if (len == -1) return null;
+    var result = RemObjects.UTIL.byteArrayToStr(this.fStream.substr(this.fStreamPos, len));
+    this.fStreamPos += len;
+    return result;
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.read = function read(aType) {
+    var value;
+    switch (aType) {
+        case "datFixedChar"://ok
+        case "datString"://ok
+        case "datMemo"://ok
+            var len = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = this.fStream.substr(this.fStreamPos, len);
+            this.fStreamPos += len;
+            break;
+
+        case "datBlob":
+        case "datCursor":
+            var len = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+
+            value = this.fStream.substr(this.fStreamPos, len);
+            this.fStreamPos += len;
+            break;
+
+        /*            value = "";
+                    for (var i = this.fStreamPos; i < this.fStreamPos + len; i++) {
+                        value += String.fromCharCode(this.fStream.charCodeAt(i) & 0xFF);
+                    };
+                    this.fStreamPos += len;
+                    break;
+        */
+        case "datWideString"://ok
+        case "datFixedWideChar"://ok
+        case "datWideMemo"://ok
+        case "datXml"://ok
+            var len = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = RemObjects.UTIL.byteArrayToUtf16(this.fStream.substr(this.fStreamPos, len));
+            this.fStreamPos += len;
+            break;
+
+
+        case "datAutoInc"://ok
+        case "datInteger"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, true);
+            this.fStreamPos += 4;
+            break;
+
+        case "datSmallInt"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 2), 16, true);
+            this.fStreamPos += 2;
+            break;
+
+        case "datDateTime"://ok
+            var utcValue = this.fParser.decodeFloat(this.fStream.substr(this.fStreamPos, 8), 52, 11);
+            utcValue = new Date(Math.round((utcValue - 25569.0) * 86400000));
+            value = new Date(utcValue.getUTCFullYear(), utcValue.getUTCMonth(), utcValue.getUTCDate(), utcValue.getUTCHours(), utcValue.getUTCMinutes(), utcValue.getUTCSeconds());
+            //value = this.fParser.decodeFloat(this.fStream.substr(this.fStreamPos, 8), 52, 11);
+            //value = new Date(Math.round((value - 25569.0) * 86400000));
+            this.fStreamPos += 8;
+            break;
+
+        case "datCurrency"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 6), 48, true) / 10000;
+            this.fStreamPos += 8;
+            break;
+        case "datFloat"://ok
+            value = this.fParser.decodeFloat(this.fStream.substr(this.fStreamPos, 8), 52, 11);
+            this.fStreamPos += 8;
+            break;
+
+        case "datSingleFloat": //ok
+            value = this.fParser.decodeFloat(this.fStream.substr(this.fStreamPos, 4), 23, 8);
+            this.fStreamPos += 4;
+            break;
+
+        case "datBoolean"://ok
+            value = !(this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 1), 8, false) == 0);
+            this.fStreamPos += 1;
+            break;
+
+        case "datByte"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 1), 8, false);
+            this.fStreamPos += 1;
+            break;
+
+        case "datLargeInt"://ok
+        case "datLargeAutoInc"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 6), 48, true);
+            this.fStreamPos += 8;
+            break;
+
+        case "datLargeUInt"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 6), 48, false);
+            this.fStreamPos += 8;
+            break;
+
+        case "datShortInt"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 1), 8, true);
+            this.fStreamPos += 1;
+            break;
+
+        case "datWord"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 2), 16, false);
+            this.fStreamPos += 2;
+            break;
+
+        case "datDecimal"://ok
+            var decimal = [];
+            for (var i = 0; i < 6; i++) {
+                decimal[i] = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 2), 16, false);
+                this.fStreamPos += 2;
+            };
+            decimal[6] = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = parseFloat(RemObjects.UTIL.decimalToString(decimal));
+            break;
+
+        case "datGuid"://ok
+            value = "{" + this.fStream.substr(this.fStreamPos, 36) + "}";
+            this.fStreamPos += 36;
+            break;
+
+        case "datCardinal"://ok
+            value = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            break;
+
+        /*
+            datBlob: BlobToStreamAsStr(Stream, Value);
+        * */
+
+        default:
+            throw new Error("Bin2DataStreamer.read: unknown type " + aType);
+    };
+    return value;
+};
+
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readParam = function readParam(aPropCount) {
+
+    var result = {};
+    for (var i = 0; i < aPropCount; i++) {
+        result[this.readAnsiStringWithLength()] = this.readUtf8StringWithLength();
+    };
+
+    return result;
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readField = function readField(aPropCount) {
+
+    function camelize(str) {
+        return str.substr(0, 1).toLowerCase() + str.substr(1);
+    };
+
+    var result = new RemObjects.DataAbstract.Field();
+
+    try {
+        for (var i = 0; i < aPropCount; i++) {
+            result[camelize(this.readAnsiStringWithLength())] = this.readUtf8StringWithLength();
+        };
+
+        result.type = result.dataType;
+        if (result.type == "datAutoInc" || result.type == "datLargeAutoInc") {
+            result.fAutoIncSequence = -1;
+        };
+        result.logChanges = (result.logChanges == "True");
+        return result;
+    } catch (e) {
+        throw new Error("Bin2DataStreamer.readField: " + e.message);
+    };
+};
+
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readDataset = function readDataset(aDataset) {
+    if (this.readByte() != 0) {//schema present
+        this.readInteger(); //skip schema end position
+        var fieldCount = this.readInteger();
+        var propCount = this.readInteger();
+        aDataset.fields = [];
+        aDataset.keyfields = [];
+        for (var i = 0; i < fieldCount; i++) {
+            var f = this.readField(propCount);
+            aDataset.fields.push(f);
+            if (f.inPrimaryKey.toLowerCase() == "true") {
+                aDataset.keyfields.push(f.name);
+            };
+        };
+
+        var paramCount = this.readInteger();
+        propCount = this.readInteger();
+        for (var i = 0; i < paramCount; i++) {
+            this.readParam(propCount);
+        };
+    } else {
+        this.readInteger(); //skip schema end position
+    };
+    var rowCount = this.readInteger();
+    var fieldCount = this.readInteger();
+    aDataset.rows = [];
+    aDataset.fNextRecId = 0;
+
+    for (var i = 0; i < fieldCount; i++) {
+        this.readAnsiStringWithLength(); //skip field name
+        this.readByte(); //skip field type
+        this.readInteger(); //skip field size
+    };
+
+    var bitMaskSize = Math.floor((fieldCount + 7) / 8);
+    for (var i = 0; i < rowCount; i++) {
+        var r = aDataset.intAppendRow();
+        r.state = RemObjects.DataAbstract.Enum.RowStates.rsUnchanged;
+
+        var bitMask = "";
+        for (var b = 0; b < bitMaskSize; b++) {
+            bitMask += RemObjects.UTIL.zeroPad(this.fParser.decodeInt(this.fStream.substr(this.fStreamPos + bitMaskSize - 1 - b, 1), 8, false).toString(2), 8);
+        };
+        var bitMaskArray = bitMask.split("").reverse();
+
+        this.fStreamPos += bitMaskSize;
+
+        for (var j = 0; j < fieldCount; j++) {
+            r.__newValues[j] = (bitMaskArray[j] == '1') ? null : this.read(aDataset.fields[j].type);
+        };
+    };
+
+
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.writeInteger = function writeInteger(aValue) {
+    this.write("datInteger", aValue);
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.writeByte = function writeByte(aValue) {
+    this.fStream += this.fParser.encodeInt(aValue, 8, true);
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.writeAnsiStringWithLength = function writeAnsiStringWithLength(aValue) {
+    this.fStream += this.fParser.encodeInt(aValue.length, 32, false) + aValue;
+};
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.write = function write(aType, aValue) {
+    switch (aType) {
+        case "datFixedChar"://ok
+        case "datString"://ok
+        case "datMemo"://ok
+        case "datBlob":
+        case "datCursor":
+            this.writeAnsiStringWithLength(aValue);
+            break;
+
+        case "datWideString"://ok
+        case "datFixedWideChar"://ok
+        case "datWideMemo"://ok
+        case "datXml"://ok
+            this.fStream += this.fParser.encodeInt(aValue.length * 2, 32, true);
+            this.fStream += RemObjects.UTIL.utf16ToByteArray(aValue);
+            break;
+
+        case "datAutoInc"://ok
+        case "datInteger"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 32, true);
+            break;
+
+        case "datSmallInt"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 16, true);
+            break;
+
+        case "datDateTime"://todo:test
+            if (!(aValue instanceof Date)) {
+                throw new Error("Not a Date value: " + aValue);
+            };
+            this.fStream += this.fParser.encodeFloat((aValue - aValue.getTimezoneOffset() * 60000) / 86400000 + 25569.0, 52, 11);
+            break;
+
+        case "datCurrency":
+            var cur = this.fParser.encodeInt(aValue * 10000, 48, true);
+            this.fStream += cur;
+            if ((cur.charCodeAt(cur.length - 1) == 0) || (cur.charCodeAt(cur.length - 1) == 0xFF)) {
+                this.fStream += cur.substr(cur.length - 1, 1) + cur.substr(cur.length - 1, 1);
+            };
+            break;
+
+        case "datFloat"://ok
+            this.fStream += this.fParser.encodeFloat(aValue, 52, 11);
+            break;
+
+        case "datSingleFloat": //ok
+            this.fStream += this.fParser.encodeFloat(aValue, 23, 8);
+            break;
+
+        case "datBoolean"://ok
+            this.fStream += this.fParser.encodeInt(aValue ? 1 : 0, 8, false);
+            break;
+
+        case "datByte"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 8, false);
+            break;
+
+        case "datLargeInt"://ok
+        case "datLargeAutoInc"://ok
+            var large = this.fParser.encodeInt(aValue, 48, true);
+            this.fStream += large;
+            if ((large.charCodeAt(large.length - 1) == 0) || (large.charCodeAt(large.length - 1) == 0xFF)) {
+                this.fStream += large.substr(large.length - 1, 1) + large.substr(large.length - 1, 1);
+            };
+            break;
+
+        case "datLargeUInt"://ok
+            var large = this.fParser.encodeInt(aValue, 48, false);
+            this.fStream += large;
+            if ((large.charCodeAt(large.length - 1) == 0) || (large.charCodeAt(large.length - 1) == 0xFF)) {
+                this.fStream += large.substr(large.length - 1, 1) + large.substr(large.length - 1, 1);
+            };
+            break;
+
+        case "datShortInt"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 8, true);
+            break;
+
+        case "datWord"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 16, false);
+            break;
+
+        case "datDecimal"://ok
+            var decimal = RemObjects.UTIL.stringToDecimal(aValue.toString());
+            for (var i = 0; i < 6; i++) {
+                this.fStream += this.fParser.encodeInt(decimal[i], 16, false);
+            };
+            this.fStream += this.fParser.encodeInt(decimal[6], 32, false);
+            break;
+
+        case "datGuid"://ok
+            this.fStream += aValue.substr(1, 36);
+            break;
+
+        case "datCardinal"://ok
+            this.fStream += this.fParser.encodeInt(aValue, 32, false);
+            break;
+
+        /*
+            datBlob: BlobToStreamAsStr(Stream, Value);
+        * */
+
+        default:
+            throw new Error("Bin2DataStreamer.write: unknown type " + aType);
+    };
+};
+
+
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.writeDelta = function writeDelta(aDeltas, aSendReducedDelta) {
+    function indexOf(anArray, aValue) {
+        for (var i = 0; i < anArray.length; i++) {
+            if (anArray[i] == aValue) return i;
+        };
+        return -1;
+    };
+
+    var offsets = [];
+    for (var d = 0; d < aDeltas.deltas.length; d++) {
+        offsets.push(this.fStream.length);
+        var aDelta = aDeltas.deltas[d];
+
+        this.writeInteger(aDelta.data.length); //changes count
+
+        this.writeInteger(aDelta.loggedfields.length); //fields count
+        for (var i = 0; i < aDelta.loggedfields.length; i++) {
+            this.writeAnsiStringWithLength(RemObjects.UTIL.strToByteArray(aDelta.loggedfields[i].name));
+            this.writeByte(indexOf(RemObjects.DataAbstract.DADataType, aDelta.loggedfields[i].type));
+        };
+
+        this.writeInteger(aDelta.keyfields.length); //key fields count
+        for (var i = 0; i < aDelta.keyfields.length; i++) {
+            this.writeAnsiStringWithLength(RemObjects.UTIL.strToByteArray(aDelta.keyfields[i]));
+        };
+
+        this.writeByte(aSendReducedDelta ? 1 : 0); //hasReducedDelta
+
+        this.writeInteger(aDelta.data.length); //changes count
+
+        var bitMaskSize = Math.floor((aDelta.loggedfields.length + 7) / 8);
+
+        var bitMaskOld;
+        var bitMaskNew;
+
+        for (var i = 0; i < aDelta.data.length; i++) {
+            bitMaskOld = new Array(bitMaskSize);
+            bitMaskNew = new Array(bitMaskSize);
+
+            for (var j = 0; j < aDelta.loggedfields.length; j++) {
+                bitMaskNew[j] = (aDelta.data[i]["new"][j] == null || typeof (aDelta.data[i]["new"][j]) == 'undefined') ? "0" : "1";
+                bitMaskOld[j] = (aDelta.data[i].old[j] == null || typeof (aDelta.data[i].old[j]) == 'undefined') ? "0" : "1";
+            };
+
+
+            this.writeInteger(indexOf(RemObjects.DataAbstract.Enum.ChangeTypeNames, aDelta.data[i].changetype));
+            this.writeInteger(aDelta.data[i].recid);
+            this.writeInteger(RemObjects.DataAbstract.Enum.ChangeStatus.csPending);
+            this.writeAnsiStringWithLength("");
+
+            //            this.fStream += this.fParser.encodeInt(parseInt(bitMaskOld.reverse().join(""), 2), bitMaskSize * 8, false);
+            //            bitMaskOld.reverse();
+            for (var b = 0; b < bitMaskSize; b++) {
+                this.fStream += this.fParser.encodeInt(parseInt(bitMaskOld.slice(b * 8, (b + 1) * 8).reverse().join(""), 2), 8, false);
+            };
+
+            for (var j = 0; j < aDelta.loggedfields.length; j++) {
+                if (bitMaskOld[j] == "1") {
+                    this.write(aDelta.loggedfields[j].type, aDelta.data[i].old[j]);
+                };
+            };
+
+            //            this.fStream += this.fParser.encodeInt(parseInt(bitMaskNew.reverse().join(""), 2), bitMaskSize * 8, false);
+            //            bitMaskNew.reverse();
+            for (var b = 0; b < bitMaskSize; b++) {
+                this.fStream += this.fParser.encodeInt(parseInt(bitMaskNew.slice(b * 8, (b + 1) * 8).reverse().join(""), 2), 8, false);
+            };
+
+            for (var j = 0; j < aDelta.loggedfields.length; j++) {
+                if (bitMaskNew[j] == "1") {
+                    this.write(aDelta.loggedfields[j].type, aDelta.data[i]["new"][j]);
+                };
+            };
+        };
+    };
+
+    this.fStreamInfoPos = this.fStream.length; //will be used in finalizeWrite
+
+    this.writeInteger(aDeltas.deltas.length); //delta count
+
+    for (var i = 0; i < aDeltas.deltas.length; i++) {
+        this.writeInteger(1); //etDelta
+        this.writeAnsiStringWithLength(RemObjects.UTIL.strToByteArray(aDeltas.deltas[i].name));
+        this.writeInteger(offsets[i] + 12); //first delta position
+    };
+
+};
+
+RemObjects.DataAbstract.Bin2DataStreamer.prototype.readDelta = function readDelta() {
+
+    var savedPos = this.fStreamPos;
+    this.fStreamPos = this.fStreamInfoPos;
+    var deltaCount = this.readInteger();
+    var result = new RemObjects.DataAbstract.Deltas();
+
+    for (var d = 0; d < deltaCount; d++) {
+        var delta = new RemObjects.DataAbstract.Delta();
+
+        var deltaType = this.readInteger();
+        delta.name = RemObjects.UTIL.byteArrayToStr(this.readAnsiStringWithLength());
+        var deltaPos = this.readInteger();
+
+        result.deltas.push(delta);
+
+    };
+
+    this.fStreamPos = savedPos;
+
+    for (var d = 0; d < deltaCount; d++) {
+
+        delta = result.deltas[d];
+
+        var changesCount = this.readInteger();
+        if (changesCount != 0) {
+            var fieldsCount = this.readInteger();
+
+            for (var i = 0; i < fieldsCount; i++) {
+                delta.loggedfields[i] = {};
+                delta.loggedfields[i].name = RemObjects.UTIL.byteArrayToStr(this.readAnsiStringWithLength());
+                delta.loggedfields[i].type = RemObjects.DataAbstract.DADataType[this.readByte()];
+            };
+
+            var keyFieldsCount = this.readInteger();
+            for (var i = 0; i < keyFieldsCount; i++) {
+                delta.keyfields[i] = RemObjects.UTIL.byteArrayToStr(this.readAnsiStringWithLength());
+            };
+
+
+            if (changesCount != 0) {
+
+                this.readByte(); //reduced?
+                changesCount = this.readInteger();
+
+                var bitMaskSize = Math.floor((fieldsCount + 7) / 8);
+
+                //                var bitMaskOld;
+                //                var bitMaskNew;
+
+                for (var i = 0; i < changesCount; i++) { //read changes
+
+                    var change = new RemObjects.DataAbstract.Change();
+                    change.changetype = RemObjects.DataAbstract.Enum.ChangeTypeNames[this.readInteger()];
+                    change.recid = this.readInteger();
+                    change.status = RemObjects.DataAbstract.Enum.ChangeStatusNames[this.readInteger()];
+                    change.message = RemObjects.UTIL.byteArrayToStr(this.readAnsiStringWithLength());
+
+                    //                    bitMaskOld = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, bitMaskSize), bitMaskSize * 8);
+                    //                    this.fStreamPos += bitMaskSize;
+                    //                    bitMaskOld = bitMaskOld.toString(2).split("").reverse();
+
+                    var bitMaskOld = "";
+                    for (var b = 0; b < bitMaskSize; b++) {
+                        bitMaskOld += RemObjects.UTIL.zeroPad(this.fParser.decodeInt(this.fStream.substr(this.fStreamPos + bitMaskSize - 1 - b, 1), 8, false).toString(2), 8);
+                    };
+                    var bitMaskOld = bitMaskOld.split("").reverse();
+                    this.fStreamPos += bitMaskSize;
+
+                    for (var j = 0; j < fieldsCount; j++) {
+                        change.old[j] = (bitMaskOld[j] == "1") ? this.read(delta.loggedfields[j].type) : null;
+                    };
+
+                    //                    bitMaskNew = this.fParser.decodeInt(this.fStream.substr(this.fStreamPos, bitMaskSize), bitMaskSize * 8);
+                    //                    this.fStreamPos += bitMaskSize;
+                    //                    bitMaskNew = bitMaskNew.toString(2).split("").reverse();
+
+                    var bitMaskNew = "";
+                    for (var b = 0; b < bitMaskSize; b++) {
+                        bitMaskNew += RemObjects.UTIL.zeroPad(this.fParser.decodeInt(this.fStream.substr(this.fStreamPos + bitMaskSize - 1 - b, 1), 8, false).toString(2), 8);
+                    };
+                    var bitMaskNew = bitMaskNew.split("").reverse();
+                    this.fStreamPos += bitMaskSize;
+
+                    for (var j = 0; j < fieldsCount; j++) {
+                        change["new"][j] = (bitMaskNew[j] == "1") ? this.read(delta.loggedfields[j].type) : null;
+                    };
+
+                    delta.data.push(change);
+                };
+
+            };
+        };
+    }
+    return result;
+};
+
+RemObjects.DataAbstract.DynamicWhere.prototype.toXML = function toXML() {
+    if (!this.fExpression) throw new Error("Dynamic Where: fExpression is null");
+    return '<query xmlns="http://www.remobjects.com/schemas/dataabstract/queries/5.0" version="5.0"><where>' +
+        this.fExpression.toXML() + '</where></query>';
+};
+
+RemObjects.DataAbstract.ConstantExpression.prototype.toXML = function toXML() {
+    return '<constant type="' + this.fType + '" null="' + this.fNull + '">' + this.fValue + '</constant>';
+};
+
+RemObjects.DataAbstract.NullExpression.prototype.toXML = function toXML() {
+    return '<null />';
+};
+
+RemObjects.DataAbstract.FieldExpression.prototype.toXML = function toXML() {
+    return '<field>' + this.fName + '</field>';
+};
+
+RemObjects.DataAbstract.MacroExpression.prototype.toXML = function toXML() {
+    return '<macro>' + this.fName + '</macro>';
+};
+
+RemObjects.DataAbstract.ListExpression.prototype.toXML = function toXML() {
+    var result = "<list>";
+    for (var i = 0; i < this.fItems.length; i++) {
+        result += this.fItems[i].toXML();
+    };
+    result += "</list>";
+    return result;
+};
+
+
+RemObjects.DataAbstract.UnaryExpression.prototype.toXML = function toXML() {
+    if (!this.fNode) throw new Error("UnaryExpression: fNode is null");
+    return '<unaryoperation' + (this.fOperator ? ' operator="' + this.fOperator + '"' : '') + '>'
+        + this.fNode.toXML() + '</unaryoperation>';
+};
+
+RemObjects.DataAbstract.BinaryExpression.prototype.toXML = function toXML() {
+    if (!(this.fNode1 || this.fNode2)) throw new Error("BinaryExpression: fNode1 or fNode2 is null");
+    return '<binaryoperation operator="' + this.fOperator + '">'
+        + this.fNode1.toXML() + this.fNode2.toXML() + '</binaryoperation>';
+};
+
+RemObjects.DataAbstract.BetweenExpression.prototype.toXML = function toXML() {
+    if (!(this.fNode1 || this.fNode2 || this.fNode3)) throw new Error("BetweenExpression: fNode1 or fNode2 or fNode3 is null");
+    return '<between>'
+        + this.fNode1.toXML() + this.fNode2.toXML() + this.fNode3.toXML() + '</between>';
+};
+
+
+RemObjects.DataAbstract.ParameterExpression.prototype.toXML = function toXML() {
+    return '<parameter type="' + this.fType + '"' + (this.fSize ? ' size="' + this.fSize + '"' : '') + '>'
+        + this.fName + '</parameter>';
+};
+
+RemObjects.DataAbstract.Views.HtmlTableView = function HtmlTAbleView(aTable, aHtmlTableId) {
+    var htmlTable = document.getElementById(aHtmlTableId);
+    var tRow = "";
+    for (var i = 0; i < aTable.fields.length; i++) {
+        tRow += "<td>" + aTable.fields[i].name + "</td>";
+    };
+    tRow = '<tr class="da_htmlTableHeader">' + tRow + "</tr>";
+
+    var r = aTable.first();
+    while (r) {
+        tRow += '<tr class = "da_htmlTableLine">';
+        for (var i = 0; i < aTable.fields.length; i++) {
+            tRow += "<td>" + (aTable.fields[i].type == "datBlob" ? "(Blob)" : r.__newValues[i]) + "</td>";
+        };
+        tRow += "</tr>";
+        r = aTable.next();
+    };
+
+    htmlTable.innerHTML = "<tbody>" + tRow + "</tbody>";
+};
+
+RemObjects.DataAbstract.Views.VerticalHtmlTableView = function VerticalHtmlTableView(aTable, aHtmlTableId) {
+    var htmlTable = document.getElementById(aHtmlTableId);
+    var tRow = "<td></td><td></td>";
+    var r = aTable.currentRow();
+    for (var i = 0; i < aTable.fields.length; i++) {
+        tRow += "<tr>";
+        tRow += '<td class="da_HtmlTableHeader">' + aTable.fields[i].name + "</td>";
+        tRow += "<td>" + (aTable.fields[i].type == "datBlob" ? "(Blob)" : r.__newValues[i]) + "</td>";
+        tRow += "</tr>";
+    };
+    htmlTable.innerHTML = "<tbody>" + tRow + "</tbody>";
+};

+ 665 - 0
demo/dataabstract/DataAbstract4_intf.js

@@ -0,0 +1,665 @@
+//----------------------------------------------------------------------------//
+// This unit was automatically generated by the RemObjects SDK after reading  //
+// the RODL file associated with this project .                               //
+//                                                                            //
+// Do not modify this unit manually, or your changes will be lost when this   //
+// unit is regenerated the next time you compile the project.                 //
+//----------------------------------------------------------------------------//
+
+
+/* This codegen depends on RemObjectsSDK.js
+* Usage:
+* var Channel = new RemObjects.SDK.HTTPClientChannel("http://localhost:8099/JSON");
+* var Message = new RemObjects.SDK.JSONMessage();
+* var Service = new NewService(Channel, Message);
+* Service.Sum(1, 2,
+*             function(result) {
+*                 alert(result);
+*             },
+*             function(msg) {alert(msg.getErrorMessage())}
+* );
+*
+*/
+
+__namespace = this;
+if ("RemObjects.DataAbstract.Server" != "") {
+	var parts = "RemObjects.DataAbstract.Server".split(".");
+	var current = this;
+    for (var i = 0; i < parts.length; i++) {
+		current[parts[i]] = current[parts[i]] || {};
+		current = current[parts[i]];
+    };
+	__namespace = current;
+};
+
+// Enum: ColumnSortDirection
+__namespace.ColumnSortDirection = function ColumnSortDirection() {
+  this.value = null;
+};
+__namespace.ColumnSortDirection.prototype = new RemObjects.SDK.ROEnumType();
+__namespace.ColumnSortDirection.prototype.enumValues = [
+    "Ascending",
+    "Descending"
+	];
+__namespace.ColumnSortDirection.prototype.constructor = __namespace.ColumnSortDirection;
+RemObjects.SDK.RTTI["ColumnSortDirection"] = __namespace.ColumnSortDirection;
+
+// Enum: ScriptExceptionType
+__namespace.ScriptExceptionType = function ScriptExceptionType() {
+  this.value = null;
+};
+__namespace.ScriptExceptionType.prototype = new RemObjects.SDK.ROEnumType();
+__namespace.ScriptExceptionType.prototype.enumValues = [
+    "ParserError",
+    "RuntimeError",
+    "Fail",
+    "UnexpectedException"
+	];
+__namespace.ScriptExceptionType.prototype.constructor = __namespace.ScriptExceptionType;
+RemObjects.SDK.RTTI["ScriptExceptionType"] = __namespace.ScriptExceptionType;
+
+
+// Struct: DataParameter
+__namespace.DataParameter = function DataParameter() {
+    this.Name = {dataType : "Utf8String", value : null};
+    this.Value = {dataType : "Variant", value : null};
+};
+__namespace.DataParameter.prototype = new RemObjects.SDK.ROStructType();
+__namespace.DataParameter.prototype.constructor = __namespace.DataParameter;
+RemObjects.SDK.RTTI["DataParameter"] = __namespace.DataParameter;
+
+// Struct: TableRequestInfo
+__namespace.TableRequestInfo = function TableRequestInfo() {
+    this.IncludeSchema = {dataType : "Boolean", value : null};
+    this.MaxRecords = {dataType : "Integer", value : null};
+    this.Parameters = {dataType : "DataParameterArray", value : null};
+    this.UserFilter = {dataType : "Utf8String", value : null};
+};
+__namespace.TableRequestInfo.prototype = new RemObjects.SDK.ROStructType();
+__namespace.TableRequestInfo.prototype.constructor = __namespace.TableRequestInfo;
+RemObjects.SDK.RTTI["TableRequestInfo"] = __namespace.TableRequestInfo;
+
+// Struct: TableRequestInfoV6
+__namespace.TableRequestInfoV6 = function TableRequestInfoV6() {
+    this.IncludeSchema = {dataType : "Boolean", value : null};
+    this.MaxRecords = {dataType : "Integer", value : null};
+    this.Parameters = {dataType : "DataParameterArray", value : null};
+    this.Sql = {dataType : "WideString", value : null};
+    this.UserFilter = {dataType : "Utf8String", value : null};
+};
+__namespace.TableRequestInfoV6.prototype = new RemObjects.SDK.ROStructType();
+__namespace.TableRequestInfoV6.prototype.constructor = __namespace.TableRequestInfoV6;
+RemObjects.SDK.RTTI["TableRequestInfoV6"] = __namespace.TableRequestInfoV6;
+
+// Struct: TableRequestInfoV5
+__namespace.TableRequestInfoV5 = function TableRequestInfoV5() {
+    this.DynamicSelectFieldNames = {dataType : "StringArray", value : null};
+    this.IncludeSchema = {dataType : "Boolean", value : null};
+    this.MaxRecords = {dataType : "Integer", value : null};
+    this.Parameters = {dataType : "DataParameterArray", value : null};
+    this.Sorting = {dataType : "ColumnSorting", value : null};
+    this.UserFilter = {dataType : "Utf8String", value : null};
+    this.WhereClause = {dataType : "Xml", value : null};
+};
+__namespace.TableRequestInfoV5.prototype = new RemObjects.SDK.ROStructType();
+__namespace.TableRequestInfoV5.prototype.constructor = __namespace.TableRequestInfoV5;
+RemObjects.SDK.RTTI["TableRequestInfoV5"] = __namespace.TableRequestInfoV5;
+
+// Struct: UserInfo
+__namespace.UserInfo = function UserInfo() {
+    this.Attributes = {dataType : "VariantArray", value : null};
+    this.Privileges = {dataType : "StringArray", value : null};
+    this.SessionID = {dataType : "Utf8String", value : null};
+    this.UserData = {dataType : "Binary", value : null};
+    this.UserID = {dataType : "Utf8String", value : null};
+};
+__namespace.UserInfo.prototype = new RemObjects.SDK.ROStructType();
+__namespace.UserInfo.prototype.constructor = __namespace.UserInfo;
+RemObjects.SDK.RTTI["UserInfo"] = __namespace.UserInfo;
+
+// Struct: ColumnSorting
+__namespace.ColumnSorting = function ColumnSorting() {
+    this.FieldName = {dataType : "Utf8String", value : null};
+    this.SortDirection = {dataType : "ColumnSortDirection", value : null};
+};
+__namespace.ColumnSorting.prototype = new RemObjects.SDK.ROStructType();
+__namespace.ColumnSorting.prototype.constructor = __namespace.ColumnSorting;
+RemObjects.SDK.RTTI["ColumnSorting"] = __namespace.ColumnSorting;
+
+
+// Array: ColumnSortingArray
+__namespace.ColumnSortingArray = function ColumnSortingArray() {
+  RemObjects.SDK.ROArrayType.call(this);
+  this.elementType = "ColumnSorting";
+};
+__namespace.ColumnSortingArray.prototype = new RemObjects.SDK.ROArrayType();
+__namespace.ColumnSortingArray.prototype.constructor = __namespace.ColumnSortingArray;
+RemObjects.SDK.RTTI["ColumnSortingArray"] = __namespace.ColumnSortingArray;
+
+// Array: DataParameterArray
+__namespace.DataParameterArray = function DataParameterArray() {
+  RemObjects.SDK.ROArrayType.call(this);
+  this.elementType = "DataParameter";
+};
+__namespace.DataParameterArray.prototype = new RemObjects.SDK.ROArrayType();
+__namespace.DataParameterArray.prototype.constructor = __namespace.DataParameterArray;
+RemObjects.SDK.RTTI["DataParameterArray"] = __namespace.DataParameterArray;
+
+// Array: StringArray
+__namespace.StringArray = function StringArray() {
+  RemObjects.SDK.ROArrayType.call(this);
+  this.elementType = "Utf8String";
+};
+__namespace.StringArray.prototype = new RemObjects.SDK.ROArrayType();
+__namespace.StringArray.prototype.constructor = __namespace.StringArray;
+RemObjects.SDK.RTTI["StringArray"] = __namespace.StringArray;
+
+// Array: TableRequestInfoArray
+__namespace.TableRequestInfoArray = function TableRequestInfoArray() {
+  RemObjects.SDK.ROArrayType.call(this);
+  this.elementType = "TableRequestInfo";
+};
+__namespace.TableRequestInfoArray.prototype = new RemObjects.SDK.ROArrayType();
+__namespace.TableRequestInfoArray.prototype.constructor = __namespace.TableRequestInfoArray;
+RemObjects.SDK.RTTI["TableRequestInfoArray"] = __namespace.TableRequestInfoArray;
+
+// Array: VariantArray
+__namespace.VariantArray = function VariantArray() {
+  RemObjects.SDK.ROArrayType.call(this);
+  this.elementType = "Variant";
+};
+__namespace.VariantArray.prototype = new RemObjects.SDK.ROArrayType();
+__namespace.VariantArray.prototype.constructor = __namespace.VariantArray;
+RemObjects.SDK.RTTI["VariantArray"] = __namespace.VariantArray;
+
+
+// Exception: ScriptException
+__namespace.ScriptException = function ScriptException(e) {
+    RemObjects.SDK.ROException.call(this, e);
+    this.fields.Line  = {dataType : "Integer", value : null};
+    this.fields.Column  = {dataType : "Integer", value : null};
+    this.fields.Event  = {dataType : "Utf8String", value : null};
+    this.fields.InnerStackTrace  = {dataType : "Utf8String", value : null};
+    this.fields.Type  = {dataType : "ScriptExceptionType", value : null};
+};
+__namespace.ScriptException.prototype = new RemObjects.SDK.ROException();
+RemObjects.SDK.RTTI["ScriptException"] = __namespace.ScriptException;
+
+
+
+// Service: DataAbstractService
+__namespace.DataAbstractService = function DataAbstractService(__channel, __message, __service_name) {
+  RemObjects.SDK.ROService.call(this, __channel, __message, __service_name);
+  this.fServiceName = this.fServiceName || __service_name || "DataAbstractService";
+};
+
+
+__namespace.DataAbstractService.prototype.GetSchema = function(
+	aFilter,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetSchema");
+        msg.write("aFilter", "Utf8String", aFilter);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Utf8String");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.GetData = function(
+	aTableNameArray,
+	aTableRequestInfoArray,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetData");
+        msg.write("aTableNameArray", "StringArray", aTableNameArray);
+        msg.write("aTableRequestInfoArray", "TableRequestInfoArray", aTableRequestInfoArray);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Binary");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.UpdateData = function(
+	aDelta,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "UpdateData");
+        msg.write("aDelta", "Binary", aDelta);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Binary");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.ExecuteCommand = function(
+	aCommandName,
+	aParameterArray,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "ExecuteCommand");
+        msg.write("aCommandName", "Utf8String", aCommandName);
+        msg.write("aParameterArray", "DataParameterArray", aParameterArray);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Integer");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.ExecuteCommandEx = function(
+	aCommandName,
+	aInputParameters,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "ExecuteCommandEx");
+        msg.write("aCommandName", "Utf8String", aCommandName);
+        msg.write("aInputParameters", "DataParameterArray", aInputParameters);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Integer");
+		var __aOutputParameters = __message.read("aOutputParameters", "DataParameterArray");
+	        __success(
+		__result
+		,
+		__aOutputParameters
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.GetTableSchema = function(
+	aTableNameArray,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetTableSchema");
+        msg.write("aTableNameArray", "StringArray", aTableNameArray);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Utf8String");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.GetCommandSchema = function(
+	aCommandNameArray,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetCommandSchema");
+        msg.write("aCommandNameArray", "StringArray", aCommandNameArray);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Utf8String");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.SQLGetData = function(
+	aSQLText,
+	aIncludeSchema,
+	aMaxRecords,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "SQLGetData");
+        msg.write("aSQLText", "Utf8String", aSQLText);
+        msg.write("aIncludeSchema", "Boolean", aIncludeSchema);
+        msg.write("aMaxRecords", "Integer", aMaxRecords);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Binary");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.SQLGetDataEx = function(
+	aSQLText,
+	aIncludeSchema,
+	aMaxRecords,
+	aDynamicWhereXML,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "SQLGetDataEx");
+        msg.write("aSQLText", "Utf8String", aSQLText);
+        msg.write("aIncludeSchema", "Boolean", aIncludeSchema);
+        msg.write("aMaxRecords", "Integer", aMaxRecords);
+        msg.write("aDynamicWhereXML", "WideString", aDynamicWhereXML);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Binary");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.SQLExecuteCommand = function(
+	aSQLText,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "SQLExecuteCommand");
+        msg.write("aSQLText", "Utf8String", aSQLText);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Integer");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.SQLExecuteCommandEx = function(
+	aSQLText,
+	aDynamicWhereXML,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "SQLExecuteCommandEx");
+        msg.write("aSQLText", "Utf8String", aSQLText);
+        msg.write("aDynamicWhereXML", "WideString", aDynamicWhereXML);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Integer");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.GetDatasetScripts = function(
+	DatasetNames,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetDatasetScripts");
+        msg.write("DatasetNames", "Utf8String", DatasetNames);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Utf8String");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.RegisterForDataChangeNotification = function(
+	aTableName,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "RegisterForDataChangeNotification");
+        msg.write("aTableName", "Utf8String", aTableName);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+	        __success(
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.DataAbstractService.prototype.UnregisterForDataChangeNotification = function(
+	aTableName,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "UnregisterForDataChangeNotification");
+        msg.write("aTableName", "Utf8String", aTableName);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+	        __success(
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+
+// Service: BaseLoginService
+__namespace.BaseLoginService = function BaseLoginService(__channel, __message, __service_name) {
+  RemObjects.SDK.ROService.call(this, __channel, __message, __service_name);
+  this.fServiceName = this.fServiceName || __service_name || "BaseLoginService";
+};
+
+
+__namespace.BaseLoginService.prototype.LoginEx = function(
+	aLoginString,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "LoginEx");
+        msg.write("aLoginString", "Utf8String", aLoginString);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Boolean");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.BaseLoginService.prototype.Logout = function(
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "Logout");
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+	        __success(
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+
+// Service: MultiDbLoginService
+__namespace.MultiDbLoginService = function MultiDbLoginService(__channel, __message, __service_name) {
+  RemObjects.SDK.ROService.call(this, __channel, __message, __service_name);
+  this.fServiceName = this.fServiceName || __service_name || "MultiDbLoginService";
+};
+
+__namespace.MultiDbLoginService.prototype = new __namespace.BaseLoginService();
+
+__namespace.MultiDbLoginService.prototype.Login = function(
+	aUserID,
+	aPassword,
+	aConnectionName,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "Login");
+        msg.write("aUserID", "Utf8String", aUserID);
+        msg.write("aPassword", "Utf8String", aPassword);
+        msg.write("aConnectionName", "Utf8String", aConnectionName);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Boolean");
+		var __aUserInfo = __message.read("aUserInfo", "UserInfo");
+	        __success(
+		__result
+		,
+		__aUserInfo
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+
+// Service: MultiDbLoginServiceV5
+__namespace.MultiDbLoginServiceV5 = function MultiDbLoginServiceV5(__channel, __message, __service_name) {
+  RemObjects.SDK.ROService.call(this, __channel, __message, __service_name);
+  this.fServiceName = this.fServiceName || __service_name || "MultiDbLoginServiceV5";
+};
+
+__namespace.MultiDbLoginServiceV5.prototype = new __namespace.MultiDbLoginService();
+
+__namespace.MultiDbLoginServiceV5.prototype.GetConnectionNames = function(
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetConnectionNames");
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "StringArray");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+__namespace.MultiDbLoginServiceV5.prototype.GetDefaultConnectionName = function(
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "GetDefaultConnectionName");
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Utf8String");
+	        __success(
+		__result
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+
+// Service: SimpleLoginService
+__namespace.SimpleLoginService = function SimpleLoginService(__channel, __message, __service_name) {
+  RemObjects.SDK.ROService.call(this, __channel, __message, __service_name);
+  this.fServiceName = this.fServiceName || __service_name || "SimpleLoginService";
+};
+
+__namespace.SimpleLoginService.prototype = new __namespace.BaseLoginService();
+
+__namespace.SimpleLoginService.prototype.Login = function(
+	aUserID,
+	aPassword,
+	__success, __error) {
+    try {
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "Login");
+        msg.write("aUserID", "Utf8String", aUserID);
+        msg.write("aPassword", "Utf8String", aPassword);
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+		var __result = __message.read("Result", "Boolean");
+		var __aUserInfo = __message.read("aUserInfo", "UserInfo");
+	        __success(
+		__result
+		,
+		__aUserInfo
+		);
+        }, __error);
+
+    } catch (e) {
+        __error(msg, e);
+    };
+};
+
+
+
+// Event sink: DataChangeNotification
+__namespace.DataChangeNotification = function DataChangeNotification() {
+	this.OnDataTableChanged = {
+		aTableName : {dataType : "Utf8String", value : null},
+		aDelta : {dataType : "Binary", value : null}
+	};
+};
+__namespace.DataChangeNotification.prototype = new RemObjects.SDK.ROEventSink();
+__namespace.DataChangeNotification.prototype.constructor = __namespace.DataChangeNotification;
+RemObjects.SDK.RTTI["DataChangeNotification"] = __namespace.DataChangeNotification;
+

+ 2012 - 0
demo/dataabstract/RemObjectsSDK.js

@@ -0,0 +1,2012 @@
+//RemObjects SDK classes
+//interface
+
+
+var RemObjects = {};
+
+
+
+RemObjects.SDK = {
+    RTTI : {
+    },
+
+    Enum : {},
+
+    ROComplexType : function ROComplexType() {
+    },
+
+    ROEnumType : function ROEnumType() {
+    },
+
+    ROStructType : function ROStructType() {
+    },
+
+    ROArrayType : function ROArrayType() {
+        this.elementType = "";
+        this.items = [];
+    },
+
+    ROException : function ROException(e) {
+        if (e) {
+            this.name = e.name;
+            this.message = e.message;
+        };
+        this.fields = new RemObjects.SDK.ROStructType();
+    },
+
+    ROEventSink : function ROEventSink() {
+    },
+    
+    ClientChannel : function ClientChannel(aUrl) {
+        this.url = aUrl;
+        //post
+    },
+
+    HTTPClientChannel : function HTTPClientChannel(aUrl) {
+        RemObjects.SDK.ClientChannel.call(this, aUrl);
+    },
+
+
+    Message : function Message() {
+        this.fClientID = RemObjects.UTIL.NewGuid();
+        this.fRequestObject = {};
+        this.fResponseObject = {};
+        //clone
+        //getClientID
+        //setClientID
+    },
+
+    JSONMessage : function JSONMessage() {
+        RemObjects.SDK.Message.call(this);
+        //initialize
+        //finalize
+        //write
+        //read
+        //requestStream
+        //setResponseStream
+
+    },
+
+    BinMessage : function BinMessage() {
+        RemObjects.SDK.Message.call(this);
+        //initialize
+        //finalize
+        //write
+        //read
+        //requestStream
+        //setResponseStream
+    },
+
+    BinHeader : function BinHeader() {
+
+
+        this.fHeader = [0x52, 0x4f, 0x31, 0x30, 0x37];  //should contain 0x1c bytes
+        for (var i = 5; i<0x1c; this.fHeader[i++] = 0);
+        //readFrom
+        //asStream
+        //isValidHeader
+        //getCompressed
+        //setCompressed
+        //getMessageType
+        //setMessageType
+        //setClientID
+
+    // Header BINARY LAYOUT: 0x1C bytes
+    //
+    // Keep in sync with
+    //  - Delphi - uROBINMessage.pas
+    //  - C#     - BinMessage.cs
+    //
+    // 52 4f 31 30  = "RO10" basic RO signature for RO 1.0
+    // XX YY ZZ --  = XX: subversion (currenly "7")
+    //		 YY: option flags: 01 = compressed
+    //		 ZZ: message type as defined in uROClientIntf
+    //     --: reserved for future use
+    // -- -- UU UU  = UU: user data (word)
+    // CC CC CC CC    0x10 bytes ClientID (guid)
+    // CC CC CC CC
+    // CC CC CC CC
+    // CC CC CC CC
+
+
+    },
+
+
+    RemoteService : function RemoteService(aChannel, aMessage, aServiceName) {
+        this.fChannel = aChannel;
+        this.fMessage = aMessage;
+        this.fServiceName = aServiceName;
+    },
+
+
+    ROService : function ROService(aChannel, aMessage, aServiceName) {
+        if (RemObjects.UTIL.checkArgumentTypes(arguments, [RemObjects.SDK.ClientChannel, RemObjects.SDK.Message, "string"]) ||
+            RemObjects.UTIL.checkArgumentTypes(arguments, [RemObjects.SDK.ClientChannel, RemObjects.SDK.Message, "undefined"])) {
+            this.fChannel = aChannel;
+            this.fMessage = aMessage;
+            this.fServiceName = aServiceName;
+        } else if (RemObjects.UTIL.checkArgumentTypes(arguments, [RemObjects.SDK.RemoteService, "undefined", "undefined"])) {
+                this.fChannel = aChannel.fChannel;
+                this.fMessage = aChannel.fMessage;
+                this.fServiceName = aChannel.fServiceName;
+        } else if (RemObjects.UTIL.checkArgumentTypes(arguments, ["string"]))  { //URL
+            var m = /https?:\/\/([-\w\.]+)+(:\d+)?\/([\w/_\.]*)/i.exec(aChannel);
+            var path;
+            if (m && m.length == 4 ) {
+                path = m[3];
+            } else {
+                m = /https?:\/\/\[((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\](:\d+)\/([\w/_\.]*)$/i.exec(aChannel);
+                if (!m) {
+                    throw new Error("ROService constructor: incorrect URL");
+                } else {
+                    path = m[14];
+                };
+            };
+            if (path.toLowerCase() == "json") {
+                this.fMessage = new RemObjects.SDK.JSONMessage();
+            } else {
+                this.fMessage = new RemObjects.SDK.BinMessage();
+            };
+            this.fChannel = new RemObjects.SDK.HTTPClientChannel(aChannel);
+            if (typeof(aMessage) == "string") this.fServiceName = aMessage;
+        } else if (!RemObjects.UTIL.checkArgumentTypes(arguments, ["undefined", "undefined", "undefined"])) {
+            throw new Error("ROService constructor: Incorrect arguments");
+        };
+        //getChannel
+        //getMessage
+        //getServiceName
+    },
+
+    EventReceiver : function EventReceiver(aChannel, aMessage, aServiceName, aTimeout) {
+        this.fChannel = aChannel;
+        this.fMessage = aMessage;
+        this.fServiceName = aServiceName;
+        this.fTimeout = aTimeout;
+        this.fActive = false;
+        this.fHandlers = {};
+        this.fInterval = null;
+        //addHandler
+        //intPollServer
+        //setActive
+        //getActive
+        //getTimeout
+        //setTimeout
+    }
+
+};
+
+RemObjects.UTIL = {
+
+    testBrowser : function testBrowser() {
+        var result = "";
+        if (typeof(JSON) == 'undefined')
+            result += "Browser doesn't support JSON\n";
+
+        var AJAX;
+        if (typeof(XMLHttpRequest) == 'undefined') {
+            try {
+                AJAX = new XMLHttpRequest();
+            } catch (e) {
+                try {
+                    AJAX = new ActiveXObject("Msxml2.XMLHTTP");
+                } catch (e) {
+                    try {
+                        AJAX = new ActiveXObject("Microsoft.XMLHTTP");
+                    } catch (e) {
+                        result += "Browser doesn't support XMLHttpRequest object\n";
+                    };
+                };
+            };
+        };
+
+        result += RemObjects.UTIL.testBrowserBinary();
+        return result;
+    },
+
+    testBrowserBinary : function testBrowserBinary() {
+        var result = "";
+        if (!(((typeof(XMLHttpRequest) != 'undefined') && (typeof(XMLHttpRequest.prototype.sendAsBinary) != 'undefined')) || (typeof(Uint8Array) != 'undefined') ))
+            result += "Browser doesn't support sending binary data\n";
+        return result;
+    },
+
+    browserHasBinarySupport : function browserHasBinarySupport() {
+        return RemObjects.UTIL.testBrowserBinary() == "";
+    },
+
+    showMessage : function(msg) {
+        //for non-browser environments:
+        //replace alert() call with something appropriate
+        alert(msg);
+    },
+
+    showError : function showError(msg, e) {
+        var result = "";
+        if (e) {
+            result += e.name + ": " + e.message;
+        } else {
+            result += msg.getErrorMessage() + "\n";
+        };
+        result += "\nCall stack:\n";
+        var fn = showError;
+      //  if (!(fn.caller)) //possibly IE
+//            fn = arguments.callee;
+//        while((fn = fn.caller) !== null) {
+//            var fnName = fn.toString().match(/^function\s*(\w+)\(/);
+//            fnName = (fnName) ? fnName[1] : 'anonymous function';
+//            result += fnName;
+//            result += "\n";
+  //      }
+        RemObjects.UTIL.showMessage(result);
+    },
+
+    toJSON : function toJSON(aValue) {
+        if(typeof(JSON) != 'undefined') {
+            var jsonString = JSON.stringify(aValue);
+            jsonString = jsonString.replace(/[\u007F-\uFFFF]/g, function(chr) {
+                return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4)
+            });
+            return jsonString;
+        } else {
+            throw new Error("Your browser doesn't support JSON.stringify");
+        };
+    },
+
+    parseJSON : function parseJSON(aValue) {
+        if(typeof(JSON) != 'undefined') {
+            return JSON.parse(aValue);
+        } else {
+            throw new Error("Your browser doesn't support JSON.parse");
+        };
+    },
+
+    NewGuid : function NewGuid() {
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
+        function(c) {
+        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+        return v.toString(16); })
+    },
+
+    GuidToArray : function GuidToArray(aGuid) {
+        var result = [];
+        aGuid = aGuid.replace(/-/g, "");
+        for (var i = 3; i >= 0; result.push(parseInt(aGuid.substr(i-- * 2, 2), 16)));
+        for (var i = 5; i >= 4; result.push(parseInt(aGuid.substr(i-- * 2, 2), 16)));
+        for (var i = 7; i >= 6; result.push(parseInt(aGuid.substr(i-- * 2, 2), 16)));
+        for (var i = 8; i < 16; result.push(parseInt(aGuid.substr(i++ * 2, 2), 16)));
+//        for (var i = 0; i < 16; result.push(parseInt(aGuid.substr(i++ * 2, 2), 16)));
+        return result;
+    },
+
+    guidToByteArray : function guidToByteArray(aGuid) {
+        function readPart(str, start, end) {
+            var result = "";
+            for (var i = start; i <= end; result += String.fromCharCode(parseInt(aGuid.substr(i++ * 2 + 1, 2), 16)));
+            return result;
+        };
+
+        function readPartReversed(str, start, end) {
+            var result = "";
+            for (var i = end; i >= start; result += String.fromCharCode(parseInt(aGuid.substr(i-- * 2 + 1, 2), 16)));
+            return result;
+        };
+        aGuid = aGuid.replace(/-/g, "");
+        return readPartReversed(aGuid, 0, 3) + readPartReversed(aGuid, 4, 5)
+               + readPartReversed(aGuid, 6, 7) + readPart(aGuid, 8, 9) + readPart(aGuid, 10, 15);
+    },
+
+    zeroPad : function zeroPad(num, count) {
+                var numZeropad = num + '';
+                while (numZeropad.length < count) {
+                    numZeropad = "0" + numZeropad;
+                }
+                return numZeropad;
+    },
+
+    byteArrayToGuid : function byteArrayToGuid(byteArray) {
+        function readPartReversed(str, start, end) {
+            var result = "";
+            for (var i = end; i >= start; i--) {
+                result += RemObjects.UTIL.zeroPad((str.charCodeAt(i) & 0xFF).toString(16).toUpperCase(), 2);
+            };
+            return result;
+        };
+        function readPart(str, start, end) {
+            var result = "";
+            for (var i = start; i <= end; i++) {
+                result += RemObjects.UTIL.zeroPad((str.charCodeAt(i) & 0xFF).toString(16).toUpperCase(), 2);
+            };
+            return result;
+        };
+        return "{" + readPartReversed(byteArray, 0, 3) + "-"
+                   + readPartReversed(byteArray, 4, 5) + "-"
+                   + readPartReversed(byteArray, 6, 7) + "-"
+                   + readPart(byteArray, 8, 9) + "-"
+                   + readPart(byteArray, 10, 15)
+                + "}";
+    },
+
+
+    strToByteArray : function strToByteArray(str) {
+        var byteArray = [];
+        for (var i = 0; i < str.length; i++)
+            if (str.charCodeAt(i) <= 0x7F)
+                byteArray.push(str.substr(i, 1));
+            else {
+                var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
+                for (var j = 0; j < h.length; j++)
+                    byteArray.push(String.fromCharCode(parseInt(h[j], 16)));
+            };
+        return byteArray.join("");
+    },
+
+
+
+    byteArrayToStr : function byteArrayToStr(byteArray) {
+        var str = '';
+        for (var i = 0; i < byteArray.length; i++)
+            str += byteArray.charCodeAt(i) <= 0x7F ?
+                    byteArray.charCodeAt(i) === 0x25 ? "%25" : // %
+                            byteArray.substr(i, 1) :
+                    "%" + (byteArray.charCodeAt(i) & 0xFF).toString(16).toUpperCase();
+        return decodeURIComponent(str);
+    },
+
+    byteArrayToUtf16 : function byteArrayToUtf16(byteArray) {
+        var str = '';
+        for (var i = 0; i < byteArray.length / 2; i++) 
+            str += String.fromCharCode((byteArray.charCodeAt(i * 2) & 0xFF) + ((byteArray.charCodeAt(i * 2 + 1) & 0xFF) << 8));
+        return str;
+    },
+
+    utf16ToByteArray : function utf16ToByteArray(str) {
+        var byteArray = "";
+        for (var i = 0; i < str.length; i++) {
+            byteArray += String.fromCharCode(str.charCodeAt(i) & 0xFF);
+            byteArray += String.fromCharCode((str.charCodeAt(i) & 0xFF00) >> 8);
+        };
+        return byteArray;
+    },
+
+    ISO8601toDateTime : function ISO8601toDateTime(str) {
+        var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
+            "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
+            "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
+        var d = ("" + str).match(new RegExp(regexp));
+
+        if (!d) return null;
+
+        var offset = 0;
+        var date = new Date(d[1], 0, 1);
+
+        if (d[3]) { date.setMonth(d[3] - 1); }
+        if (d[5]) { date.setDate(d[5]); }
+        if (d[7]) { date.setHours(d[7]); }
+        if (d[8]) { date.setMinutes(d[8]); }
+        if (d[10]) { date.setSeconds(d[10]); }
+        if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
+        if (d[14]) {
+            offset = (Number(d[16]) * 60) + Number(d[17]);
+            offset *= ((d[15] == '-') ? 1 : -1);
+            offset -= date.getTimezoneOffset();
+            var time = (Number(date) + (offset * 60 * 1000));
+            date.setTime(Number(time));
+        }
+
+        return date;
+    },
+
+    dateTimeToSOAPString : function dateTimeToSOAPString(aValue) {
+        //'yyyy-mm-ddThh:nn:ss'
+        return aValue.getFullYear() + '-' + RemObjects.UTIL.zeroPad(aValue.getMonth() + 1, 2)
+               + '-' + RemObjects.UTIL.zeroPad(aValue.getDate(), 2)
+               + 'T' + RemObjects.UTIL.zeroPad(aValue.getHours(), 2)
+               + ':' + RemObjects.UTIL.zeroPad(aValue.getMinutes(), 2)
+               + ':' + RemObjects.UTIL.zeroPad(aValue.getSeconds(), 2);
+    },
+
+    decimalToString : function decimalToString(aDecimal) { //aDecimal - array [0..6]
+        var sign = (aDecimal[6] & 0x80000000) != 0;
+        var scale = (aDecimal[6] & 0xFF0000) >> 16;
+        var pos = 31;
+        var aDec = aDecimal.slice();
+        var aResult = [];
+        var modres;
+        var d;
+        while ((aDec[0] != 0) || (aDec[1] != 0) || (aDec[2] != 0)
+                || (aDec[3] != 0) || (aDec[4] != 0) || (aDec[5] != 0)
+                || ((31 - pos) < scale)) {
+          modres = 0;
+          for (var i = 5; i >= 0; i--) {
+              d = (modres << 16) | aDec[i];
+              modres = d % 10;
+              aDec[i] = Math.floor(d / 10);
+          };
+          aResult[pos] = modres.toString(10);
+          pos--;
+          if ((31 - pos) == scale) {
+            aResult[pos] = ".";
+            pos--;
+            if ((aDec[0] == 0) && (aDec[1] == 0) && (aDec[2] == 0)) {
+              aResult[pos] = '0';
+              pos--;
+            };
+          };
+        };
+        if (pos == 31)
+            return "0";
+        if (sign) {
+          aResult[pos] = '-';
+          pos--;
+        };
+        return aResult.join("");
+    },
+
+    stringToDecimal : function stringToDecimal(aString) {
+        var mulres;
+        var d;
+        var aRes = [0, 0, 0, 0, 0, 0, 0];
+        var pos = 0;
+        var scalepos = -1;
+        var c;
+        var n;
+        for (var i = 1; i <= aString.length; i++) {
+            mulres = 0;
+            c = aString.substr(i - 1, 1);
+            if (n = parseFloat(c)) {
+                mulres = n
+            } else if (c == "-") {
+                aRes[6] = 0x80000000;
+                continue;
+            } else if (c == ".") {
+                if (scalepos == -1)
+                    scalepos = pos;
+                continue;
+            } else
+                continue;
+
+
+            for (var j = 0; j < 6; j++) {
+                d = aRes[j] * 10 + mulres;
+                mulres = d >> 16;
+                aRes[j] = d & 0xffff;
+            };
+            pos++;
+        };
+        if (scalepos != -1) {
+            pos = pos - scalepos;
+            aRes[6] = aRes[6] | (pos << 16);
+        };
+        return aRes;
+    },
+
+    toBase64 : function toBase64(aValue) {
+        if (typeof(btoa) != 'undefined') {
+            return btoa(aValue);
+        } else {
+            throw(new Error("Base64 encoding is not supported by your browser."));
+            //return $.base64Encode(aValue);
+        };
+    },
+
+    fromBase64 : function fromBase64 (aValue) {
+        if (typeof(atob) != 'undefined') {
+            return atob(aValue.replace(/(\n|\r)+/g, ""));
+        } else {
+            throw(new Error("Base64 decoding is not supported by your browser."));
+            //      return $.base64Decode(aValue);
+        };
+
+    },
+
+    checkArgumentTypes : function checkArgumentTypes (args, types) {
+        for (var i = 0; i < types.length; i++) {
+            if (typeof(types[i]) == "string") {
+                if (typeof(args[i]) != types[i]) return false;
+            } else {
+                if (!(args[i] instanceof types[i])) return false;
+            };
+        };
+        return true;
+    }
+
+};
+
+
+RemObjects.SDK.Enum.MessageType = {
+    mtMessage      : 0,
+    mtException    : 1,
+    mtEvent        : 2,
+    mtPoll         : 0x80,
+    mtPollResponse : 0x81
+};
+
+
+
+//RO.SDK implementation
+
+RemObjects.SDK.ROEventSink.prototype = new RemObjects.SDK.ROComplexType();
+RemObjects.SDK.ROEventSink.prototype.constructor = RemObjects.SDK.ROEventSink;
+RemObjects.SDK.ROEventSink.prototype.readEvent = function readEvent(aMessage, aName) {
+    for (var prop in this[aName]) {
+        if ((typeof this[aName][prop]) != "function") {
+            this[aName][prop].value = aMessage.read(prop, this[aName][prop].dataType);
+        };
+    };
+};
+
+RemObjects.SDK.ROException.prototype = new Error();
+
+RemObjects.SDK.ROEnumType.prototype = new RemObjects.SDK.ROComplexType();
+RemObjects.SDK.ROEnumType.prototype.constructor = RemObjects.SDK.ROEnumType;
+
+
+RemObjects.SDK.ROEnumType.prototype.writeTo = function writeTo(aMessage) {
+    aMessage.write("", "Integer", this.enumValues.indexOf(this.value));
+};
+
+RemObjects.SDK.ROEnumType.prototype.readFrom = function readFrom(aMessage) {
+    this.value = this.enumValues[aMessage.read("", "Integer")];
+};
+
+RemObjects.SDK.ROEnumType.prototype.toObject = function toObject() {
+    return this.value;
+};
+
+RemObjects.SDK.ROEnumType.prototype.fromObject = function fromObject(aValue) {
+    this.value = aValue; //todo: add check
+};
+
+
+RemObjects.SDK.ROStructType.prototype = new RemObjects.SDK.ROComplexType();
+RemObjects.SDK.ROStructType.prototype.constructor = RemObjects.SDK.ROStructType;
+
+RemObjects.SDK.ROStructType.prototype.writeTo = function writeTo(aMessage) {
+    for (var prop in this) {
+        if ((typeof this[prop]) != "function") {
+            aMessage.write(prop, this[prop].dataType, this[prop].value);
+        };
+    };
+};
+
+RemObjects.SDK.ROStructType.prototype.readFrom = function readFrom(aMessage) {
+    for (var prop in this) {
+        if ((typeof this[prop]) != "function") {
+            this[prop].value = aMessage.read(prop, this[prop].dataType);
+        };
+    };
+};
+
+
+RemObjects.SDK.ROStructType.prototype.toObject = function toObject(aStoreType) {
+    var result = {};
+    for (var prop in this) {
+        if ((typeof this[prop]) != "function") {
+            if (this[prop].value instanceof RemObjects.SDK.ROComplexType) {
+                result[prop] = this[prop].value.toObject(aStoreType);
+            } else
+                result[prop] = this[prop].value;
+        };
+    };
+    if(aStoreType) result.__type =  /function\s*(.*?)\(/.exec(this.constructor.toString())[1];
+    return result;
+};
+
+RemObjects.SDK.ROStructType.prototype.fromObject = function fromObject(aValue) {
+    for (var prop in this) {
+        if ((typeof this[prop]) != "function") { //!!!
+            if (RemObjects.SDK.RTTI[this[prop].dataType] && RemObjects.SDK.RTTI[this[prop].dataType].prototype instanceof RemObjects.SDK.ROComplexType) {
+                this[prop].value = new RemObjects.SDK.RTTI[this[prop].dataType]();
+                this[prop].value.fromObject(aValue[prop]);
+            } else {
+                if (this[prop].dataType == "DateTime") {
+                    this[prop].value = RemObjects.UTIL.ISO8601toDateTime(aValue[prop]);
+                } else {
+                    this[prop].value = aValue[prop];
+                };
+            };
+
+        };
+    };
+    return this;
+};
+
+
+RemObjects.SDK.ROArrayType.prototype = new RemObjects.SDK.ROComplexType();
+RemObjects.SDK.ROArrayType.prototype.constructor = RemObjects.SDK.ROArrayType;
+
+
+RemObjects.SDK.ROArrayType.prototype.writeTo = function writeTo(aMessage) {
+    for (var i=0; i<this.items.length; i++ ) {
+        var constructorName = /function\s*(.*?)\(/.exec(this.items[i].constructor.toString())[1];
+        aMessage.write("", RemObjects.SDK.RTTI[constructorName] ? constructorName : this.elementType, this.items[i]);
+    };
+};
+
+RemObjects.SDK.ROArrayType.prototype.readFrom = function readFrom(aMessage) {
+    for (var i=0; i<this.items.length; i++ ) {
+        this.items[i] = aMessage.read("", this.elementType);
+    };
+};
+
+
+RemObjects.SDK.ROArrayType.prototype.toObject = function toObject(aStoreType) {
+    var result = [];
+    for (var i = 0; i < this.items.length; i++)
+        if (this.items[i] instanceof RemObjects.SDK.ROComplexType) {
+            var tmp = this.items[i].toObject(aStoreType);
+            if(aStoreType) tmp.__type =  /function\s*(.*?)\(/.exec(this.items[i].constructor.toString())[1];
+            result.push(tmp);
+        } else
+            result.push(this.items[i]);
+    return result;
+};
+
+RemObjects.SDK.ROArrayType.prototype.fromObject = function fromObject(aValue) {
+    if (!aValue) return this;
+    var itemType = RemObjects.SDK.RTTI[this.elementType];
+    if(itemType) {
+        for (var i = 0; i < aValue.length; i++) {
+            var item = new itemType();
+            item.fromObject(aValue[i]);
+            this.items.push(item);
+        };
+    } else {
+        if (this.elementType == "DateTime") {
+            for (var i = 0; i < aValue.length; i++) {
+                this.items.push(RemObjects.UTIL.ISO8601toDateTime(aValue[i]));
+            };
+        } else {
+            this.items = aValue;
+        };
+    };
+    return this;
+};
+
+
+RemObjects.SDK.ROService.prototype.getChannel = function getChannel() {
+    return this.fChannel;
+};
+
+RemObjects.SDK.ROService.prototype.getMessage = function getMessage() {
+    return this.fMessage;
+};
+
+RemObjects.SDK.ROService.prototype.getServiceName = function getServiceName() {
+    return this.fServiceName;
+};
+
+RemObjects.SDK.ClientChannel.prototype.dispatch = function dispatch(aMessage, onSuccessFunction, onErrorFunction) {
+    function handleException(e) {
+        if (((e.name == "EROSessionNotFound") || (e.name == "SessionNotFoundException")) && !(that.retrying)) {
+            if (that.onLoginNeeded) {
+                that.onLoginNeeded(function() {
+                    that.retrying = true;
+                    that.dispatch(aMessage, function(__msg) {
+                        that.retrying = false;
+                        onSuccessFunction(__msg)
+                    },
+                            function(__msg, __e) {
+                                that.retrying = false;
+                                onErrorFunction(__msg, __e);
+                            });
+                });
+            };
+        } else {
+//                    if (window[e.name] && window[e.name].prototype instanceof RemObjects.SDK.ROException) {
+            if (RemObjects.SDK.RTTI[e.name] && RemObjects.SDK.RTTI[e.name].prototype instanceof RemObjects.SDK.ROException) {
+                e = new RemObjects.SDK.RTTI[e.name](e);
+                e.fields.readFrom(aMessage);
+            };
+            if (onErrorFunction)
+                onErrorFunction(aMessage, e);
+        };
+    };
+    var that = this;
+    this.post(aMessage.requestStream(), aMessage instanceof RemObjects.SDK.BinMessage, function ajax_post_success(__response) {
+        try {
+            aMessage.setResponseStream(__response);
+            if (onSuccessFunction)
+                onSuccessFunction(aMessage);
+        } catch (e) {
+            handleException(e);
+        };
+    }, function ajax_post_error(__response, __status) {
+            aMessage.setErrorResponse("AJAX status: " + __status + "\nResponse: " +__response);
+            try {
+                if (__response)
+                    aMessage.setResponseStream(__response);
+                if (that.onAjaxError) that.onAjaxError(__response, __status);
+                if (onErrorFunction) onErrorFunction(aMessage);
+            } catch (e) {
+                handleException(e);
+            };
+    });
+};
+
+RemObjects.SDK.ClientChannel.prototype.onLoginNeeded = function onLoginNeeded(aCallback) {
+    RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign channel.onLoginNeeded and call aCallback there after successful login");
+    aCallback();
+};
+
+RemObjects.SDK.HTTPClientChannel.prototype = new RemObjects.SDK.ClientChannel("");
+RemObjects.SDK.HTTPClientChannel.prototype.constructor = RemObjects.SDK.HTTPClientChannel;
+
+
+RemObjects.SDK.HTTPClientChannel.prototype.post = function post(aMessage, isBinary, onSuccess, onError) {
+  var ajaxObject;
+  if ((typeof(Ti) != 'undefined') && (typeof(Ti.Network) != 'undefined')) {
+      ajaxObject = new TitaniumAjaxWrapper(this.url);
+  } else {
+      ajaxObject = new AjaxWrapper(this.url);
+  };
+  ajaxObject.post(aMessage, isBinary, onSuccess, onError);
+};
+
+
+RemObjects.SDK.Message.prototype.clone = function clone() {
+    var cloned = new this.constructor();
+    cloned.fClientID = this.fClientID;
+    return cloned;
+};
+
+RemObjects.SDK.Message.prototype.getClientID = function getClientID() {
+    return this.fClientID;
+};
+
+RemObjects.SDK.Message.prototype.setClientID = function setClientID(aValue) {
+    this.fClientID = aValue;
+};
+
+RemObjects.SDK.Message.prototype.setErrorResponse = function setErrorResponse(aResponse) {
+    this.fResponseObject.error = {message: aResponse};
+};
+
+RemObjects.SDK.Message.prototype.getErrorMessage = function getErrorMessage() {
+    if (this.fResponseObject.error)
+        return this.fResponseObject.error.message;
+    else
+        return "";
+};
+
+
+RemObjects.SDK.BinHeader.prototype.asStream = function asStream() {
+    var result = "";
+    var parser = new BinaryParser();
+    for (var i = 0; i < 0x1c; i++)
+        result += parser.encodeInt(this.fHeader[i], 8, false);
+    return result;
+};
+
+RemObjects.SDK.BinHeader.prototype.readFrom = function readFrom(aStream) {
+    var parser = new BinaryParser();
+    for (var i = 0; i < 0x1c; i++)
+        this.fHeader[i] = parser.decodeInt(aStream.substr(i, 1), 8, false);
+};
+
+RemObjects.SDK.BinHeader.prototype.isValidHeader = function isValidHeader() {
+    var tmp = "";
+    for (var i = 0; i < 5; tmp+=String.fromCharCode(this.fHeader[i++]));
+    return (tmp == "RO107");
+};
+
+RemObjects.SDK.BinHeader.prototype.getCompressed = function getCompressed() {
+    return this.fHeader[5];
+};
+
+RemObjects.SDK.BinHeader.prototype.setCompressed = function setCompressed(aValue) {
+    this.fHeader[5] = aValue ? 1 : 0;
+};
+
+RemObjects.SDK.BinHeader.prototype.getMessageType = function getMessageType() {
+    return this.fHeader[6];
+};
+
+RemObjects.SDK.BinHeader.prototype.setMessageType = function setMessageType(aValue) {
+    this.fHeader[6] = aValue;
+};
+
+RemObjects.SDK.BinHeader.prototype.setClientID = function setClientID(aValue) {
+    var guid = RemObjects.UTIL.GuidToArray(aValue);
+    this.fHeader.length -= 16;
+    this.fHeader = this.fHeader.concat(guid);
+};
+
+
+RemObjects.SDK.BinMessage.prototype = new RemObjects.SDK.Message();
+RemObjects.SDK.BinMessage.prototype.constructor = RemObjects.SDK.BinMessage;
+
+
+RemObjects.SDK.BinMessage.prototype.initialize = function initialize(aServiceName, aMethodName, aMessageType) {
+    var header = new RemObjects.SDK.BinHeader();
+    header.setCompressed(false);
+    header.setMessageType(aMessageType || RemObjects.SDK.Enum.MessageType.mtMessage);
+    header.setClientID(this.fClientID);
+    this.fRequestObject = header.asStream();
+    this.parser = new BinaryParser();
+    if (aMessageType != RemObjects.SDK.Enum.MessageType.mtPoll) {
+        this.fRequestObject += this.parser.encodeInt(aServiceName.length, 32, false) + aServiceName;
+        this.fRequestObject += this.parser.encodeInt(aMethodName.length, 32, false) + aMethodName;
+    };
+};
+
+RemObjects.SDK.BinMessage.prototype.finalize = function finalize() {
+
+};
+
+RemObjects.SDK.BinMessage.prototype.write = function write(aName, aType, aValue) {
+
+    if (RemObjects.SDK.RTTI[aType]) {
+        if (aValue instanceof RemObjects.SDK.ROComplexType) { //not null
+            if (!(aValue instanceof RemObjects.SDK.ROEnumType)) {
+                this.fRequestObject += this.parser.encodeInt(1, 8, false);
+                if (aValue instanceof RemObjects.SDK.ROStructType)
+                    this.writeStrWithLength(aType);
+                if (aValue instanceof RemObjects.SDK.ROArrayType)
+                    this.fRequestObject += this.parser.encodeInt(aValue.items.length, 32, false);
+            };
+            aValue.writeTo(this);
+        } else { //null
+            if (!(RemObjects.SDK.RTTI[aType].prototype instanceof RemObjects.SDK.ROEnumType)) {
+                this.fRequestObject += this.parser.encodeInt(0, 8, false);
+                if (RemObjects.SDK.RTTI[aType].prototype instanceof RemObjects.SDK.ROStructType)
+                    this.writeStrWithLength(aType);
+            };
+        };
+    } else
+    switch (aType) {
+
+        case "Decimal":
+            var decimal = RemObjects.UTIL.stringToDecimal(aValue.toString());
+            for (var i = 0; i < 6; i++)
+                this.fRequestObject += this.parser.encodeInt(decimal[i], 16, false);
+            this.fRequestObject += this.parser.encodeInt(decimal[6], 32, false);
+            break;
+
+        case "Double":
+            this.fRequestObject += this.parser.encodeFloat(aValue, 52, 11);
+            break;
+
+        case "Boolean":
+            this.fRequestObject += this.parser.encodeInt((aValue ? 1 : 0), 32, false);
+            break;
+
+        case "Binary":
+            this.fRequestObject += this.parser.encodeInt(1, 8, false);
+            this.writeStrWithLength(aValue);
+            break;
+        
+        case "Integer":
+            this.fRequestObject += this.parser.encodeInt(aValue, 32, true);
+            break;
+
+        case "Int64":
+            this.fRequestObject += this.parser.encodeInt(aValue, 64, true);
+            break;
+
+        case "Currency":
+            var cur = this.parser.encodeInt(aValue * 10000, 48, true);
+            this.fRequestObject += cur;
+            if ((cur.charCodeAt(cur.length - 1) == 0) || (cur.charCodeAt(cur.length - 1) == 0xFF)) {
+                this.fRequestObject += cur.substr(cur.length - 1, 1) + cur.substr(cur.length - 1, 1);
+            };
+            break;
+
+        case "Guid":
+            this.fRequestObject += RemObjects.UTIL.guidToByteArray(aValue);
+            break;
+
+        case "DateTime":
+            this.fRequestObject += this.parser.encodeFloat((aValue - aValue.getTimezoneOffset() * 60000) / 86400000 + 25569.0, 52, 11);
+            break;
+
+        case "WideString":
+            this.fRequestObject += this.parser.encodeInt(aValue.length, 32, true);
+            this.fRequestObject += RemObjects.UTIL.utf16ToByteArray(aValue);
+            break;
+        case "Xml":
+        case "Utf8String":
+            aValue = RemObjects.UTIL.strToByteArray(aValue);
+        case "AnsiString":
+            this.writeStrWithLength(aValue);
+            break;
+        case "Variant":
+            this.writeVariant(aValue);
+            break;
+
+        default:
+        throw new Error("BinMessage.write: Unknown type of " + aName + " - " + aType);
+    };
+
+};
+
+RemObjects.SDK.BinMessage.prototype.writeVariant = function writeVariant(aValue) {
+    var tmpValue;
+    var tmpInt;
+    var tmpFloat;
+    if ((aValue == undefined) || (aValue == null)) {
+        this.writeInteger(0x0001); //varNull
+    } else if (aValue instanceof Date) {
+        this.writeInteger(0x0007); //varDateTime
+        this.write("", "DateTime", aValue);
+    } else if (typeof(aValue) == "boolean") {
+        this.writeInteger(0x000B); //varBoolean
+        this.write("", "Boolean", aValue);
+    } else if(!isNaN(aValue) && typeof (aValue) != "string") {
+        if((tmpInt = parseInt(aValue)) == (tmpFloat = parseFloat(aValue))) {
+            this.writeInteger(0x0003); //varInt32
+            this.write("", "Integer", tmpInt);
+        } else {
+            this.writeInteger(0x0005); //varDouble
+            this.write("", "Double", tmpFloat);
+        };
+    } else if (typeof(aValue) == "string") {
+        this.writeInteger(0x0008); //varString
+        this.write("", "Utf8String", aValue);
+    } else if (aValue.length) {
+        this.writeInteger(0x200C); //varVariant
+        this.writeInteger(0); //lowBound
+        this.writeInteger(aValue.length - 1); //highBound
+        for (var i = 0; i < aValue.length; i++)
+            this.writeVariant(aValue[i]);
+    } else throw new Error("writeVariant: unknown type")
+
+};
+
+RemObjects.SDK.BinMessage.prototype.writeInteger = function writeVariant(aValue) {
+    this.fRequestObject += this.parser.encodeInt(aValue, 32, true);
+};
+
+RemObjects.SDK.BinMessage.prototype.writeStrWithLength = function writeStrWithLength(aValue) {
+    this.fRequestObject += this.parser.encodeInt(aValue.length, 32, false) + aValue;
+};
+
+RemObjects.SDK.BinMessage.prototype.readByte = function readByte() {
+
+    var result = -1;
+    if (this.fStreamPos < this.fResponseString.length) {
+        result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 1), 8, false) & 0xFF;
+        this.fStreamPos += 1;
+    };
+    return result;
+};
+
+RemObjects.SDK.BinMessage.prototype.readCompressed = function readCompressed() {
+    var result = "";
+    var b, inflator = new RemObjects.ZLIB.Inflator(this);
+    this.fStreamPos += 2;
+    while ((b = inflator.readByte()) >= 0) {
+        result += String.fromCharCode(b);
+    };
+    return result;
+};
+
+RemObjects.SDK.BinMessage.prototype.read = function read(valueName, valueType) {
+    var value;
+
+    var rttiType = RemObjects.SDK.RTTI[valueType];
+    if (rttiType && rttiType.prototype instanceof RemObjects.SDK.ROComplexType) {
+        if (rttiType.prototype instanceof RemObjects.SDK.ROEnumType) {
+            value = new rttiType();
+            value.readFrom(this);
+            return value;
+        };
+
+        if (!(this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos++, 1), 8, false))) { // Not assigned
+            return null;
+        };
+
+        if (rttiType.prototype instanceof RemObjects.SDK.ROStructType) {
+            var realType = this.read("", "AnsiString");
+            if (valueType !== realType) {
+                value = new RemObjects.SDK.RTTI[realType]();
+            } else {
+                value = new rttiType();
+            };
+            value.readFrom(this);
+            return value;
+        }
+
+        value = new rttiType();	
+        if (value instanceof RemObjects.SDK.ROArrayType) {
+            value.items.length = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+        };
+        value.readFrom(this);
+
+        return value;
+    }
+
+    switch (valueType) {
+        case "Decimal":
+            var decimal = [];
+            for (var i = 0; i < 6; i++) {
+                decimal[i] = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 2), 16, false);
+                this.fStreamPos += 2;
+            };
+            decimal[6] = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = parseFloat(RemObjects.UTIL.decimalToString(decimal));
+            break;
+
+        case "Double":
+            value = this.parser.decodeFloat(this.fResponseString.substr(this.fStreamPos, 8), 52, 11);
+            this.fStreamPos += 8;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "DateTime":
+            var utcValue = this.parser.decodeFloat(this.fResponseString.substr(this.fStreamPos, 8), 52, 11);
+            utcValue = new Date(Math.round((utcValue - 25569.0) * 86400000));
+            value = new Date(utcValue.getUTCFullYear(), utcValue.getUTCMonth(), utcValue.getUTCDate(),  utcValue.getUTCHours(), utcValue.getUTCMinutes(), utcValue.getUTCSeconds());
+            this.fStreamPos += 8;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "Boolean":
+            value = !(this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false) == 0);
+            this.fStreamPos += 4;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "Integer":
+            value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, true);
+            this.fStreamPos += 4;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "Int64":
+            value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 6), 48, true);
+            this.fStreamPos += 8;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "Currency":
+            value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 6), 48, true) / 10000;
+            this.fStreamPos += 8;
+            this.fResponseObject[valueName] = value;
+            break;
+
+        case "Xml":
+        case "Utf8String":
+            var len = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = RemObjects.UTIL.byteArrayToStr(this.fResponseString.substr(this.fStreamPos, len));
+            this.fStreamPos += len;
+            break;
+
+        case "WideString":
+            var len = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = RemObjects.UTIL.byteArrayToUtf16(this.fResponseString.substr(this.fStreamPos, len * 2));
+            this.fStreamPos += len * 2;
+            break;
+
+        case "Binary":
+            var isAssigned = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 1), 8, false);
+            this.fStreamPos += 1;
+            if (isAssigned == 0) {
+                value = null;
+                break;
+            };
+            var len = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = "";
+            for (var i = this.fStreamPos; i < this.fStreamPos + len; i++) {
+                value += String.fromCharCode(this.fResponseString.charCodeAt(i) & 0xFF);
+            };
+            this.fStreamPos += len;
+            break;
+
+        case "AnsiString":
+            var len = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            value = this.fResponseString.substr(this.fStreamPos, len);
+            this.fStreamPos += len;
+            break;
+
+        case "Guid":
+            value = RemObjects.UTIL.byteArrayToGuid(this.fResponseString.substr(this.fStreamPos, 16));
+            this.fStreamPos += 16;
+            break;
+
+        case "Variant":
+            value = this.readVariant();
+            break;
+
+        default:
+            if (RemObjects.SDK.RTTI[valueType] && (typeof(RemObjects.SDK.RTTI[valueType]) == "function") && (RemObjects.SDK.RTTI[valueType].prototype instanceof RemObjects.SDK.ROComplexType)) {
+                value = new RemObjects.SDK.RTTI[valueType]();
+                value.readFrom(this);
+            } else {
+                this.fResponseObject.error = {message : "BinMessage.read: Unknown type of " + valueName + " - " + valueType};
+                throw new Error(this.fResponseObject.error.message);
+            };
+    };
+    return value;
+};
+
+RemObjects.SDK.BinMessage.prototype.readVariant = function readVariant() {
+    var code = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+    this.fStreamPos += 4;
+    var result;
+
+    if ((code & 0x2000) == 0x2000) {
+        if (code == 0x2011) { //varBinary
+            var binLength = this.read("", "Integer");
+            result = this.fResponseString.substr(this.fStreamPos, binLength);
+            this.fStreamPos += binLength;
+        } else {//varArray
+            //var itemCode = code & 0xFFF;
+            result = [];
+            var lowBound = this.read("", "Integer");
+            var highBound = this.read("", "Integer");
+            for (var i = lowBound; i <= highBound; i++)
+                result[i] = this.readVariant();
+        };
+        return result;
+    };
+
+    switch(code) {
+        case 0x000A: //varError
+        case 0x0000: return undefined; //varEmpty
+        case 0x0001: return null; //varNull
+        case 0x0002: //varInt16
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 2), 16, true);
+            this.fStreamPos += 2;
+            return result;
+        case 0x0003: //varInt32
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, true);
+            this.fStreamPos += 4;
+            return result;
+        case 0x0004: //varSingle
+            result = this.parser.decodeFloat(this.fResponseString.substr(this.fStreamPos, 4), 23, 8);
+            this.fStreamPos += 4;
+            return result;
+        case 0x0005: return this.read("", "Double");//varDouble
+        case 0x0006: return this.read("", "Currency");//varCurrency
+        case 0x0007: return this.read("", "DateTime");//varDateTime
+//varDispatch = 0x0009
+        case 0x000B: return this.read("", "Boolean");//varBoolean
+//varVariant = 0x000C
+//varUnknown = 0x000D
+        case 0x000E: return this.read("", "Decimal");//varDecimal
+        case 0x0010: //varInt8
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 1), 8, true);
+            this.fStreamPos += 1;
+            return result;
+        case 0x0011: //varByte
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 1), 8, false);
+            this.fStreamPos += 1;
+            return result;
+        case 0x0012: //varWord
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 2), 16, false);
+            this.fStreamPos += 2;
+            return result;
+        case 0x0013: //varLongWord
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4;
+            return result;
+        case 0x0014: //varInt64
+            result = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 8), 48, true);
+            this.fStreamPos += 8;
+            return result;
+        case 0x0072: return this.read("", "Guid");//varGuid
+        case 0x0100: return this.read("", "AnsiString");//varDelphiString
+        case 0x0008: //varOleStr
+        case 0x0102: return this.read("", "Utf8String");//varDelphiUtfString
+        default : throw new Error("readVariant: unknown varCode 0x" + code.toString(16));
+    };
+};
+
+
+RemObjects.SDK.BinMessage.prototype.requestStream = function requestStream() {
+    return this.fRequestObject;
+};
+
+RemObjects.SDK.BinMessage.prototype.setResponseStream = function setResponseStream(aResponse) {
+    this.fResponseString = aResponse;
+    var header = new RemObjects.SDK.BinHeader();
+    header.readFrom(this.fResponseString.substr(0, 28));
+    this.fStreamPos = 28; //skip header
+
+    if (!header.isValidHeader()) {
+        this.fResponseObject.error = {message : "Invalid response: unsupported binary message signature"};
+        throw new Error(this.fResponseObject.error.message);
+    };
+    if (header.getCompressed()) {
+
+        this.fResponseString = this.readCompressed();
+        this.fStreamPos = 0;
+
+        //this.fResponseObject.error = {message : "Invalid response: compression is not supported for binary message"};
+        //throw new Error(this.fResponseObject.error.message);
+    };
+
+    switch (header.getMessageType()) {
+
+        case RemObjects.SDK.Enum.MessageType.mtMessage :
+            var value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4 + value; //skip service name
+            value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            this.fStreamPos += 4 + value; //skip method name
+            break;
+
+        case RemObjects.SDK.Enum.MessageType.mtException :
+            var value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            var exceptionClass = this.fResponseString.substr(this.fStreamPos + 4, value);
+            this.fStreamPos +=  4 + value;
+            value = this.parser.decodeInt(this.fResponseString.substr(this.fStreamPos, 4), 32, false);
+            var exceptionMessage = this.fResponseString.substr(this.fStreamPos + 4, value);
+            this.fStreamPos += 4 + value; //skip method name
+            this.fResponseObject.error = {message : exceptionClass + ":\n" + exceptionMessage};
+            var __e = new Error(exceptionMessage);
+            __e.name = exceptionClass;
+            throw __e;
+            break;
+        case RemObjects.SDK.Enum.MessageType.mtPollResponse :
+        case RemObjects.SDK.Enum.MessageType.mtEvent :
+            break;
+
+        default:
+            throw new Error("Unsupported binary message type - 0x" + header.getMessageType().toString(16));
+    };
+
+
+};
+
+
+RemObjects.SDK.JSONMessage.prototype = new RemObjects.SDK.Message();
+RemObjects.SDK.JSONMessage.prototype.constructor = RemObjects.SDK.JSONMessage;
+
+RemObjects.SDK.JSONMessage.prototype.initialize = function initialize(aServiceName, aMethodName, aMessageType) {
+    this.fRequestObject.id = "{" + this.fClientID + "}";
+    this.fRequestObject.method = aServiceName + "." + aMethodName;
+    this.fRequestObject.params = {};
+    if (aMessageType) this.fRequestObject.type = aMessageType;
+};
+
+RemObjects.SDK.JSONMessage.prototype.finalize = function finalize() {
+
+};
+
+RemObjects.SDK.JSONMessage.prototype.write = function write(aName, aType, aValue) {
+    if (aValue instanceof RemObjects.SDK.ROComplexType) {
+        this.fRequestObject.params[aName] = aValue.toObject(true);
+    } else if (aValue instanceof Date) {
+        this.fRequestObject.params[aName] = RemObjects.UTIL.dateTimeToSOAPString(aValue);
+    } else if (aType == "Binary") {
+        this.fRequestObject.params[aName] = RemObjects.UTIL.toBase64(aValue);
+    } else {
+        this.fRequestObject.params[aName] = aValue;
+    };
+};
+
+
+RemObjects.SDK.JSONMessage.prototype.read = function read(aName, aType) {
+    if (this.fResponseObject.result == undefined) {
+        throw new Error("JSONMessage.read: result property is missing:\n" + RemObjects.UTIL.toJSON(this.fResponseObject));
+    };
+
+    if (this.fResponseObject.result[aName] == undefined) {
+        throw new Error("JSONMessage.read error:\n" + aName + ":" + aType + " is missing.");
+    };
+
+    var result;
+    aType = this.fResponseObject.result[aName].__type || aType;
+    if (RemObjects.SDK.RTTI[aType] && RemObjects.SDK.RTTI[aType].prototype instanceof RemObjects.SDK.ROComplexType) {
+        result = new RemObjects.SDK.RTTI[aType]();
+        if (RemObjects.SDK.RTTI[aType].prototype instanceof RemObjects.SDK.ROEnumType) {
+            result.value = this.fResponseObject.result[aName];
+        } else {
+            result.fromObject(this.fResponseObject.result[aName]);
+        };
+    } else if ((aType == "DateTime") || ((aType == "Variant") && (RemObjects.UTIL.ISO8601toDateTime(this.fResponseObject.result[aName])))) {
+        result = RemObjects.UTIL.ISO8601toDateTime(this.fResponseObject.result[aName]);
+    } else {
+        result = this.fResponseObject.result[aName];
+    };
+    return result;
+};
+
+
+RemObjects.SDK.JSONMessage.prototype.requestStream = function requestStream() {
+    return RemObjects.UTIL.toJSON(this.fRequestObject);
+};
+
+RemObjects.SDK.JSONMessage.prototype.setResponseStream = function setResponseStream(aResponse) {
+    try {
+        this.fResponseObject = RemObjects.UTIL.parseJSON(aResponse);
+    } catch (e) {
+        throw new Error("JSONMessage.setResponseStream:\n JSON parsing error: " + e.message + "\nServer response:\n" + aResponse);
+    };
+    if (this.fResponseObject.error) {
+        var __e;
+        __e = new Error(this.fResponseObject.error.message);
+        if  (this.fResponseObject.error.name == "ROJSONException") {
+            __e.name = this.fResponseObject.error.roexception.type;
+        } else {
+            __e.name = this.fResponseObject.error.name;
+        };
+        throw __e;
+    };
+    if (!this.fResponseObject.result) {
+        this.fResponseObject.result = this.fResponseObject.params;
+    };
+};
+
+RemObjects.SDK.EventReceiver.prototype.addHandler = function addHandler(anEventName, aCallback) {
+    this.fHandlers[anEventName] = aCallback;
+};
+
+RemObjects.SDK.EventReceiver.prototype.setActive = function setActive(aValue) {
+    this.fActive = aValue;
+    if (this.fActive) {
+        var that = this;
+        this.fInterval = setInterval(function () {that.intPollServer();}, this.fTimeout);
+    } else {
+        clearInterval(this.fInterval);
+    };
+};
+
+RemObjects.SDK.EventReceiver.prototype.getActive = function getActive() {
+    return this.fActive;
+};
+
+RemObjects.SDK.EventReceiver.prototype.getTimeout = function getTimeout() {
+    return this.fTimeout;
+};
+
+RemObjects.SDK.EventReceiver.prototype.setTimeout = function setTimeout(aValue) {
+    this.fTimeout = aValue;
+    if (this.fActive) {
+        this.setActive(false);
+        this.setActive(true);
+    };
+};
+
+RemObjects.SDK.EventReceiver.prototype.intPollServer = function intPollServer() {
+    try {
+        var that = this;
+        var msg = this.fMessage.clone();
+        msg.initialize(this.fServiceName, "poll", RemObjects.SDK.Enum.MessageType.mtPoll);
+
+        msg.write("MaxMessageCount", "Integer", 10);
+        if (msg instanceof RemObjects.SDK.BinMessage) {
+            msg.write("", "Guid", msg.getClientID());
+        };
+
+        msg.finalize();
+        this.fChannel.dispatch(msg, function (__message) {
+            var msgCount = __message.read("MessageCount", "Integer");
+            var msgLeft = __message.read("MessagesLeft", "Integer");
+            for (var i = 0; i < msgCount; i++) {
+                var events = __message.clone();
+				events.initialize("", "", RemObjects.SDK.Enum.MessageType.mtPollResponse);
+                var eventsStream = __message.read("Message_" + i, "Binary");
+                if (__message instanceof RemObjects.SDK.JSONMessage) {
+                    eventsStream = RemObjects.UTIL.fromBase64(eventsStream);
+                };
+                events.setResponseStream(eventsStream);
+                var sinkName = events.read("InterfaceName", "AnsiString");
+                var eventName = events.read("MessageName", "AnsiString");
+                if (RemObjects.SDK.RTTI[sinkName] && RemObjects.SDK.RTTI[sinkName].prototype instanceof RemObjects.SDK.ROComplexType) {
+                    var sink = new RemObjects.SDK.RTTI[sinkName]();
+                    sink.readEvent(events, eventName);
+                    if (that.fHandlers[eventName]) {
+                        that.fHandlers[eventName](RemObjects.SDK.ROStructType.prototype.toObject.call(sink[eventName]));
+                    };
+                } else {
+                    throw new Error("EventReceiver.intPollServer: unknown event sink");
+                };
+            };
+        }, RemObjects.UTIL.showError);
+
+    } catch (e) {
+        RemObjects.UTIL.showError(msg, e);
+    };
+        
+};
+
+
+function TitaniumAjaxWrapper(url) {
+            this.updating = false;
+            this.urlCall = url;
+};
+
+
+TitaniumAjaxWrapper.prototype.post = function post(passData, isBinary, onSuccessFunction, onErrorFunction) {
+
+    if (this.updating) {
+        return false;
+    };
+    this.AJAX = null;
+
+    this.AJAX = Ti.Network.createHTTPClient({
+        onload: function(e) {
+            Ti.API.info("onload");
+            if (isBinary) {
+                onSuccessFunction(this.responseData, 200);
+            } else {
+                onSuccessFunction(this.responseText, 200);
+            };
+        },
+        onerror: function(e) {
+            Ti.API.info('XHR Error ' + e.error);
+        },
+        timeout:5000
+    });
+    this.updating = new Date();
+    var uri = this.urlCall + '?' + this.updating.getTime();
+
+    Ti.API.info("TitaniumAjaxWrapper " + uri);
+    this.AJAX.open("POST", uri);
+    // if (isBinary) {
+    //     this.AJAX.setRequestHeader('Content-Type','multipart/form-data');
+    // }
+    this.AJAX.send(passData);
+};
+
+function AjaxWrapper(url) {
+            this.updating = false;
+            this.urlCall = url;
+};
+
+
+AjaxWrapper.prototype.abort = function abort() {
+    if (this.updating) {
+        this.updating = false;
+        this.AJAX.abort();
+        this.AJAX = null;
+    };
+};
+
+AjaxWrapper.prototype.post = function post(passData, isBinary, onSuccessFunction, onErrorFunction) {
+    function isIE10Up() {
+        if (!navigator)
+            return false;
+        // Project Spartan is not yet supported
+        // This code detects only IE 10 and IE 11
+        return (navigator.userAgent.indexOf("MSIE 10") != -1)
+                    || (!!navigator.userAgent.match(/Trident\/7\./));
+    };
+
+    if (this.updating) {
+        return false;
+    };
+    this.AJAX = null;
+
+    try {
+        this.AJAX = new XMLHttpRequest();
+    } catch (e) {
+        // Internet Explorer Browsers
+        try {
+            this.AJAX = new ActiveXObject("Msxml2.XMLHTTP");
+        } catch (e) {
+            try {
+                this.AJAX = new ActiveXObject("Microsoft.XMLHTTP");
+            } catch (e) {
+                throw new Error("Your browser doesn't support XMLHttpRequest object");
+            };
+        };
+    };
+
+
+         if (this.AJAX == null) {
+             return false;
+         } else {
+             this.onSuccess = onSuccessFunction || function () {
+             };
+             this.onError = onErrorFunction || function () {
+             };
+             var that = this;
+
+             this.AJAX.onreadystatechange = function onreadystatechange() {
+                 if (that.AJAX.readyState == 4) {
+                     that.updating = false;
+                     if (that.AJAX.status == 200) {
+                         if (isIE10Up() && typeof(that.AJAX.response) != "string") {
+                             var response = "";
+                             var arr = new Uint8Array(that.AJAX.response);
+                             for (var i = 0; i < arr.length; i++) response += String.fromCharCode(arr[i]);
+                             that.onSuccess(response, that.AJAX.status/*, that.AJAX.responseXML*/);
+                         } else {
+                             that.onSuccess(that.AJAX.responseText, that.AJAX.status, that.AJAX.responseXML);
+                         };
+                     } else {
+                        that.onError(that.AJAX.responseText, that.AJAX.status, that.AJAX.responseXML);
+                     };
+                     that.AJAX = null;
+                 };
+             };
+             this.updating = new Date();
+                 var uri = this.urlCall + '?' + this.updating.getTime();
+                 this.AJAX.open("POST", uri, true);
+                 //this.AJAX.setRequestHeader("Content-Length", passData.length);
+                 if (isBinary == true) {
+                    if (this.AJAX.overrideMimeType)
+                        this.AJAX.overrideMimeType('text/plain; charset=x-user-defined');
+                    this.AJAX.setRequestHeader("Content-type", "application/octet-stream");
+                    if (this.AJAX.sendAsBinary) {
+                        this.AJAX.sendAsBinary(passData);
+                    } else {
+                        var len = passData.length;
+                        var data = new Uint8Array(len);
+                        for (var i=0; i<len; i++) {
+                            data[i] = passData.charCodeAt(i);
+                        };
+                        if (isIE10Up()) {
+                            this.AJAX.responseType = "arraybuffer";
+                        };
+                        this.AJAX.send(data.buffer);
+                    };
+                 } else {
+                     this.AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+                     this.AJAX.send(passData);
+                 };
+            return true;
+         };
+};
+
+// binary parser by Jonas Raoni Soares Silva
+function BinaryParser() {
+    this.BigEndian = true;
+};
+
+BinaryParser.prototype.warn = function warn(msg) {throw new Error(msg)};
+
+BinaryParser.prototype.decodeFloat = function decodeFloat( data, precisionBits, exponentBits ){
+    var b = new this.Buffer( this.bigEndian, data );
+    b.checkBuffer( precisionBits + exponentBits + 1 );
+    var bias = Math.pow( 2, exponentBits - 1 ) - 1, signal = b.readBits( precisionBits + exponentBits, 1 ), exponent = b.readBits( precisionBits, exponentBits ), significand = 0,
+    divisor = 2, curByte = b.buffer.length + ( -precisionBits >> 3 ) - 1;
+    do
+        for( var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 );
+    while( precisionBits -= startBit );
+    return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 );
+};
+BinaryParser.prototype.encodeFloat = function encodeFloat( data, precisionBits, exponentBits ){
+    var bias = Math.pow( 2, exponentBits - 1 ) - 1, minExp = -bias + 1, maxExp = bias, minUnnormExp = minExp - precisionBits,
+    status = isNaN( n = parseFloat( data ) ) || n == -Infinity || n == +Infinity ? n : 0,
+    exp = 0, len = 2 * bias + 1 + precisionBits + 3, bin = new Array( len ),
+    signal = ( n = status !== 0 ? 0 : n ) < 0, n = Math.abs( n ), intPart = Math.floor( n ), floatPart = n - intPart,
+    i, lastBit, rounded, j, result;
+    for( i = len; i; bin[--i] = 0 );
+    for( i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor( intPart / 2 ) );
+    for( i = bias + 1; floatPart > 0 && i; ( bin[++i] = ( ( floatPart *= 2 ) >= 1 ) - 0 ) && --floatPart );
+    for( i = -1; ++i < len && !bin[i]; );
+    if( bin[( lastBit = precisionBits - 1 + ( i = ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - ( exp = minExp - 1 ) ) ) + 1] ){
+        if( !( rounded = bin[lastBit] ) ) {
+            for( j = lastBit + 2; !rounded && j < len; rounded = bin[j++] ) {};
+        };
+        for( j = lastBit + 1; rounded && --j >= 0; ( bin[j] = !bin[j] - 0 ) && ( rounded = 0 ) ){};
+    };
+    for( i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i]; ){};
+    if( ( exp = bias + 1 - i ) >= minExp && exp <= maxExp ) {
+        ++i;
+    } else if( exp < minExp ){
+        exp != bias + 1 - len && exp < minUnnormExp && this.warn( "encodeFloat::float underflow" );
+        i = bias + 1 - ( exp = minExp - 1 );
+    };
+    if( intPart || status !== 0 ){
+        this.warn( intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status );
+        exp = maxExp + 1;
+        i = bias + 2;
+        if( status == -Infinity ) {
+            signal = 1;
+        } else if( isNaN( status ) )
+            bin[i] = 1;
+    };
+    for( n = Math.abs( exp + bias ), j = exponentBits + 1, result = ""; --j; result = ( n % 2 ) + result, n = n >>= 1 );
+    for( n = 0, j = 0, i = ( result = ( signal ? "1" : "0" ) + result + bin.slice( i, i + precisionBits ).join( "" ) ).length, r = []; i; j = ( j + 1 ) % 8 ){
+        n += ( 1 << j ) * result.charAt( --i );
+        if( j == 7 ){
+            r[r.length] = String.fromCharCode( n );
+            n = 0;
+        };
+    };
+    r[r.length] = n ? String.fromCharCode( n ) : "";
+    return ( this.bigEndian ? r.reverse() : r ).join( "" );
+};
+
+BinaryParser.prototype.encodeInt = function encodeInt(number, bits, signed){
+           var max = Math.pow(2, bits), r = [];
+           (number >= max || number < -Math.pow(2, bits-1)) && this.warn("encodeInt::overflow") && (number = 0);
+           number < 0 && (number += max);
+           for(; number; r[r.length] = String.fromCharCode(number % 256), number = Math.floor(number / 256));
+           for(bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0");
+           return (this.bigEndian ? r.reverse() : r).join("");
+       };
+
+BinaryParser.prototype.decodeInt = function decodeInt(data, bits, signed){
+           var b = new this.Buffer(this.bigEndian, data), x = b.readBits(0, bits), max = Math.pow(2, bits);
+           return signed && x >= max / 2 ? x - max : x;
+       };
+
+
+
+   with({p: (BinaryParser.prototype.Buffer = function Buffer(bigEndian, buffer){
+   this.bigEndian = bigEndian || 0, this.buffer = [], this.setBuffer(buffer);
+   }).prototype}){
+   p.readBits = function(start, length){
+       //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
+       function shl(a, b){
+//           for(++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
+           for(++b; --b; a = ((a %= 0x7fffffffffff + 1) & 0x400000000000) == 0x400000000000 ? a * 2 : (a - 0x400000000000) * 2 + 0x7fffffffffff + 1);
+           return a;
+       };
+       if(start < 0 || length <= 0)
+           return 0;
+       this.checkBuffer(start + length);
+       for(var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - (start >> 3) - 1,
+           lastByte = this.buffer.length + (-(start + length) >> 3), diff = curByte - lastByte,
+           sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1))
+           + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[ lastByte++ ] & ((1 << offsetLeft) - 1))
+           << (diff-- << 3) - offsetRight : 0); diff; sum += shl(this.buffer[ lastByte++ ], (diff-- << 3) - offsetRight)
+       );
+       return sum;
+   };
+   p.setBuffer = function setBuffer(data){
+       if(data){
+           for(var l, i = l = data.length, b = this.buffer = new Array(l); i; b[l - i] = data.charCodeAt(--i) & 0xFF);
+//           for(var l, i = l = data.length, b = this.buffer = new Array(l); i; b[l - i] = data.charCodeAt(--i));
+           this.bigEndian && b.reverse();
+       };
+   };
+   p.hasNeededBits = function hasNeededBits(neededBits){
+       return this.buffer.length >= -(-neededBits >> 3);
+   };
+   p.checkBuffer = function checkBuffer(neededBits){
+       if(!this.hasNeededBits(neededBits)) {
+           throw new Error("checkBuffer::missing bytes");};
+   };
+   };
+
+/*
+DEFLATE implementation based on http://www.codeproject.com/Articles/26980/Binary-Formats-in-JavaScript-Base64-Deflate-and-UT
+Copyright (c) 2008 notmasteryet
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+RemObjects.ZLIB = {
+    staticCodes:null,
+    staticDistances:null,
+    encodedLengthStart:new Array(3, 4, 5, 6, 7, 8, 9, 10,
+            11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99,
+            115, 131, 163, 195, 227, 258),
+    encodedLengthAdditionalBits:new Array(0, 0, 0, 0, 0, 0, 0, 0,
+            1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0),
+    encodedDistanceStart:new Array(1, 2, 3, 4, 5, 7, 9,
+            13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049,
+            3073, 4097, 6145, 8193, 12289, 16385, 24577),
+    encodedDistanceAdditionalBits:new Array(0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4,
+            5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13),
+    clenMap:new Array(16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15),
+
+
+    buildCodes:function buildCodes(lengths) {
+        var codes = new Array(lengths.length);
+        var maxBits = lengths[0];
+        for (var i = 1; i < lengths.length; i++) {
+            if (maxBits < lengths[i]) maxBits = lengths[i];
+        };
+
+        var bitLengthsCount = new Array(maxBits + 1);
+        for (var i = 0; i <= maxBits; i++) bitLengthsCount[i] = 0;
+
+        for (var i = 0; i < lengths.length; i++) {
+            ++bitLengthsCount[lengths[i]];
+        };
+
+        var nextCode = new Array(maxBits + 1);
+        var code = 0;
+        bitLengthsCount[0] = 0;
+        for (var bits = 1; bits <= maxBits; bits++) {
+            code = (code + bitLengthsCount[bits - 1]) << 1;
+            nextCode[bits] = code;
+        };
+
+        for (var n = 0; n < codes.length; n++) {
+            var len = lengths[n];
+            if (len != 0) {
+                codes[n] = nextCode[len];
+                nextCode[len]++;
+            };
+        };
+        return codes;
+    },
+
+    initializeStaticTrees:function initializeStaticTrees() {
+        var codes = new Array(288);
+        var codesLengths = new Array(288);
+
+        for (var i = 0; i <= 143; i++) {
+            codes[i] = 0x0030 + i;
+            codesLengths[i] = 8;
+        };
+        for (var i = 144; i <= 255; i++) {
+            codes[i] = 0x0190 + i - 144;
+            codesLengths[i] = 9;
+        };
+        for (var i = 256; i <= 279; i++) {
+            codes[i] = 0x0000 + i - 256;
+            codesLengths[i] = 7;
+        };
+        for (var i = 280; i <= 287; i++) {
+            codes[i] = 0x00C0 + i - 280;
+            codesLengths[i] = 8;
+        };
+        RemObjects.ZLIB.staticCodes = RemObjects.ZLIB.buildTree(codes, codesLengths);
+
+        var distances = new Array(32);
+        var distancesLengths = new Array(32);
+        for (var i = 0; i <= 31; i++) {
+            distances[i] = i;
+            distancesLengths[i] = 5;
+        };
+        RemObjects.ZLIB.staticDistances = RemObjects.ZLIB.buildTree(distances, distancesLengths);
+    },
+
+    buildTree:function buildTree(codes, lengths) {
+        var nonEmptyCodes = new Array(0);
+        for (var i = 0; i < codes.length; ++i) {
+            if (lengths[i] > 0) {
+                var code = new Object();
+                code.bits = codes[i];
+                code.length = lengths[i];
+                code.index = i;
+                nonEmptyCodes.push(code);
+            };
+        };
+        return RemObjects.ZLIB.buildTreeBranch(nonEmptyCodes, 0, 0);
+    },
+
+    buildTreeBranch: function buildTreeBranch(codes, prefix, prefixLength) {
+        if (codes.length == 0) return null;
+
+        var zeros = new Array(0);
+        var ones = new Array(0);
+        var branch = new Object();
+        branch.isLeaf = false;
+        for (var i = 0; i < codes.length; ++i) {
+            if (codes[i].length == prefixLength && codes[i].bits == prefix) {
+                branch.isLeaf = true;
+                branch.index = codes[i].index;
+                break;
+            } else {
+                var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0;
+                if (nextBit) {
+                    ones.push(codes[i]);
+                } else {
+                    zeros.push(codes[i]);
+                };
+            };
+        };
+        if (!branch.isLeaf) {
+            branch.zero = RemObjects.ZLIB.buildTreeBranch(zeros, (prefix << 1), prefixLength + 1);
+            branch.one = RemObjects.ZLIB.buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1);
+        };
+        return branch;
+    },
+
+    readDynamicTrees:function readDynamicTrees(bitReader) {
+        var hlit = bitReader.readLSB(5) + 257;
+        var hdist = bitReader.readLSB(5) + 1;
+        var hclen = bitReader.readLSB(4) + 4;
+
+        var clen = new Array(19);
+        for (var i = 0; i < clen.length; ++i) clen[i] = 0;
+        for (var i = 0; i < hclen; ++i) clen[RemObjects.ZLIB.clenMap[i]] = bitReader.readLSB(3);
+
+        var clenCodes = RemObjects.ZLIB.buildCodes(clen);
+        var clenTree = RemObjects.ZLIB.buildTree(clenCodes, clen);
+
+        var lengthsSequence = new Array(0);
+        while (lengthsSequence.length < hlit + hdist) {
+            var p = clenTree;
+            while (!p.isLeaf) {
+                p = bitReader.readBit() ? p.one : p.zero;
+            };
+
+            var code = p.index;
+            if (code <= 15) {
+                lengthsSequence.push(code);
+            } else if (code == 16) {
+                var repeat = bitReader.readLSB(2) + 3;
+                for (var q = 0; q < repeat; ++q)
+                    lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]);
+            } else if (code == 17) {
+                var repeat = bitReader.readLSB(3) + 3;
+                for (var q = 0; q < repeat; ++q)
+                    lengthsSequence.push(0);
+            } else if (code == 18) {
+                var repeat = bitReader.readLSB(7) + 11;
+                for (var q = 0; q < repeat; ++q)
+                    lengthsSequence.push(0);
+            };
+        };
+
+        var codesLengths = lengthsSequence.slice(0, hlit);
+        var codes = RemObjects.ZLIB.buildCodes(codesLengths);
+        var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist);
+        var distances = RemObjects.ZLIB.buildCodes(distancesLengths);
+
+        var result = new Object();
+        result.codesTree = RemObjects.ZLIB.buildTree(codes, codesLengths);
+        result.distancesTree = RemObjects.ZLIB.buildTree(distances, distancesLengths);
+        return result;
+    },
+
+    BitReader:function BitReader(reader) {
+        this.bitsLength = 0;
+        this.bits = 0;
+        this.reader = reader;
+        this.readBit = function () {
+            if (this.bitsLength == 0) {
+                var nextByte = this.reader.readByte();
+                if (nextByte < 0) throw new "Unexpected end of stream";
+                this.bits = nextByte;
+                this.bitsLength = 8;
+            };
+
+            var bit = (this.bits & 1) != 0;
+            this.bits >>= 1;
+            --this.bitsLength;
+            return bit;
+        };
+        this.align = function () {
+            this.bitsLength = 0;
+        };
+        this.readLSB = function (length) {
+            var data = 0;
+            for (var i = 0; i < length; ++i) {
+                if (this.readBit()) data |= 1 << i;
+            };
+            return data;
+        };
+        this.readMSB = function (length) {
+            var data = 0;
+            for (var i = 0; i < length; ++i) {
+                if (this.readBit()) data = (data << 1) | 1; else data <<= 1;
+            };
+            return data;
+        };
+    },
+
+
+    Inflator:function Inflator(reader) {
+        this.reader = reader;
+        this.bitReader = new RemObjects.ZLIB.BitReader(reader);
+        this.buffer = new Array(0);
+        this.bufferPosition = 0;
+        this.state = 0;
+        this.blockFinal = false;
+        this.readByte = function () {
+            while (this.bufferPosition >= this.buffer.length) {
+                var item = this.decodeItem();
+                if (item == null) return -1;
+                switch (item.itemType) {
+                    case 0:
+                        this.buffer = this.buffer.concat(item.array);
+                        break;
+                    case 2:
+                        this.buffer.push(item.symbol);
+                        break;
+                    case 3:
+                        var j = this.buffer.length - item.distance;
+                        for (var i = 0; i < item.length; i++) {
+                            this.buffer.push(this.buffer[j++]);
+                        };
+                        break;
+                };
+            };
+            var symbol = this.buffer[this.bufferPosition++];
+            if (this.bufferPosition > 0xC000) {
+                var shift = this.buffer.length - 0x8000;
+                if (shift > this.bufferPosition) shift = this.bufferPosition;
+                this.buffer.splice(0, shift);
+                this.bufferPosition -= shift;
+            };
+            return symbol;
+        };
+
+        this.decodeItem = function () {
+            if (this.state == 2) return null;
+
+            var item;
+            if (this.state == 0) {
+                this.blockFinal = this.bitReader.readBit();
+                var blockType = this.bitReader.readLSB(2);
+                switch (blockType) {
+                    case 0:
+                        this.bitReader.align();
+                        var len = this.bitReader.readLSB(16);
+                        var nlen = this.bitReader.readLSB(16);
+                        if ((len & ~nlen) != len) throw "Invalid block type 0 length";
+
+                        item = new Object();
+                        item.itemType = 0;
+                        item.array = new Array(len);
+                        for (var i = 0; i < len; ++i) {
+                            var nextByte = this.reader.readByte();
+                            if (nextByte < 0) throw "Uncomplete block";
+                            item.array[i] = nextByte;
+                        }
+                        if (this.blockFinal) this.state = 2;
+                        return item;
+                    case 1:
+                        this.codesTree = RemObjects.ZLIB.staticCodes;
+                        this.distancesTree = RemObjects.ZLIB.staticDistances;
+                        this.state = 1;
+                        break;
+                    case 2:
+                        var dynamicTrees = RemObjects.ZLIB.readDynamicTrees(this.bitReader);
+                        this.codesTree = dynamicTrees.codesTree;
+                        this.distancesTree = dynamicTrees.distancesTree;
+                        this.state = 1;
+                        break;
+                    default:
+                        throw new "Invalid block type (3)";
+                };
+            };
+
+            item = new Object();
+            var p = this.codesTree;
+            while (!p.isLeaf) {
+                p = this.bitReader.readBit() ? p.one : p.zero;
+            };
+            if (p.index < 256) {
+                item.itemType = 2;
+                item.symbol = p.index;
+            } else if (p.index > 256) {
+                var lengthCode = p.index;
+                if (lengthCode > 285) throw new "Invalid length code";
+
+                var length = RemObjects.ZLIB.encodedLengthStart[lengthCode - 257];
+                if (RemObjects.ZLIB.encodedLengthAdditionalBits[lengthCode - 257] > 0) {
+                    length += this.bitReader.readLSB(RemObjects.ZLIB.encodedLengthAdditionalBits[lengthCode - 257]);
+                };
+
+                p = this.distancesTree;
+                while (!p.isLeaf) {
+                    p = this.bitReader.readBit() ? p.one : p.zero;
+                };
+
+                var distanceCode = p.index;
+                var distance = RemObjects.ZLIB.encodedDistanceStart[distanceCode];
+                if (RemObjects.ZLIB.encodedDistanceAdditionalBits[distanceCode] > 0) {
+                    distance += this.bitReader.readLSB(RemObjects.ZLIB.encodedDistanceAdditionalBits[distanceCode]);
+                };
+
+                item.itemType = 3;
+                item.distance = distance;
+                item.length = length;
+            } else {
+                item.itemType = 1;
+                this.state = this.blockFinal ? 2 : 0;
+            };
+            return item;
+        };
+    }
+};
+
+RemObjects.ZLIB.initializeStaticTrees();

+ 57 - 0
demo/dataabstract/sampleda.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta charset="utf-8" />
+    <title>
+      Data Abstract dataset demo
+    </title>
+
+    <!-- Bootstrap -->
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
+    
+    <style type="text/css">
+      body {
+        padding-top: 60px;
+      }
+    </style>
+    <script src="RemObjectsSDK.js" type="text/javascript"></script>
+    <script src="DataAbstract.js" type="text/javascript"></script>
+    <script src="DataAbstract4_intf.js" type="text/javascript"></script>
+    <script src="sampleda.js" type="text/javascript"></script>
+  </head>
+
+  <body role="document">
+    <div class="navbar navbar-fixed-top" role="navigation">
+      <div class="container">
+        <div class="navbar-header">
+          <div class="navbar-brand">Data Abstract for Pas2JS</div>
+        </div>
+        <button id="btn-fetch" class="btn btn-default">Load Data</button>
+        <div><a href="sampleda.lpr"> View pascal sources</a></div>
+      </div>
+    </div>
+    <div id="wrapper" class="container" style="display:none;">
+      <div class="content">
+        <div class="row">
+          <table class="table table-bordered table-striped">
+            <thead>
+              <th>ID</th>
+              <th>Name</th>
+              <th>Phone</th>
+            </thead>
+            <tbody id="tableRows"></tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+    <hr />
+    </footer>
+
+    <script>
+      window.addEventListener("load",rtl.run);
+    </script>
+  </body>
+</html>

+ 81 - 0
demo/dataabstract/sampleda.lpi

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="11"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="sampleda"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="1">
+      <Item0 Name="PasJSWebBrowserProject" Value="1"/>
+    </CustomData>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+      <Modes Count="0"/>
+    </RunParams>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="sampleda.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target FileExt=".js">
+      <Filename Value="sampleda"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="js"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <AllowLabel Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <TargetOS Value="browser"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc"/>
+      <CompilerPath Value="$(pas2js)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 119 - 0
demo/dataabstract/sampleda.lpr

@@ -0,0 +1,119 @@
+program sampleda;
+
+{$mode objfpc}
+
+uses
+  JS, Classes, SysUtils, Web, DB, dasdk, dadataset;
+
+Type
+
+  { TSampleForm }
+
+  TSampleForm = Class(TComponent)
+  Private
+    divWrapper : TJSHTMLElement;
+    btnGetData : TJSHTMLButtonElement;
+    tblBody : TJSHTMLElement;
+    FConn : TDAConnection;
+    FDataset : TDADataset;
+    procedure AfterLoad(DataSet: TDataSet; Data: JSValue);
+    procedure BindElements;
+    procedure CreateDataset;
+    procedure DoClientsOpen(DataSet: TDataSet);
+    function DoGetDataClick(aEvent: TJSMouseEvent): boolean;
+    procedure DoLoginOK(result: Boolean; UserInfo: TDAUserInfo);
+  Public
+    Procedure Show;
+  end;
+
+{ TSampleForm }
+
+procedure TSampleForm.CreateDataset;
+
+begin
+  FConn:=TDaConnection.Create(Self);
+  FConn.URL:='https://sample.remobjects.com/bin';
+  FConn.OnLogin:=@DoLoginOK;
+  FConn.StreamerType:=stBin;
+  FDataset:=TDaDataset.Create(Self);
+  FDataset.DAConnection:=FConn;
+  FDataset.TableName:='Clients';
+  FDataset.AfterOpen:=@DoClientsOpen;
+end;
+
+procedure TSampleForm.BindElements;
+
+begin
+  btnGetData:=TJSHTMLButtonElement(Document.getElementById('btn-fetch'));
+  btnGetData.onClick:=@DoGetDataClick;
+  tblBody:=TJSHTMLElement(Document.getElementById('tableRows'));
+  divWrapper:=TJSHTMLElement(Document.getElementById('wrapper'));
+end;
+
+procedure TSampleForm.AfterLoad(DataSet: TDataSet; Data: JSValue);
+begin
+  Writeln('Loaded');
+end;
+
+procedure TSampleForm.DoClientsOpen(DataSet: TDataSet);
+
+  Function escape(S : String) : String;
+
+  begin
+    Result:=StringReplace(S,'&','&amp;',[rfReplaceAll]);
+    Result:=StringReplace(S,'<','&lt;',[rfReplaceAll]);
+    Result:=StringReplace(S,'>','&gt;',[rfReplaceAll]);
+    Result:=StringReplace(S,'"','&quot;',[rfReplaceAll]);
+    Result:=StringReplace(S,'''','&#39;',[rfReplaceAll]);
+  end;
+
+Var
+  FID,FName,FPhone : TField;
+  HTML : String;
+
+begin
+  Writeln('Clients open :',Dataset.RecordCount);
+  FID:=Dataset.FieldByname('ClientId');
+  FName:=Dataset.FieldByname('ClientName');
+  FPhone:=Dataset.FieldByname('ContactPhone');
+  While not Dataset.EOF do
+    begin
+    html:=Html+'<TR><TD>'+Escape(FID.AsString)+'</TD>'
+              +'<TD>'+Escape(FName.AsString)+'</TD>'
+              +'<TD>'+Escape(FPhone.AsString)+'</TD></TR>';
+    Dataset.Next;
+    end;
+  tblBody.InnerHTMl:=HTML;
+  divWrapper['style']:='';
+end;
+
+function TSampleForm.DoGetDataClick(aEvent: TJSMouseEvent): boolean;
+begin
+  FConn.LoginEx('User=simple;Password=simple;');
+  Result:=False;
+end;
+
+procedure TSampleForm.DoLoginOK(result: Boolean; UserInfo: TDAUserInfo);
+begin
+  Writeln('Login :',result);
+  if Result then
+    begin
+    divWrapper['style']:='display: none;';
+    FDataset.Active:=False;
+    FDataset.Load([],@AfterLoad);
+    end
+  else
+    window.Alert('Failed to log in !')
+end;
+
+procedure TSampleForm.Show;
+
+begin
+  CreateDataset;
+  BindElements;
+end;
+
+begin
+  With TSampleForm.Create(Nil) do
+    Show;
+end.

+ 318 - 0
packages/dataabstract/da.pas

@@ -0,0 +1,318 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2018 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    Remobjects Data Abstract external classes.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit DA;
+
+{$mode objfpc}
+{$modeswitch externalclass}
+
+interface
+
+uses Types, JS, DASDK;
+
+Type
+  // Forward classes
+  TDADataTable = class;
+  TDABIN2DataStreamer = class;
+  TDAJSONDataStreamer = class;
+  TDARemoteDataAdapter = Class;
+  TDAChange = class;
+  TDADelta = class;
+  TDADeltas = class;
+  TDAField = class;
+  TDALookupField = class;
+  TDADataTableRow = class;
+  TDAExpression  = class;
+  TDADynamicWhere = class;
+  TDAConstantExpression = class;
+  TDAParameterExpression  = class;
+  TDANullExpression  = class;
+  TDAFieldExpression  = class;
+  TDAMacroExpression  = class;
+  TDAUnaryExpression  = class;
+  TDABinaryExpression  = class;
+  TDABetweenExpression  = class;
+  TDAListExpression  = class;
+  TDAUtil = Class;
+  TDARemoteDataAdaptor = Class;
+
+
+  TDAStream = String;
+
+  TDADataStreamer = class external name 'RemObjects.DataAbstract.DataStreamer' (TJSObject)
+  Public 
+    procedure initializeRead;
+    procedure initializeWrite;
+    procedure finalizeWrite;
+    function getStream : TDAStream;
+    procedure setStream(aStream : TDAStream);
+    procedure readDataset(aDataset : TDADataTable);
+    function readDelta : TDADelta;
+    procedure writeDelta(aDelta : TDADelta);
+    Property Stream : TDAStream Read getStream write setStream;
+  end;
+
+  TDADataStreamerClass = Class of TDADataStreamer;
+  
+  TDABIN2DataStreamer = class external name 'RemObjects.DataAbstract.Bin2DataStreamer' (TDADataStreamer)
+    function readByte : Byte;
+    function readInteger : NativeInt;
+    function readAnsiStringWithLength : String;
+    function readUtf8StringWithLength : string;
+    function read (aType : string) : TJSObject;
+    function readParam (acount : Integer) : TDADataParameter;
+    function readField(acount : Integer) : TDAField;
+    Procedure writeByte(aValue : Byte);
+    Procedure writeInteger(aValue : NativeInt);
+    Procedure writeAnsiStringWithLength(aValue : String);
+    Procedure write(aType : string; aValue : TJSObject);
+  end;
+  
+  TDAJSONDataStreamer = class external name 'RemObjects.DataAbstract.JSONDataStreamer' (TDADataStreamer)
+  end;  
+  
+  TDARemoteDataAdapter = Class external name 'RemObjects.DataAbstract.RemoteDataAdapter' (TJSObject)
+  Public
+    Constructor New(Const aURL, aDataServiceName, aLoginServiceName : String; 
+                    aStreamerClass : TDADataStreamerClass);
+  end;
+
+  TDAChange = class external name 'RemObjects.DataAbstract.Change' (TJSObject)
+  end;
+
+  TDAChangeArray = array of TDAChange;
+  
+  TLogField = record
+    name : string;
+    datatype : string;
+  end;
+  TLogFieldArray = array of TLogfield;
+  
+  TDADelta = class external name 'RemObjects.DataAbstract.Delta' (TJSObject)
+  Private
+    FData : TDAChangeArray; external name 'data'; 
+    FKeyFields : TStringDynArray; external name 'keyfields';
+    FLoggedFields : TLogFieldArray; external name 'loggedfields';
+    FName : string; external name 'name';
+  Public
+    Function intFindId(anId : Integer) : TDAChange;
+    Property data : TDAChangeArray Read FData;
+    Property keyFields : TStringDynArray Read FKeyFields;
+    Property LoggedFields : TLogFieldArray Read FLoggedFields; 
+    Property Name : String Read FName;
+  end;
+
+  TDADeltas = class external name 'RemObjects.DataAbstract.Deltas' (TJSObject)
+  Public
+    Function FindByName (Const aName : String) : TDADelta;
+  end;
+
+  TDATableRowNotifyEvent = reference to procedure(row : TDADataTableRow);
+  TDADataTableRowArray = array of TDADataTableRow;
+  TDAFieldArray = Array of TDAField;
+
+  TDADataTable = class external name 'RemObjects.DataAbstract.DataTable' (TJSObject)
+  Public
+    name : string;
+    rows : TDADataTableRowArray;
+    fields : TDAFieldArray;
+    deletedrows : TDADataTableRowArray;
+    frecordbuffer : TJSArray;
+    fNextRecID : Integer;
+    fIndex : Integer;
+    bofFlag : Boolean;
+    eofFlag : Boolean;
+    dynamicWhere : TJSObject;
+    onNewRecord : TDATableRowNotifyEvent;
+    onBeforeDelete: TDATableRowNotifyEvent;
+    onAfterDelete: TDATableRowNotifyEvent;
+    onBeforeScroll: TDATableRowNotifyEvent;
+    onAfterScroll: TDATableRowNotifyEvent;
+    Procedure checkRequired;
+    Procedure locate(aName : String; aValue : JSValue);
+    procedure addLookupField(const aName,aSourceField : String; aLookupTable : TDADataTable;
+                             const aLookupKeyField, aLookupResultField : String);
+    procedure getNextId;
+    function appendRow  : TDADataTableRow;
+    procedure deleteRow;
+    procedure markDeleted;
+    function fieldNumByName(Const aName : string) : Integer;
+    function fieldByName(Const aName : string) : TDAField;
+    procedure setFieldValue(Const aName : string; aValue : JSValue);
+    function getFieldValue(Const aName : string) : JSValue;
+    procedure setFieldAsString(Const aName, aValue : String);
+    function getFieldAsString(Const aName : string) : String;
+    function currentRow : TDADataTableRow;
+    procedure first;
+    procedure last;
+    procedure next;
+    procedure prev;
+    Function findId(anID: Integer) : TDADataTableRow;
+    function eof : boolean;
+    function bof : boolean;
+    procedure post;
+    procedure cancel;
+  end;
+ 
+  TDAField = class external name 'RemObjects.DataAbstract.Field' (TJSObject)
+  Public
+    alignment : string;
+    blobtype: string;
+    businessClassID : String;
+    calculated : string;
+    customAttributes : string;
+    dataType : string;
+    name: string;
+    type_ : string external name 'type';
+    logChanges : boolean;
+    readOnly : boolean;
+    serverAutoRefresh : Boolean;
+    serverCalculated : Boolean;
+    description : string;
+    decimalPrecision : Integer;
+    decimalScale : integer;
+    defaultValue : string;
+    dictionaryEntry : String;
+    displayLabel : String;
+    displayWidth : integer;
+    inPrimaryKey : Boolean;
+    visible : boolean;
+    required : boolean;
+    size : integer;
+    Procedure checkReadOnly;
+  end;
+
+  TDALookupField = class external name 'RemObjects.DataAbstract.LookupField' (TJSObject)
+  Public
+    sourceField : string;
+    lookupTable : TDADataTable;
+    lookupKeyField: String;
+    lookupResultField : string;
+  end;
+
+  TDADataTableRow = class external name 'RemObjects.DataAbstract.DataTableRow' (TJSObject)
+  Public
+    recID : Integer;
+    state : string;
+    __oldValues : array of JSValue;
+    __newValues : array of JSValue;
+  end;
+
+  TDAExpression  = class external name 'RemObjects.DataAbstract.Expression' (TJSObject);
+
+  TDADynamicWhere = class external name 'RemObjects.DataAbstract.DynamicWhere' (TJSObject)
+  Public
+    constructor New(anExpression : TDAExpression);
+    function toXML : String;
+  end;
+ 
+  TDAConstantExpression  = class external name 'RemObjects.DataAbstract.ConstantExpression' (TDAExpression)
+  Public
+    constructor new (aType : String; aValue : JSValue; ANull : Byte);
+  end;
+
+  TDAParameterExpression  = class external name 'RemObjects.DataAbstract.ParameterExpression' (TDAExpression)
+  Public
+    constructor new (const aName, aType : String; aSize : Integer);
+  end;
+
+  TDANullExpression  = class external name 'RemObjects.DataAbstract.NullExpression' (TDAExpression)
+  public
+    constructor new;
+  end;
+
+  
+  TDAFieldExpression  = class external name 'RemObjects.DataAbstract.FieldExpression' (TDAExpression)
+  public
+    constructor new(aName : string);
+  end;
+  
+  TDAMacroExpression  = class external name 'RemObjects.DataAbstract.MacroExpression' (TDAExpression)
+  public
+    constructor new(aName : string);
+  end;
+  
+  
+  TDAUnaryExpression  = class external name 'RemObjects.DataAbstract.UnaryExpression' (TDAExpression)
+  public
+    constructor new(aNode : TDAExpression; aOperator : string);
+  end;
+  
+  TDABinaryExpression  = class external name 'RemObjects.DataAbstract.BinaryExpression' (TDAExpression)
+   public
+    constructor new(aNode1,aNode2 : TDAExpression; aOperator : string);
+  end;
+  
+  TDABetweenExpression  = class external name 'RemObjects.DataAbstract.BetweenExpression' (TDAExpression)
+  public
+    constructor new(aNode1,aNode2,aNode3 : TDAExpression);
+  end;
+  
+  TDAListExpression  = class external name 'RemObjects.DataAbstract.ListExpression' (TDAExpression)
+  public
+    constructor new(aList : array of TDAExpression);
+  end;
+  
+
+  TDAUtil = Class external name 'RemObjects.DataAbstract.Util' (TJSObject)
+  Public
+    function createDataParameter(aName : String;aValue : JSValue) : TJSObject;
+    function createRequestInfo(IncludeSchema : Boolean; MaxRecords : Integer; UserFilter : String; Parameters  : Array of JSValue) : TJSObject;
+    function createRequestInfoV5(IncludeSchema : Boolean; MaxRecords : Integer; UserFilter : String; Parameters  : Array of JSValue) : TJSOBject;
+    function createRequestInfoV6(SQL : String; MaxRecords : Integer; UserFilter : String; Parameters  : Array of JSValue) : TJSObject;
+    procedure setupScriptingCallBacks;
+  end;  
+  
+  TDACallBack = procedure;
+  TDALoginNeededCallBack = reference to procedure(aCallBack : TDACallBack);   
+  TDAChangeFailHandler = reference to procedure (aData : TDAChange);
+  
+  TDARemoteDataAdaptor = Class external name 'RemObjects.DataAbstract.RemoteDataAdapter' (TJSObject)
+  Private
+    FSendReducedDelta : boolean; external name 'sendReducedDelta';
+  Public
+    onLoginNeeded : TDALoginNeededCallBack;
+    onChangeFail : TDAChangeFailHandler;
+    function getDataService() : TDADataAbstractService;
+    function getLoginService() : TDASimpleLoginService;
+    procedure login(aUserID,aPassword,aConnectionName : String; OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    procedure logout(OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    function createStreaer: TDAJSONDatastreamer;
+    procedure setSendReducedDelta  (aValue : Boolean);
+    procedure getSchema(aFilter : String;OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    function buildDelta(aTable : TDADataTable) : TDADelta;
+    procedure createTableFromSchema(const aTableName : String; aTable : TDADataTable; CallBack: TDACallBack);
+    procedure executeCommand(const aName : String; Parameters: TDADataParameterArray; OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    function getAutoGetScripts : Boolean;
+    procedure setAutoGetScripts(aValue : boolean);
+    Procedure getSQLData(aTable : TDADataTable; const SQL : String;OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    Procedure getData(aTable : TDADataTable; aRequest : TDATableRequestInfo;OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    procedure applyUpdates(aTable : TDADataTable; OnSuccess : TDASuccessEvent; OnFailed : TDAFailedEvent);
+    property sendReducedDelta : Boolean Read FSendReducedDelta Write setSendReducedDelta;
+    property AutoGetScripts : boolean Read getAutoGetScripts write setAutoGetScripts;
+  end;
+
+  TDAHTMLTableView = class external name 'RemObjects.DataAbstract.Views.HtmlTableView'
+  Public
+    constructor new(aTable : TDADataTable; aHTMLTableID : String);
+  end;
+
+  TDAVerticalHTMLTableView = class external name 'RemObjects.DataAbstract.Views.VerticalHtmlTableView'
+  Public
+    constructor new(aTable : TDADataTable; aHTMLTableID : String);
+  end;
+Implementation
+
+end.

+ 511 - 0
packages/dataabstract/dadataset.pas

@@ -0,0 +1,511 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2018 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    Dataset which talks to Remobjects Data Abstract server.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit dadataset;
+
+interface
+
+uses Types, Classes, DB, jsonDataset, JS, rosdk, da, dasdk;
+
+Type
+  EDADataset = Class(EDatabaseError);
+  TDAConnection = Class;
+
+  { TDADataset }
+
+  TDADataset = class(TBaseJSONDataset)
+  private
+    FParams: TParams;
+    FTableName: String;
+    FDAConnection: TDAConnection;
+    FWhereClause: String;
+    function DataTypeToFieldType(s: String): TFieldType;
+    procedure SetParams(AValue: TParams);
+  Protected
+    Procedure MetaDataToFieldDefs; override;
+  Public
+    constructor create(aOwner : TComponent); override;
+    Destructor Destroy; override;
+    function DoGetDataProxy: TDataProxy; override;
+    // DA is index based. So create array field mapper.
+    function CreateFieldMapper : TJSONFieldMapper; override;
+    Procedure CreateFieldDefs(a : TJSArray);
+    Property TableName : String Read FTableName Write FTableName;
+    Property DAConnection : TDAConnection Read FDAConnection Write FDAConnection;
+    Property Params : TParams Read FParams Write SetParams;
+    Property WhereClause : String Read FWhereClause Write FWhereClause;
+  end;
+
+  TDADataRequest = Class(TDataRequest)
+  Public
+    Procedure doSuccess(res : JSValue) ;
+    Procedure DoFail(response : TJSOBject; fail : String) ;
+  End;
+
+  { TDADataProxy }
+
+  TDADataProxy = class(TDataProxy)
+  private
+    FConnection: TDAConnection;
+    function ConvertParams(DADS: TDADataset): TDADataParameterDataArray;
+  Protected
+    Function GetDataRequestClass : TDataRequestClass; override;
+  Public
+    Function DoGetData(aRequest : TDataRequest) : Boolean; override;
+    Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; override;
+    Property Connection : TDAConnection Read FConnection Write FConnection;
+  end;
+
+  TDAMessageType = (mtAuto, // autodetect from URL
+                    mtBin,  // use BinMessage
+                    mtJSON); // Use JSONMessage.
+  TDAStreamerType = (stJSON,stBin);
+
+  { TDAConnection }
+
+  TDAConnection = class(TComponent)
+  private
+    FDataService: TDADataAbstractService;
+    FDataserviceName: String;
+    FLoginService: TDASimpleLoginService;
+    FLoginServiceName: String;
+    FMessageType: TDAMessageType;
+    FMessage : TROmessage;
+    FChannel : TROHTTPClientChannel;
+    FOnLoginFailed: TDAFailedEvent;
+    FOnLogin: TDALoginSuccessEvent;
+    FStreamerType: TDAStreamerType;
+    FURL: String;
+    procedure ClearConnection;
+    Function GetDataService : TDADataAbstractService;
+    function GetLoginService: TDASimpleLoginService;
+    procedure SetDataserviceName(AValue: String);
+    procedure SetLoginServiceName(AValue: String);
+    procedure SetMessageType(AValue: TDAMessageType);
+    procedure SetURL(AValue: String);
+  Protected
+    Procedure CreateChannelAndMessage; virtual;
+    function DetectMessageType(Const aURL: String): TDAMessageType; virtual;
+    Function CreateDataService : TDADataAbstractService; virtual;
+    Function CreateLoginService : TDASimpleLoginService; virtual;
+  Public
+    Constructor create(aOwner : TComponent); override;
+    Destructor Destroy; override;
+    // Returns a non-auto MessageType, but raises exception if it cannot be determined;
+    Function EnsureMessageType : TDAMessageType;
+    // Returns DataService, but raises exception if it is nil;
+    Function EnsureDataservice : TDADataAbstractService;
+    // Returns SimpleLoginService, but raises exception if it is nil;
+    Function EnsureLoginservice : TDASimpleLoginService;
+    // Call this to login. This is an asynchronous call, check the result using OnLoginOK and OnLoginFailed calls.
+    Procedure Login(aUserName, aPassword : String);
+    Procedure LoginEx(aLoginString : String);
+    // You can set this. If you didn't set this, and URL is filled, an instance will be created.
+    Property DataService : TDADataAbstractService Read GetDataService Write FDataService;
+    //  You can set this. If you didn't set this, and URL is filled, an instance will be created.
+    Property LoginService : TDASimpleLoginService Read GetLoginService Write FLoginService;
+  Published
+    // If set, this is the message type that will be used when auto-creating the service. Setting this while dataservice is Non-Nil will remove the reference
+    Property MessageType : TDAMessageType Read FMessageType Write SetMessageType;
+    // if set, URL is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
+    Property URL : String Read FURL Write SetURL;
+    // DataServiceName is used to create a DataService. Setting this while dataservice is Non-Nil will remove the reference
+    Property DataserviceName : String Read FDataserviceName Write SetDataserviceName;
+    // LoginServiceName is used to create a login service. Setting this while loginservice is Non-Nil will remove the reference
+    Property LoginServiceName : String read FLoginServiceName write SetLoginServiceName;
+    // Called when login call is executed.
+    Property OnLogin : TDALoginSuccessEvent Read FOnLogin Write FOnLogin;
+    // Called when login call failed. When call was executed but user is wrong OnLogin is called !
+    Property OnLoginCallFailed : TDAFailedEvent Read FOnLoginFailed Write FOnLoginFailed;
+    // Streamertype : format of the data package in the message.
+    Property StreamerType : TDAStreamerType Read FStreamerType Write FStreamerType;
+  end;
+
+
+implementation
+
+uses strutils, sysutils;
+
+{ TDAConnection }
+
+
+function TDAConnection.GetDataService: TDADataAbstractService;
+begin
+  if (FDataservice=Nil) then
+    FDataservice:=CreateDataService;
+  Result:=FDataService;
+end;
+
+function TDAConnection.GetLoginService: TDASimpleLoginService;
+begin
+  if (FLoginService=Nil) then
+    FLoginService:=CreateLoginService;
+  Result:=FLoginService;
+end;
+
+procedure TDAConnection.SetDataserviceName(AValue: String);
+begin
+  if FDataserviceName=AValue then Exit;
+  ClearConnection;
+  FDataserviceName:=AValue;
+end;
+
+procedure TDAConnection.SetLoginServiceName(AValue: String);
+begin
+  if FLoginServiceName=AValue then Exit;
+  FLoginServiceName:=AValue;
+end;
+
+procedure TDAConnection.SetMessageType(AValue: TDAMessageType);
+begin
+  if FMessageType=AValue then Exit;
+  ClearConnection;
+  FMessageType:=AValue;
+end;
+
+procedure TDAConnection.ClearConnection;
+
+begin
+  FDataservice:=Nil;
+  FChannel:=Nil;
+  FMessage:=Nil;
+end;
+
+procedure TDAConnection.SetURL(AValue: String);
+begin
+  if FURL=AValue then Exit;
+  ClearConnection;
+  FURL:=AValue;
+end;
+
+procedure TDAConnection.CreateChannelAndMessage;
+
+
+begin
+  if (FChannel=Nil) then
+    FChannel:=TROHTTPClientChannel.New(URL);
+  if (FMessage=Nil) then
+    Case EnsureMessageType of
+      mtBin : fMessage:=TROBINMessage.New;
+      mtJSON : fMessage:=TROJSONMessage.New;
+    end;
+end;
+
+function TDAConnection.DetectMessageType(Const aURL : String) : TDAMessageType;
+
+Var
+  S : String;
+
+begin
+  S:=aURL;
+  Delete(S,1,RPos('/',S));
+  case lowercase(S) of
+    'bin' : Result:=mtBin;
+    'json' : Result:=mtJSON;
+  else
+    Raise EDADataset.Create(Name+': Could not determine message type from URL: '+aURL);
+  end;
+end;
+
+
+function TDAConnection.CreateDataService: TDADataAbstractService;
+
+begin
+  Result:=Nil;
+  if URL='' then exit;
+  CreateChannelAndMessage;
+  Result:=TDADataAbstractService.New(FChannel,FMessage,DataServiceName);
+end;
+
+function TDAConnection.CreateLoginService: TDASimpleLoginService;
+begin
+  Result:=Nil;
+  if URL='' then exit;
+  CreateChannelAndMessage;
+  Result:=TDASimpleLoginService.New(FChannel,FMessage,LoginServiceName);
+end;
+
+constructor TDAConnection.create(aOwner: TComponent);
+begin
+  inherited create(aOwner);
+  FDataServiceName:='DataService';
+  FLoginServiceName:='LoginService';
+end;
+
+destructor TDAConnection.Destroy;
+begin
+  ClearConnection;
+  inherited Destroy;
+end;
+
+function TDAConnection.EnsureMessageType: TDAMessageType;
+begin
+  Result:=MessageType;
+  if Result=mtAuto then
+    Result:=DetectMessageType(URL);
+end;
+
+function TDAConnection.EnsureDataservice: TDADataAbstractService;
+
+begin
+  Result:=Dataservice;
+  if (Result=Nil) then
+    Raise EDADataset.Create('No data service available. ');
+end;
+
+function TDAConnection.EnsureLoginservice: TDASimpleLoginService;
+
+begin
+  Result:=LoginService;
+  if (Result=Nil) then
+    Raise EDADataset.Create('No login service available. ');
+end;
+
+procedure TDAConnection.Login(aUserName, aPassword: String);
+
+begin
+  EnsureLoginService.Login(aUserName,aPassword,FOnLogin,FOnLoginFailed);
+end;
+
+procedure TDAConnection.LoginEx(aLoginString: String);
+begin
+  EnsureLoginService.LoginEx(aLoginString,FOnLogin,FOnLoginFailed);
+end;
+
+{ TDADataset }
+
+function TDADataset.DataTypeToFieldType(s : String) : TFieldType;
+
+Const
+  FieldStrings : Array [TFieldType] of string = (
+    '','String', 'Integer', 'LargeInt', 'Boolean', 'Float', 'Date',
+    'Time', 'DateTime',  'AutoInc', 'Blob', 'Memo', 'FixedChar',
+    'Variant','Dataset');
+
+
+begin
+  if (Copy(S,1,3)='dat') then
+    system.Delete(S,1,3);
+  Result:=High(TFieldType);
+  While (Result>ftUnknown) and Not SameText(FieldStrings[Result],S) do
+    Result:=Pred(Result);
+  if Result=ftUnknown then
+    case LowerCase(s) of
+     'widestring' : result:=ftString;
+     'currency' : result:=ftFloat;
+    end;
+end;
+
+procedure TDADataset.SetParams(AValue: TParams);
+begin
+  if FParams=AValue then Exit;
+  FParams.Assign(AValue);
+end;
+
+procedure TDADataset.MetaDataToFieldDefs;
+
+begin
+  if Not isArray(Metadata['fields']) then
+    exit;
+  CreateFieldDefs(TJSArray(Metadata['fields']));
+end;
+
+function TDADataset.DoGetDataProxy: TDataProxy;
+begin
+  Result:=TDADataProxy.Create(Self);
+  TDADataProxy(Result).Connection:=DAConnection;
+end;
+
+constructor TDADataset.create(aOwner: TComponent);
+begin
+  inherited;
+  DataProxy:=nil;
+  FParams:=TParams.Create(Self);
+end;
+
+destructor TDADataset.Destroy;
+begin
+  FreeAndNil(FParams);
+  Inherited;
+end;
+
+procedure TDADataset.CreateFieldDefs(a: TJSArray);
+
+Var
+  I : Integer;
+  F : TDAField;
+  fn,dt : string;
+  fs : Integer;
+  FT : TFieldType;
+  req : boolean;
+
+begin
+  FieldDefs.Clear;
+  For I:=0 to A.length-1 do
+    begin
+    F:=TDAField(A.Elements[i]);
+    fn:=F.Name;
+    fs:=F.Size;
+    dt:=F.type_;
+    req:=F.Required;
+    Ft:=DataTypeToFieldType(dT);
+    if (ft=ftBlob) and (fs=0) then
+      fs:=1;
+    FieldDefs.Add(fn,ft,fs,Req);
+    end;
+end;
+
+function TDADataset.CreateFieldMapper: TJSONFieldMapper;
+begin
+  Result := TJSONArrayFieldMapper.Create;
+end;
+
+{ TDADataProxy }
+
+function TDADataProxy.ConvertParams(DADS : TDADataset) : TDADataParameterDataArray;
+
+Var
+  I : integer;
+begin
+  Result:=Nil;
+  Writeln('Converting ',DADS.Params.Count,' parameters.');
+  if DADS.Params.Count=0 then
+     Exit;
+  SetLength(Result,DADS.Params.Count);
+  for I:=0 to DADS.Params.Count-1 do
+    begin
+    Result[i].Name:=DADS.Params[i].Name;
+    Result[i].Value:=DADS.Params[i].Value;
+    end;
+end;
+
+function TDADataProxy.DoGetData(aRequest: TDataRequest): Boolean;
+
+Var
+  TN : TDAStringArray;
+  TIA : TDATableRequestInfoArray;
+  TID : TDATableRequestInfoV5Data;
+  TI : TDATableRequestInfoV5;
+  Srt : TDAColumnSortingData;
+  R : TDADataRequest;
+  DADS : TDADataset;
+  PA : TDADataParameterDataArray;
+  DS : TDADataAbstractService;
+begin
+  // DA does not support this option...
+  if loAtEOF in aRequest.LoadOptions then
+    exit(False);
+  DADS:=aRequest.Dataset as TDADataset;
+  R:=aRequest as TDADatarequest;
+  if (Connection=Nil) then
+    Raise EDADataset.Create(Name+': Cannot get data without connection');
+  DS:=Connection.EnsureDataservice;
+  TN:=TDAStringArray.New;
+  TN.fromObject([DADS.TableName]);
+  TID.maxRecords:=-1;
+  TID.IncludeSchema:=True;
+  Srt.FieldName:='';
+  Srt.SortDirection:='Ascending';
+  TID.Sorting:=Srt;
+  TID.UserFilter:='';
+  if DADS.WhereClause<>'' then
+    TID.WhereClause:=DADS.WhereClause;
+  PA:=ConvertParams(DADS);
+  if Length(PA)>0 then
+    TID.Parameters:=Pa;
+  TIA:=TDATableRequestInfoArray.new;
+  // We need to manually fill the array
+  TI:=TDATableRequestInfoV5.New;
+  TI.FromObject(TID);
+  TJSArray(TIA.items).push(TI);
+  DS.GetData(TN,TIA,@R.doSuccess,@R.doFail);
+  Result:=True;
+end;
+
+function TDADataProxy.GetDataRequestClass: TDataRequestClass;
+begin
+  Result:=TDADataRequest;
+end;
+
+function TDADataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;
+begin
+  Result:=False;
+end;
+
+{ TDADataRequest }
+
+procedure TDADataRequest.DoFail(response: TJSOBject; fail: String);
+
+Var
+  O : TJSOBject;
+  S : TStringDynArray;
+  Msg : String;
+  I : Integer;
+
+begin
+  if isObject(fail) then
+    begin
+    O:=TJSOBject(JSValue(fail));
+    S:=TJSObject.getOwnPropertyNames(O);
+    for I:=0 to Length(S)-1 do
+      begin
+      msg:=Msg+sLineBreak+S[i];
+      Msg:=Msg+' : '+String(O[S[i]]);
+      end;
+    end
+  else
+    Msg:=Fail;
+
+  writeln('Data request or processing failed: ',Msg);
+  Success:=rrFail;
+end;
+
+procedure TDADataRequest.doSuccess(res: JSValue);
+
+Var
+  S : String;
+  Rows : TJSArray;
+  DADS : TDADataset;
+  DStr : TDADataStreamer;
+  DT : TDADatatable;
+  I : Integer;
+
+begin
+//  Writeln('Data loaded, dataset active: ',Dataset.Active);
+  DADS:=Dataset as TDADataset;
+  if not Assigned(DADS.DAConnection) then
+    Raise EDADataset.Create(DADS.Name+': Cannot process response, connection not available');
+  S:=String(Res);
+  if (DADS.DAConnection.EnsureMessageType=mtJSON) then
+    S:=TROUtil.Frombase64(S);
+  Case DADS.DAConnection.StreamerType of
+    stJSON : DStr:=TDABIN2DataStreamer.new;
+    stBIN: DStr:=TDABIN2DataStreamer.new;
+  end;
+  DStr.Stream:=S;
+  DStr.initializeRead;
+  DT:=TDADataTable.New;
+  DStr.ReadDataset(DT);
+  Rows:=TJSArray.New;
+  for I:=0 to length(DT.rows)-1 do
+     Rows.Push(DT.Rows[i].__newValues);
+  (Dataset as TDADataset).Metadata:=New(['fields',TJSArray(DT.Fields)]);
+  // Data:=aJSON['data'];
+  (Dataset as TDADataset).Rows:=Rows;
+  Success:=rrOK;
+  DoAfterRequest;
+end;
+
+end.

+ 217 - 0
packages/dataabstract/dasdk.pas

@@ -0,0 +1,217 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2018 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    Remobjects Data Abstract external classes definitions
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit dasdk;
+
+{$mode objfpc}
+{$modeswitch externalclass}
+
+interface
+
+uses JS, ROSDK;
+
+Type
+  TDAUserInfo = Class;
+  TDASuccessEvent = Procedure (res : JSValue) of object;
+  TDAFailedEvent = Procedure (response : TJSOBject; fail : String) of object;
+
+  TDALoginSuccessEvent = Reference to Procedure (result : Boolean; UserInfo : TDAUserInfo);
+
+  TDABaseLoginService = class external name 'RemObjects.DataAbstract.Server.SimpleLoginService' (TJSObject)
+  Public
+    Constructor new(ch : TROHTTPClientChannel; msg : TROMessage; aServiceName : string);
+    Procedure LoginEx(aLoginString :String; aSuccess : TDALoginSuccessEvent; aFailure : TDAFailedEvent);
+    Procedure Logout(aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+  end;
+
+  TDASimpleLoginService = class external name 'RemObjects.DataAbstract.Server.SimpleLoginService' (TDABaseLoginService)
+  Public
+    Procedure Login(aUserId,aPassword :String; aSuccess : TDALoginSuccessEvent; aFailure : TDAFailedEvent);
+  end;
+
+  TDAStringArray = class external name 'RemObjects.DataAbstract.Server.StringArray'
+  Public
+    constructor new;
+    procedure fromObject(aItems : array of string); overload;
+  end;
+
+  TDADataParameterData = Record
+    Name: string;
+    Value : JSValue;
+  End;
+  TDADataParameterDataArray = array of TDADataParameterData;
+
+  TDADataParameter = class external name 'RemObjects.DataAbstract.Server.DataParameter' (TROStructType)
+  Public
+    constructor new;
+    Procedure fromObject(aItem : TDADataParameterData); overload;
+  Public
+    Name : TROValue;
+    Value : TROValue;
+  end;
+
+  TDADataParameterArray = class external name 'RemObjects.DataAbstract.Server.DataParameterArray' (TROArrayType)
+  Public
+    constructor new;
+    Procedure fromObject(aItems : Array of TDADataParameterData); overload;
+    function toObject : TDADataParameterDataArray; reintroduce;
+  Public
+    items : Array of TDADataParameter;
+  end;
+
+  TDAColumnSortingData = record
+    FieldName : String;
+    SortDirection : String;
+  end;
+  TDAColumnSortingDataArray = Array of TDAColumnSortingData;
+
+  TDAColumnSorting = class external name 'RemObjects.DataAbstract.Server.ColumnSorting' (TROStructType)
+  Public
+    FieldName : TROValue;
+    Direction : TROValue;
+  end;
+
+  TDAColumnSortingArray = class external name 'RemObjects.DataAbstract.Server.ColumnSortingArray' (TROArrayType)
+  Public
+    constructor new;
+    Procedure fromObject(aItems : Array of TDAColumnSortingData); overload;
+    function toObject : TDAColumnSortingDataArray; reintroduce;
+  Public
+    items : Array of TDAColumnSorting;
+  end;
+
+  TDATableRequestInfoData = record
+    IncludeSchema : boolean;
+    MaxRecords : Integer;
+    Parameters : TDADataParameterDataArray;
+    UserFilter : String;
+  end;
+  TDATableRequestInfoDataArray = array of TDATableRequestInfoData;
+
+  TDATableRequestInfo = class external name 'RemObjects.DataAbstract.Server.TableRequestInfo' (TROStructType)
+  Public
+    constructor new;
+    procedure fromObject(aItem : TDATableRequestInfoData);reintroduce; overload;
+    procedure fromObject(aItem : TJSObject);reintroduce; overload;
+    Function toObject : TDATableRequestInfoData; reintroduce;
+  Public
+    IncludeSchema : TROValue;
+    MaxRecords : TROValue;
+    Parameters : TROValue;
+    UserFilter : TROValue;
+  end;
+
+  TDATableRequestInfoV5Data = record
+    DynamicSelectFieldNames : Array of string;
+    IncludeSchema : boolean;
+    MaxRecords : Integer;
+    Parameters : Array of TDADataParameterData;
+    UserFilter : String;
+    Sorting : TDAColumnSortingData;
+    WhereClause : String;
+  end;
+
+  TDATableRequestInfoV5 = class external name 'RemObjects.DataAbstract.Server.TableRequestInfoV5' (TROStructType)
+  Public
+    constructor new;
+    procedure fromObject(aItem : TDATableRequestInfoV5Data);reintroduce;overload;
+    procedure fromObject(aItem : TJSObject);reintroduce;overload;
+    function toObject : TDATableRequestInfoV5Data;reintroduce;
+  Public
+    DynamicSelectFieldNames : TROValue;
+    IncludeSchema : TROValue;
+    MaxRecords : TROValue;
+    Parameters : TROValue;
+    UserFilter : TROValue;
+    Sorting : TROValue;
+    WhereClause : TROValue;
+  end;
+
+  TDATableRequestInfoV6Data = record
+    IncludeSchema : boolean;
+    MaxRecords : Integer;
+    Parameters : Array of TDADataParameterData;
+    SQL : String;
+    UserFilter : String;
+  end;
+
+  TDATableRequestInfoV6 = class external name 'RemObjects.DataAbstract.Server.TableRequestInfoV6' (TROStructType)
+  Public
+    constructor new;
+    procedure fromObject(aItem : TDATableRequestInfoData);reintroduce;overload;
+    procedure fromObject(aItem : TJSObject);reintroduce;overload;
+    function toObject : TDATableRequestInfoV6Data;reintroduce;
+  Public
+    IncludeSchema : TROValue;
+    MaxRecords : TROValue;
+    Parameters : TDADataParameterArray;
+    Sql : TROValue;
+    UserFilter : TROValue;
+  end;
+
+  TDAUserInfoData = record
+    Attributes : array of JSValue;
+    Privileges : Array of string;
+    SessionID : String;
+    UserData : JSValue;
+    UserID : String;
+  end;
+
+  TDAUserInfo = class external name 'RemObjects.DataAbstract.Server.UserInfo' (TROStructType)
+    constructor new;
+    procedure fromObject(aItem : TDAUserInfo);reintroduce; overload;
+    procedure fromObject(aItem : TJSObject);reintroduce; overload;
+    function toObject : TDAUserInfoData;reintroduce;
+  Public
+    Attributes : TROValue;
+    Privileges : TROValue;
+    SessionID : TROValue;
+    UserData : TROValue;
+    UserID : TROValue;
+  end;
+
+  TDATableRequestInfoArray = class external name 'RemObjects.DataAbstract.Server.TableRequestInfoArray'  (TROArrayType)
+  Public
+    constructor new;
+    procedure fromObject(aItems : Array of TDATableRequestInfoData);overload;
+    procedure fromObject(aItems : Array of TDATableRequestInfoV5Data);overload;
+    procedure fromObject(aItems : Array of TDATableRequestInfoV6Data);overload;
+    procedure fromObject(aItems : array of TJSObject);overload;
+  Public
+    items : array of TDATableRequestInfo;
+  end;
+
+  TDADataAbstractService = class external name 'RemObjects.DataAbstract.Server.DataAbstractService'
+  Public
+    Constructor new(ch : TROHTTPClientChannel; msg : TROMessage; aServiceName : string);
+    Procedure GetSchema(aFilter : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure GetData(aTables : TDAStringArray; info : TDATableRequestInfoArray; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure UpdateData(aDelta : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure ExecuteCommand(aCommandName : String; params : TDADataParameterArray; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure ExecuteCommandEx(aCommandName : String; params : TDADataParameterArray; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure GetTableSchema(aTableNameArray : TDAStringArray;aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure GetCommandSchema(aCommandNameArray : TDAStringArray;aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure GetSQLData(aSQLText : String; aIncludeSchema : Boolean; aMaxRecords : Integer; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure GetSQLDataEx(aSQLText : String; aIncludeSchema : Boolean; aMaxRecords : Integer; aDynamicWhereXML : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure SQLExecuteCommand(aSQLText : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure SQLExecuteCommandEx(aSQLText,aDynamicWhereXML : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure getDatasetScripts(DatasetNames : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure RegisterForDataChangeNotification(aTableName : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+    Procedure UnregisterForDataChangeNotification(aTableName : String; aSuccess : TDASuccessEvent; aFailure : TDAFailedEvent);
+  end;
+
+implementation
+
+end.

+ 211 - 0
packages/dataabstract/rosdk.pas

@@ -0,0 +1,211 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2018 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    Remobjects SDK external classes definitions
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit ROSDK;
+
+{$mode objfpc}
+{$modeswitch externalclass}
+
+interface
+
+uses
+  Types, JS;
+
+Type
+  TROValue = record
+    dataType : string;
+    value : JSValue;
+  end;
+  TROComplexType = class;
+  TROEnumType = class;
+  TROStructType = class;
+  TROArrayType = class;
+  TROException = class;
+  TROEventSink = class;
+  TROClientChannel = class;
+  TROHTTPClientChannel = class;
+  TROMessage = class;
+  TROJSONMessage = class;
+  TROBinMessage = class;
+  TROBinHeader = class;
+  TRORemoteService = class;
+  TROService = class;
+  TROEventReceiver = class;
+
+  TROUtil = class external name 'RemObjects.UTIL' (TJSObject)
+  Public
+    class function toBase64 (Const aValue : String) : String;
+    class function fromBase64 (Const aValue : String) : String;
+    class procedure showMessage (Const msg : string);
+    class procedure showError(Const msg : string; e : JSValue);
+    class function toJSON(aValue : jsValue) : String;
+    class function parseJSON (Const aValue : string) : JSValue;
+    class function NewGuid() : string;
+    class function GuidToArray(Const aGuid : string) : TIntegerDynArray;
+    class function guidToByteArray(Const aGuid : string): String;
+    class function zeroPad(const num : string; count : integer) : String;
+    class function byteArrayToGuid(const byteArray : string) : String;
+    class function strToByteArray (const str : string) : string;
+    class function byteArrayToStr(const byteArray : string) : string;
+    class function byteArrayToUtf16(const byteArray : string) : string;
+    class function utf16ToByteArray(const str : string) : string;
+    class function ISO8601toDateTime(const str: String) : TJSDate;
+    class function dateTimeToSOAPString(aValue: TJSDate) : string;
+    class function decimalToString(aDecimal : array of integer ) : string;
+    class function stringToDecimal(const aString : String) : TIntegerDynArray;
+    class Function checkArgumentTypes (args : Array of JSValue; types : array of string) : Boolean;
+  end;
+
+
+  TROException = class external name 'RemObjects.SDK.ROException' (TJSError);
+
+  TROComplexType = class external name 'RemObjects.SDK.ROComplexType' (TJSObject)
+  Public
+    Procedure readFrom(aMessage : TROMessage);
+    Procedure writeTo(aMessage : TROMessage);
+  end;
+
+  TROEnumType = class external name 'RemObjects.SDK.ROEnumType' (TROComplexType)
+  Public
+    Procedure fromObject(aObject : TJSObject); overload;
+    Function toObject(aStoreType : Boolean) : TJSObject;overload;
+  end;
+
+  TROStructType = class external name 'RemObjects.SDK.ROStructType' (TROComplexType)
+  Public
+    Procedure fromObject(aObject : TJSObject);overload;
+    Function toObject(aStoreType : Boolean) : TJSObject;overload;
+  end;
+
+  TROArrayType = class external name 'RemObjects.SDK.ROArrayType' (TROComplexType)
+  Public
+    Procedure fromObject(aObject : Array of TJSObject);overload;
+    Function toObject(aStoreType : Boolean)  : TJSObjectDynArray;overload;
+  end;
+
+  TRODispatchSuccessEvent = reference to Procedure (msg : TROMessage);
+  TRODispatchFailedEvent = reference to Procedure (msg : TROMessage; aError : TJSError);
+  TROCallBack = Procedure;
+  TROOnLoginNeeded =  reference to procedure(aCallBack : TROCallBack);
+
+  TROClientChannel = class external name 'RemObjects.SDK.ClientChannel' (TJSObject)
+  Public
+    onLoginNeeded : TROOnLoginNeeded;
+  Public
+    Constructor new(aURL : String);
+    Procedure dispatch(aMessage : TROMessage; onSuccess : TRODispatchSuccessEvent; OnError : TRODispatchFailedEvent);
+  end;
+
+
+  TROHTTPCallback = reference to procedure (aResponse : String; aStatus : Integer);
+  TROHTTPClientChannel = class external name 'RemObjects.SDK.HTTPClientChannel' (TROClientChannel)
+  Public
+    Procedure post(aMessage : TROMessage; isBinary : Boolean; OnSuccess,OnError : TROHTTPCallback);
+  end;
+
+  TROEventSink = class external name 'RemObjects.SDK.ROEventSink' (TJSObject)
+  Public
+    Procedure readEvent(aMessage : TROMessage; aName : string);
+  end;
+
+
+  TROMessage = class external name 'RemObjects.SDK.Message' (TJSObject)
+  Public
+    constructor new;
+    Function Clone : TROMessage;
+    function getClientID : String;
+    procedure setClientID(const aValue : String);
+    function getErrorMessage : String;
+    procedure setErrorResponse (Const aResponse : String);
+    Procedure initialize (Const aServiceName,aMethodName : string; aMessageType : Integer);
+    Procedure finalize;
+    function requestStream : String; // Dummy
+    procedure setResponseStream(const aValue : String);
+    function read (const aName,aType : String) : TROValue;
+    Procedure write (const aName,aType : String; aValue : JSValue);
+    Property ClientID : String Read getClientID Write setClientID;
+  end;
+
+  TROJSONMessage = class external name 'RemObjects.SDK.JSONMessage' (TROMessage)
+  end;
+
+  TROBinHeader = class external name 'RemObjects.SDK.BinHeader' (TJSObject)
+  Public
+    function asStream: String;
+    Procedure ReadFrom(aStream : String);
+    function isValidHeader : Boolean;
+    function getCompressed : Boolean;
+    Procedure setCompressed(aValue : Boolean);
+    function getMessageType : integer;
+    Procedure setMessageType(aValue : integer);
+    procedure setClientID(aValue : String);
+    Property Compressed : Boolean Read getCompressed Write setCompressed;
+    Property MessageType : Integer Read getMessageType write SetMessageType;
+  end;
+
+  TROBinMessage = class external name 'RemObjects.SDK.BinMessage' (TROMessage)
+  public
+    constructor new;
+    Procedure writeVariant(aValue : JSValue);
+    Procedure writeinteger(aValue : Integer);
+    Procedure writeStrWithLength(aValue : string);
+    function readByte : Byte;
+    function readCompressed : String;
+    function readVariant : JSValue;
+  end;
+
+  TROEventCallback = reference to procedure (event : TJSObject); // Or TROComplexType ?
+
+  TROEventReceiver  = class external name 'RemObjects.SDK.ROEventReceiver' (TJSObject)
+  Public
+    Constructor new(aChannel : TROClientChannel; aMessage : TROMessage; aServiceName : string; aTimeOut : Integer);
+    Procedure addHandler(anEventName : String; aCallback : TROEventCallback);
+    Procedure setActive(aValue : boolean);
+    function getActive : Boolean;
+    function getTimeout : integer;
+    procedure setTimeout(aValue : Integer);
+    Procedure intPollServer;
+    Property Active : Boolean Read GetActive Write SetActive;
+    Property TimeOut : Integer read GetTimeOut Write SetTimeout;
+  end;
+
+  TRORemoteService = Class external name 'RemObjects.SDK.RemoteService' (TJSObject)
+    Constructor new(aChannel : TROClientChannel; aMessage : TROMessage; aServiceName : string);
+  end;
+
+  TROService = Class external name 'RemObjects.SDK.ROService' (TJSObject)
+  Public
+    Constructor new(aService : TRORemoteService);
+    Constructor new(aChannel : TROClientChannel; aMessage : TROMessage; aServiceName : string);
+    function getMessage : TROMessage;
+    function getChannel : TROClientChannel;
+    function getServiceName : String;
+    Property Message : TROMessage Read getMessage;
+    Property Channel : TROClientChannel Read getChannel;
+    Property ServiceName : String Read getServiceName;
+  end;
+
+  TROBinaryParser = Class external name 'BinaryParser' (TJSObject)
+    procedure warn;
+    function decodeFloat(data : JSValue; precisionbits,exponentbits :Integer) : double;
+    function encodeFloat(value: double; precisionbits,exponentbits :Integer) : string;
+    function decodeInt(data : JSValue; bits : Integer; Signed : boolean) : NativeInt;
+    function encodeInt(data : NativeInt; bits : Integer; Signed : boolean) : String;
+  end;
+
+implementation
+
+end.
+