#101 Re: mORMot 1 » mORMot Service dependencies » 2015-06-02 14:33:18

Btw, are the any examples for using dddInfraApps.pas? It looks interesting. Thanks

#102 Re: mORMot 1 » Usage figures of mORMot » 2015-05-31 08:50:12

@ab yes, I think I got the wrong end of the stick. This delphi looks like an ERP from Oracle.

Enterprise Resource Planning (ERP) from SAP and Oracle and more recently from www.odoo.com seem like an attractive alternative to company boards. They seem to be targeting their current marketing to SMEs.  I guess the main benefit of our Delphi is the 'tailor made' aspect, perhaps especially in the area of UI.

#103 Re: mORMot 1 » Usage figures of mORMot » 2015-05-29 08:13:02

If you want something to support Delphi in general this review could be useful:

http://www.ncua.gov/about/Leadership/CO … phiSAP.pdf

Apparently this USA government department saved almost $500000 in annual SAP licences by moving to Delphi and also got a system which their users found better than anything they'd used before.

#105 Re: mORMot 1 » Synopse mORMot Framework SAD 1.18.pdf is corrupted » 2015-04-20 14:05:10

Which link are you using to download it?

It seems to work for me.

#106 Re: mORMot 1 » Online docs » 2015-03-21 17:23:42

Just found the problem is only with firefox.

Chrome seems to be automatically replacing the backslashes with forward slashes

#107 Re: mORMot 1 » Online docs » 2015-03-21 09:32:26

most of the links in this section
http://synopse.info/files/html/Synopse% … #TITLE_210
give a 404 not found error

#108 mORMot 1 » Online docs » 2015-03-21 07:13:26

esmondb
Replies: 4

In the online docs at

Synopse mORMot Framework SAD 1.18.html

the link to RecordSaveJSON gives a 404 error.

Looks like the url has a backslash which should be a forward slash:

http://synopse.info/files/html/api-1.18 … RDSAVEJSON

#109 Re: mORMot 1 » JSONDecode and null values » 2015-03-18 09:19:45

Thanks smile

There's probably the same issue on lines
36307
36335

#110 mORMot 1 » JSONDecode and null values » 2015-03-18 08:58:20

esmondb
Replies: 3

I'm trying to use JSONDecode to consume some JSON with null values. If it encounters a null it breaks off the decoding due to this line in SynCommons.pas

36274     if (Value=nil) or not(EndOfObject in [',','}']) then
               exit; // invalid item separator

Can it be changed to:

36274     if not(EndOfObject in [',','}']) then
               exit; // invalid item separator

#111 Re: mORMot 1 » multipart/form-data not implemented yet » 2015-02-18 07:35:08

One way could be to use:
https://github.com/blueimp/jQuery-File-Upload

It has an option 'sequentialUploads' which divides up a request into single part multipart/form-data requests. That should make it fairly easy to extract the file from the posted data by just stripping out the headers and two boundary strings. But it wouldn't completely handle multipart/form-data requests.

#112 Re: mORMot 1 » SSL now WORKS! » 2015-02-01 08:41:20

Thanks for the quick answer.

#113 Re: mORMot 1 » SSL now WORKS! » 2015-02-01 08:19:28

esmondb wrote:

I've noticed that TSQLHttpServer still responds to non SSL requests on port 80. Is there a way to stop this?

Sorry, this statement was incorrect.
I'd forgotten to delete my old HTTPServer creation code, so in my code I should have deleted the first of these two lines:

  aHTTPServer := TSQLHttpServer.Create('80',[aServer]);
  aHTTPServer := TSQLHttpServer.Create('443',[aServer],'+',useHttpApiRegisteringURI,32,secSSL);

However, if I want to enable both SSL and plain connections is the above thread safe? ie having two TSQLHttpServer using one TSQLRestServer (yes, above I need to rename the second HttpServer.)

#114 Re: mORMot 1 » SSL now WORKS! » 2015-01-30 10:18:07

I've just set up SSL on my server and it works great!

btw when using windows 2012 the IIS Manager can simply install the certificate and registered it without having to use makecert and netsh as described in the docs (was using a free certificate from www.startssl.com)

I've noticed that TSQLHttpServer still responds to non SSL requests on port 80. Is there a way to stop this?

#115 Re: mORMot 1 » TSQLRestServerURIContext.ReturnFile headers » 2014-11-12 11:23:51

I guess that could be useful otherwise the default filename for the client is taken from the url which is probably the service name.

In which case the function could become:

procedure TSQLRestServerURIContext.ReturnFile(const FileName: TFileName;
  Handle304NotModified: boolean; const ContentType: RawUTF8; const AttachmentFileName: RawUTF8{=''});
var FileTime: TDateTime;
    clientHash, serverHash: RawUTF8;
begin
  FileTime := FileAgeToDateTime(FileName);
  if FileTime=0 then
    Error('',HTML_NOTFOUND) else begin
    if ContentType<>'' then
      Call.OutHead := HEADER_CONTENT_TYPE+ContentType else
      Call.OutHead := HEADER_CONTENT_TYPE+GetMimeContentType(nil,0,FileName);
    Call.OutStatus := HTML_SUCCESS;
    if Handle304NotModified then begin
      clientHash := FindIniNameValue(pointer(Call.InHead),'IF-NONE-MATCH: ');
      serverHash := '"'+DateTimeToIso8601(FileTime,false)+'"';
      Call.OutHead := Call.OutHead+#13#10'ETag: '+serverHash;
      if clientHash=serverHash then begin
        Call.OutStatus := HTML_NOTMODIFIED;
        exit;
      end;
    end;
    // Content-Type: appears twice: 1st to notify static file, 2nd for mime type
    Call.OutHead := STATICFILE_CONTENT_TYPE_HEADER+#13#10+Call.OutHead;
    if AttachmentFileName <> '' then
      Call.OutHead := Call.OutHead+#13#10+'Content-Disposition: attachment; filename="'+AttachmentFileName+'"';
    StringToUTF8(FileName,Call.OutBody);
  end;
end;

#116 Re: mORMot 1 » TSQLRestServerURIContext.ReturnFile headers » 2014-11-12 10:30:14

Thanks, I thought there could be an easier.

#117 mORMot 1 » TSQLRestServerURIContext.ReturnFile headers » 2014-11-12 09:26:27

esmondb
Replies: 5

Could TSQLRestServerURIContext.ReturnFile in mORMot.pas be extended so that additional http headers can be added? I want to add 'Content-Disposition: attachment; filename="myfile.ext"'.

The modified function below with an extra 'additionalHeader' parameter seems to work. Can this feature be added?

procedure TSQLRestServerURIContext.ReturnFile(const FileName: TFileName;
  Handle304NotModified: boolean; const ContentType: RawUTF8; const AdditionalHeader: RawUTF8{=''});
var FileTime: TDateTime;
    clientHash, serverHash: RawUTF8;
begin
  FileTime := FileAgeToDateTime(FileName);
  if FileTime=0 then
    Error('',HTML_NOTFOUND) else begin
    if ContentType<>'' then
      Call.OutHead := HEADER_CONTENT_TYPE+ContentType else
      Call.OutHead := HEADER_CONTENT_TYPE+GetMimeContentType(nil,0,FileName);
    Call.OutStatus := HTML_SUCCESS;
    if Handle304NotModified then begin
      clientHash := FindIniNameValue(pointer(Call.InHead),'IF-NONE-MATCH: ');
      serverHash := '"'+DateTimeToIso8601(FileTime,false)+'"';
      Call.OutHead := Call.OutHead+#13#10'ETag: '+serverHash;
      if clientHash=serverHash then begin
        Call.OutStatus := HTML_NOTMODIFIED;
        exit;
      end;
    end;
    // Content-Type: appears twice: 1st to notify static file, 2nd for mime type
    Call.OutHead := STATICFILE_CONTENT_TYPE_HEADER+#13#10+Call.OutHead;
    if AdditionalHeader <> '' then
      Call.OutHead := Call.OutHead+#13#10+AdditionalHeader;
    StringToUTF8(FileName,Call.OutBody);
  end;
end;

#118 Re: mORMot 1 » .ods (open document spreadsheet) » 2014-11-01 13:41:20

Thanks a lot for implementing this.

I've found a few bugs which should be fixed by changing the lines below in mORMot.pas

I haven't got Microsoft Office to test it but it now passes this ODF validator:
https://odf-validator.rhcloud.com/

17590 ODSContentHeader: RawUTF8 = '<office:document-content office:version="1.2" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"'+

17597 ODSsettings: RawUTF8 = XMLUTF8_HEADER+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-settings>';

17622 W.Add(FieldCount);

#119 Re: mORMot 1 » .ods (open document spreadsheet) » 2014-10-31 11:05:54

Thanks, I've got this working. Yes, mimetype was wrong and it was missing the manifest.xml.

Only problem now is McAfee complains of 'Potentially annoying download detected' in firefox. I guess this is just a false positive but could it be due to the http headers which are:
'Content-Type:application/vnd.oasis.opendocument.spreadsheet; name="download.ods'+#13#10+'Content-Disposition: attachment; filename="download.ods"'

It's probably not the most elegant but the code below works.

const //ODS boilerplate files
  //mimetype
  ODSmimetype = 'application/vnd.oasis.opendocument.spreadsheet';
  ODSXMLHeader = '<?xml version="1.0" encoding="UTF-8"?>';
  //content.xml
  ODSContentHeader = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"'+
  ' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ><office:body><office:spreadsheet><table:table table:name="Sheet1" ><table:table-column table:number-columns-repeated="';
  ODSContentFooter = '</table:table><table:named-expressions/></office:spreadsheet></office:body></office:document-content>';
  //styles.xml
  ODSstyles = ODSXMLHeader+'<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-styles>';
  //meta.xml
  ODSmeta = ODSXMLHeader+'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-meta>';
  //settings.xml
  ODSsettings = ODSXMLHeader+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"><office:settings>';
  //manifest.xml
  ODSmanifest = ODSXMLHeader+'<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2"><manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>'+
  '<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+
  '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>';


procedure TSQLTable.GetODSContent(Dest: TMemoryStream);
var U: PPUTF8Char;
    F,R: integer;
    W: TTextWriter;
    Z: TZipWrite;
    TempFile: array[0..MAX_PATH] of Char;
begin
  GetTempPath(SizeOf(TempFile) - 1, TempFile);
  GetTempFileName(TempFile, '~', 0, TempFile);
  Z := TZipWrite.Create(TempFile);
  Z.AddStored('mimetype', PAnsiChar(ODSmimetype), length(ODSmimetype));
  Z.AddDeflated('styles.xml', PAnsiChar(ODSstyles), length(ODSstyles));
  Z.AddDeflated('meta.xml', PAnsiChar(ODSmeta), length(ODSmeta));
  Z.AddDeflated('settings.xml', PAnsiChar(ODSsettings), length(ODSsettings));
  Z.AddDeflated('META-INF/manifest.xml', PAnsiChar(ODSmanifest), length(ODSmanifest));
  W := TTextWriter.Create(Dest,16384);
  try
    W.AddString(ODSXMLHeader);
    W.AddCR;
    W.AddString(ODSContentHeader);
    W.Add(RowCount);
    W.AddShort('" />');
    U := pointer(fResults);
    for R := 0 to RowCount do begin
      W.AddShort('<table:table-row>');
      for F := 0 to FieldCount-1 do begin
        W.AddShort('<table:table-cell office:value-type="string"><text:p>');
        W.AddXmlEscape(U^);
        W.AddShort('</text:p></table:table-cell>');
        inc(U); // points to next value
      end;
      W.AddShort('</table:table-row>');
    end;
    W.AddShort(ODSContentFooter);
    W.Flush;
    Z.AddDeflated('content.xml', PAnsiChar(W.Text), length(W.Text));
    Dest.LoadFromFile(TempFile);
  finally
    Z.Free;
    W.Free;
    DeleteFile(TempFile);
  end;
end;

#120 mORMot 1 » .ods (open document spreadsheet) » 2014-10-30 22:53:21

esmondb
Replies: 6

CSV doesn't handle line breaks in a field very well and maybe a minimalist ods export from TSQLTable could be a useful alternative.

Based on TSQLTable.GetCSVValues below is a function that produces the content.xml spreadsheet file of a .ods zipped file. (.ods files are generally a zip file conataining mainly xml files).

Could this be added to mORMot? Using a file saved by LibreOffice as a template I've got the boiler-plate xml down to under 1k.

I'm still working on the zipping side. Could someone help here? Ideally I want to extend the function to return a complete zipped open document file. mORMot's documentation is generally great but the part on zipping is a bit terse. I'd be grateful for any help which saves me having to work it out myself. This is the file structure of the zip file needed:

mimetype
content.xml
styles.xml
meta.xml
settings.xml
META-INF/manifest.xml

I've looked at TZipWrite but this writes to disc. Is there an equivalent which returns a memory stream?

Using windows built-in zipping the function below makes a file which is read ok by Libra Office.

reference links:
http://www.forensicswiki.org/wiki/Open_Document_Format
http://what-when-how.com/how-to-build-a … ry-part-2/
http://mashupguide.net/1.0/html/ch17s03.xhtml

const //ODS boilerplate files
  ODSXMLHeader = '<?xml version="1.0" encoding="UTF-8"?>';
  //content.xml
  ODSContentHeader = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"'+
  ' xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" ><office:body><office:spreadsheet><table:table table:name="Sheet1" ><table:table-column table:number-columns-repeated="';
  ODSContentFooter = '</table:table><table:named-expressions/></office:spreadsheet></office:body></office:document-content>';
  //styles.xml
  ODSstyles = ODSXMLHeader+'<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-styles>';
  //meta.xml
  ODSmeta = ODSXMLHeader+'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"></office:document-meta>';
  //settings.xml
  ODSsettings = ODSXMLHeader+'<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2"><office:settings>';
  //mimetype
  ODSmimetype = ODSXMLHeader+'application/vnd.oasis.opendocument.spreadsheet';


procedure TSQLTable.GetODSContent(Dest: TStream);
var U: PPUTF8Char;
    F,R: integer;
    W: TTextWriter;
begin
  W := TTextWriter.Create(Dest,16384);
  try
    W.AddString(ODSXMLHeader);
    W.AddCR;
    W.AddString(ODSContentHeader);
    W.Add(RowCount);
    W.AddShort('" />');
    U := pointer(fResults);
    for R := 0 to RowCount do begin
      W.AddShort('<table:table-row>');
      for F := 0 to FieldCount-1 do begin
        W.AddShort('<table:table-cell office:value-type="string"><text:p>');
        W.AddXmlEscape(U^);
        W.AddShort('</text:p></table:table-cell>');
        inc(U); // points to next value
      end;
      W.AddShort('</table:table-row>');
    end;
    W.AddShort(ODSContentFooter);
  finally
    W.Flush;
    W.Free;
  end;
end;

#121 Re: mORMot 1 » Switching Response Format (XML/JSON) » 2014-10-10 06:22:46

It would just be convenient to be able to select between expanded/unexpanded in the client request.

But both formats are fine. Here's a jquery plugin which detects the format and feeds it into an html table:

(function ( $ ) {

  $.fn.jsonToTable = function( JSONobj ) {
    var i, ii, rowCount, fieldCount, tableRow, fields,
    table = this;
    if (table.find("thead").length == 0) {
      table.append($("<thead></thead>").append("<tr></tr>"));
    }
    if (table.find("thead").find("tr").length == 0) {
      table.find("thead").append("<tr></tr>");
    }
    table.find("thead").find("tr").empty();
    if (table.find("tbody").length == 0) {
      table.append($("<tbody></tbody>"));
    }
    table.find("tbody").empty();
    if(JSONobj.hasOwnProperty("fieldCount")){
      fieldCount = JSONobj.fieldCount;
      for (i = 0; i < fieldCount; i++) {
        table.find("thead").find("tr").append("<th>"+JSONobj.values[i]+"</th>");
      };
      rowCount = JSONobj.rowCount;
      for (i = 0; i < rowCount; i++) {
        tableRow = $("<tr></tr>");//.addClass(rowClass);
        for ( ii = 0; ii < fieldCount; ii++) {
          tableRow.append($("<td>" + JSONobj.values[ii+((i+1)*fieldCount)] + "</td>"));
        };
        table.append(tableRow);
      };
    } else if (JSONobj.values.length > 0) {
      fields = Object.keys(JSONobj.values[0]);
      fieldCount = fields.length;
      rowCount = JSONobj.values.length;
      for (i = 0; i < fieldCount; i++) {
        table.find("thead").find("tr").append("<th>"+fields[i]+"</th>");
      };
      for (i = 0; i < rowCount; i++) {
        tableRow = $("<tr></tr>");//.addClass(rowClass);
        for ( ii = 0; ii < fieldCount; ii++) {
          tableRow.append($("<td>" + JSONobj.values[i][fields[ii]] + "</td>"));
        };
        table.append(tableRow);
      };
    }
    return table;
  };
  
}( jQuery ));

#122 Re: mORMot 1 » Switching Response Format (XML/JSON) » 2014-10-09 12:21:30

Could there be another switch, set by the HTTP header, to select between the expanded and non-expanded mORMot JSON formats?

I've been experimenting with javascript clients and it's not that difficult to expand the JSON response client side and this would give the benefit of the terser format for transmission.

#123 mORMot 1 » YUI paging link dead » 2014-10-09 10:26:16

esmondb
Replies: 0

There are some references to http://developer.yahoo.com/yui/datatable/#data in mORMot.pas for the YUI DataSource Request Syntax.

This now seems to have moved to:
http://yui.github.io/yui2/docs/yui_2.9. … .html#data

#124 Re: mORMot 1 » Sample 4 / ExecuteList » 2014-10-05 19:14:48

And I guess "if" can be an error. Thanks

#125 Re: mORMot 1 » Sample 4 / ExecuteList » 2014-10-05 18:53:29

Would it be worth adding a separate built in function to return basic model info or just field names? It could be accessed with a URI such as http://host/root/tablename/fieldnames

#126 Re: mORMot 1 » Sample 4 / ExecuteList » 2014-10-05 16:55:32

Okay but I would prefer the expanded format to return just an empty array if the result is empty. Then a javascript client wouldn't have to do a typeof check to see if the result is an object or array. This could be done by removing lines 4185-4192 in SynSQLite3.pas:

    if (result=0) and W.Expand then begin
      // we want the field names at least, even with no data: we allow RowCount=0
      W.Expand := false; //  {"FieldCount":2,"Values":["col1","col2"]}
      W.CancelAll;
      for i := 0 to FieldCount-1 do
        W.ColNames[i] := sqlite3.column_name(Request,i);
      W.AddColumns;
    end;

I can't see this breaking standard mORMot clients.

#127 Re: mORMot 1 » Sample 4 / ExecuteList » 2014-10-03 11:20:00

I understand the different formats but sample 4 has NoAJAXJSON set to false so it should be using the expanded format. However, using EngineList on the client gets a response in the non expanded format if result is an empty list otherwise the response is expanded. This seems inconsistent.

#128 mORMot 1 » Sample 4 / ExecuteList » 2014-10-03 05:24:15

esmondb
Replies: 7

When retrieving a record with sample 4 the raw JSON response from the server is something like:

[{"ID":1,"Time":135192192214,"Name":"Tim","Question":"Hello world"}]

but if a record isn't found instead of an empty JSON array which I expected you get an object:

{"fieldCount":4,"values":["ID","Time","Name","Question"],"rowCount":0}

Is this right? It uses the ExecuteList function which line 12331 of mORMot.pas says "/// Execute directly a SQL statement, expecting a list of resutls"

#129 Re: mORMot 1 » Javascript authentication » 2014-09-15 06:42:26

Here's a go at a typescript version. It's very much an experiment and untested but could be a good starting point for someone.

I've created a sample web page which emulates the sample 4 client. It's a bit of a hack as there isn't any client side ORM but basic functions work.

It uses a 'singleton' design which made it simpler to write but restricts it to one active login at a time - there's probably a better way. On the plus

side it has no JavaScript library dependencies. Any feedback welcome.

mORMotClient.ts

module mORMot {

  function getXhr(): XMLHttpRequest {
    for(var i=0; i<4; i++){
      try{
        return i ?
          new ActiveXObject([, "Msxml2", "Msxml3", "Microsoft"][i] + ".XMLHTTP")
          : new XMLHttpRequest;
      }
      catch(e){
        // ignore when it fails.
      }
    }
  }

  export interface logInOutCallback {
    (success: boolean, data?: string): void;
  }

  export interface onServerRespose {
    (HTTPstatus: number, data?:{} ) : void;
  }

  export class Client {
    private static _instance: Client; //singleton
    private static allowInstantiation:Boolean;
    public static getInstance():Client{
      if (Client._instance == null) {
        Client.allowInstantiation = true;
        Client._instance = new Client();
        Client.allowInstantiation = false;
      }
      return Client._instance;
    }
    private User : string = "";
    private Root : string = "";
    private ServerURL : string = "";
    private loggedIn : boolean = false;
    private SessionID : number = 0;
    private SessionIDHexa8 : string = "";
    private SessionPrivateKey : number = 0;
    private SessionTickCountOffset : number = 0;
    private PasswordHashHexa : string = "";
    private ServerTimeStampOffset : number = 0;
    private LoginCallback : logInOutCallback;
    private LogoutCallback : logInOutCallback;
    constructor() {
      if (!Client.allowInstantiation) {
        throw new Error("Error: Instantiation failed: Use mORMot.Client.getInstance() instead of new.");
      }
    }

    //
    //   hexadecimal representation of a number
    //   (note toString(16) is implementation-dependant, and
    //   in IE returns signed numbers when used on full words)
    //
    private Sha256toHexStr(n: number): string{
      var s="", v, i;
      for (i=7; i>=0; i = i -1) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
      return s;
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
    /*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
    /*              single-byte character encoding (c) Chris Veness 2002-2010                         */
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

    //var Utf8 = {};  // Utf8 namespace

    /**
     * Encode multi-byte Unicode string into utf-8 multiple single-byte characters
     * (BMP / basic multilingual plane only)
     *
     * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
     *
     * @param {String} strUni Unicode string to be encoded as UTF-8
     * @returns {String} encoded string
     */
    private Utf8encode(strUni: string): string {
      // use regular expressions & String.replace callback function for better efficiency
      // than procedural approaches
      var strUtf = strUni.replace(
          /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
          function(c) {
            var cc = c.charCodeAt(0);
            return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
        );
      strUtf = strUtf.replace(
          /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
          function(c) {
            var cc = c.charCodeAt(0);
            return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
        );
      return strUtf;
    }

    /**
     * Decode utf-8 encoded string back into multi-byte Unicode characters
     *
     * @param {String} strUtf UTF-8 string to be decoded back to Unicode
     * @returns {String} decoded string
     */
    private Utf8decode(strUtf: string): string {
      // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
      var strUni = strUtf.replace(
          /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
          function(c) {  // (note parentheses for precence)
            var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f);
            return String.fromCharCode(cc); }
        );
      strUni = strUni.replace(
          /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
          function(c) {  // (note parentheses for precence)
            var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
            return String.fromCharCode(cc); }
        );
      return strUni;
    }

    private Sha256ROTR(n, x) { return (x >>> n) | (x << (32-n)); }
    private Sha256Sigma0(x) { return this.Sha256ROTR(2,  x) ^ this.Sha256ROTR(13, x) ^ this.Sha256ROTR(22, x); }
    private Sha256Sigma1(x) { return this.Sha256ROTR(6,  x) ^ this.Sha256ROTR(11, x) ^ this.Sha256ROTR(25, x); }
    private Sha256sigma0(x) { return this.Sha256ROTR(7,  x) ^ this.Sha256ROTR(18, x) ^ (x>>>3);  }
    private Sha256sigma1(x) { return this.Sha256ROTR(17, x) ^ this.Sha256ROTR(19, x) ^ (x>>>10); }
    private Sha256Ch(x, y, z)  { return (x & y) ^ (~x & z); }
    private Sha256Maj(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); }

    private Sha256hash(msg: string, utf8encode?: boolean): string {
      var K,H,l,N,M,i,j,W,a, b, c, d, e, f, g, h, t, T1, T2;
      utf8encode =  (typeof utf8encode == 'undefined') ? true : utf8encode;
      // convert string to UTF-8, as SHA only deals with byte-streams
      if (utf8encode) {msg = this.Utf8encode(msg);}
      // constants [§4.2.2]
      K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
           0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
           0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
           0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
           0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
           0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
           0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
           0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
      // initial hash value [§5.3.1]
      H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

      // PREPROCESSING      
      msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]

      // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
      l = msg.length/4 + 2;  // length (in 32-bit integers) of msg + ‘1’ + appended length
      N = Math.ceil(l/16);   // number of 16-integer-blocks required to hold 'l' ints
      M = [N];//new Array(N);

      for (i=0; i<N; i = i+1) {
          M[i] = new Array(16);
          for (j=0; j<16; j = j +1) {  // encode 4 chars per integer, big-endian encoding
              M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
                        (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
          } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
      }
      // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
      // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
      // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
      M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
      M[N-1][15] = (((msg.length-1)*8) & 0xffffffff);

      // HASH COMPUTATION [§6.1.2]

      W = new Array(64);
      for (i=0; i<N; i = i+1) {

        // 1 - prepare message schedule 'W'
        for (t=0;  t<16; t = t+1) {W[t] = M[i][t];}
        for (t=16; t<64; t = t+1) {W[t] = (this.Sha256sigma1(W[t-2]) + W[t-7] + this.Sha256sigma0(W[t-15]) + W[t-16]) & 0xffffffff;}

        // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
        a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];

        // 3 - main loop (note 'addition modulo 2^32')
        for (t=0; t<64; t = t+1) {
          T1 = h + this.Sha256Sigma1(e) + this.Sha256Ch(e, f, g) + K[t] + W[t];
          T2 = this.Sha256Sigma0(a) + this.Sha256Maj(a, b, c);
          h = g;
          g = f;
          f = e;
          e = (d + T1) & 0xffffffff;
          d = c;
          c = b;
          b = a;
          a = (T1 + T2) & 0xffffffff;
        }
         // 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
        H[0] = (H[0]+a) & 0xffffffff;
        H[1] = (H[1]+b) & 0xffffffff;
        H[2] = (H[2]+c) & 0xffffffff;
        H[3] = (H[3]+d) & 0xffffffff;
        H[4] = (H[4]+e) & 0xffffffff;
        H[5] = (H[5]+f) & 0xffffffff;
        H[6] = (H[6]+g) & 0xffffffff;
        H[7] = (H[7]+h) & 0xffffffff;
      }

      return this.Sha256toHexStr(H[0]) + this.Sha256toHexStr(H[1]) + this.Sha256toHexStr(H[2]) + this.Sha256toHexStr(H[3]) +
             this.Sha256toHexStr(H[4]) + this.Sha256toHexStr(H[5]) + this.Sha256toHexStr(H[6]) + this.Sha256toHexStr(H[7]);
    }

    /*
      CRC-32 (as it is in ZMODEM) in table form

      Copyright (C) 1986 Gary S. Brown. You may use this program, or
      code or tables extracted from it, as desired without restriction.

      Modified by Anders Danielsson, February 5, 1989 and March 10, 2006.

      This is also known as FCS-32 (as it is in PPP), described in
      RFC-1662 by William Allen Simpson, see RFC-1662 for references.
    */
    private Crc32Tab = [ /* CRC polynomial 0xEDB88320 */
    0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
    0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
    0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
    0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
    0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
    0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
    0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
    0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
    0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
    0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
    0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
    0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
    0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
    0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
    0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
    0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
    0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
    0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
    0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
    0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
    0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
    0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
    0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
    0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
    0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
    0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
    0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
    0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
    0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
    0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
    0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
    0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D];

    private Crc32Add(crc,c)
    /*
      'crc' should be initialized to 0xFFFFFFFF and after the computation it should be
      complemented (inverted).

      CRC-32 is also known as FCS-32.

      If the FCS-32 is calculated over the data and over the complemented FCS-32, the
      result will always be 0xDEBB20E3 (without the complementation).
    */
    {
      return Client._instance.Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
    }

    private crc32(str, crc)
    {
      var n, len;
      len = str.length;
      //var crc;
      if (typeof(crc) == "undefined") { crc = 0xFFFFFFFF; }
      else {
        crc = crc^0xFFFFFFFF; //crc = ~crc;
        if (crc < 0) {
          crc = 4294967296 + crc;
        } 
      }
      //crc=0xFFFFFFFF;
      for (n=0; n<len; n = n+1) {
        crc = Client._instance.Crc32Add(crc,str.charCodeAt(n));
      }
      crc = crc^0xFFFFFFFF; //crc = ~crc;
      if (crc < 0) {
        crc = 4294967296 + crc;
      } 
      return crc;//^0xFFFFFFFF;
    }

    public logIn(url: string, root: string, username: string, password: string, callbackFunc?: logInOutCallback) {
      var SynAuth = this, d = new Date();   
      SynAuth.ServerURL = url;
      SynAuth.Root = root;
      SynAuth.User = username;
      SynAuth.PasswordHashHexa = SynAuth.Sha256hash("salt" + password);
      SynAuth.SessionTickCountOffset = d.getTime();
      if (callbackFunc) { SynAuth.LoginCallback = callbackFunc; }
      var XHR: XMLHttpRequest = getXhr();
      XHR.open("GET", SynAuth.ServerURL + "/" + SynAuth.Root + "/TimeStamp");
      XHR.onreadystatechange = SynAuth.onGotTimestamp;
      XHR.send(null);
    }

    public logout(callbackFunc?: logInOutCallback): void {
      if (callbackFunc) { Client._instance.LogoutCallback = callbackFunc; }
      var XHR: XMLHttpRequest = getXhr();
      XHR.open("GET", Client._instance.ServerURL +"/"+ Client._instance.signUrl(Client._instance.Root+"/Auth?UserName="+Client._instance.User

+"&Session="+Client._instance.SessionID));
      //XHR.onreadystatechange = Client._instance.onLogout;
      XHR.send(null);
    }

    public logoutSync(): boolean {
      var XHR: XMLHttpRequest = getXhr();
      XHR.open("GET", Client._instance.ServerURL +"/"+ Client._instance.signUrl(Client._instance.Root+"/Auth?UserName="+Client._instance.User

+"&Session="+Client._instance.SessionID), false);
      XHR.send(null);
      var success: boolean = (XHR.status >= 200 && XHR.status < 300)
      if (success) {
        Client._instance.ServerURL = '';
        Client._instance.Root = '';
      	Client._instance.User = '';
        Client._instance.SessionID = 0;
        Client._instance.SessionIDHexa8 = '';
        Client._instance.SessionPrivateKey = 0;
        Client._instance.LoginCallback = null;
        Client._instance.LogoutCallback = null;
      }
      return success;
    }

    public signUrl(url: string): string {
      if (Client._instance.loggedIn === true) {
        var Tix, Nonce, s, ss, d = new Date();
        Tix = d.getTime() - Client._instance.SessionTickCountOffset;
        Nonce = Tix.toString(16);
        while(Nonce.length < 8) { Nonce = '0'+Nonce; }
        if (Nonce.length > 8) { Nonce = Nonce.slice(Nonce.length-8); }
        ss = Client._instance.crc32(url, Client._instance.crc32(Nonce, Client._instance.SessionPrivateKey)).toString(16);
        while(ss.length < 8) { ss = '0'+ss; }
        s = url.indexOf("?") === -1 ? url+'?session_signature=' : url+'&session_signature=';
        return s + Client._instance.SessionIDHexa8 + Nonce + ss;
      } else {
        return url;
      }
    }

    public retrieveRecordWhere(table:string, select: string, where: string, callback?: onServerRespose) {
      var XHR: XMLHttpRequest = getXhr(),
          s: string = Client._instance.ServerURL+'/'+Client._instance.signUrl(Client._instance.Root + '/' + table +
                    '?select=' + select + '&where=' +where+'&limit=1');
      XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
      XHR.open("GET", s);
      XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
      XHR.send(null);
    }

    public retrieveRecord(table:string, id:number, forUpdate:boolean) {
      var XHR: XMLHttpRequest = getXhr();
      XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
      var s: string = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
      if (!forUpdate) {
        XHR.open("GET", s);
      } else {
        XHR.open("LOCK", s);
      }
      XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
      XHR.send(null);
    }

    public addRecord(table:string, record) {
      var XHR: XMLHttpRequest = getXhr();
      var s: string = Client._instance.ServerURL+"/"+Client._instance.signUrl(Client._instance.Root + "/" + table);
      XHR.onreadystatechange = Client._instance.onAddRecordCallback;
      XHR.open("POST", s);
      XHR.send(JSON.stringify(record || {}));
    }

    public unlockRecord(table: string, id: number) {
      //UNLOCK
      var s: string = Client._instance.ServerURL+"/"+Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
      var XHR: XMLHttpRequest = getXhr();
      XHR.open("UNLOCK", "/" + s);
      XHR.onreadystatechange = Client._instance.onUnlockRecordCallback;
      XHR.send(null);
    }

    //Login callbacks
    private onGotTimestamp = function(ev: Event) {
      if (this.readyState == 4) {
        if (this.status !== 200) {
          if(Client._instance.LoginCallback instanceof Function) {
              Client._instance.LoginCallback(false, this.status + " " + this.statusText);
          }
        } else {
          var s:string = '', d = new Date(), clientTime: string = '';
          var timestamp: number = parseInt(this.responseText, 10);
          s = d.getFullYear().toString(2);
          while(s.length < 13) { s = '0'+s;}
          clientTime = s;
          s = d.getMonth().toString(2);
          while(s.length < 4) { s = '0'+s;}
          clientTime = clientTime +s;
          s = (d.getDate()-1).toString(2);
          while(s.length < 5) { s = '0'+s;}
          clientTime = clientTime +s;
          s = d.getHours().toString(2);
          while(s.length < 5) { s = '0'+s;}
          clientTime = clientTime +s;
          s = d.getMinutes().toString(2);
          while(s.length < 6) { s = '0'+s;}
          clientTime = clientTime +s;
          s = d.getSeconds().toString(2);
          while(s.length < 6) { s = '0'+s;}
          clientTime = clientTime +s;
          Client._instance.ServerTimeStampOffset = (timestamp - parseInt(clientTime,2));
          var XHR: XMLHttpRequest = getXhr();//new XMLHttpRequest();
          XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName="+Client._instance.User, true);
          XHR.onreadystatechange = Client._instance.onGotNonce;
          XHR.send(null);
          XHR.ontimeout = function () { alert("timeout!");}
        }
      }
    }

    private onGotNonce = function(ev: Event) {
      if (this.readyState == 4) {
        if (this.status !== 200) {
          if(Client._instance.LoginCallback instanceof Function) {
            Client._instance.LoginCallback(false, this.status + " " + this.statusText);
          }
        } else {
          //create client nonce
          var aClientNonce = "", s: string = "", d = new Date();
          aClientNonce = d.getFullYear().toString();
          s = d.getMonth().toString();
          if (s.length === 1) { s = '0'+s;}
          aClientNonce = aClientNonce + '-' + s;
          s = d.getDate().toString();
          if (s.length === 1) { s = '0'+s;}
          aClientNonce = aClientNonce + '-' + s + ' ';
          s = d.getHours().toString();
          if (s.length === 1) { s = '0'+s;}
          aClientNonce = aClientNonce + s;
          s = d.getMinutes().toString();
          if (s.length === 1) { s = '0'+s;}
          aClientNonce = aClientNonce + ':' + s;
          s = d.getSeconds().toString();
          if (s.length === 1) { s = '0'+s;}
          aClientNonce = aClientNonce + ':' + s;
          aClientNonce = Client._instance.Sha256hash(aClientNonce);
          var data = JSON.parse(this.responseText);
          s = Client._instance.ServerURL + "/"+Client._instance.Root+"/Auth?UserName="+Client._instance.User+"&Password=" + 
              Client._instance.Sha256hash(Client._instance.Root+data.result+aClientNonce+Client._instance.User+Client._instance.PasswordHashHexa) + 
              "&ClientNonce="+aClientNonce;
          var XHR: XMLHttpRequest = getXhr();
          XHR.open("GET", s, true);
          XHR.onreadystatechange = Client._instance.onGotSession;
          XHR.send(null);
        }
      }
    }

    private onGotSession = function(ev: Event) {
      if (this.readyState == 4) {
        if (this.status !== 200) {
          if(Client._instance.LoginCallback instanceof Function) {
            Client._instance.LoginCallback(false, this.status + " " + this.statusText);
          }
        } else {
          var data = JSON.parse(this.responseText);
          var i = data.result.indexOf("+");
          Client._instance.SessionID = parseInt(data.result.slice(0, i), 10);
          Client._instance.SessionIDHexa8 = Client._instance.SessionID.toString(16);
          while(Client._instance.SessionIDHexa8.length < 8) { Client._instance.SessionIDHexa8 = '0'+Client._instance.SessionIDHexa8; }
          Client._instance.loggedIn = true;
          Client._instance.SessionPrivateKey = Client._instance.crc32(Client._instance.PasswordHashHexa, Client._instance.crc32(data.result, 0));
          if(Client._instance.LoginCallback instanceof Function) {
            Client._instance.LoginCallback(true, Client._instance.User);
          }
        }
      }
    }

    private onLogout = function (ev: Event) {
      if (this.readyState == 4) {
        var success: boolean = (this.status >= 200 && this.status < 300);
        if (Client._instance.LogoutCallback instanceof Function) {
          Client._instance.LogoutCallback(success, "");
        }
        if (success) {
          Client._instance.ServerURL = '';
          Client._instance.Root = '';
          Client._instance.User = '';
          Client._instance.SessionID = 0;
          Client._instance.SessionIDHexa8 = "";
          Client._instance.SessionPrivateKey = 0;
          Client._instance.LoginCallback = null;
          Client._instance.LogoutCallback = null;
        }
      }
    }

    private onRetrieveRecordCallback = function (ev: Event) {
      if (this.readyState == 4) {
        //var success = (this.status >= 200 && this.status < 300);
        var obj = JSON.parse(this.response) || {}; //JSON.parse(this.responseText || {});
        if (Client._instance.onRetrieveRecord instanceof Function){//onRetrieveRecord
          Client._instance.onRetrieveRecord(this.status, obj);
        }
      }
    }

    private onAddRecordCallback = function (ev: Event) {
      if (this.readyState == 4) {
        if (Client._instance.onAddRecord instanceof Function) {
          Client._instance.onAddRecord(this.status, this.getResponseHeader('Location'));
        }
      }
    }

    private onUnlockRecordCallback = function (ev: Event) {
      if (this.readyState == 4) {
        var success = (this.status >= 200 && this.status < 300);
        if(Client._instance.onUnlockRecord instanceof Function){
          Client._instance.onUnlockRecord(success);
        }
      }
    }

    //public events
    public onRetrieveRecord: (HTTPstatus: number, data:{}) => void;
    public onAddRecord: (HTTPstatus: number, recordLocation: string) => void;
    public onUnlockRecord: (success: boolean) => void;
  }
}

mORMotClient.js

var mORMot;
(function (mORMot) {
    function getXhr() {
        for (var i = 0; i < 4; i++) {
            try  {
                return i ? new ActiveXObject([, "Msxml2", "Msxml3", "Microsoft"][i] + ".XMLHTTP") : new XMLHttpRequest;
            } catch (e) {
                // ignore when it fails.
            }
        }
    }

    var Client = (function () {
        function Client() {
            this.User = "";
            this.Root = "";
            this.ServerURL = "";
            this.loggedIn = false;
            this.SessionID = 0;
            this.SessionIDHexa8 = "";
            this.SessionPrivateKey = 0;
            this.SessionTickCountOffset = 0;
            this.PasswordHashHexa = "";
            this.ServerTimeStampOffset = 0;
            /*
            CRC-32 (as it is in ZMODEM) in table form
            
            Copyright (C) 1986 Gary S. Brown. You may use this program, or
            code or tables extracted from it, as desired without restriction.
            
            Modified by Anders Danielsson, February 5, 1989 and March 10, 2006.
            
            This is also known as FCS-32 (as it is in PPP), described in
            RFC-1662 by William Allen Simpson, see RFC-1662 for references.
            */
            this.Crc32Tab = [
                0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
                0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
                0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
                0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
                0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
                0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
                0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
                0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
                0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
                0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
                0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
                0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
                0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
                0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
                0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
                0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
                0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
                0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
                0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
                0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
                0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
                0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
                0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
                0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
                0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
                0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
                0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
                0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
                0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
                0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
                0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
                0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D];
            //Login callbacks
            this.onGotTimestamp = function (ev) {
                if (this.readyState == 4) {
                    if (this.status !== 200) {
                        if (Client._instance.LoginCallback instanceof Function) {
                            Client._instance.LoginCallback(false, this.status + " " + this.statusText);
                        }
                    } else {
                        var s = '', d = new Date(), clientTime = '';
                        var timestamp = parseInt(this.responseText, 10);
                        s = d.getFullYear().toString(2);
                        while (s.length < 13) {
                            s = '0' + s;
                        }
                        clientTime = s;
                        s = d.getMonth().toString(2);
                        while (s.length < 4) {
                            s = '0' + s;
                        }
                        clientTime = clientTime + s;
                        s = (d.getDate() - 1).toString(2);
                        while (s.length < 5) {
                            s = '0' + s;
                        }
                        clientTime = clientTime + s;
                        s = d.getHours().toString(2);
                        while (s.length < 5) {
                            s = '0' + s;
                        }
                        clientTime = clientTime + s;
                        s = d.getMinutes().toString(2);
                        while (s.length < 6) {
                            s = '0' + s;
                        }
                        clientTime = clientTime + s;
                        s = d.getSeconds().toString(2);
                        while (s.length < 6) {
                            s = '0' + s;
                        }
                        clientTime = clientTime + s;
                        Client._instance.ServerTimeStampOffset = (timestamp - parseInt(clientTime, 2));
                        var XHR = getXhr();
                        XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName=" + Client._instance.User, true);
                        XHR.onreadystatechange = Client._instance.onGotNonce;
                        XHR.send(null);
                        XHR.ontimeout = function () {
                            alert("timeout!");
                        };
                    }
                }
            };
            this.onGotNonce = function (ev) {
                if (this.readyState == 4) {
                    if (this.status !== 200) {
                        if (Client._instance.LoginCallback instanceof Function) {
                            Client._instance.LoginCallback(false, this.status + " " + this.statusText);
                        }
                    } else {
                        //create client nonce
                        var aClientNonce = "", s = "", d = new Date();
                        aClientNonce = d.getFullYear().toString();
                        s = d.getMonth().toString();
                        if (s.length === 1) {
                            s = '0' + s;
                        }
                        aClientNonce = aClientNonce + '-' + s;
                        s = d.getDate().toString();
                        if (s.length === 1) {
                            s = '0' + s;
                        }
                        aClientNonce = aClientNonce + '-' + s + ' ';
                        s = d.getHours().toString();
                        if (s.length === 1) {
                            s = '0' + s;
                        }
                        aClientNonce = aClientNonce + s;
                        s = d.getMinutes().toString();
                        if (s.length === 1) {
                            s = '0' + s;
                        }
                        aClientNonce = aClientNonce + ':' + s;
                        s = d.getSeconds().toString();
                        if (s.length === 1) {
                            s = '0' + s;
                        }
                        aClientNonce = aClientNonce + ':' + s;
                        aClientNonce = Client._instance.Sha256hash(aClientNonce);
                        var data = JSON.parse(this.responseText);
                        s = Client._instance.ServerURL + "/" + Client._instance.Root + "/Auth?UserName=" + Client._instance.User + "&Password=" + 

Client._instance.Sha256hash(Client._instance.Root + data.result + aClientNonce + Client._instance.User + Client._instance.PasswordHashHexa) + 

"&ClientNonce=" + aClientNonce;
                        var XHR = getXhr();
                        XHR.open("GET", s, true);
                        XHR.onreadystatechange = Client._instance.onGotSession;
                        XHR.send(null);
                    }
                }
            };
            this.onGotSession = function (ev) {
                if (this.readyState == 4) {
                    if (this.status !== 200) {
                        if (Client._instance.LoginCallback instanceof Function) {
                            Client._instance.LoginCallback(false, this.status + " " + this.statusText);
                        }
                    } else {
                        var data = JSON.parse(this.responseText);
                        var i = data.result.indexOf("+");
                        Client._instance.SessionID = parseInt(data.result.slice(0, i), 10);
                        Client._instance.SessionIDHexa8 = Client._instance.SessionID.toString(16);
                        while (Client._instance.SessionIDHexa8.length < 8) {
                            Client._instance.SessionIDHexa8 = '0' + Client._instance.SessionIDHexa8;
                        }
                        Client._instance.loggedIn = true;
                        Client._instance.SessionPrivateKey = Client._instance.crc32(Client._instance.PasswordHashHexa, Client._instance.crc32

(data.result, 0));
                        if (Client._instance.LoginCallback instanceof Function) {
                            Client._instance.LoginCallback(true, Client._instance.User);
                        }
                    }
                }
            };
            this.onLogout = function (ev) {
                if (this.readyState == 4) {
                    var success = (this.status >= 200 && this.status < 300);
                    if (Client._instance.LogoutCallback instanceof Function) {
                        Client._instance.LogoutCallback(success, "");
                    }
                    if (success) {
                        Client._instance.ServerURL = '';
                        Client._instance.Root = '';
                        Client._instance.User = '';
                        Client._instance.SessionID = 0;
                        Client._instance.SessionIDHexa8 = "";
                        Client._instance.SessionPrivateKey = 0;
                        Client._instance.LoginCallback = null;
                        Client._instance.LogoutCallback = null;
                    }
                }
            };
            this.onRetrieveRecordCallback = function (ev) {
                if (this.readyState == 4) {
                    //var success = (this.status >= 200 && this.status < 300);
                    var obj = JSON.parse(this.response) || {};
                    if (Client._instance.onRetrieveRecord instanceof Function) {
                        Client._instance.onRetrieveRecord(this.status, obj);
                    }
                }
            };
            this.onAddRecordCallback = function (ev) {
                if (this.readyState == 4) {
                    if (Client._instance.onAddRecord instanceof Function) {
                        Client._instance.onAddRecord(this.status, this.getResponseHeader('Location'));
                    }
                }
            };
            this.onUnlockRecordCallback = function (ev) {
                if (this.readyState == 4) {
                    var success = (this.status >= 200 && this.status < 300);
                    if (Client._instance.onUnlockRecord instanceof Function) {
                        Client._instance.onUnlockRecord(success);
                    }
                }
            };
            if (!Client.allowInstantiation) {
                throw new Error("Error: Instantiation failed: Use mORMot.Client.getInstance() instead of new.");
            }
        }
        Client.getInstance = function () {
            if (Client._instance == null) {
                Client.allowInstantiation = true;
                Client._instance = new Client();
                Client.allowInstantiation = false;
            }
            return Client._instance;
        };

        //
        //   hexadecimal representation of a number
        //   (note toString(16) is implementation-dependant, and
        //   in IE returns signed numbers when used on full words)
        //
        Client.prototype.Sha256toHexStr = function (n) {
            var s = "", v, i;
            for (i = 7; i >= 0; i = i - 1) {
                v = (n >>> (i * 4)) & 0xf;
                s += v.toString(16);
            }
            return s;
        };

        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
        /*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
        /*              single-byte character encoding (c) Chris Veness 2002-2010                         */
        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
        //var Utf8 = {};  // Utf8 namespace
        /**
        * Encode multi-byte Unicode string into utf-8 multiple single-byte characters
        * (BMP / basic multilingual plane only)
        *
        * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
        *
        * @param {String} strUni Unicode string to be encoded as UTF-8
        * @returns {String} encoded string
        */
        Client.prototype.Utf8encode = function (strUni) {
            // use regular expressions & String.replace callback function for better efficiency
            // than procedural approaches
            var strUtf = strUni.replace(/[\u0080-\u07ff]/g, function (c) {
                var cc = c.charCodeAt(0);
                return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);
            });
            strUtf = strUtf.replace(/[\u0800-\uffff]/g, function (c) {
                var cc = c.charCodeAt(0);
                return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f);
            });
            return strUtf;
        };

        /**
        * Decode utf-8 encoded string back into multi-byte Unicode characters
        *
        * @param {String} strUtf UTF-8 string to be decoded back to Unicode
        * @returns {String} decoded string
        */
        Client.prototype.Utf8decode = function (strUtf) {
            // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
            var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, function (c) {
                var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f);
                return String.fromCharCode(cc);
            });
            strUni = strUni.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, function (c) {
                var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f;
                return String.fromCharCode(cc);
            });
            return strUni;
        };

        Client.prototype.Sha256ROTR = function (n, x) {
            return (x >>> n) | (x << (32 - n));
        };
        Client.prototype.Sha256Sigma0 = function (x) {
            return this.Sha256ROTR(2, x) ^ this.Sha256ROTR(13, x) ^ this.Sha256ROTR(22, x);
        };
        Client.prototype.Sha256Sigma1 = function (x) {
            return this.Sha256ROTR(6, x) ^ this.Sha256ROTR(11, x) ^ this.Sha256ROTR(25, x);
        };
        Client.prototype.Sha256sigma0 = function (x) {
            return this.Sha256ROTR(7, x) ^ this.Sha256ROTR(18, x) ^ (x >>> 3);
        };
        Client.prototype.Sha256sigma1 = function (x) {
            return this.Sha256ROTR(17, x) ^ this.Sha256ROTR(19, x) ^ (x >>> 10);
        };
        Client.prototype.Sha256Ch = function (x, y, z) {
            return (x & y) ^ (~x & z);
        };
        Client.prototype.Sha256Maj = function (x, y, z) {
            return (x & y) ^ (x & z) ^ (y & z);
        };

        Client.prototype.Sha256hash = function (msg, utf8encode) {
            var K, H, l, N, M, i, j, W, a, b, c, d, e, f, g, h, t, T1, T2;
            utf8encode = (typeof utf8encode == 'undefined') ? true : utf8encode;

            // convert string to UTF-8, as SHA only deals with byte-streams
            if (utf8encode) {
                msg = this.Utf8encode(msg);
            }

            // constants [§4.2.2]
            K = [
                0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
                0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
                0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
                0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
                0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
                0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
                0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
                0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];

            // initial hash value [§5.3.1]
            H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

            // PREPROCESSING
            msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]

            // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
            l = msg.length / 4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
            N = Math.ceil(l / 16); // number of 16-integer-blocks required to hold 'l' ints
            M = [N]; //new Array(N);

            for (i = 0; i < N; i = i + 1) {
                M[i] = new Array(16);
                for (j = 0; j < 16; j = j + 1) {
                    M[i][j] = (msg.charCodeAt(i * 64 + j * 4) << 24) | (msg.charCodeAt(i * 64 + j * 4 + 1) << 16) | (msg.charCodeAt(i * 64 + j * 4 + 2) 

<< 8) | (msg.charCodeAt(i * 64 + j * 4 + 3));
                }
            }

            // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
            // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
            // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
            M[N - 1][14] = ((msg.length - 1) * 8) / Math.pow(2, 32);
            M[N - 1][14] = Math.floor(M[N - 1][14]);
            M[N - 1][15] = (((msg.length - 1) * 8) & 0xffffffff);

            // HASH COMPUTATION [§6.1.2]
            W = new Array(64);
            for (i = 0; i < N; i = i + 1) {
                for (t = 0; t < 16; t = t + 1) {
                    W[t] = M[i][t];
                }
                for (t = 16; t < 64; t = t + 1) {
                    W[t] = (this.Sha256sigma1(W[t - 2]) + W[t - 7] + this.Sha256sigma0(W[t - 15]) + W[t - 16]) & 0xffffffff;
                }

                // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
                a = H[0];
                b = H[1];
                c = H[2];
                d = H[3];
                e = H[4];
                f = H[5];
                g = H[6];
                h = H[7];

                for (t = 0; t < 64; t = t + 1) {
                    T1 = h + this.Sha256Sigma1(e) + this.Sha256Ch(e, f, g) + K[t] + W[t];
                    T2 = this.Sha256Sigma0(a) + this.Sha256Maj(a, b, c);
                    h = g;
                    g = f;
                    f = e;
                    e = (d + T1) & 0xffffffff;
                    d = c;
                    c = b;
                    b = a;
                    a = (T1 + T2) & 0xffffffff;
                }

                // 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
                H[0] = (H[0] + a) & 0xffffffff;
                H[1] = (H[1] + b) & 0xffffffff;
                H[2] = (H[2] + c) & 0xffffffff;
                H[3] = (H[3] + d) & 0xffffffff;
                H[4] = (H[4] + e) & 0xffffffff;
                H[5] = (H[5] + f) & 0xffffffff;
                H[6] = (H[6] + g) & 0xffffffff;
                H[7] = (H[7] + h) & 0xffffffff;
            }

            return this.Sha256toHexStr(H[0]) + this.Sha256toHexStr(H[1]) + this.Sha256toHexStr(H[2]) + this.Sha256toHexStr(H[3]) + this.Sha256toHexStr(H

[4]) + this.Sha256toHexStr(H[5]) + this.Sha256toHexStr(H[6]) + this.Sha256toHexStr(H[7]);
        };

        Client.prototype.Crc32Add = function (crc, c) {
            return Client._instance.Crc32Tab[(crc ^ c) & 0xFF] ^ ((crc >> 8) & 0xFFFFFF);
        };

        Client.prototype.crc32 = function (str, crc) {
            var n, len;
            len = str.length;

            //var crc;
            if (typeof (crc) == "undefined") {
                crc = 0xFFFFFFFF;
            } else {
                crc = crc ^ 0xFFFFFFFF; //crc = ~crc;
                if (crc < 0) {
                    crc = 4294967296 + crc;
                }
            }

            for (n = 0; n < len; n = n + 1) {
                crc = Client._instance.Crc32Add(crc, str.charCodeAt(n));
            }
            crc = crc ^ 0xFFFFFFFF; //crc = ~crc;
            if (crc < 0) {
                crc = 4294967296 + crc;
            }
            return crc;
        };

        Client.prototype.logIn = function (url, root, username, password, callbackFunc) {
            var SynAuth = this, d = new Date();
            SynAuth.ServerURL = url;
            SynAuth.Root = root;
            SynAuth.User = username;
            SynAuth.PasswordHashHexa = SynAuth.Sha256hash("salt" + password);
            SynAuth.SessionTickCountOffset = d.getTime();
            if (callbackFunc) {
                SynAuth.LoginCallback = callbackFunc;
            }
            var XHR = getXhr();
            XHR.open("GET", SynAuth.ServerURL + "/" + SynAuth.Root + "/TimeStamp");
            XHR.onreadystatechange = SynAuth.onGotTimestamp;
            XHR.send(null);
        };

        Client.prototype.logout = function (callbackFunc) {
            if (callbackFunc) {
                Client._instance.LogoutCallback = callbackFunc;
            }
            var XHR = getXhr();
            XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/Auth?UserName=" + Client._instance.User 

+ "&Session=" + Client._instance.SessionID));

            //XHR.onreadystatechange = Client._instance.onLogout;
            XHR.send(null);
        };

        Client.prototype.logoutSync = function () {
            var XHR = getXhr();
            XHR.open("GET", Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/Auth?UserName=" + Client._instance.User 

+ "&Session=" + Client._instance.SessionID), false);
            XHR.send(null);
            var success = (XHR.status >= 200 && XHR.status < 300);
            if (success) {
                Client._instance.ServerURL = '';
                Client._instance.Root = '';
                Client._instance.User = '';
                Client._instance.SessionID = 0;
                Client._instance.SessionIDHexa8 = '';
                Client._instance.SessionPrivateKey = 0;
                Client._instance.LoginCallback = null;
                Client._instance.LogoutCallback = null;
            }
            return success;
        };

        Client.prototype.signUrl = function (url) {
            if (Client._instance.loggedIn === true) {
                var Tix, Nonce, s, ss, d = new Date();
                Tix = d.getTime() - Client._instance.SessionTickCountOffset;
                Nonce = Tix.toString(16);
                while (Nonce.length < 8) {
                    Nonce = '0' + Nonce;
                }
                if (Nonce.length > 8) {
                    Nonce = Nonce.slice(Nonce.length - 8);
                }
                ss = Client._instance.crc32(url, Client._instance.crc32(Nonce, Client._instance.SessionPrivateKey)).toString(16);
                while (ss.length < 8) {
                    ss = '0' + ss;
                }
                s = url.indexOf("?") === -1 ? url + '?session_signature=' : url + '&session_signature=';
                return s + Client._instance.SessionIDHexa8 + Nonce + ss;
            } else {
                return url;
            }
        };

        Client.prototype.retrieveRecordWhere = function (table, select, where, callback) {
            var XHR = getXhr(), s = Client._instance.ServerURL + '/' + Client._instance.signUrl(Client._instance.Root + '/' + table + '?select=' + select 

+ '&where=' + where + '&limit=1');
            XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
            XHR.open("GET", s);
            XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
            XHR.send(null);
        };

        Client.prototype.retrieveRecord = function (table, id, forUpdate) {
            var XHR = getXhr();
            XHR.onreadystatechange = Client._instance.onRetrieveRecordCallback;
            var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
            if (!forUpdate) {
                XHR.open("GET", s);
            } else {
                XHR.open("LOCK", s);
            }
            XHR.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
            XHR.send(null);
        };

        Client.prototype.addRecord = function (table, record) {
            var XHR = getXhr();
            var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table);
            XHR.onreadystatechange = Client._instance.onAddRecordCallback;
            XHR.open("POST", s);
            XHR.send(JSON.stringify(record || {}));
        };

        Client.prototype.unlockRecord = function (table, id) {
            //UNLOCK
            var s = Client._instance.ServerURL + "/" + Client._instance.signUrl(Client._instance.Root + "/" + table + "/" + id);
            var XHR = getXhr();
            XHR.open("UNLOCK", "/" + s);
            XHR.onreadystatechange = Client._instance.onUnlockRecordCallback;
            XHR.send(null);
        };
        return Client;
    })();
    mORMot.Client = Client;
})(mORMot || (mORMot = {}));

sample4client.html

<!DOCTYPE html>

<html>
<head>
  <title>mORMot sample 4 client web app</title>
</head>
<body>
  <h2>mORMot sample 4 client web app</h2>
  <div id="content"></div>
  <br>

  <form>
    <button type="button" id="login">
      log in
    </button>
    <button type="button" id="logout" >
      log out
    </button>
    <br><br>
    Your name: <input type="text" id="name">
    <button type="button" id="retrieveRecord" >
      Find a previous message
    </button><br>
    Your message:<br>
    <textarea style="width:400px" rows="5" id="message"></textarea><br>
    <button type="button" id="addRecord" >
      Add the message
    </button>
  </form>

  <script src="mORMotClient.js"></script>
  <script src="sample4.js"></script>
</body>
</html>

sample4.js

var mORMotClient = mORMot.Client.getInstance();

mORMotClient.onRetrieveRecord = onGetRecord;
mORMotClient.onAddRecord = onAddRec;

function onlogin(success, data) {
    document.getElementById("content").innerHTML = "login status: " + success + ' ' + data;
}

function onAddRec(httpStatus, location) {
  document.getElementById("content").innerHTML = "http status: " + httpStatus + ' Location:' + location;
}

function loginClickHandler(ev) {
    mORMotClient.logIn("http://localhost:8080", "root", "User", "synopse", onlogin);
}

function addRecClickHandler(ev) {
    var rec = { Time: 0,
                Name: document.getElementById("name").value,
                Question: document.getElementById("message").value
              };
    mORMotClient.addRecord("SampleRecord", rec);
}

function onGetRecord(HTTPValue, rec) {
  try {
    document.getElementById("message").value = rec[0].Question;
  } catch(e) {
    document.getElementById("message").value = "";
  }
}

function retrieveRecClickHandler(ev) {
    mORMotClient.retrieveRecordWhere("SampleRecord","ID,Time,Name,Question","Name=:(%27"+encodeURIComponent(document.getElementById

("name").value)+"%27):");
}

function logoutClickHandler(ev) {
    mORMotClient.logout();
}

document.getElementById("login").onclick = loginClickHandler;
document.getElementById("addRecord").onclick = addRecClickHandler;
document.getElementById("retrieveRecord").onclick = retrieveRecClickHandler;
document.getElementById("logout").onclick = logoutClickHandler;

#130 Re: mORMot 1 » automating backup » 2014-08-28 09:28:13

Many thanks for implementing this. As soon as I've managed to make some money from my endeavours I'll make a donation.

#131 Re: mORMot 1 » OFF- TOPC - Sustainable Design » 2014-05-28 05:43:43

I think think the main thing that makes mORMot sustainable is it's KISS attitude. The problem is it's not mainstream so managers will bring up the 'what if the main developer gets run over by a bus' problem but it's gaining popularity and being KISS makes it easier for others to take up the baton.

#133 Re: mORMot 1 » Will there be TSynLog and TMemoryMapText for linux OS ? » 2014-05-18 12:51:18

re value hosting:

Speaking on the 'phone to ikoula they were very helpful but warned their 1 Euro/month windows plan could escalate to 100 Euro if you go too far over the bandwidth limit. But it looks great for a test server. I'm looking at atlantic.net which seem to offer very good value.

#134 Re: mORMot 1 » Javascript authentication » 2014-05-03 08:20:58

From what I remember I added the starting "/" to the url root as it messed up the hash if included.

#135 Re: PDF Engine » Vertical lines are missing randomaly in QReport + QRShape » 2014-03-06 17:15:55

The full version of Acrobat has a preflight feature which might find the problem.

#136 Re: mORMot 1 » dsp TMessage errors. » 2014-02-24 22:26:13

As a first step would it make sense to extract the string and JSON handling routines out of SynCommons to a separate unit? I've found this set of routines seem to compile fine under Lazarus on OSX and I guess are fairly platform agnostic. It then shouldn't be too much work to get a basic OSX client up and running.

This may be a half-baked idea but could these string and JSON routines be put in a .inc file. This could then be included in SynCommons.pas but also in a new unit called, eg, SynSrings.pas so it wouldn't be a breaking change and people could migrate gradually to new unit names.

#137 mORMot 1 » Spell checking » 2014-02-17 23:29:27

esmondb
Replies: 6

Could SQLite's Spellfix1 extension support be added to the roadmap? Not sure of the best way to implement it. An alternative could be to use hunspell.
I guess it has more features but would be more work to implement and uses much more RAM as it seems to use an in-memory hash table.

#138 Re: mORMot 1 » JSONToObject example - nested objects » 2013-12-21 11:23:20

btw this feature is great for parsing MQL query responses from www.freebase.com (hope the plug doesn't get pulled on this one)

#140 Re: mORMot 1 » JSONToObject example - nested objects » 2013-12-20 09:21:28

I my situation I'm using a different string each time.

I modified SynSelfTests so the lines above are run twice consecutively and it gives an AV sad

#141 Re: mORMot 1 » JSONToObject example - nested objects » 2013-12-20 09:02:04

I've just been experimenting with TTextWriter.RegisterCustomJSONSerializerFromText and using RecordLoadJSON which works great except if it is called more than once. The second time RecordLoadJSON is called it causes an AV while calling FinalizeNestedRecord.

This can be seen if you repeat lines 4035 - 4041 in SynSelfTests.pas more than once (copied below)

Thanks in advance for your help

U := '{"transactions":[{"TRTYPE":"INCOME","TRDATE":"2013-12-09 02:30:04","TRAA":"1.23",'+
   '"TRCAT1":{"TITYPE":"C1","TIID":"1","TICID":"","TIDSC30":"description1","TIORDER":"0","TIDEL":"false"},'+
   '"TRCAT2":{"TITYPE":"C2","TIID":"2","TICID":"","TIDSC30":"description2","TIORDER":"0","TIDEL":"false"},'+
   '"TRCAT3":{"TITYPE":"C3","TIID":"3","TICID":"","TIDSC30":"description3","TIORDER":"0","TIDEL":"false"},'+
   '"TRRMK":"Remark",'+
   '"TRACID":{"TITYPE":"AC","TIID":"4","TICID":"","TIDSC30":"account1","TIORDER":"0","TIDEL":"false"}}]}';
  RecordLoadJSON(Trans,@U[1],TypeInfo(TTestCustomJSON2));

#142 Re: mORMot 1 » Newbie to mORMot needs helps. » 2013-11-17 09:20:38

Software development is harder than it seems. I think AB's approach is correct and there's no escaping an incline on the learning curve. The 'bare bones' example's I looked at when starting out were good enough for me.

#143 Re: mORMot 1 » External DB Problem » 2013-11-12 14:14:34

+1 for progressive conversion

#144 mORMot 1 » LookupEdit instead of comboBox » 2013-10-28 17:44:16

esmondb
Replies: 1

TRecordEditForm in mORMotUIEdit.pas uses a combobox to select related TSQLRecord descendant fields in a record but this doesn't seem that scalable. Does anyone know of a good open source 'LookUp Edit' to use instead? I've had a go at writing one below but have a nasty feeling it's reinventing the wheel. (the code works but has barely been tested and is fairly crude)

unit FTSLookupEdit;

interface

uses
  Classes, StdCtrls, ExtCtrls, Messages, Controls, Windows, Forms,
  SysUtils, Graphics;

type
  TFTSLookupEdit = class;

  TFTSPopupList = class;

  TFTSTickMark = class;

  TIDLabel = class;

  TResultIDs = array of integer;

  TLookUpEvent = procedure(Sender: TFTSLookupEdit;
                           Search: string;
                           MaxResults: integer;
                           var IDs: TResultIDs;  //IDs must match ResultStrings size
                           ResultStrings: TStrings) of object;

  TFTSLookupEdit = class(TCustomLabeledEdit)
  private
    fListbox: TFTSPopupList;
    fTickMark: TFTSTickMark;
    fRecordNumberLabel: TIDLabel;
    fDropDownLineCount: integer;
    fMaxResults: integer;
    fResultIDs: TResultIDs;
    fSelectedID: integer;
    fShowID: boolean;
    fShowTick: boolean;
    fLastSearch: string;
    procedure ShowDropDown;
    procedure HideDropDown;
    procedure SetTickPosition;
    procedure SetListBoxPosition;
    procedure SetRecordNumberLabelPosition;
    procedure SetChildPositions;
    procedure SetRecordFromListbox;
    procedure SetID(ID: integer);
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
    procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
    procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
    procedure CMCancelMode(var Message: TCMCancelMode); message CM_CANCELMODE;
  protected
    fOnLookUp: TLookUpEvent;
    fOnChangedID: TNotifyEvent;
    procedure SetParent(AParent: TWinControl); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
       X, Y: Integer); override;
    procedure Change; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure CMBidimodechanged(var Message: TMessage); message CM_BIDIMODECHANGED;
    procedure CMVisiblechanged(var Message: TMessage); message CM_VISIBLECHANGED;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetBounds(ALeft: integer; ATop: integer; AWidth: integer; AHeight: integer); override;
    function Focused: Boolean; override;
    property SelectedID: integer read fSelectedID write SetID;
  published
    property DropDownLineCount: integer read fDropDownLineCount write fDropDownLineCount default 8;
    property MaxResults: integer read fMaxResults write fMaxResults default 32;
    property OnLookUp: TLookUpEvent read fOnLookUp write fOnLookUp;
    property OnChangedID: TNotifyEvent read fOnChangedID write fOnChangedID;
    property ShowID: boolean read fShowID write fShowID default true;
    property ShowTick: boolean read fShowTick write fShowTick default true;
    // Inherited properties
    property Anchors;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property ImeMode;
    property ImeName;
    property Name;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property EditLabel;
    // Inherited events
    property OnChange;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDrag;
  end;

  TFTSPopupList = class(TCustomListBox)
  private
    fLookUpOwner: TFTSLookupEdit;
    fDefaultWindowProc: TWndMethod;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
    procedure PopupListWindowProc(var Message: TMessage);
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TFTSTickMark = class(TGraphicControl)
  private
    fBitmap: TBitmap;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

  TIDLabel = class(TCustomLabel)
  protected
    procedure AdjustBounds; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

{$R *.RES}

constructor TFTSLookupEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Height := 21;
  fListbox := TFTSPopupList.Create(self);
  fListbox.Parent := self;
  fSelectedID := 0;
  fShowID := True;
  fShowTick := True;
  fDropDownLineCount := 8;
  fMaxResults := 32;
  fLastSearch := '';
  fTickMark := TFTSTickMark.Create(self);
  fTickMark.Parent := self;
  fTickMark.FreeNotification(Self);
  fRecordNumberLabel := TIDLabel.Create(Self);
  fRecordNumberLabel.Parent := self;
  fRecordNumberLabel.FreeNotification(Self);
  fRecordNumberLabel.FocusControl := Self;
  fRecordNumberLabel.Caption := '';
end;

procedure TFTSLookupEdit.SetParent(AParent: TWinControl);
begin
  inherited SetParent(AParent);
  if fTickMark <> nil then
    fTickMark.Parent := AParent;
  if fListbox <> nil then
    fListbox.Parent := AParent;
  if fRecordNumberLabel <> nil then
    fRecordNumberLabel.Parent := AParent;
end;

procedure TFTSLookupEdit.SetTickPosition;
begin
  if fTickMark <> nil then
    fTickMark.SetBounds(Left + Width - 18, Top - 18, 16, 16);
end;

procedure TFTSLookupEdit.SetListBoxPosition;
var
  P: TPoint;
  ListHeight: Integer;
begin
  if (fListBox <> nil) and (Parent <> nil) then begin
    P := parent.ClientOrigin;
    if fListbox.Items.Count < fDropDownLineCount then
      ListHeight := (fListbox.Items.Count * FListBox.ItemHeight) +2
    else
      ListHeight := (fDropDownLineCount * FListBox.ItemHeight) +2;
    if (Top + P.Y + Height + ListHeight) > Screen.WorkAreaHeight then
      fListbox.SetBounds(Left + P.X, Top + P.Y - ListHeight, Width, ListHeight)
    else
      fListbox.SetBounds(Left + P.X, Top + P.Y + Height, Width, ListHeight);
  end;
end;

procedure TFTSLookupEdit.SetRecordNumberLabelPosition;
begin
  if fRecordNumberLabel = nil then Exit;
  //should look at position option. for now just puts it top right
  fRecordNumberLabel.SetBounds(Left + Width - fRecordNumberLabel.Width - 20,
    Top - fRecordNumberLabel.Height - LabelSpacing,
    fRecordNumberLabel.Width, fRecordNumberLabel.Height);
end;

procedure TFTSLookupEdit.SetChildPositions;
begin
  SetTickPosition;
  SetListBoxPosition;
  SetRecordNumberLabelPosition;
end;

procedure TFTSLookUpEdit.SetRecordFromListbox;
var
  NewID: integer;
begin
  if fListBox.ItemIndex > -1 then begin
    NewID := fResultIDs[fListBox.ItemIndex];
    if Text <> fListBox.Items.Strings[fListBox.ItemIndex] then begin
      Text := fListBox.Items.Strings[fListBox.ItemIndex];
      SelStart := length(Text);
      SelLength := 0;
    end;
    fListBox.Items.Clear;
    SetLength(fResultIDs, 0);
  end else
    NewID := 0;
  if NewID <> fSelectedID then begin
    fSelectedID := NewID;
    if NewID > 0 then begin
      if fShowID then
        fRecordNumberLabel.Caption := inttostr(NewID);
      if fShowTick then
        fTickMark.Visible := True;
    end else begin
      fTickMark.Visible := False;
      fRecordNumberLabel.Caption := '';
    end;
    if assigned(fOnChangedID) then
      fOnChangedID(self);
  end;
end;

procedure TFTSLookUpEdit.SetID(ID: integer);
begin
  fSelectedID := ID;
  fTickMark.Visible := ID > 0;
  if fShowID then
    fRecordNumberLabel.Caption := inttostr(ID);
end;

procedure TFTSLookupEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
  SetChildPositions;
end;

function TFTSLookupEdit.Focused: Boolean;
var
  FocusedWnd: HWND;
begin
  Result := False;
  if HandleAllocated = true then begin
    FocusedWnd := GetFocus;
    Result := (FocusedWnd = fListbox.Handle) or (FocusedWnd = Handle);
  end;
end;

procedure TFTSLookupEdit.ShowDropDown;
begin
  if fListbox.Items.Count > 0 then begin
    SetListBoxPosition;
    fListbox.visible := true;
    if fListbox.ItemIndex = -1 then
      fListbox.ItemIndex := 0;
  end else
    fListbox.Visible := false;
end;

procedure TFTSLookupEdit.HideDropDown;
begin
  fListbox.visible := false;
end;

procedure TFTSLookupEdit.WMSize(var Message: TWMSize);
begin
  inherited;
  SetChildPositions;
end;

procedure TFTSLookupEdit.WMKillFocus(var Message: TWMKillFocus);
begin
  if not Focused then HideDropDown;
  inherited;
end;

procedure TFTSLookupEdit.CMCancelMode(var Message: TCMCancelMode);
begin
  if (Message.Sender <> Self) and (Message.Sender <> FListBox) then
    HideDropDown;
end;

procedure TFTSLookupEdit.KeyDown(var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_ESCAPE: HideDropDown;
    VK_UP:     if not fListBox.Visible then
                 ShowDropDown
               else if fListBox.ItemIndex > 0 then
                 fListBox.ItemIndex := fListBox.ItemIndex -1;
    VK_DOWN:   if not fListBox.Visible then
                 ShowDropDown
               else if (fListBox.Items.Count > 0) and
                (fListBox.ItemIndex < fListBox.Items.Count -1) then
                 fListBox.ItemIndex := fListBox.ItemIndex +1;
    VK_HOME:   if fListBox.Items.Count > 0 then
                 fListBox.ItemIndex := 0;
    VK_END:    if fListBox.Items.Count > 0 then
                 fListBox.ItemIndex := fListBox.Items.Count -1;
    VK_RETURN: if fListBox.Visible then begin
                 SetRecordFromListbox;
                 HideDropDown;
               end else
                 inherited KeyDown(Key, Shift);
    VK_TAB:    HideDropDown;
    else begin
      inherited KeyDown(Key, Shift);
      exit;
    end;
  end;
  Key := 0;
end;

procedure TFTSLookupEdit.KeyPress(var Key: Char);
begin
  if Key in [#13,#27] then Key := #0
  else inherited KeyPress(Key);
end;

procedure TFTSLookupEdit.MouseDown(Button: TMouseButton; Shift: TShiftState;
       X, Y: Integer);
begin
  if fListbox.Visible then fListbox.Visible := false
  else ShowDropDown;
  inherited MouseDown(Button, Shift, X, Y);
end;

procedure TFTSLookupEdit.Change;
var
  i, ii, listCnt: integer;
  s : string;
begin
  if Modified = true then begin
    s := lowercase(Text);
    fListbox.Items.BeginUpdate;
    if (fLastSearch <> '') and (pos(fLastSearch, Text) > 0) and (fListbox.Count < fMaxResults) then begin
      //local filter - may not completely match remote query.
      listCnt := fListbox.Count;
      for i := listCnt -1 downto 0 do
        if pos(s,lowercase(fListbox.Items.Strings[i])) = 0 then begin
          fListbox.Items.Delete(i);
          for ii := i to fListbox.Count-1 do
            fResultIDs[ii] := fResultIDs[ii+1];
        end;
      SetLength(fResultIDs,fListbox.Count);
    end else if assigned(fOnLookUp) then begin
      fListbox.Clear;
      fOnLookUp(self, Text, fMaxResults, fResultIDs, fListbox.Items)
    end;
    fListbox.Items.EndUpdate;
    i := fListbox.Items.IndexOf(Text);
    if i > -1 then begin
      fListbox.ItemIndex := i;
      SetRecordFromListbox;
    end else begin
      fRecordNumberLabel.Caption := '';
      fTickMark.Visible := False;
      if fSelectedID <> 0 then begin
        fSelectedID := 0;
        if assigned(fOnChangedID) then
          fOnChangedID(self);
      end;
      if (fListbox.Items.Count > 0) and (fListbox.ItemIndex = -1) then
        fListbox.ItemIndex := 0;
    end;
    ShowDropDown;
    fLastSearch := Text;
  end;
  inherited Change;
end;

procedure TFTSLookupEdit.CMBidimodechanged(var Message: TMessage);
begin
  inherited;
  if fRecordNumberLabel <> nil then
    fRecordNumberLabel.BiDiMode := BiDiMode;
end;

procedure TFTSLookupEdit.CMEnabledchanged(var Message: TMessage);
begin
  inherited;
  if fRecordNumberLabel <> nil then
    fRecordNumberLabel.Enabled := Enabled;
end;

procedure TFTSLookupEdit.CMVisiblechanged(var Message: TMessage);
begin
  inherited;
  if fTickMark <> nil then
    //ControlStyle := ControlStyle + [csNoDesignVisible];
    if fShowTick then
      fTickMark.Visible := Visible;
    //else
      //fTickMark.Visible := False;
  if fRecordNumberLabel <> nil then
    if fShowID then
      fRecordNumberLabel.Visible := Visible;
  if fListBox <> nil then
    if not visible then
      fListBox.Visible := Visible;
end;

procedure TFTSLookupEdit.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (AComponent = fTickMark) and (Operation = opRemove) then
    fTickMark := nil;
  if (AComponent = fListBox) and (Operation = opRemove) then
    fListBox := nil;
  if (AComponent = fRecordNumberLabel) and (Operation = opRemove) then
    fRecordNumberLabel := nil;
end;

{-------------}
{TFTSPopupList}
{-------------}

constructor TFTSPopupList.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fLookUpOwner := TFTSLookupEdit(AOwner);
  AutoComplete := False;
  Ctl3D := False;
  TabStop := False;
  self.BorderStyle := bsNone;
  fDefaultWindowProc := self.WindowProc;
  self.WindowProc := PopupListWindowProc;
  ControlStyle := ControlStyle + [csNoDesignVisible];
  Visible := false;
end;

procedure TFTSPopupList.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do begin
    Style := Style or WS_POPUP or WS_BORDER;
    if CheckWin32Version(5, 1) then
      WindowClass.Style := WindowClass.style or CS_SAVEBITS or CS_DROPSHADOW
    else
      WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
    if NewStyleControls then
      ExStyle := ExStyle or WS_EX_NOACTIVATE or WS_EX_TOOLWINDOW;//WS_EX_TOPMOST
    AddBiDiModeExStyle(ExStyle);
  end;
end;

procedure TFTSPopupList.PopupListWindowProc(var Message: TMessage);
var
  P: TPoint;
begin
  case Message.Msg of
    WM_MOUSEMOVE:           begin
                              P := Point(Message.LParamLo, Message.LParamHi);
                              if BiDiMode = bdRightToLeft then
                                P.X := - P.X;
                              self.ItemIndex := self.ItemAtPos(P,true);
                              Message.Result := 0;
                            end;
    WM_LBUTTONDOWN:         begin
                              fLookUpOwner.SetRecordFromListbox;
                              fLookUpOwner.HideDropDown;
                              Message.Result := 0;
                            end;
    WM_LBUTTONDBLCLK:       fLookUpOwner.HideDropDown;
    WM_LBUTTONUP, WM_RBUTTONDOWN..WM_MOUSELAST: Message.Result := 0;
    WM_ACTIVATE:            Message.Result := 0;
    WM_SETCURSOR:           Message.Result := 1;
    WM_MOUSEACTIVATE:       Message.Result := MA_NOACTIVATE;
    else
      fDefaultWindowProc(Message);
  end;
end;

{------------}
{TFTSTickMark}
{------------}

constructor TFTSTickMark.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fBitmap := TBitmap.Create;
  if FindResource(HInstance, pchar('TICKMARK'), RT_BITMAP) <> 0 then
    fBitmap.LoadFromResourceName(HInstance, 'TICKMARK')
  else begin
    fBitmap.Width := 16;
    fBitmap.Height := 16;
    fBitmap.Canvas.MoveTo(0,12);
    fBitmap.Canvas.LineTo(4,16);
    fBitmap.Canvas.LineTo(16,0);
  end;
  fBitmap.Transparent := true;
  Height := 16;
  Width := 16;
  Name := 'SubTick';
  Visible := False;
end;

destructor TFTSTickMark.Destroy;
begin
  fBitmap.Free;
  inherited Destroy;
end;

procedure TFTSTickMark.Paint;
begin
  with inherited Canvas do
    Draw(0,0, fBitmap);
end;

{--------}
{TIDLabel}
{--------}

constructor TIDLabel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Name := 'SubIDLabel';
end;

procedure TIDLabel.AdjustBounds;
begin
  inherited AdjustBounds;
  if height < 16 then //reserve space for top tick when top aligned
    height := 16;
  if Owner is TFTSLookupEdit then
    with Owner as TFTSLookupEdit do
      SetRecordNumberLabelPosition;
end;

procedure Register;
begin
   RegisterComponents('Additional', [TFTSLookupEdit]);
end;

end.

#145 Re: mORMot 1 » TGDIPages footer page numbers » 2013-10-01 05:24:33

Thanks for the help. I've worked out how to do it smile

#146 Re: mORMot 1 » TGDIPages footer page numbers » 2013-09-30 12:04:07

OK, I'll try and do something along the lines of ExportPDFStream.

#147 Re: mORMot 1 » TGDIPages footer page numbers » 2013-09-30 11:43:25

Could TGDIPages.SetMetaFileForPage and getMetaFileForPage be given public scope. Then I can simply do something like:

for i := 0 to NumberOfReports do
  //make report...
  report.EndDoc;
  for ii := 0 to report.PageCount -1 do begin
    MergedReport.NewPage;
    MergedReport.SetMetaFileForPage(MergedReport.PageCount-2, report.GetMetaFileForPage(ii));
  end;
end;

It's not perfect because NewPage flushes the last page overwriting the content. As a workaround the Merged report has one more page than needed and gets the penultimate page written to.

#148 Re: mORMot 1 » Lazarus support » 2013-09-23 14:52:07

That sounds great for javascript but I can't see the advantage if one is writing a client in pascal which needs to be compiled manually.

I would vote for synapse rather than indy. How much work would implementing OSX compatibility be just for client side? Seeing as it's based on FreeBSD doing it should also go a long way to making it compatible with other *NIXs.  I'm happy to help if I can.

#149 Re: mORMot 1 » Lazarus support » 2013-09-23 10:09:19

What do you mean by a 'unit code generator'?

#150 Re: mORMot 1 » Lazarus support » 2013-09-23 05:59:21

I was only thinking of implementing the client side of Sample 4

Board footer

Powered by FluxBB