Browse Source

* Initial import

michael 7 years ago
parent
commit
e6289151a9
100 changed files with 19546 additions and 0 deletions
  1. 246 0
      demo/fcldb/countries-1.json
  2. 194 0
      demo/fcldb/countries-2.json
  3. 218 0
      demo/fcldb/countries-3.json
  4. 638 0
      demo/fcldb/countries.json
  5. 14 0
      demo/fcldb/demodb.html
  6. 74 0
      demo/fcldb/demodb.lpi
  7. 131 0
      demo/fcldb/demodb.lpr
  8. 15 0
      demo/fcldb/demoload.html
  9. 73 0
      demo/fcldb/demoload.lpi
  10. 167 0
      demo/fcldb/demoload.lpr
  11. 15 0
      demo/fcldb/demorest.html
  12. 73 0
      demo/fcldb/demorest.lpi
  13. 354 0
      demo/fcldb/demorest.lpr
  14. 334 0
      demo/fcldb/restdata.pp
  15. 61 0
      demo/fcldb/restserver.lpi
  16. 90 0
      demo/fcldb/restserver.pas
  17. 2 0
      demo/fpcunit/browsertest.cfg
  18. 12 0
      demo/fpcunit/browsertest.html
  19. 81 0
      demo/fpcunit/browsertest.lpi
  20. 16 0
      demo/fpcunit/browsertest.lpr
  21. 66 0
      demo/fpcunit/demotests.pp
  22. 370 0
      demo/fpcunit/fpcunit.css
  23. 213 0
      demo/fpcunit/frmrunform.pp
  24. 42 0
      demo/fpreport/README.md
  25. 4 0
      demo/fpreport/bootstrap.min.css
  26. 695 0
      demo/fpreport/frmmain.pp
  27. 10 0
      demo/fpreport/reportdemo.html
  28. 74 0
      demo/fpreport/reportdemo.lpi
  29. 8 0
      demo/fpreport/reportdemo.lpr
  30. 622 0
      demo/hotreload/dirwatch.pp
  31. 2 0
      demo/hotreload/hotreload.cfg
  32. 9 0
      demo/hotreload/hotreload.html
  33. 70 0
      demo/hotreload/hotreload.lpi
  34. 116 0
      demo/hotreload/hotreload.lpr
  35. 62 0
      demo/hotreload/server.lpi
  36. 366 0
      demo/hotreload/server.lpr
  37. 1 0
      demo/hotreload/status.json
  38. 41 0
      demo/jquery/demoadd.html
  39. 73 0
      demo/jquery/demoadd.lpi
  40. 5 0
      demo/jquery/demoadd.pas
  41. 32 0
      demo/jquery/demoaddclass.html
  42. 5 0
      demo/jquery/demoaddclass.pas
  43. 33 0
      demo/jquery/demoaddclass2.html
  44. 16 0
      demo/jquery/demoaddclass2.pas
  45. 51 0
      demo/rtl/README.md
  46. 52 0
      demo/rtl/ajax.pas
  47. 637 0
      demo/rtl/countries.json
  48. 39 0
      demo/rtl/demo_njsprocess.pas
  49. 13 0
      demo/rtl/demoajax.html
  50. 71 0
      demo/rtl/demoajax.lpi
  51. 45 0
      demo/rtl/demoajax.lpr
  52. 16 0
      demo/rtl/demobrowserconsole.html
  53. 73 0
      demo/rtl/demobrowserconsole.lpi
  54. 16 0
      demo/rtl/demobrowserconsole.lpr
  55. 17 0
      demo/rtl/democanvas2d.html
  56. 69 0
      demo/rtl/democanvas2d.lpi
  57. 93 0
      demo/rtl/democanvas2d.pas
  58. 13 0
      demo/rtl/democlasstopas.html
  59. 69 0
      demo/rtl/democlasstopas.lpi
  60. 65 0
      demo/rtl/democlasstopas.pas
  61. 13 0
      demo/rtl/democollection.html
  62. 69 0
      demo/rtl/democollection.lpi
  63. 110 0
      demo/rtl/democollection.pas
  64. 68 0
      demo/rtl/democomponents.lpi
  65. 58 0
      demo/rtl/democomponents.lpr
  66. 69 0
      demo/rtl/demodatetime.lpi
  67. 61 0
      demo/rtl/demodatetime.pas
  68. 13 0
      demo/rtl/demodocument1.html
  69. 7 0
      demo/rtl/demodocument1.pas
  70. 12 0
      demo/rtl/demodombuttonevent.html
  71. 69 0
      demo/rtl/demodombuttonevent.lpi
  72. 42 0
      demo/rtl/demodombuttonevent.pas
  73. 69 0
      demo/rtl/demojsarray.lpi
  74. 47 0
      demo/rtl/demojsarray.pas
  75. 24 0
      demo/rtl/demojsdataarray.pas
  76. 26 0
      demo/rtl/demojsregexp.pas
  77. 50 0
      demo/rtl/demojsstring.pas
  78. 13 0
      demo/rtl/demonew.html
  79. 27 0
      demo/rtl/demonew.pas
  80. 73 0
      demo/rtl/demorouter.lpi
  81. 42 0
      demo/rtl/demorouter.pas
  82. 70 0
      demo/rtl/demortti.lpi
  83. 135 0
      demo/rtl/demortti.pas
  84. 32 0
      demo/rtl/demostringlist.pas
  85. 14 0
      demo/rtl/demouncaughtexception.html
  86. 69 0
      demo/rtl/demouncaughtexception.lpi
  87. 11 0
      demo/rtl/demouncaughtexception.pas
  88. 15 0
      demo/rtl/demoxhr.html
  89. 70 0
      demo/rtl/demoxhr.lpi
  90. 128 0
      demo/rtl/demoxhr.lpr
  91. 154 0
      src/packages/fcl-base/browserapp.pas
  92. 632 0
      src/packages/fcl-base/custapp.pas
  93. 51 0
      src/packages/fcl-base/fcl_base_pas2js.lpk
  94. 15 0
      src/packages/fcl-base/fcl_base_pas2js.pas
  95. 138 0
      src/packages/fcl-base/nodejsapp.pas
  96. 8780 0
      src/packages/fcl-db/db.pas
  97. 134 0
      src/packages/fcl-db/dbconst.pas
  98. 986 0
      src/packages/fcl-db/jsondataset.pas
  99. 53 0
      src/packages/fcl-db/pas2js_fcldb.lpk
  100. 15 0
      src/packages/fcl-db/pas2js_fcldb.pas

+ 246 - 0
demo/fcldb/countries-1.json

@@ -0,0 +1,246 @@
+{
+  "metaData" : { "fields" : [ 
+    { "name": "Name", "type": "string"}, 
+    { "name" : "Population", "type": "int" }
+    ],
+    "root" : "Data"     
+  },
+  "Data" : [
+    {
+      "Name" : "Afghanistan",
+      "Population" : 31628000
+    },
+    {
+      "Name" : "Albania",
+      "Population" : 2894000
+    },
+    {
+      "Name" : "Algeria",
+      "Population" : 38934000
+    },
+    {
+      "Name" : "Angola",
+      "Population" : 24228000
+    },
+    {
+      "Name" : "Argentina",
+      "Population" : 42980000
+    },
+    {
+      "Name" : "Armenia",
+      "Population" : 3006000
+    },
+    {
+      "Name" : "Australia",
+      "Population" : 23491000
+    },
+    {
+      "Name" : "Austria",
+      "Population" : 8534000
+    },
+    {
+      "Name" : "Azerbaijan",
+      "Population" : 9538000
+    },
+    {
+      "Name" : "Bahrain",
+      "Population" : 1362000
+    },
+    {
+      "Name" : "Bangladesh",
+      "Population" : 159078000
+    },
+    {
+      "Name" : "Belarus",
+      "Population" : 9470000
+    },
+    {
+      "Name" : "Belgium",
+      "Population" : 11225000
+    },
+    {
+      "Name" : "Benin",
+      "Population" : 10598000
+    },
+    {
+      "Name" : "Bolivia",
+      "Population" : 10562000
+    },
+    {
+      "Name" : "Bosnia and Herzegovina",
+      "Population" : 3818000
+    },
+    {
+      "Name" : "Botswana",
+      "Population" : 2220000
+    },
+    {
+      "Name" : "Brazil",
+      "Population" : 206078000
+    },
+    {
+      "Name" : "Bulgaria",
+      "Population" : 7226000
+    },
+    {
+      "Name" : "Burkina Faso",
+      "Population" : 17589000
+    },
+    {
+      "Name" : "Burundi",
+      "Population" : 10817000
+    },
+    {
+      "Name" : "Cambodia",
+      "Population" : 15328000
+    },
+    {
+      "Name" : "Cameroon",
+      "Population" : 22773000
+    },
+    {
+      "Name" : "Canada",
+      "Population" : 35540000
+    },
+    {
+      "Name" : "Central African Republic",
+      "Population" : 4804000
+    },
+    {
+      "Name" : "Chad",
+      "Population" : 13587000
+    },
+    {
+      "Name" : "Chile",
+      "Population" : 17763000
+    },
+    {
+      "Name" : "China",
+      "Population" : 1364270000
+    },
+    {
+      "Name" : "Colombia",
+      "Population" : 47791000
+    },
+    {
+      "Name" : "Congo Dem. Rep.",
+      "Population" : 74877000
+    },
+    {
+      "Name" : "Congo Rep.",
+      "Population" : 4505000
+    },
+    {
+      "Name" : "Costa Rica",
+      "Population" : 4758000
+    },
+    {
+      "Name" : "Croatia",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Cuba",
+      "Population" : 11379000
+    },
+    {
+      "Name" : "Cyprus",
+      "Population" : 1154000
+    },
+    {
+      "Name" : "Czech Republic",
+      "Population" : 10511000
+    },
+    {
+      "Name" : "Denmark",
+      "Population" : 5640000
+    },
+    {
+      "Name" : "Dominican Republic",
+      "Population" : 10406000
+    },
+    {
+      "Name" : "Ecuador",
+      "Population" : 15903000
+    },
+    {
+      "Name" : "Egypt Arab Rep.",
+      "Population" : 89580000
+    },
+    {
+      "Name" : "El Salvador",
+      "Population" : 6108000
+    },
+    {
+      "Name" : "Eritrea",
+      "Population" : 5110000
+    },
+    {
+      "Name" : "Estonia",
+      "Population" : 1314000
+    },
+    {
+      "Name" : "Ethiopia",
+      "Population" : 96959000
+    },
+    {
+      "Name" : "Finland",
+      "Population" : 5464000
+    },
+    {
+      "Name" : "France",
+      "Population" : 66207000
+    },
+    {
+      "Name" : "Gabon",
+      "Population" : 1688000
+    },
+    {
+      "Name" : "Gambia The",
+      "Population" : 1928000
+    },
+    {
+      "Name" : "Georgia",
+      "Population" : 4504000
+    },
+    {
+      "Name" : "Germany",
+      "Population" : 80890000
+    },
+    {
+      "Name" : "Ghana",
+      "Population" : 26787000
+    },
+    {
+      "Name" : "Greece",
+      "Population" : 10958000
+    },
+    {
+      "Name" : "Guatemala",
+      "Population" : 16015000
+    },
+    {
+      "Name" : "Guinea-Bissau",
+      "Population" : 1801000
+    },
+    {
+      "Name" : "Guinea",
+      "Population" : 12276000
+    },
+    {
+      "Name" : "Haiti",
+      "Population" : 10572000
+    },
+    {
+      "Name" : "Honduras",
+      "Population" : 7962000
+    },
+    {
+      "Name" : "Hong Kong SAR China",
+      "Population" : 7242000
+    },
+    {
+      "Name" : "Hungary",
+      "Population" : 9862000
+    }
+  ]
+}

+ 194 - 0
demo/fcldb/countries-2.json

@@ -0,0 +1,194 @@
+{
+  "metaData" : { "fields" : [ 
+    { "name": "Name", "type": "string"}, 
+    { "name" : "Population", "type": "int" }
+    ],
+    "root" : "Data"     
+  },
+  "Data" : [
+    {
+      "Name" : "India",
+      "Population" : 1295292000
+    },
+    {
+      "Name" : "Indonesia",
+      "Population" : 254455000
+    },
+    {
+      "Name" : "Iran Islamic Rep.",
+      "Population" : 78144000
+    },
+    {
+      "Name" : "Iraq",
+      "Population" : 34812000
+    },
+    {
+      "Name" : "Ireland",
+      "Population" : 4613000
+    },
+    {
+      "Name" : "Israel",
+      "Population" : 8215000
+    },
+    {
+      "Name" : "Italy",
+      "Population" : 61336000
+    },
+    {
+      "Name" : "Jamaica",
+      "Population" : 2721000
+    },
+    {
+      "Name" : "Japan",
+      "Population" : 127132000
+    },
+    {
+      "Name" : "Jordan",
+      "Population" : 6607000
+    },
+    {
+      "Name" : "Kazakhstan",
+      "Population" : 17289000
+    },
+    {
+      "Name" : "Kenya",
+      "Population" : 44864000
+    },
+    {
+      "Name" : "Korea Dem. Rep.",
+      "Population" : 25027000
+    },
+    {
+      "Name" : "Korea Rep.",
+      "Population" : 50424000
+    },
+    {
+      "Name" : "Kosovo",
+      "Population" : 1823000
+    },
+    {
+      "Name" : "Kuwait",
+      "Population" : 3753000
+    },
+    {
+      "Name" : "Kyrgyz Republic",
+      "Population" : 5834000
+    },
+    {
+      "Name" : "Lao PDR",
+      "Population" : 6689000
+    },
+    {
+      "Name" : "Latvia",
+      "Population" : 1990000
+    },
+    {
+      "Name" : "Lebanon",
+      "Population" : 4547000
+    },
+    {
+      "Name" : "Lesotho",
+      "Population" : 2109000
+    },
+    {
+      "Name" : "Liberia",
+      "Population" : 4397000
+    },
+    {
+      "Name" : "Libya",
+      "Population" : 6259000
+    },
+    {
+      "Name" : "Lithuania",
+      "Population" : 2929000
+    },
+    {
+      "Name" : "Macedonia FYR",
+      "Population" : 2076000
+    },
+    {
+      "Name" : "Madagascar",
+      "Population" : 23572000
+    },
+    {
+      "Name" : "Malawi",
+      "Population" : 16695000
+    },
+    {
+      "Name" : "Malaysia",
+      "Population" : 29902000
+    },
+    {
+      "Name" : "Mali",
+      "Population" : 17086000
+    },
+    {
+      "Name" : "Mauritania",
+      "Population" : 3970000
+    },
+    {
+      "Name" : "Mauritius",
+      "Population" : 1261000
+    },
+    {
+      "Name" : "Mexico",
+      "Population" : 125386000
+    },
+    {
+      "Name" : "Moldova",
+      "Population" : 3556000
+    },
+    {
+      "Name" : "Mongolia",
+      "Population" : 2910000
+    },
+    {
+      "Name" : "Morocco",
+      "Population" : 33921000
+    },
+    {
+      "Name" : "Mozambique",
+      "Population" : 27216000
+    },
+    {
+      "Name" : "Myanmar",
+      "Population" : 53437000
+    },
+    {
+      "Name" : "Namibia",
+      "Population" : 2403000
+    },
+    {
+      "Name" : "Nepal",
+      "Population" : 28175000
+    },
+    {
+      "Name" : "Netherlands",
+      "Population" : 16854000
+    },
+    {
+      "Name" : "New Zealand",
+      "Population" : 4510000
+    },
+    {
+      "Name" : "Nicaragua",
+      "Population" : 6014000
+    },
+    {
+      "Name" : "Niger",
+      "Population" : 19114000
+    },
+    {
+      "Name" : "Nigeria",
+      "Population" : 177476000
+    },
+    {
+      "Name" : "Norway",
+      "Population" : 5136000
+    },
+    {
+      "Name" : "Oman",
+      "Population" : 4236000
+    }
+  ]
+}

+ 218 - 0
demo/fcldb/countries-3.json

@@ -0,0 +1,218 @@
+{
+  "metaData" : { "fields" : [ 
+    { "name": "Name", "type": "string"}, 
+    { "name" : "Population", "type": "int" }
+    ],
+    "root" : "Data"     
+  },
+  "Data" : [
+    {
+      "Name" : "Pakistan",
+      "Population" : 185044000
+    },
+    {
+      "Name" : "Panama",
+      "Population" : 3868000
+    },
+    {
+      "Name" : "Papua New Guinea",
+      "Population" : 7464000
+    },
+    {
+      "Name" : "Paraguay",
+      "Population" : 6553000
+    },
+    {
+      "Name" : "Peru",
+      "Population" : 30973000
+    },
+    {
+      "Name" : "Philippines",
+      "Population" : 99139000
+    },
+    {
+      "Name" : "Poland",
+      "Population" : 37996000
+    },
+    {
+      "Name" : "Portugal",
+      "Population" : 10397000
+    },
+    {
+      "Name" : "Puerto Rico",
+      "Population" : 3548000
+    },
+    {
+      "Name" : "Qatar",
+      "Population" : 2172000
+    },
+    {
+      "Name" : "Romania",
+      "Population" : 19911000
+    },
+    {
+      "Name" : "Russian Federation",
+      "Population" : 143820000
+    },
+    {
+      "Name" : "Rwanda",
+      "Population" : 11342000
+    },
+    {
+      "Name" : "Saudi Arabia",
+      "Population" : 30887000
+    },
+    {
+      "Name" : "Senegal",
+      "Population" : 14673000
+    },
+    {
+      "Name" : "Serbia",
+      "Population" : 7129000
+    },
+    {
+      "Name" : "Sierra Leone",
+      "Population" : 6316000
+    },
+    {
+      "Name" : "Singapore",
+      "Population" : 5470000
+    },
+    {
+      "Name" : "Slovak Republic",
+      "Population" : 5419000
+    },
+    {
+      "Name" : "Slovenia",
+      "Population" : 2062000
+    },
+    {
+      "Name" : "Somalia",
+      "Population" : 10518000
+    },
+    {
+      "Name" : "South Africa",
+      "Population" : 54002000
+    },
+    {
+      "Name" : "South Sudan",
+      "Population" : 11911000
+    },
+    {
+      "Name" : "Spain",
+      "Population" : 46405000
+    },
+    {
+      "Name" : "Sri Lanka",
+      "Population" : 20639000
+    },
+    {
+      "Name" : "Sudan",
+      "Population" : 39350000
+    },
+    {
+      "Name" : "Swaziland",
+      "Population" : 1269000
+    },
+    {
+      "Name" : "Sweden",
+      "Population" : 9690000
+    },
+    {
+      "Name" : "Switzerland",
+      "Population" : 8190000
+    },
+    {
+      "Name" : "Syrian Arab Republic",
+      "Population" : 22158000
+    },
+    {
+      "Name" : "Tajikistan",
+      "Population" : 8296000
+    },
+    {
+      "Name" : "Tanzania",
+      "Population" : 51823000
+    },
+    {
+      "Name" : "Thailand",
+      "Population" : 67726000
+    },
+    {
+      "Name" : "Timor-Leste",
+      "Population" : 1212000
+    },
+    {
+      "Name" : "Togo",
+      "Population" : 7115000
+    },
+    {
+      "Name" : "Trinidad and Tobago",
+      "Population" : 1354000
+    },
+    {
+      "Name" : "Tunisia",
+      "Population" : 10997000
+    },
+    {
+      "Name" : "Turkey",
+      "Population" : 75932000
+    },
+    {
+      "Name" : "Turkmenistan",
+      "Population" : 5307000
+    },
+    {
+      "Name" : "Uganda",
+      "Population" : 37783000
+    },
+    {
+      "Name" : "Ukraine",
+      "Population" : 45363000
+    },
+    {
+      "Name" : "United Arab Emirates",
+      "Population" : 9086000
+    },
+    {
+      "Name" : "United Kingdom",
+      "Population" : 64510000
+    },
+    {
+      "Name" : "United States",
+      "Population" : 318857000
+    },
+    {
+      "Name" : "Uruguay",
+      "Population" : 3420000
+    },
+    {
+      "Name" : "Uzbekistan",
+      "Population" : 30743000
+    },
+    {
+      "Name" : "Venezuela RB",
+      "Population" : 30694000
+    },
+    {
+      "Name" : "Vietnam",
+      "Population" : 90730000
+    },
+    {
+      "Name" : "West Bank and Gaza",
+      "Population" : 4295000
+    },
+    {
+      "Name" : "Yemen Rep.",
+      "Population" : 26184000
+    },
+    {
+      "Name" : "Zambia",
+      "Population" : 15721000
+    },
+    {
+      "Name" : "Zimbabwe",
+      "Population" : 15246000
+    }
+  ]
+}

+ 638 - 0
demo/fcldb/countries.json

@@ -0,0 +1,638 @@
+{
+  "metaData" : { "fields" : [ 
+    { "name": "Name", "type": "string"}, 
+    { "name" : "Population", "type": "int" }
+    ],
+    "root" : "Data"     
+  },
+  "Data" : [
+    {
+      "Name" : "Afghanistan",
+      "Population" : 31628000
+    },
+    {
+      "Name" : "Albania",
+      "Population" : 2894000
+    },
+    {
+      "Name" : "Algeria",
+      "Population" : 38934000
+    },
+    {
+      "Name" : "Angola",
+      "Population" : 24228000
+    },
+    {
+      "Name" : "Argentina",
+      "Population" : 42980000
+    },
+    {
+      "Name" : "Armenia",
+      "Population" : 3006000
+    },
+    {
+      "Name" : "Australia",
+      "Population" : 23491000
+    },
+    {
+      "Name" : "Austria",
+      "Population" : 8534000
+    },
+    {
+      "Name" : "Azerbaijan",
+      "Population" : 9538000
+    },
+    {
+      "Name" : "Bahrain",
+      "Population" : 1362000
+    },
+    {
+      "Name" : "Bangladesh",
+      "Population" : 159078000
+    },
+    {
+      "Name" : "Belarus",
+      "Population" : 9470000
+    },
+    {
+      "Name" : "Belgium",
+      "Population" : 11225000
+    },
+    {
+      "Name" : "Benin",
+      "Population" : 10598000
+    },
+    {
+      "Name" : "Bolivia",
+      "Population" : 10562000
+    },
+    {
+      "Name" : "Bosnia and Herzegovina",
+      "Population" : 3818000
+    },
+    {
+      "Name" : "Botswana",
+      "Population" : 2220000
+    },
+    {
+      "Name" : "Brazil",
+      "Population" : 206078000
+    },
+    {
+      "Name" : "Bulgaria",
+      "Population" : 7226000
+    },
+    {
+      "Name" : "Burkina Faso",
+      "Population" : 17589000
+    },
+    {
+      "Name" : "Burundi",
+      "Population" : 10817000
+    },
+    {
+      "Name" : "Cambodia",
+      "Population" : 15328000
+    },
+    {
+      "Name" : "Cameroon",
+      "Population" : 22773000
+    },
+    {
+      "Name" : "Canada",
+      "Population" : 35540000
+    },
+    {
+      "Name" : "Central African Republic",
+      "Population" : 4804000
+    },
+    {
+      "Name" : "Chad",
+      "Population" : 13587000
+    },
+    {
+      "Name" : "Chile",
+      "Population" : 17763000
+    },
+    {
+      "Name" : "China",
+      "Population" : 1364270000
+    },
+    {
+      "Name" : "Colombia",
+      "Population" : 47791000
+    },
+    {
+      "Name" : "Congo Dem. Rep.",
+      "Population" : 74877000
+    },
+    {
+      "Name" : "Congo Rep.",
+      "Population" : 4505000
+    },
+    {
+      "Name" : "Costa Rica",
+      "Population" : 4758000
+    },
+    {
+      "Name" : "Croatia",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Cuba",
+      "Population" : 11379000
+    },
+    {
+      "Name" : "Cyprus",
+      "Population" : 1154000
+    },
+    {
+      "Name" : "Czech Republic",
+      "Population" : 10511000
+    },
+    {
+      "Name" : "Denmark",
+      "Population" : 5640000
+    },
+    {
+      "Name" : "Dominican Republic",
+      "Population" : 10406000
+    },
+    {
+      "Name" : "Ecuador",
+      "Population" : 15903000
+    },
+    {
+      "Name" : "Egypt Arab Rep.",
+      "Population" : 89580000
+    },
+    {
+      "Name" : "El Salvador",
+      "Population" : 6108000
+    },
+    {
+      "Name" : "Eritrea",
+      "Population" : 5110000
+    },
+    {
+      "Name" : "Estonia",
+      "Population" : 1314000
+    },
+    {
+      "Name" : "Ethiopia",
+      "Population" : 96959000
+    },
+    {
+      "Name" : "Finland",
+      "Population" : 5464000
+    },
+    {
+      "Name" : "France",
+      "Population" : 66207000
+    },
+    {
+      "Name" : "Gabon",
+      "Population" : 1688000
+    },
+    {
+      "Name" : "Gambia The",
+      "Population" : 1928000
+    },
+    {
+      "Name" : "Georgia",
+      "Population" : 4504000
+    },
+    {
+      "Name" : "Germany",
+      "Population" : 80890000
+    },
+    {
+      "Name" : "Ghana",
+      "Population" : 26787000
+    },
+    {
+      "Name" : "Greece",
+      "Population" : 10958000
+    },
+    {
+      "Name" : "Guatemala",
+      "Population" : 16015000
+    },
+    {
+      "Name" : "Guinea-Bissau",
+      "Population" : 1801000
+    },
+    {
+      "Name" : "Guinea",
+      "Population" : 12276000
+    },
+    {
+      "Name" : "Haiti",
+      "Population" : 10572000
+    },
+    {
+      "Name" : "Honduras",
+      "Population" : 7962000
+    },
+    {
+      "Name" : "Hong Kong SAR China",
+      "Population" : 7242000
+    },
+    {
+      "Name" : "Hungary",
+      "Population" : 9862000
+    },
+    {
+      "Name" : "India",
+      "Population" : 1295292000
+    },
+    {
+      "Name" : "Indonesia",
+      "Population" : 254455000
+    },
+    {
+      "Name" : "Iran Islamic Rep.",
+      "Population" : 78144000
+    },
+    {
+      "Name" : "Iraq",
+      "Population" : 34812000
+    },
+    {
+      "Name" : "Ireland",
+      "Population" : 4613000
+    },
+    {
+      "Name" : "Israel",
+      "Population" : 8215000
+    },
+    {
+      "Name" : "Italy",
+      "Population" : 61336000
+    },
+    {
+      "Name" : "Jamaica",
+      "Population" : 2721000
+    },
+    {
+      "Name" : "Japan",
+      "Population" : 127132000
+    },
+    {
+      "Name" : "Jordan",
+      "Population" : 6607000
+    },
+    {
+      "Name" : "Kazakhstan",
+      "Population" : 17289000
+    },
+    {
+      "Name" : "Kenya",
+      "Population" : 44864000
+    },
+    {
+      "Name" : "Korea Dem. Rep.",
+      "Population" : 25027000
+    },
+    {
+      "Name" : "Korea Rep.",
+      "Population" : 50424000
+    },
+    {
+      "Name" : "Kosovo",
+      "Population" : 1823000
+    },
+    {
+      "Name" : "Kuwait",
+      "Population" : 3753000
+    },
+    {
+      "Name" : "Kyrgyz Republic",
+      "Population" : 5834000
+    },
+    {
+      "Name" : "Lao PDR",
+      "Population" : 6689000
+    },
+    {
+      "Name" : "Latvia",
+      "Population" : 1990000
+    },
+    {
+      "Name" : "Lebanon",
+      "Population" : 4547000
+    },
+    {
+      "Name" : "Lesotho",
+      "Population" : 2109000
+    },
+    {
+      "Name" : "Liberia",
+      "Population" : 4397000
+    },
+    {
+      "Name" : "Libya",
+      "Population" : 6259000
+    },
+    {
+      "Name" : "Lithuania",
+      "Population" : 2929000
+    },
+    {
+      "Name" : "Macedonia FYR",
+      "Population" : 2076000
+    },
+    {
+      "Name" : "Madagascar",
+      "Population" : 23572000
+    },
+    {
+      "Name" : "Malawi",
+      "Population" : 16695000
+    },
+    {
+      "Name" : "Malaysia",
+      "Population" : 29902000
+    },
+    {
+      "Name" : "Mali",
+      "Population" : 17086000
+    },
+    {
+      "Name" : "Mauritania",
+      "Population" : 3970000
+    },
+    {
+      "Name" : "Mauritius",
+      "Population" : 1261000
+    },
+    {
+      "Name" : "Mexico",
+      "Population" : 125386000
+    },
+    {
+      "Name" : "Moldova",
+      "Population" : 3556000
+    },
+    {
+      "Name" : "Mongolia",
+      "Population" : 2910000
+    },
+    {
+      "Name" : "Morocco",
+      "Population" : 33921000
+    },
+    {
+      "Name" : "Mozambique",
+      "Population" : 27216000
+    },
+    {
+      "Name" : "Myanmar",
+      "Population" : 53437000
+    },
+    {
+      "Name" : "Namibia",
+      "Population" : 2403000
+    },
+    {
+      "Name" : "Nepal",
+      "Population" : 28175000
+    },
+    {
+      "Name" : "Netherlands",
+      "Population" : 16854000
+    },
+    {
+      "Name" : "New Zealand",
+      "Population" : 4510000
+    },
+    {
+      "Name" : "Nicaragua",
+      "Population" : 6014000
+    },
+    {
+      "Name" : "Niger",
+      "Population" : 19114000
+    },
+    {
+      "Name" : "Nigeria",
+      "Population" : 177476000
+    },
+    {
+      "Name" : "Norway",
+      "Population" : 5136000
+    },
+    {
+      "Name" : "Oman",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Pakistan",
+      "Population" : 185044000
+    },
+    {
+      "Name" : "Panama",
+      "Population" : 3868000
+    },
+    {
+      "Name" : "Papua New Guinea",
+      "Population" : 7464000
+    },
+    {
+      "Name" : "Paraguay",
+      "Population" : 6553000
+    },
+    {
+      "Name" : "Peru",
+      "Population" : 30973000
+    },
+    {
+      "Name" : "Philippines",
+      "Population" : 99139000
+    },
+    {
+      "Name" : "Poland",
+      "Population" : 37996000
+    },
+    {
+      "Name" : "Portugal",
+      "Population" : 10397000
+    },
+    {
+      "Name" : "Puerto Rico",
+      "Population" : 3548000
+    },
+    {
+      "Name" : "Qatar",
+      "Population" : 2172000
+    },
+    {
+      "Name" : "Romania",
+      "Population" : 19911000
+    },
+    {
+      "Name" : "Russian Federation",
+      "Population" : 143820000
+    },
+    {
+      "Name" : "Rwanda",
+      "Population" : 11342000
+    },
+    {
+      "Name" : "Saudi Arabia",
+      "Population" : 30887000
+    },
+    {
+      "Name" : "Senegal",
+      "Population" : 14673000
+    },
+    {
+      "Name" : "Serbia",
+      "Population" : 7129000
+    },
+    {
+      "Name" : "Sierra Leone",
+      "Population" : 6316000
+    },
+    {
+      "Name" : "Singapore",
+      "Population" : 5470000
+    },
+    {
+      "Name" : "Slovak Republic",
+      "Population" : 5419000
+    },
+    {
+      "Name" : "Slovenia",
+      "Population" : 2062000
+    },
+    {
+      "Name" : "Somalia",
+      "Population" : 10518000
+    },
+    {
+      "Name" : "South Africa",
+      "Population" : 54002000
+    },
+    {
+      "Name" : "South Sudan",
+      "Population" : 11911000
+    },
+    {
+      "Name" : "Spain",
+      "Population" : 46405000
+    },
+    {
+      "Name" : "Sri Lanka",
+      "Population" : 20639000
+    },
+    {
+      "Name" : "Sudan",
+      "Population" : 39350000
+    },
+    {
+      "Name" : "Swaziland",
+      "Population" : 1269000
+    },
+    {
+      "Name" : "Sweden",
+      "Population" : 9690000
+    },
+    {
+      "Name" : "Switzerland",
+      "Population" : 8190000
+    },
+    {
+      "Name" : "Syrian Arab Republic",
+      "Population" : 22158000
+    },
+    {
+      "Name" : "Tajikistan",
+      "Population" : 8296000
+    },
+    {
+      "Name" : "Tanzania",
+      "Population" : 51823000
+    },
+    {
+      "Name" : "Thailand",
+      "Population" : 67726000
+    },
+    {
+      "Name" : "Timor-Leste",
+      "Population" : 1212000
+    },
+    {
+      "Name" : "Togo",
+      "Population" : 7115000
+    },
+    {
+      "Name" : "Trinidad and Tobago",
+      "Population" : 1354000
+    },
+    {
+      "Name" : "Tunisia",
+      "Population" : 10997000
+    },
+    {
+      "Name" : "Turkey",
+      "Population" : 75932000
+    },
+    {
+      "Name" : "Turkmenistan",
+      "Population" : 5307000
+    },
+    {
+      "Name" : "Uganda",
+      "Population" : 37783000
+    },
+    {
+      "Name" : "Ukraine",
+      "Population" : 45363000
+    },
+    {
+      "Name" : "United Arab Emirates",
+      "Population" : 9086000
+    },
+    {
+      "Name" : "United Kingdom",
+      "Population" : 64510000
+    },
+    {
+      "Name" : "United States",
+      "Population" : 318857000
+    },
+    {
+      "Name" : "Uruguay",
+      "Population" : 3420000
+    },
+    {
+      "Name" : "Uzbekistan",
+      "Population" : 30743000
+    },
+    {
+      "Name" : "Venezuela RB",
+      "Population" : 30694000
+    },
+    {
+      "Name" : "Vietnam",
+      "Population" : 90730000
+    },
+    {
+      "Name" : "West Bank and Gaza",
+      "Population" : 4295000
+    },
+    {
+      "Name" : "Yemen Rep.",
+      "Population" : 26184000
+    },
+    {
+      "Name" : "Zambia",
+      "Population" : 15721000
+    },
+    {
+      "Name" : "Zimbabwe",
+      "Population" : 15246000
+    }
+  ]
+}

+ 14 - 0
demo/fcldb/demodb.html

@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <title>JSON Dataset demo</title>
+    <meta charset="utf-8"/>
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+    <script type="application/javascript" src="demodb.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 74 - 0
demo/fcldb/demodb.lpi

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demodb"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="pas2js_fcldb"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="pas2js_rtl"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demodb.lpr"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="demoxhr"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demodb"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 131 - 0
demo/fcldb/demodb.lpr

@@ -0,0 +1,131 @@
+program demoxhr;
+
+uses SysUtils, JS, Web, DB, JSonDataset, DBConst;
+
+Type
+
+  { TForm }
+
+  TForm = Class
+    XHR : TJSXMLHttpRequest;
+    Table,
+    Panel,
+    PanelContent,
+    Button : TJSElement;
+    DS : TExtJSJSONObjectDataSet;
+
+    function onLoad(Event: TEventListenerEvent): boolean;
+    Constructor create;
+    function CreateTable: TJSElement;
+  private
+    function ButtonClick(Event: TJSMouseEvent): boolean;
+    function CreateRow(AName: String; APopulation: NativeInt): TJSElement;
+  end;
+
+
+function TForm.CreateRow(AName : String; APopulation : NativeInt) : TJSElement;
+
+Var
+  C : TJSElement;
+
+begin
+  Result:=document.createElement('TR');
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.appendChild(Document.createTextNode(AName));
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.AppendChild(document.createTextNode(IntToStr(APopulation)));
+end;
+
+function TForm.CreateTable : TJSElement;
+
+Var
+  TH,R,H : TJSElement;
+
+begin
+  Result:=document.createElement('TABLE');
+  Result.className:='table table-striped table-bordered table-hover table-condensed';
+  TH:=document.createElement('THEAD');
+  Result.Append(TH);
+  R:=document.createElement('TR');
+  TH.Append(R);
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Name'));
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Population'));
+end;
+
+function TForm.onLoad(Event: TEventListenerEvent): boolean;
+
+var
+  i : integer;
+  C,J : TJSObject;
+  N,TB : TJSElement;
+
+begin
+  console.log('Result of call ',xhr.Status);
+{  While (PanelContent.childNodes.length>0) do
+    PanelContent.removeChild(PanelContent.childNodes.item(PanelContent.childNodes.length-1));}
+  if (xhr.status = 200) then
+    begin
+    J:=TJSJSON.parse(xhr.responseText);
+    DS.Metadata:=TJSObject(J.Properties['metaData']);
+    DS.Rows:=TJSArray(J.Properties['Data']);
+    DS.Open;
+    Table:=CreateTable;
+    Document.Body.append(Table);
+    TB:=document.createElement('TBODY');
+    Table.Append(TB);
+    While not DS.EOF do
+      begin
+      TB.Append(CreateRow(DS.FieldByName('Name').AsString,DS.FieldByName('Population').AsInteger));
+      DS.Next;
+      end;
+    end
+  else
+    begin
+    N:=Document.CreateElement('div');
+    N.appendChild(Document.createTextNode('Failed to load countries: '+IntToStr(xhr.Status)));
+    PanelContent.append(N);
+    end;
+  Result := True;
+end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+
+begin
+  xhr:=TJSXMLHttpRequest.New;
+  xhr.addEventListener('load', @OnLoad);
+  xhr.open('GET', 'countries.json', true);
+  xhr.send;
+  Result:=true;
+end;
+
+constructor TForm.create;
+
+
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button:=document.createElement('input');
+  Button['id']:='Button1';
+  Button['type']:='submit';
+  Button.className:='btn btn-default';
+  Button['value']:='Fetch countries';
+  TJSHTMLElement(Button).onclick:=@ButtonClick;
+  document.body.appendChild(panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button);
+  DS:=TExtJSJSONObjectDataSet.Create(Nil);
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 15 - 0
demo/fcldb/demoload.html

@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <title>JSON Dataset demo</title>
+    <meta charset="utf-8"/>
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+    <script type="application/javascript" src="demoload.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.showUncaughtExceptions=true;
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 73 - 0
demo/fcldb/demoload.lpi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demoload"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="pas2js_fcldb"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="pas2js_rtl"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demoload.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demoload"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 167 - 0
demo/fcldb/demoload.lpr

@@ -0,0 +1,167 @@
+program demoload;
+
+uses SysUtils, Classes, JS, Web, DB, JSONDataset, RestConnection;
+
+Type
+
+  { TRestDataset }
+
+  TRestDataset = Class(TExtJSJSONObjectDataSet)
+  private
+    FConnection: TRestConnection;
+  Protected
+    Function DoGetDataProxy: TDataProxy; override;
+  Public
+    Property Connection: TRestConnection Read FConnection Write FConnection;
+  end;
+
+  { TForm }
+
+  TForm = Class
+    Table,
+    TBody : TJSElement;
+    Panel,
+    PanelContent,
+    Button : TJSElement;
+    DS : TRestDataset;
+    Conn : TRestConnection;
+    Constructor create;
+    function CreateTable: TJSElement;
+  private
+    procedure AddRecords;
+    function ButtonClick(Event: TJSMouseEvent): boolean;
+    function CreateRow(AName: String; APopulation: NativeInt): TJSElement;
+    procedure DoAfterLoad(DataSet: TDataSet);
+    procedure DoGetURL(Sender: TComponent; aRequest: TDataRequest; Var aURL: String);
+    procedure DoLoadFail(DataSet: TDataSet; ID: Integer; const ErrorMsg: String);
+    procedure DSOpen(DataSet: TDataSet);
+  end;
+
+function TRestDataset.DoGetDataProxy: TDataProxy;
+begin
+  Result:=Connection.DataProxy;
+end;
+
+
+function TForm.CreateRow(AName : String; APopulation : NativeInt) : TJSElement;
+
+Var
+  C : TJSElement;
+
+begin
+  Result:=document.createElement('TR');
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.appendChild(Document.createTextNode(AName));
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.AppendChild(document.createTextNode(IntToStr(APopulation)));
+end;
+
+procedure TForm.DoAfterLoad(DataSet: TDataSet);
+begin
+  if Dataset.Active then
+    begin
+    Writeln('Loading additional records to table');
+    // We're on the last record of the last batch, so move forward 1 record
+    Dataset.Next;
+    AddRecords;
+    end;
+end;
+
+procedure TForm.DoGetURL(Sender: TComponent; aRequest: TDataRequest; Var aURL: String);
+begin
+  aURL:='countries-'+IntToStr(aRequest.requestID)+'.json';
+end;
+
+procedure TForm.DoLoadFail(DataSet: TDataSet; ID: Integer; const ErrorMsg: String);
+Var
+  N : TJSElement;
+begin
+  N:=Document.CreateElement('div');
+  N.appendChild(Document.createTextNode('Failed to load countries...'+ErrorMsg));
+  PanelContent.append(N);
+end;
+
+
+function TForm.CreateTable : TJSElement;
+
+Var
+  TH,R,H : TJSElement;
+
+begin
+  Result:=document.createElement('TABLE');
+  Result.className:='table table-striped table-bordered table-hover table-condensed';
+  TH:=document.createElement('THEAD');
+  Result.Append(TH);
+  R:=document.createElement('TR');
+  TH.Append(R);
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Name'));
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Population'));
+end;
+
+procedure TForm.DSOpen(DataSet: TDataSet);
+
+begin
+  console.log('Dataset opened');
+  Table:=CreateTable;
+  Document.Body.append(Table);
+  TBody:=document.createElement('TBODY');
+  Table.Append(TBody);
+  AddRecords;
+end;
+
+procedure TForm.AddRecords;
+
+begin
+  Writeln('Adding records to table');
+  While not DS.EOF do
+    begin
+    TBody.Append(CreateRow(DS.FieldByName('Name').AsString,DS.FieldByName('Population').AsInteger));
+    DS.Next;
+    end;
+end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+
+begin
+  DS.Load([],Nil);
+  Result:=true;
+end;
+
+constructor TForm.create;
+
+
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button:=document.createElement('input');
+  Button['id']:='Button1';
+  Button['type']:='submit';
+  Button.className:='btn btn-default';
+  Button['value']:='Fetch countries';
+  TJSHTMLElement(Button).onclick:=@ButtonClick;
+  document.body.appendChild(panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button);
+  DS:=TRestDataset.Create(Nil);
+  Conn:=TRestConnection.Create(nil);
+  Conn.BaseURL:='countries.json';
+  Conn.OnGetURL:=@DoGetURL;
+  DS.Connection:=Conn;
+  DS.OnLoadFail:=@DoLoadFail;
+  DS.AfterLoad:=@DoAfterLoad;
+  DS.AfterOpen:=@DSOpen;
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 15 - 0
demo/fcldb/demorest.html

@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <title>JSON Dataset demo</title>
+    <meta charset="utf-8"/>
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+    <script type="application/javascript" src="demorest.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.showUncaughtExceptions=true;
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 73 - 0
demo/fcldb/demorest.lpi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demorest"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="pas2js_fcldb"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="pas2js_rtl"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demorest.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demorest"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 354 - 0
demo/fcldb/demorest.lpr

@@ -0,0 +1,354 @@
+program demorest;
+
+uses sysutils, classes, js, web, db, jsondataset, restconnection;
+
+Type
+
+  { TRestDataset }
+
+  TRestDataset = Class(TExtJSJSONObjectDataSet)
+  private
+    FConnection: TRestConnection;
+  Protected
+    Function DoGetDataProxy: TDataProxy; override;
+  Public
+    Property Connection: TRestConnection Read FConnection Write FConnection;
+  end;
+  { TForm }
+
+  TForm = Class
+  private
+    Table,
+    TBody : TJSElement;
+    Panel,
+    PanelContent,
+    ButtonDelete,
+    ButtonAdd,
+    ButtonApplyUpdates,
+    ButtonChange,
+    ButtonFetch : TJSElement;
+    EName,
+    EPopulation: TJSHTMLInputElement;
+    DS : TRestDataset;
+    Conn : TRestConnection;
+  Public
+    Constructor create;
+    function CreateTable: TJSElement;
+    procedure AddRecords;
+    function ApplyUpdatesClick(aEvent: TJSMouseEvent): boolean;
+    function ButtonAddClick(aEvent: TJSMouseEvent): boolean;
+    function ButtonDeleteClick(aEvent: TJSMouseEvent): boolean;
+    function ButtonFetchClick(Event: TJSMouseEvent): boolean;
+    function CreateInput(aParent: TJSElement; Atype, AName, ALabel, aID: String): TJSHTMLInputElement;
+    function CreateRow(AID : Integer; AName: String; APopulation: NativeInt): TJSElement;
+    function DoAddRecord(aEvent: TJSMouseEvent): boolean;
+    procedure DoAfterLoad(DataSet: TDataSet);
+    function DoEditRecord(aEvent: TJSMouseEvent): boolean;
+    procedure DoGetURL(Sender: TComponent; aRequest: TDataRequest; Var aURL: String);
+    procedure DoLoadFail(DataSet: TDataSet; ID: Integer; const ErrorMsg: String);
+    procedure DSAfterApplyUpdates(DataSet: TDataSet);
+    procedure DSOpen(DataSet: TDataSet);
+    procedure ResetButtons(Sender: TDataset);
+    function SelectRecord(aEvent: TJSMouseEvent): boolean;
+  end;
+
+function TRestDataset.DoGetDataProxy: TDataProxy;
+begin
+  Result:=Connection.DataProxy;
+end;
+
+
+function TForm.CreateRow(AID : Integer; AName : String; APopulation : NativeInt) : TJSElement;
+
+Var
+  C : TJSElement;
+  S : String;
+
+begin
+  Result:=document.createElement('TR');
+  S:=IntToStr(AID);
+  Result.id:=S;
+  C:=document.createElement('TD');
+  C.id:=S+'_name';
+  Result.Append(C);
+  C.appendChild(Document.createTextNode(AName));
+  Result.id:=IntToStr(AID);
+  C:=document.createElement('TD');
+  C.id:=S+'_population';
+  Result.Append(C);
+  C.AppendChild(document.createTextNode(IntToStr(APopulation)));
+  TJSHTMLElement(Result).onclick:=@SelectRecord;
+end;
+
+function TForm.DoAddRecord(aEvent: TJSMouseEvent): boolean;
+
+begin
+  DS.Append;
+  DS.FieldByName('name').AsString:=EName.value;
+  DS.FieldByName('population').AsString:=EPopulation.value;
+  DS.Post;
+  Result:=True;
+end;
+
+function TForm.DoEditRecord(aEvent: TJSMouseEvent): boolean;
+
+Var
+  E : TJSElement;
+
+begin
+  Writeln('Updating record: ',DS.RecNo);
+  DS.Edit;
+  DS.FieldByName('name').AsString:=EName.value;
+  DS.FieldByName('population').AsString:=EPopulation.value;
+  DS.Post;
+  E:=Document.getElementById(IntToStr(DS.RecNo)+'_name');
+  if Assigned(E) then
+    E.innerText:=EName.value;
+  E:=Document.getElementById(IntToStr(DS.RecNo)+'_population');
+  if Assigned(E) then
+    E.innerText:=EPopulation.value;
+  Result:=True;
+end;
+
+procedure TForm.DoAfterLoad(DataSet: TDataSet);
+begin
+  if Dataset.Active then
+    begin
+    Writeln('Loading additional records to table');
+    // We're on the last record of the last batch, so move forward 1 record
+    Dataset.Next;
+    AddRecords;
+    end;
+end;
+
+procedure TForm.DoGetURL(Sender: TComponent; aRequest: TDataRequest; Var aURL: String);
+begin
+  aURL:='http://localhost:3000/countries/?limit=20&offset='+IntToStr((aRequest.RequestID-1)*20);
+end;
+
+procedure TForm.DoLoadFail(DataSet: TDataSet; ID: Integer; const ErrorMsg: String);
+Var
+  N : TJSElement;
+begin
+  N:=Document.CreateElement('div');
+  N.appendChild(Document.createTextNode('Failed to load countries...'+ErrorMsg));
+  PanelContent.append(N);
+end;
+
+procedure TForm.DSAfterApplyUpdates(DataSet: TDataSet);
+begin
+  Window.Alert('Updates applied on server!');
+  EName.value:='';
+  EPopulation.value:='0';
+end;
+
+
+function TForm.CreateTable : TJSElement;
+
+Var
+  TH,R,H : TJSElement;
+
+begin
+  Result:=document.createElement('TABLE');
+  Result.className:='table table-striped table-bordered table-hover table-condensed';
+  TH:=document.createElement('THEAD');
+  Result.Append(TH);
+  R:=document.createElement('TR');
+  TH.Append(R);
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Name'));
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Population'));
+end;
+
+procedure TForm.DSOpen(DataSet: TDataSet);
+
+begin
+  console.log('Dataset opened');
+  if not Assigned(Table) then
+    begin
+    Table:=CreateTable;
+    Document.Body.append(Table);
+    TBody:=document.createElement('TBODY');
+    Table.Append(TBody);
+    end;
+  DS.First;
+  AddRecords;
+end;
+
+function TForm.SelectRecord(aEvent: TJSMouseEvent): boolean;
+
+Var
+  aID : integer;
+  e : TJSElement;
+
+begin
+  e:=aEvent.target;
+  While Assigned(e) and Not SameText(e.nodeName,'tr') do
+    e:=e.parentElement;
+  if Not Assigned(E) then exit;
+  aId:=StrToIntDef(e.ID,-1);
+  Writeln('Jumping to record : ',aID);
+  if (AID<>-1) then
+    begin
+    DS.RecNo:=AID;
+    EName.value:=DS.FieldByName('Name').AsString;
+    EPopulation.value:=DS.FieldByName('Population').AsString;
+    TJSHTMLInputElement(ButtonChange).disabled:=false;
+    TJSHTMLInputElement(ButtonDelete).disabled:=false;
+    end
+end;
+
+procedure TForm.AddRecords;
+
+begin
+  While not DS.EOF do
+    begin
+    // Writeln(DS.RecNo, ' - ',DS.FieldByName('Name').AsString,'-',DS.FieldByName('Population').AsInteger);
+    TBody.Append(CreateRow(DS.RecNo, DS.FieldByName('Name').AsString,DS.FieldByName('Population').AsInteger));
+    DS.Next;
+    end;
+  TJSHTMLInputElement(ButtonChange).disabled:=false;
+end;
+
+function TForm.ApplyUpdatesClick(aEvent: TJSMouseEvent): boolean;
+begin
+  DS.ApplyUpdates;
+end;
+
+function TForm.ButtonAddClick(aEvent: TJSMouseEvent): boolean;
+begin
+  DS.Append;
+  DS.FieldByname('Name').AsString:=EName.value;
+  DS.FieldByname('Population').AsLargeInt:=StrToInt(EPopulation.Value);
+  DS.Post;
+  AddRecords;
+end;
+
+function TForm.ButtonDeleteClick(aEvent: TJSMouseEvent): boolean;
+
+Var
+  ID : Integer;
+  E : TJSElement;
+
+begin
+  ID:=DS.RecNo;
+  DS.Delete;
+  EName.value:='';
+  EPopulation.Value:='';
+  E:=Document.getElementById(IntToStr(DS.RecNo));
+  if E<>Nil then
+    E.parentElement.removeChild(e);
+end;
+
+function TForm.ButtonFetchClick(Event: TJSMouseEvent): boolean;
+
+begin
+  if Assigned(TBody) then
+    TBody.innerHTML:='';
+  DS.Load([],Nil);
+end;
+
+Function TForm.CreateInput(aParent : TJSElement; Atype,AName,ALabel,aID : String) : TJSHTMLInputElement;
+
+Var
+  aDiv,aLabelDiv : TJSElement;
+
+begin
+  adiv:=document.createElement('div');
+  adiv.className:='form-group';
+  aLabelDiv:=document.createElement('label');
+  aLabelDiv['for']:=aID;
+  aLabelDiv.innerText:=ALabel;
+  aDiv.appendChild(aLabelDiv);
+  Result:=TJSHTMLInputElement(document.createElement('input'));
+  Result.id:=AID;
+  Result.name:=AName;
+  Result['type']:=AType;
+  aDiv.appendChild(Result);
+  aParent.appendChild(aDiv);
+end;
+
+Procedure TForm.ResetButtons (Sender : TDataset);
+
+begin
+  TJSHTMLInputElement(ButtonChange).disabled:=True;
+  TJSHTMLInputElement(ButtonDelete).disabled:=True;
+end;
+
+constructor TForm.create;
+
+Var
+  F : TJSElement;
+
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  document.body.appendChild(panel);
+  Panel.appendChild(PanelContent);
+  // fetch button
+  ButtonFetch:=document.createElement('button');
+  ButtonFetch['id']:='ButtonFetch';
+  ButtonFetch.className:='btn btn-default';
+  ButtonFetch.InnerText:='Fetch countries';
+  TJSHTMLElement(ButtonFetch).onclick:=@ButtonFetchClick;
+  PanelContent.AppendChild(ButtonFetch);
+  // Input form
+  F:=document.createElement('form');
+  F.ClassName:='form-inline';
+  PanelContent.appendChild(F);
+  EName:=CreateInput(F, 'text','EName','Name','IDName');
+  EPopulation:=CreateInput(F, 'number','EPopulation','Population','IDPopulation');
+  // Add/Apply updates button
+  // Add
+  ButtonAdd:=document.createElement('button');
+  ButtonAdd['id']:='ButtonAdd';
+  ButtonAdd.className:='btn btn-default';
+  ButtonAdd.InnerText:='Add record';
+  TJSHTMLElement(ButtonAdd).OnClick:=@DoAddRecord;
+  PanelContent.AppendChild(ButtonAdd);
+  // Edit
+  ButtonChange:=document.createElement('button');
+  ButtonChange['id']:='ButtonChange';
+  ButtonChange.className:='btn btn-default';
+  ButtonChange.InnerText:='Update record';
+  TJSHTMLInputElement(ButtonChange).disabled:=true;
+  TJSHTMLElement(ButtonChange).OnClick:=@DoEditRecord;
+  PanelContent.AppendChild(ButtonChange);
+  // Delete
+  ButtonDelete:=document.createElement('button');
+  ButtonDelete['id']:='ButtonDelete';
+  ButtonDelete.className:='btn btn-default';
+  ButtonDelete.InnerText:='Delete record';
+  TJSHTMLInputElement(ButtonDelete).disabled:=true;
+  TJSHTMLElement(ButtonDelete).OnClick:=@ButtonDeleteClick;
+  PanelContent.AppendChild(ButtonDelete);
+  // Apply updates
+  ButtonApplyUpdates:=document.createElement('button');
+  ButtonApplyUpdates['id']:='ButtonAdd';
+  ButtonApplyUpdates.className:='btn btn-default';
+  ButtonApplyUpdates.InnerText:='Apply updates';
+  TJSHTMLElement(ButtonApplyUpdates).onclick:=@ApplyUpdatesClick;
+  PanelContent.AppendChild(ButtonApplyUpdates);
+  DS:=TRestDataset.Create(Nil);
+  Conn:=TRestConnection.Create(nil);
+  Conn.BaseURL:='/countries';
+  Conn.OnGetURL:=@DoGetURL;
+  DS.Connection:=Conn;
+  DS.OnLoadFail:=@DoLoadFail;
+  DS.AfterLoad:=@DoAfterLoad;
+  DS.AfterOpen:=@DSOpen;
+  DS.AfterApplyUpdates:=@DSAfterApplyUpdates;
+  DS.AfterPost:=@ResetButtons;
+  DS.AfterDelete:=@ResetButtons;
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 334 - 0
demo/fcldb/restdata.pp

@@ -0,0 +1,334 @@
+unit restdata;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, httpdefs;
+
+Procedure HandleRest(aRequest : TRequest; AResponse : TResponse);
+
+
+
+implementation
+
+uses jsonparser,fpjson;
+
+Var
+  Packet : TJSONObject;
+  LastID : integer;
+
+Function Data : TJSONArray;
+
+begin
+  Result:=Packet.Arrays['Data'];
+end;
+
+Function MetaData : TJSONObject;
+begin
+  Result:=Packet.Objects['metaData'];
+end;
+
+Procedure LoadRestData;
+
+Var
+  F : TFileStream;
+  D : TJSONData;
+  I : Integer;
+
+begin
+  F:=TFileStream.Create('countries.json',fmOpenRead or fmShareDenyWrite);
+  try
+    D:=GetJSON(F);
+    if not (D is TJSONObject) then
+      D.Free
+    else
+      Packet:=D as TJSONObject;
+    MetaData.Arrays['fields'].Add(TJSONObject.Create(['name','id','type','int']));
+    For I:=0 to Data.Count-1 do
+      Data.Objects[i].Add('id',I+1);
+    LastID:=Data.Count;
+  finally
+    F.Free;
+  end;
+end;
+
+Procedure DoneRestData;
+
+begin
+  Packet.Free;
+end;
+
+Procedure SendRecords(aFirst,aLast : integer; AResponse : TResponse);
+
+Var
+  J: TJSONObject;
+  A : TJSONArray;
+  I : Integer;
+
+begin
+  A:=TJSONArray.Create;
+  J:=TJSONObject.Create(['metaData',MetaData.Clone,'Data',A]);
+  try
+    I:=AFirst;
+    While (I<=ALast) and (I<Data.Count) do
+      begin
+      A.Add(Data[i].Clone);
+      Inc(I);
+      end;
+    AResponse.Content:=J.FormatJSON();
+    AResponse.ContentLength:=Length(AResponse.Content);
+    AResponse.ContentType:='application/json';
+    AResponse.SendContent;
+  finally
+    J.Free;
+  end;
+end;
+
+Function IndexOfID(ID : Integer) : Integer;
+
+begin
+  Result:=Data.Count-1;
+  While (Result>=0) and (Data.Objects[Result].Integers['id']<>ID) do
+   Dec(Result);
+
+end;
+
+Procedure NotFound(AResponse : TResponse);
+
+begin
+  AResponse.Code:=404;
+  AResponse.CodeText:='Not found';
+  AResponse.SendResponse;
+end;
+
+Procedure InvalidParam(AResponse : TResponse; AContent : String = '');
+
+begin
+  AResponse.Code:=400;
+  AResponse.CodeText:='Bad request';
+  AResponse.COntent:=AContent;
+  AResponse.ContentLength:=Length(AContent);
+  AResponse.SendResponse;
+end;
+
+Function GetRequestID(aRequest : TRequest) : Integer;
+
+Var
+  P : String;
+  I : integer;
+
+begin
+  P:=ARequest.PathInfo;
+  if (P<>'') and (P[1]='/') then
+    Delete(P,1,1);
+  I:=Pos('/',P);
+  if I=0 then
+    I:=Length(P);
+  Delete(P,1,I);
+  if (P<>'') then
+    Result:=StrToIntDef(P,-1)
+  else
+    Result:=-2;
+end;
+
+Procedure GetRecords(aRequest : TRequest; AResponse : TResponse);
+
+Var
+  P : String;
+  I,ID,First,Last : Integer;
+
+begin
+  First:=0;
+  Last:=Data.Count-1;
+  ID:=GetRequestID(ARequest);
+  if ID=-1 then
+    begin
+    Writeln('Bad GET request: ',aRequest.PATHINFO);
+    InvalidParam(AResponse);
+    exit;
+    end;
+  if (ID<>-2) then
+    begin
+    First:=IndexOfID(ID);
+    if First=-1 then
+      begin
+      NotFound(AResponse);
+      exit;
+      end;
+    Last:=First;
+    SendRecords(First,Last,AResponse);
+    end
+  else
+    begin
+    First:=StrToIntDef(ARequest.QueryFields.Values['Offset'],First);
+    if (ARequest.QueryFields.Values['Limit']<>'') then
+      Last:=First+StrToIntDef(ARequest.QueryFields.Values['Limit'],20)-1; // Default page size of 20
+    Writeln('Count ',Data.Count,', Offset: ',First,', Last: ',Last);
+    SendRecords(First,last,AResponse);
+    end;
+end;
+
+
+Function  GetObject(aRequest : TRequest; Out AReason : String) : TJSONObject;
+
+Var
+  D : TJSONData;
+  C,I : Integer;
+
+begin
+  Result:=Nil;
+  if Not SameText(aRequest.ContentType,'application/json') then
+    Exit;
+  try
+    D:=GetJSON(aRequest.Content,true);
+    if (D is TJSONObject) then
+      begin
+      Result:=D as TJSONObject;
+      C:=Result.Count-1;
+      D:=Nil;
+      end
+    else
+      begin
+      FreeAndNil(D);
+      end;
+    I:=C;
+    While Assigned(Result) and (I>=0) do
+      begin
+      if (Pos(Result.Names[I]+';','id;Name;Population;')=0) then
+        begin
+        AReason:='Invalid property: '+Result.Names[I];
+        FreeAndNil(Result);
+        end;
+      Dec(I);
+      end;
+  except
+    On E : Exception do
+      AReason:=E.ClassName+': '+E.Message;
+  end;
+end;
+
+Procedure CreateRecord(aRequest : TRequest; AResponse : TResponse);
+
+Var
+  O,D,F : TJSONObject;
+  I : integer;
+  R : String;
+
+
+begin
+  I:=GetRequestID(ARequest);
+  if I<>-2 then
+    begin
+    Writeln('Bad POST request: ',aRequest.PathInfo);
+    R:='No ID allowed for POST';
+    InvalidParam(AResponse,R);
+    exit;
+    end;
+  O:=GetObject(ARequest,R);
+  if (O=Nil) then
+    begin
+    Writeln('Bad POST request: ',aRequest.Content,' : ',R);
+    InvalidParam(AResponse,R);
+    exit;
+    end;
+  try
+    D:=TJSONObject.Create;
+    I:=Data.Add(D);
+    D.Add('id',LastID);
+    D.add('Name',O.Strings['Name']);
+    D.add('Population',O.Int64s['Population']);
+    Inc(LastID);
+    SendRecords(I,I,AResponse);
+  finally
+    O.Free;
+  end;
+end;
+
+Procedure UpdateRecord(aRequest : TRequest; AResponse : TResponse);
+
+Var
+  ID,Idx : Integer;
+  O,D : TJSONObject;
+  R : String;
+
+begin
+  ID:=GetRequestID(ARequest);
+  if ID<0 then
+    begin
+    R:='Need ID for PUT';
+    InvalidParam(AResponse,R);
+    exit;
+    end;
+  IDx:=IndexOfID(ID);
+  if IDX=-1 then
+    begin
+    NotFound(AResponse);
+    exit;
+    end;
+  O:=GetObject(ARequest,R);
+  if (O=Nil) then
+    begin
+    Writeln('Bad PUT request: ',aRequest.Content,' : ',R);
+    InvalidParam(AResponse,R);
+    exit;
+    end;
+  D:=Data.Objects[Idx];
+  if O.IndexOfName('Name')<>-1 then
+    D.Strings['Name']:=O.Strings['Name'];
+  if O.IndexOfName('Population')<>-1 then
+    D.Int64s['Population']:=O.Int64s['Population'];
+  SendRecords(Idx,Idx,AResponse);
+end;
+
+Procedure DeleteRecord(aRequest : TRequest; AResponse : TResponse);
+
+Var
+  ID,Idx : Integer;
+  R : String;
+
+
+begin
+  ID:=GetRequestID(ARequest);
+  if ID<0 then
+    begin
+    R:='Need ID for DELETE';
+    InvalidParam(AResponse,R);
+    exit;
+    end;
+  ID:=GetRequestID(ARequest);
+  IDx:=IndexOfID(ID);
+  if IDX=-1 then
+    begin
+    NotFound(AResponse);
+    exit;
+    end;
+  Data.Delete(Idx);
+  AResponse.Code:=204;
+  AResponse.CodeText:='No content';
+  AResponse.SendResponse;
+end;
+
+
+Procedure HandleRest(aRequest : TRequest; AResponse : TResponse);
+
+begin
+  Writeln('Method : ',ARequest.Method);
+  Case UpperCase(ARequest.Method) of
+    'GET': GetRecords(ARequest,AResponse);
+    'POST': CreateRecord(ARequest,AResponse);
+    'PUT': UpdateRecord(ARequest,AResponse);
+    'DELETE': DeleteRecord(ARequest,AResponse);
+  else
+    Raise EHTTP.CreateHelp('Method not allowed',405);
+  end;
+end;
+
+initialization
+  LoadRestData;
+
+finalization
+  DoneRestData;
+end.
+

+ 61 - 0
demo/fcldb/restserver.lpi

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="restserver"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <Units Count="2">
+      <Unit0>
+        <Filename Value="restserver.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="restdata.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="restserver"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 90 - 0
demo/fcldb/restserver.pas

@@ -0,0 +1,90 @@
+program restserver;
+
+uses sysutils,custhttpapp, fpwebfile, httproute, restdata;
+
+Type
+
+  { THTTPApplication }
+
+  THTTPApplication = Class(TCustomHTTPApplication)
+  private
+    FQuiet: Boolean;
+    procedure Usage(Msg: String);
+  published
+    procedure DoLog(EventType: TEventType; const Msg: String); override;
+    Procedure DoRun; override;
+    property Quiet : Boolean read FQuiet Write FQuiet;
+  end;
+
+Var
+  Application : THTTPApplication;
+
+{ THTTPApplication }
+
+procedure THTTPApplication.DoLog(EventType: TEventType; const Msg: String);
+begin
+  if Quiet then
+    exit;
+  if IsConsole then
+    Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
+  else
+    inherited DoLog(EventType, Msg);
+end;
+
+procedure THTTPApplication.Usage(Msg : String);
+
+begin
+  if (Msg<>'') then
+    Writeln('Error: ',Msg);
+  Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
+  Writeln('Where options is one or more of : ');
+  Writeln('-d --directory=dir  Base directory from which to serve files.');
+  Writeln('                    Default is current working directory: ',GetCurrentDir);
+  Writeln('-h --help           This help text');
+  Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
+  Writeln('-n --noindexpage    Do not allow index page.');
+  Writeln('-p --port=NNNN      TCP/IP port to listen on (default is 3000)');
+  Writeln('-q --quiet          Do not write diagnostic messages');
+  Halt(Ord(Msg<>''));
+end;
+
+procedure THTTPApplication.DoRun;
+
+Var
+  S,IndexPage,D : String;
+
+begin
+  S:=Checkoptions('hqd:ni:p:',['help','quiet','noindexpage','directory:','port:','indexpage:']);
+  if (S<>'') or HasOption('h','help') then
+    usage(S);
+  Quiet:=HasOption('q','quiet');
+  Port:=StrToIntDef(GetOptionValue('p','port'),3000);
+  D:=GetOptionValue('d','directory');
+  if D='' then
+    D:=GetCurrentDir;
+  Log(etInfo,'Listening on port %d, serving files from directory: %s',[Port,D]);
+{$ifdef unix}
+  MimeTypesFile:='/etc/mime.types';
+{$endif}
+  TSimpleFileModule.BaseDir:=IncludeTrailingPathDelimiter(D);
+  TSimpleFileModule.OnLog:=@Log;
+  If not HasOption('n','noindexpage') then
+    begin
+    IndexPage:=GetOptionValue('i','indexpage');
+    if IndexPage='' then
+      IndexPage:='index.html';
+    Log(etInfo,'Using index page %s',[IndexPage]);
+    TSimpleFileModule.IndexPageName:=IndexPage;
+    end;
+  inherited;
+end;
+
+begin
+  HTTPRouter.RegisterRoute('/countries/*',@HandleRest);
+  TSimpleFileModule.RegisterDefaultRoute;
+  Application:=THTTPApplication.Create(Nil);
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+

+ 2 - 0
demo/fpcunit/browsertest.cfg

@@ -0,0 +1,2 @@
+-Jirtl.js
+-Jc

+ 12 - 0
demo/fpcunit/browsertest.html

@@ -0,0 +1,12 @@
+<HTML>
+<Title>FPCUnit Test demo</Title>
+<script SRC="browsertest.js" type="application/javascript"></script>
+<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+<link href="fpcunit.css" rel="stylesheet">
+</body>
+<div id="fpcunit-controller"></div>
+<div id="fpcunit"></div>
+<script>
+    rtl.run();
+</script>
+</HTML>

+ 81 - 0
demo/fpcunit/browsertest.lpi

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="browsertest"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="2">
+      <Item1>
+        <PackageName Value="fpcunit_pas2js"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="pas2js_rtl"/>
+      </Item2>
+    </RequiredPackages>
+    <Units Count="3">
+      <Unit0>
+        <Filename Value="browsertest.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="demotests.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+      <Unit2>
+        <Filename Value="frmrunform.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit2>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="browsertest"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc -Jminclude $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 16 - 0
demo/fpcunit/browsertest.lpr

@@ -0,0 +1,16 @@
+program browsertest;
+
+uses
+  SysUtils, BrowserTestRunner, demotests, frmrunform;
+
+Var
+  Application : TTestRunner;
+
+begin
+  Application:=TTestRunner.Create(Nil);
+  Application.RunFormClass:=TTestRunForm;
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+

+ 66 - 0
demo/fpcunit/demotests.pp

@@ -0,0 +1,66 @@
+unit demotests;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testregistry;
+
+Type
+
+  { TMyTestCase }
+
+  TMyTestCase = Class(TTestCase)
+  Published
+    Procedure TestWillFail;
+    Procedure TestMustFail;
+    Procedure TestWillError;
+    Procedure TestWillWork;
+    Procedure TestWillWorkToo;
+    Procedure TestWillDefinitelyWork;
+    Procedure TestWeLLIgnoreThisOne;
+  end;
+
+implementation
+
+{ TMyTestCase }
+
+procedure TMyTestCase.TestWillFail;
+begin
+  Fail('Aarrggghhhhh this test failed');
+end;
+
+procedure TMyTestCase.TestMustFail;
+begin
+  Fail('Uh-oh, this test failed too...');
+end;
+
+procedure TMyTestCase.TestWillError;
+begin
+  Raise Exception.Create('A random error');
+end;
+
+procedure TMyTestCase.TestWillWork;
+begin
+end;
+
+procedure TMyTestCase.TestWillWorkToo;
+begin
+
+end;
+
+procedure TMyTestCase.TestWillDefinitelyWork;
+begin
+
+end;
+
+procedure TMyTestCase.TestWeLLIgnoreThisOne;
+begin
+  Ignore('Not today, thank you!')
+end;
+
+initialization
+  RegisterTest('DemoSuite',TMyTestCase);
+end.
+

+ 370 - 0
demo/fpcunit/fpcunit.css

@@ -0,0 +1,370 @@
+@charset "utf-8";
+
+body {
+  margin:0;
+}
+
+#fpcunit {
+  font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
+  margin: 60px 50px;
+}
+
+#fpcunit ul,
+#fpcunit li {
+  margin: 0;
+  padding: 0;
+}
+
+#fpcunit ul {
+  list-style: none;
+}
+
+#fpcunit h1,
+#fpcunit h2 {
+  margin: 0;
+}
+
+#fpcunit h1 {
+  margin-top: 15px;
+  font-size: 1em;
+  font-weight: 200;
+}
+
+#fpcunit h1 a {
+  text-decoration: none;
+  color: inherit;
+}
+
+#fpcunit h1 a:hover {
+  text-decoration: underline;
+}
+
+#fpcunit .suite .suite h1 {
+  margin-top: 0;
+  font-size: .8em;
+}
+
+#fpcunit .suite .suite .suite h1 {
+  margin-top: 0;
+  font-size: .8em;
+}
+
+#fpcunit .suite h1 {
+  margin-top: 0;
+  font-size: .8em;
+}
+
+#fpcunit .hidden {
+  display: none;
+}
+
+#fpcunit h2 {
+  font-size: 12px;
+  font-weight: normal;
+  cursor: pointer;
+}
+
+#fpcunit .suite {
+  margin-left: 15px;
+}
+
+#fpcunit .test {
+  margin-left: 15px;
+  overflow: hidden;
+}
+
+#fpcunit .test.pending:hover h2::after {
+  content: '(pending)';
+  font-family: arial, sans-serif;
+}
+
+#fpcunit .test.pass.medium .duration {
+  background: #c09853;
+}
+
+#fpcunit .test.pass.slow .duration {
+  background: #b94a48;
+}
+
+#fpcunit .test.pass::before {
+  content: '✓';
+  font-size: 12px;
+  display: block;
+  float: left;
+  margin-right: 5px;
+  color: #00d6b2;
+}
+
+#fpcunit .test.pass .duration {
+  font-size: 9px;
+  margin-left: 5px;
+  padding: 2px 5px;
+  color: #fff;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+  -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  -ms-border-radius: 5px;
+  -o-border-radius: 5px;
+  border-radius: 5px;
+}
+
+#fpcunit .test.pass.fast .duration {
+  display: none;
+}
+
+#fpcunit .test.pending {
+  color: #0b97c4;
+}
+
+#fpcunit .test.pending::before {
+  content: '◦';
+  color: #0b97c4;
+}
+
+#fpcunit .test.fail {
+  color: #B00;
+}
+
+#fpcunit .test.fail pre {
+  color: black;
+}
+
+#fpcunit .test.fail::before {
+  content: '✖';
+  font-size: 12px;
+  display: block;
+  float: left;
+  margin-right: 5px;
+  color: #B00;
+}
+
+#fpcunit .test.ignore {
+  color:  #FF7F50;
+}
+
+#fpcunit .test.ignore pre {
+  color: #FF7F50;
+}
+
+#fpcunit .test.ignore::before {
+  content: '✖';
+  font-size: 12px;
+  display: block;
+  float: left;
+  margin-right: 5px;
+  color: #FF7F50;
+}
+
+#fpcunit .test.error {
+  color: #F00;
+}
+
+#fpcunit .test.error pre {
+  color: #F00;
+}
+
+#fpcunit .test.error::before {
+  content: '✖';
+  font-size: 12px;
+  display: block;
+  float: left;
+  margin-right: 5px;
+  color: #F00;
+}
+
+#fpcunit .test pre.error {
+  color: #c00;
+  max-height: 300px;
+  overflow: auto;
+}
+
+#fpcunit .test .html-error {
+  overflow: auto;
+  color: black;
+  line-height: 1.5;
+  display: block;
+  float: left;
+  clear: left;
+  font: 12px/1.5 monaco, monospace;
+  margin: 5px;
+  padding: 15px;
+  border: 1px solid #eee;
+  max-width: 85%; /*(1)*/
+  max-width: -webkit-calc(100% - 42px);
+  max-width: -moz-calc(100% - 42px);
+  max-width: calc(100% - 42px); /*(2)*/
+  max-height: 300px;
+  word-wrap: break-word;
+  border-bottom-color: #ddd;
+  -webkit-box-shadow: 0 1px 3px #eee;
+  -moz-box-shadow: 0 1px 3px #eee;
+  box-shadow: 0 1px 3px #eee;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+
+#fpcunit .test .html-error pre.error {
+  border: none;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+  -webkit-box-shadow: 0;
+  -moz-box-shadow: 0;
+  box-shadow: 0;
+  padding: 0;
+  margin: 0;
+  margin-top: 18px;
+  max-height: none;
+}
+
+/**
+ * (1): approximate for browsers not supporting calc
+ * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
+ *      ^^ seriously
+ */
+#fpcunit .test pre {
+  display: block;
+  float: left;
+  clear: left;
+  font: 12px/1.5 monaco, monospace;
+  margin: 5px;
+  padding: 15px;
+  border: 1px solid #eee;
+  max-width: 85%; /*(1)*/
+  max-width: -webkit-calc(100% - 42px);
+  max-width: -moz-calc(100% - 42px);
+  max-width: calc(100% - 42px); /*(2)*/
+  word-wrap: break-word;
+  border-bottom-color: #ddd;
+  -webkit-box-shadow: 0 1px 3px #eee;
+  -moz-box-shadow: 0 1px 3px #eee;
+  box-shadow: 0 1px 3px #eee;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+
+#fpcunit .test h2 {
+  position: relative;
+}
+
+#fpcunit .test a.replay {
+  position: absolute;
+  top: 3px;
+  right: 0;
+  text-decoration: none;
+  vertical-align: middle;
+  display: block;
+  width: 15px;
+  height: 15px;
+  line-height: 15px;
+  text-align: center;
+  background: #eee;
+  font-size: 15px;
+  -webkit-border-radius: 15px;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+  -webkit-transition:opacity 200ms;
+  -moz-transition:opacity 200ms;
+  -o-transition:opacity 200ms;
+  transition: opacity 200ms;
+  opacity: 0.3;
+  color: #888;
+}
+
+#fpcunit .test:hover a.replay {
+  opacity: 1;
+}
+
+#fpcunit-report.pass .test.fail {
+  display: none;
+}
+
+#fpcunit-report.fail .test.pass {
+  display: none;
+}
+
+#fpcunit-report.pending .test.pass,
+#fpcunit-report.pending .test.fail {
+  display: none;
+}
+#fpcunit-report.pending .test.pass.pending {
+  display: block;
+}
+
+#fpcunit-error {
+  color: #c00;
+  font-size: 1.5em;
+  font-weight: 100;
+  letter-spacing: 1px;
+}
+
+#fpcunit-stats {
+  position: fixed;
+  top: 15px;
+  right: 10px;
+  font-size: 12px;
+  margin: 0;
+  color: #888;
+  z-index: 1;
+}
+
+#fpcunit-stats .progress {
+  float: right;
+  padding-top: 0;
+
+  /**
+   * Set safe initial values, so mochas .progress does not inherit these
+   * properties from Bootstrap .progress (which causes .progress height to
+   * equal line height set in Bootstrap).
+   */
+  height: auto;
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+  background-color: initial;
+}
+
+#fpcunit-stats em {
+  color: black;
+}
+
+#fpcunit-stats a {
+  text-decoration: none;
+  color: inherit;
+}
+
+#fpcunit-stats a:hover {
+  border-bottom: 1px solid #eee;
+}
+
+#fpcunit-stats li {
+  display: inline-block;
+  margin: 0 5px;
+  list-style: none;
+  padding-top: 11px;
+}
+
+#fpcunit-stats canvas {
+  width: 40px;
+  height: 40px;
+}
+
+#fpcunit code .comment { color: #ddd; }
+#fpcunit code .init { color: #2f6fad; }
+#fpcunit code .string { color: #5890ad; }
+#fpcunit code .keyword { color: #8a6343; }
+#fpcunit code .number { color: #2f6fad; }
+
+@media screen and (max-device-width: 480px) {
+  #fpcunit {
+    margin: 60px 0px;
+  }
+
+  #fpcunit #stats {
+    position: absolute;
+  }
+}

+ 213 - 0
demo/fpcunit/frmrunform.pp

@@ -0,0 +1,213 @@
+unit frmrunform;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, js, web, hotreloadclient, browserTestRunner;
+
+Type
+
+  { TTestRunForm }
+
+  TTestRunForm = Class(TRunForm)
+  private
+    FElementID: String;
+    BOutput : TJSHTMLInputElement;
+    BRebuild : TJSHTMLInputElement;
+    BRun : TJSHTMLInputElement;
+    BuildID : TJSHTMLElement;
+    FLastReq : TJSXMLHttpRequest;
+    POutput: TJSElement;
+    function RecompileClick(Event{%H-}: TJSMouseEvent): boolean;
+    procedure CreatePanel(aParent: TJSElement);
+    function DoReloadChange(Event{%H-}: TEventListenerEvent): boolean;
+    function doResponse(Event{%H-}: TEventListenerEvent): boolean;
+    function RunClick(aEvent{%H-}: TJSMouseEvent): boolean;
+    function ToggleOutput(aEvent{%H-}: TJSMouseEvent): boolean;
+  Public
+    Constructor create(AOwner : TComponent); override;
+    Procedure Initialize; override;
+    // Where to place the control. Default fpcunit-control
+    Property ElementID : String Read FElementID Write FElementID;
+  end;
+
+implementation
+
+function TTestRunForm.doResponse(Event: TEventListenerEvent): boolean;
+
+Var
+  Data : TJSObject;
+  S : String;
+
+begin
+  Result:=True;
+  console.warn('Compile request response received');
+  try
+     Data:=TJSJSON.parse(FLastReq.responseText);
+     if data['success'] then
+       begin
+       if isInteger(data['compileID']) then
+         begin
+         S:=String(data['compileID']);
+         BuildID.innerText:=S;
+         end;
+       end
+     else
+       BuildID.innerText:='no build yet';
+  except
+    console.error('Compile request response received non-json response: '+FLastReq.ResponseText);
+  end;
+  FreeAndNil(FLastReq);
+end;
+
+function TTestRunForm.RunClick(aEvent: TJSMouseEvent): boolean;
+begin
+  Result:=True;
+  if Assigned(OnRun) then
+    OnRun(Self);
+end;
+
+function TTestRunForm.ToggleOutput(aEvent: TJSMouseEvent): boolean;
+
+Var
+  S : String;
+  P : Integer;
+
+begin
+  Result:=True;
+  S:=POutput.className;
+  P:=Pos(' in',S);
+  if P>0 then
+    S:=Copy(S,1,P-1)
+  else
+    S:=S+' in';
+  POutput.className:=S;
+end;
+
+function TTestRunForm.RecompileClick(Event: TJSMouseEvent): boolean;
+
+Var
+  Req : TJSXMLHttpRequest;
+
+begin
+  Result:=True;
+   console.info('Recompile requested');
+   Req:=TJSXMLHttpRequest.new;
+   Req.addEventListener('load',@DoResponse);
+   Req.open('POST','$sys/compile');
+   Req.send;
+   FLastReq:=Req;
+end;
+
+
+Procedure TTestRunForm.CreatePanel(aParent : TJSElement);
+
+Var
+  Panel : TJSElement;
+  BG,D,PanelContent : TJSElement;
+  CB : TJSHTMLInputElement;
+
+begin
+  Panel:=aParent;
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  // Innner panel
+  PanelContent:=document.createElement('div');
+  Panel.appendChild(PanelContent);
+  PanelContent['class']:='panel-heading';
+  BG:=document.createElement('div');
+  BG['class']:='btn-group';
+  PanelContent.appendChild(BG);
+  // Run Button
+  BRun:=TJSHTMLInputElement(document.createElement('button'));
+  BRun['id']:=ElementID+'-BRun';
+  BRun['type']:='button';
+  BRun['class']:='btn btn-primary';
+  BRun.appendChild(document.createTextNode('Re-Run'));
+  BRun.onclick:=@RunClick;
+  BG.appendChild(BRun);
+
+  // Rebuild Button
+  BRebuild:=TJSHTMLInputElement(document.createElement('button'));
+  BRebuild['id']:=ElementID+'-BRebuild';
+  BRebuild['type']:='button';
+  BRebuild['class']:='btn btn-primary';
+  BRebuild.appendChild(document.createTextNode('Recompile'));
+  BRebuild.onclick:=@RecompileClick;
+  BG.appendChild(BRebuild);
+  // Output Button
+  BOutput:=TJSHTMLInputElement(document.createElement('button'));
+  BOutput['id']:=ElementID+'-BRebuild';
+  BOutput['type']:='button';
+  BOutput['class']:='btn btn-primary';
+  BOutput.appendChild(document.createTextNode('Output'));
+  BOutput.onclick:=@ToggleOutput;
+  BG.appendChild(BOutput);
+  // Span for build ID
+  D:=Document.createElement('span');
+  D.id:='buildlabel';
+  D['style']:='margin-left: 15px; margin-right: 15px';
+  D.appendChild(Document.createTextNode('Build ID:'));
+
+  PanelContent.appendChild(D);
+  BuildID:=TJSHTMLElement(document.createElement('span'));
+  BuildID['id']:='recompileID';
+  BuildID['class']:='badge';
+  BuildID.appendChild(document.createTextNode('no build yet'));
+  PanelContent.appendChild(BuildID);
+  // Combo box to trigger onload
+  D:=Document.createElement('label');
+  D['class']:='checkbox-inline';
+  D['style']:='margin-left: 15px';
+  cb:=TJSHTMLInputElement(document.createElement('input'));
+  cb['id']:='cbautoreload';
+  cb['type']:='checkbox';
+  cb.onchange:=@DoReloadChange;
+  // Sync with hotreload
+  cb.checked:=THotReload.getglobal.options.Reload;
+  D.AppendChild(cb);
+  D.appendChild(Document.createTextNode('Reload page on build/sync'));
+  PanelContent.appendChild(D);
+  // Collapsible panel For compiler output
+  POutput:=document.createElement('div');
+  POutput['class']:='panel-collapse collapse';
+  D:=Document.createElement('div');
+  D['class']:='panel-body';
+  D['id']:=ElementID+'-output';
+  // Tell it to hotreload.
+  THotReload.getglobal.options.OverlayElementID:=ElementID+'-output';
+  POutput.appendChild(D);
+  Panel.appendChild(POutput);
+  // Hint
+end;
+
+constructor TTestRunForm.Create(aOwner : TComponent);
+begin
+  inherited;
+  ElementID:='fpcunit-controller'
+end;
+
+procedure TTestRunForm.Initialize;
+
+Var
+  Panel : TJSElement;
+begin
+  inherited Initialize;
+  THotReload.StartHotReload;
+  Panel:=document.getElementById(Self.ElementID);
+  if Panel<>Nil then
+    CreatePanel(Panel);
+  // Start hotreload
+end;
+
+function TTestRunForm.DoReloadChange(Event: TEventListenerEvent): boolean;
+
+begin
+  Result:=True;
+  THotReload.getglobal.options.Reload:=not THotReload.getglobal.options.Reload;
+end;
+
+end.
+

+ 42 - 0
demo/fpreport/README.md

@@ -0,0 +1,42 @@
+# README #
+
+This directory contains a pas2js front-end for the WebDemo  demo of FPReport.
+It uses Bootstrap CSS to style the application.
+
+To run it, first compile and run the webdemo project.
+This is a demo project in the FPC repository in directory
+packages/fcl-report/demos
+
+It can be run on your local machine, or on another machine.
+By default, it will listen to port 8080 of the machine it runs on;
+but you can configure it to run on another port.
+
+Then, in the frmmain.pp look for the BASEURL constant.
+This is set up for webdemo running on the local machine, port 8080:
+
+    http://localhost:8080/
+
+Compile the reportdemo project with pas2js and open the reportdemo.html file.
+You can select the demo and the output format. The 'Show Report' button will then open the generated report in a new window or in the current window, depending on options.
+
+NOTE:
+The demo fetches the list of available report demos through an AJAX call.
+if webdemo is running on another machine (or in another domain) then the CORS protection will kick in and the call will fail. If that happens, you can either
+
+* Install a browser plugin to disable CORS (such as CorsE in firefox)
+* Put the HTML page on the same machine as where webdemo is running,
+  in a directory that can be reached by a webserver such as Apache.
+  and change the baseURL to http://:8080/
+* As a third altentave, the webdemo accepts a '-s' or '--start' option to
+specify a filename which will be served (and all files in the directory) to
+serve as an alternate start location
+
+for example:
+
+    home:~/fpc/packages/fcl-report/demos> ./webdemo -s /home/michael/source/pas2js/demo/fpreport/reportdemo.html
+    Point your browser to http://localhost:8080/Page  or http://localhost:8080
+    An alternate start location is available at http://localhost:8080/Start/reportdemo.html
+
+If you want to run webdemo on another machine in this manner,
+then the BaseURL must be changed to / in the frmmain.pas unit
+of the reportdemo.

File diff suppressed because it is too large
+ 4 - 0
demo/fpreport/bootstrap.min.css


+ 695 - 0
demo/fpreport/frmmain.pp

@@ -0,0 +1,695 @@
+unit frmmain;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, JS, Web;
+
+Type
+
+  { TMainForm }
+  TJSHTMLElementArray = Array of TJSHTMLElement;
+
+  TFormStyle = (fsPlain,fsHorizontal,fsInline);
+  TMainForm = Class(TComponent)
+  private
+    labelclassname,
+    controlclassname,
+    Prefix : String;
+    FNavs : TJSHTMLElementArray;
+    PTop : TJSHTMLElement;
+    POptions : TJSHTMLElement;
+    CBDemo : TJSHTMLSelectElement;
+    CBFormat : TJSHTMLSelectElement;
+    CBNewWindow : TJSHTMLInputElement;
+    PPDFOptions : TJSHTMLElement;
+    PHTMLOptions : TJSHTMLElement;
+    PImageOptions : TJSHTMLElement;
+    PTabs : TJSHTMLElementArray;
+    ImageOptionsArr,
+    PDFOptionsArr,
+    HTMLOPtionsArr : TJSHTMLElementArray;
+    function AddConfigureFramePage(aParent: TJSHTMLElement; AnArray: TJSHTMLElementArray; STartAt: integer): Integer;
+    function AddConfigureTOCPage(aParent: TJSHTMLElement; AnArray: TJSHTMLElementArray; STartAt: integer): Integer;
+    function AddConfigurePageNavigator(aParent: TJSHTMLElement; AnArray: TJSHTMLElementArray; STartAt: integer): Integer;
+    function AddEdit(AParent: TJSHTMLElement; const AName, ALabel: String): TJSHTMLInputElement;
+    procedure AddHTMLOptions(AParent: TJSHTMLElement);
+    procedure AddImageOptions(AParent: TJSHTMLElement);
+    procedure AddPDFOptions(AParent: TJSHTMLElement);
+    function AddStyleEmbedding(AParent: TJSHTMLElement): TJSHTMLElement;
+    function CreateTabSheet(Aparent: TJSHTMLElement; AIDName: String; IsActive: Boolean): TJSHTMLElement;
+    function DemosLoaded(Event: TEventListenerEvent): boolean;
+    function DoClick(aEvent: TJSMouseEvent): boolean;
+    function DoNavClick(aEvent: TJSMouseEvent): boolean;
+    procedure GetDemoList;
+    procedure Getoptions(A: TJSHTMLElementArray; L: Tstrings);
+  Protected
+    Procedure ShowError(Msg : String);
+    Function AddFormControl(AParent : TJSHTMLElement; Const AID,ALabel : String) : TJSHTMLElement;
+  Public
+    Function AddPanel(AParent : TJSHTMLElement; AHeader : String; AHeading : Integer = 0) : TJSHTMLElement;
+    Function AddCheckBox(AParent : TJSHTMLElement; Const AName,ALabel : String; IsInline : Boolean = False) : TJSHTMLInputElement;
+    Function AddNumber(AParent : TJSHTMLElement; Const AName,ALabel : String) : TJSHTMLInputElement;
+    Function AddColor(AParent : TJSHTMLElement; Const AName,ALabel : String) : TJSHTMLInputElement;
+    function AddCombo(AParent: TJSHTMLElement; const AName, ALabel: String; AValues: array of String): TJSHTMLSelectElement;
+    // Element 0 is the container, 1+i are the tabs
+    function AddNav(AParent: TJSHTMLElement; Tabs: array of string; AActive: String; AOnClick: THTMLClickEventHandler): TJSHTMLElementArray;
+    Function AddForm(AParent : TJSHTMLElement; FormStyle : TFormStyle = fsPlain) : TJSHTMLFormElement;
+    Constructor create(AOwner : TCOmponent); override;
+    Procedure Initialize;
+  end;
+
+implementation
+
+Const
+//  ServerURL = 'http://localhost:8080/';
+  ServerURL = '/';
+  BaseURL = ServerURL + 'Generate?';
+
+{ TMainForm }
+
+function TMainForm.DoNavClick(aEvent: TJSMouseEvent): boolean;
+
+
+  Procedure ActivateTab(AID: string);
+
+  Var
+    I : Integer;
+    E : TJSHTMLElement;
+
+  begin
+    For I:=1 to Length(FNavs)-1 do
+      begin
+      E:=FNavs[i];
+      if E.id=AID then
+        E.classlist.add('active')
+      else
+        E.classlist.remove('active');
+      end;
+  end;
+
+  Procedure Activate(P : TJSHTMLElement);
+
+  Var
+    I : Integer;
+    E : TJSHTMLElement;
+
+  begin
+    For I:=0 to Length(Ptabs)-1 do
+      begin
+      E:=PTabs[i];
+      if E=P then
+        begin
+        E.classlist.add('active');
+        E.classlist.add('in');
+        end
+      else
+        begin
+        E.classlist.remove('active');
+        E.classlist.remove('in');
+        end;
+      end;
+  end;
+
+Var
+  tid : String;
+
+begin
+  Result:=true;
+  tid:=aEvent.CurrentTarget.id;
+  ActivateTab(tid);
+  If (tid='navpdf') then
+    Activate(PPDFOptions)
+  else If (tid='navhtml') then
+    Activate(PHTMLOptions)
+  else If (tid='navfpimage') then
+    Activate(PImageOptions)
+end;
+
+function TMainForm.AddFormControl(AParent: TJSHTMLElement; const AID, ALabel: String): TJSHTMLElement;
+
+Var
+  FG,D : TJSElement;
+  L : TJSElement;
+
+begin
+  FG:=Document.createElement('div');
+  FG.className:='form-group';
+  AParent.appendChild(FG);
+  L:=Document.createElement('label');
+  L.Attrs['for']:=AID;
+  L.classname:=labelclassname;
+  FG.append(L);
+  if (ALabel<>'') then
+    L.append(ALabel);
+  D:=Document.createElement('div');
+  FG.Append(D);
+  D.classname:=controlclassname;
+  Result:=TJSHTMLElement(D);
+end;
+
+function TMainForm.AddPanel(AParent: TJSHTMLElement; AHeader: String; AHeading: Integer = 0): TJSHTMLElement;
+
+Var
+  P,H,H2,B : TJSElement;
+
+begin
+  P:=Document.createElement('div');
+  P.classname:='panel panel-default';
+  AParent.Append(P);
+  if (AHeader<>'') then
+    begin
+    H:=Document.createElement('div');
+    h.classname:='panel-heading';
+    P.Append(H);
+    if AHeading=0 then
+      H.Append(AHeader)
+    else
+      begin
+      H2:=Document.createElement('h'+IntToStr(AHeading));
+      H2.ClassName:='panel-title';
+      H2.Append(AHeader);
+      H.Append(H2);
+      end;
+    end;
+  B:=Document.createElement('div');
+  P.Append(B);
+  B.classname:='panel-body';
+  Result:=TJSHTMLElement(B);
+end;
+
+function TMainForm.AddNav(AParent: TJSHTMLElement; Tabs: array of string; AActive: String; AOnClick: THTMLClickEventHandler): TJSHTMLElementArray;
+
+Var
+  I : Integer;
+  TP,A,T : TJSHTMLElement;
+
+
+
+begin
+  TP:=TJSHTMLElement(Document.CreateElement('ul'));
+  TP.ClassName:='nav nav-tabs';
+  SetLength(Result,1+(Length(Tabs) div 2));
+  Result[0]:=TP;
+  AParent.Append(TP);
+  I:=0;
+  While I<Length(Tabs)-1 do
+    begin
+    T:=TJSHTMLElement(Document.CreateElement('li'));
+    T['role']:='presentation';
+    T.id:='nav'+Tabs[i];
+    if SameText(T.ID,'nav'+AActive) then
+      T.Classname:='active';
+    A:=TJSHTMLElement(Document.CreateElement('a'));
+    A.Append(Tabs[I+1]);
+    A['href']:='#';
+    T.onclick:=AOnClick;
+    T.Append(A);
+    TP.Append(T);
+    Result[1+(I div 2)]:=T;
+    inc(I,2);
+    end;
+end;
+
+function TMainForm.AddForm(AParent: TJSHTMLElement; FormStyle: TFormStyle): TJSHTMLFormElement;
+
+
+begin
+  Result:=TJSHTMLFormElement(document.CreateElement('form'));
+  Case FormStyle of
+    fsInline : Result.className:='form-inline';
+    fsHorizontal : Result.className:='form-horizontal';
+  end;
+  AParent.Append(Result);
+end;
+
+function TMainForm.AddCheckBox(AParent: TJSHTMLElement; const AName, ALabel: String; IsInline: Boolean): TJSHTMLInputElement;
+
+Var
+  FG,D,D2,L : TJSHTMLElement;
+  IE : TJSHTMLInputElement;
+
+begin
+//  FG:=AddFormControl(APArent,Prefix+AName,aLabel);
+  FG:=TJSHTMLElement(Document.createElement('div'));
+  FG.className:='form-group';
+  AParent.appendChild(FG);
+  D:=TJSHTMLElement(Document.createElement('div'));
+  D.ClassName:='col-sm-offset-3 col-sm-8';
+  FG.Append(D);
+  D2:=TJSHTMLElement(Document.createElement('div'));
+  D2.ClassName:='checkbox';
+  D.Append(D2);
+  L:=TJSHTMLElement(Document.createElement('label'));
+  L.Attrs['for']:='ID'+Prefix+AName;
+  D2.append(L);
+  IE:=TJSHTMLInputElement(Document.createElement('input'));
+  L.Append(IE);
+  if (ALabel<>'') then
+    L.append(ALabel);
+  IE['type']:='checkbox';
+//  IE.ClassName:='form-control';
+  IE.id:='ID'+Prefix+AName;
+  IE.name:=Prefix+AName;
+  IE.value:='1';
+  Result:=IE;
+end;
+
+function TMainForm.AddNumber(AParent: TJSHTMLElement; const AName, ALabel: String): TJSHTMLInputElement;
+
+Var
+  FG : TJSHTMLElement;
+  IE : TJSHTMLInputElement;
+
+begin
+  FG:=AddFormControl(APArent,Prefix+AName,aLabel);
+  IE:=TJSHTMLInputElement(Document.createElement('input'));
+  FG.Append(IE);
+  IE['type']:='number';
+  IE.ClassName:='form-control';
+  IE.id:='ID'+Prefix+AName;
+  IE.Name:=Prefix+AName;
+  Result:=IE;
+end;
+
+function TMainForm.AddColor(AParent: TJSHTMLElement; const AName, ALabel: String): TJSHTMLInputElement;
+Var
+  FG : TJSHTMLElement;
+  IE : TJSHTMLInputElement;
+
+begin
+  FG:=AddFormControl(APArent,Prefix+AName,aLabel);
+  IE:=TJSHTMLInputElement(Document.createElement('input'));
+  FG.Append(IE);
+  IE['type']:='color';
+  IE.ClassName:='form-control';
+  IE.id:='ID'+Prefix+AName;
+  IE.name:=Prefix+AName;
+  Result:=IE;
+end;
+
+function TMainForm.AddEdit(AParent: TJSHTMLElement; const AName, ALabel: String): TJSHTMLInputElement;
+
+Var
+  FG : TJSHTMLElement;
+  IE : TJSHTMLInputElement;
+
+begin
+  FG:=AddFormControl(APArent,Prefix+AName,aLabel);
+  IE:=TJSHTMLInputElement(Document.createElement('input'));
+  FG.Append(IE);
+  IE['type']:='edit';
+  IE.ClassName:='form-control';
+  IE.id:='ID'+Prefix+AName;
+  IE.name:=Prefix+AName;
+  Result:=IE;
+end;
+
+function TMainForm.AddCombo(AParent: TJSHTMLElement; const AName, ALabel: String; AValues : Array of String): TJSHTMLSelectElement;
+
+Var
+  FG: TJSHTMLElement;
+  SE : TJSHTMLSelectElement;
+  O  : TJSHTMLOptionElement;
+  I  : Integer;
+
+begin
+  FG:=AddFormControl(APArent,Prefix+AName,ALabel);
+  SE:=TJSHTMLSelectElement(Document.createElement('select'));
+
+  SE.ClassName:='form-control';
+  FG.Append(SE);
+  SE.id:='ID'+Prefix+AName;
+  SE.Name:=Prefix+AName;
+  I:=0;
+
+  While I<Length(AValues)-1 do
+    begin
+    O:=TJSHTMLOptionElement(Document.CreateElement('option'));
+    O.value:=AVAlues[i];
+    O.Append(AVAlues[i+1]);
+    SE.add(O);
+    Inc(I,2);
+    end;
+  Result:=SE;
+end;
+
+
+constructor TMainForm.create(AOwner: TCOmponent);
+begin
+  inherited create(AOwner);
+  labelclassname:='col-sm-3 control-label';
+  controlclassname:='col-sm-8';
+  Initialize;
+end;
+
+Function TMainForm.CreateTabSheet(Aparent : TJSHTMLElement;AIDName : String; IsActive : Boolean) : TJSHTMLElement;
+
+Var
+  C : String;
+
+begin
+  Result:=TJSHTMLElement(Document.createElement('div'));
+  C:='tab-pane fade';
+  if IsActive then
+    c:=c+' in active';
+  Result.id:=AIDName;
+  Result.classname:=C;
+  AParent.append(Result);
+end;
+
+Procedure TMainForm.Getoptions(A : TJSHTMLElementArray; L : Tstrings);
+
+Var
+  I : Integer;
+  E : TJSHTMLElement;
+  IE : TJSHTMLInputElement;
+  SE : TJSHTMLSelectElement;
+
+begin
+  for I:=0 to Length(A)-1 do
+    begin
+    E:=A[i];
+    if SameText(E.tagName,'INPUT') then
+      begin
+      IE:=TJSHTMLInputElement(E);
+      if IE._type='checkbox' then
+        begin
+        if IE.checked then
+          L.Add(IE.Name+'=1');
+        end
+      else if IE.value<>'' then
+        L.Add(IE.Name+'='+IE.value);
+      end
+    else if SameText(E.tagName,'SELECT') then
+      begin
+      SE:=TJSHTMLSelectElement(E);
+      L.Add(SE.Name+'='+SE.value);
+      end;
+    end;
+end;
+
+function TMainForm.DoClick(aEvent: TJSMouseEvent): boolean;
+
+Var
+  Fmt,URL : String;
+  A : TJSHTMLElementArray;
+  L : Tstrings;
+  I : Integer;
+
+begin
+  Result:=true;
+  URL:=BaseURL;
+  fmt:=CBFormat.value;
+  if (fmt='HTML') then
+    A:=HTMLOPtionsArr
+  else if (fmt='PDF') then
+    A:=PDFOptionsArr
+  else if (fmt='FPImage') then
+    A:=ImageOptionsArr
+  else
+    begin
+    ShowError('Unknown output format: '+Fmt);
+    Exit;
+    end;
+  URL:=URL+'demo='+CBDemo.value;
+  URL:=URL+'&format='+fmt;
+  L:=TstringList.Create;
+  try
+    GetOptions(A,L);
+    For I:=0 to L.Count-1 do
+      URL:=URL+'&'+L[i];
+  finally
+    L.Free;
+  end;
+  if (CBNewWindow.checked) then
+    Window.Open(URL)
+  else
+    Window.locationString:=URL;
+end;
+
+procedure TMainForm.AddPDFOptions(AParent : TJSHTMLElement);
+
+Var
+  F : TJSHTMLElement;
+  I : Integer;
+
+  Procedure AddOption(E : TJSHTMLElement);
+
+  begin
+    PDFOptionsArr[I]:=E;
+    inc(I);
+  end;
+
+begin
+  prefix:='pdf.';
+  F:=AddForm(aParent,fsHorizontal);
+  I:=0;
+  SetLength(PDFOptionsArr,9);
+  AddOption(AddCheckbox(F,'pagelayout','Create outLine'));
+  AddOption(AddCheckbox(F,'compresstext','Compress text'));
+  AddOption(AddCheckbox(F,'compressfonts','Compress fonts'));
+  AddOption(AddCheckbox(F,'compressimages','Compress images'));
+  AddOption(AddCheckbox(F,'userawjpeg','use raw JPEG'));
+  AddOption(AddCheckbox(F,'noembeddedfonts','Do not embed fonts'));
+  AddOption(AddCheckbox(F,'pageoriginattop','Page origin at top'));
+  AddOption(AddCheckbox(F,'subsetfont','Embed only used subset of font'));
+  AddOption(AddCombo(F,'pagelayout','Page layout',['single','Single page','two','Two pages','continuous','Continuous layout']));
+end;
+
+Function TMainForm.AddStyleEmbedding (AParent : TJSHTMLElement): TJSHTMLElement;
+
+begin
+  Result:=AddCombo(AParent,'styleembedding','Style embedding',[
+    'inline','Inline, in HTML element',
+    'styletag','In separate style tag',
+    'cssfile','In separate CSS file'
+  ]);
+end;
+
+function TMainForm.AddConfigureFramePage(aParent: TJSHTMLElement; AnArray: TJSHTMLElementArray; STartAt: integer): Integer;
+begin
+  AnArray[StartAt]:=AddEdit(AParent,'framecssfilename','CSS file name');
+  AnArray[StartAt+1]:=AddNumber(AParent,'toczonesize','TOC Zone size (percentage)');
+  AnArray[StartAt+2]:=AddCombo(AParent,'toczoneposition','Position of TOC zone',[
+  'left','Left',
+  'right', 'Right',
+  'top' , 'Top',
+  'bottom', 'Bottom'
+  ]);
+  Result:=3;
+end;
+
+Function TMainForm.AddConfigureTOCPage(aParent : TJSHTMLElement; AnArray : TJSHTMLElementArray; STartAt : integer) : Integer;
+
+begin
+  AnArray[StartAt]:=AddEdit(AParent,'toccssfilename','CSS file name');
+  AnArray[StartAt+1]:=AddEdit(AParent,'oddpagestyle','Odd page style elements');
+  AnArray[StartAt+2]:=AddEdit(AParent,'evenpagestyle','Even page style elements');
+  AnArray[StartAt+3]:=AddCheckBox(AParent,'skipstyling','Skip Styling');
+  Result:=4;
+end;
+
+function TMainForm.AddConfigurePageNavigator(aParent: TJSHTMLElement; AnArray: TJSHTMLElementArray; STartAt: integer): Integer;
+
+Var
+  P : TJSHTMLElement;
+
+begin
+  P:=AddPanel(Aparent,'Navigator positions',5);
+  anArray[StartAt]:=AddCheckBox(P,'topnavigator','Top',True);
+  anArray[StartAt+1]:=AddCheckBox(P,'leftnavigator','Left',True);
+  anArray[StartAt+2]:=AddCheckBox(P,'rightnavigator','Right',True);
+  anArray[StartAt+3]:=AddCheckBox(P,'bottomnavigator','Bottom',True);
+  P:=AddPanel(Aparent,'Navigator options',5);
+  anArray[StartAt+4]:=AddCheckBox(P,'firstlast','Add First/Last buttons');
+  anArray[StartAt+5]:=AddCheckBox(P,'alwaysfirstlast','Always add First/Last buttons');
+  anArray[StartAt+6]:=AddCheckBox(P,'pageno','Add page number');
+  anArray[StartAt+7]:=AddCheckBox(P,'image','Use images (Not yet implemented)');
+  anArray[StartAt+8]:=AddCheckBox(P,'skipstyling','Skip all styling');
+  anArray[StartAt+9]:=AddCheckBox(P,'usepagenofm','Use Page N/M display');
+  anArray[StartAt+10]:=AddCheckBox(P,'pagenoedit','Allow page number editing');
+  P:=AddPanel(Aparent,'Width/Color',5);
+  anArray[StartAt+11]:=AddNumber(P,'navigatorfixedwidth','Fixed width');
+  anArray[StartAt+12]:=AddNumber(P,'navigatorfixedheight','Fixed height');
+  anArray[StartAt+13]:=AddNumber(P,'navigatorfixedmargin','Fixed margin');
+  anArray[StartAt+14]:=AddColor(P,'navigatorbgcolor','Active link color');
+  anArray[StartAt+15]:=AddColor(P,'navigatorinactivebgcolor','Inactive link color');
+  Result:=16;
+end;
+
+procedure TMainForm.AddHTMLOptions(AParent : TJSHTMLElement);
+
+Var
+  F : TJSHTMLFormElement;
+  E : TJSHTMLElement;
+  I : Integer;
+
+  Procedure AddOption(E : TJSHTMLElement);
+
+  begin
+    HTMLOptionsArr[I]:=E;
+    inc(I);
+  end;
+
+begin
+  Prefix:='html.';
+  F:=AddForm(aParent,fsHorizontal);
+  I:=0;
+  SetLength(HTMLOptionsArr,100);
+  E:=AddPanel(F,'HTML Options',4);
+  AddOption(AddCheckBox(E,'fixedpositioning','Use fixed positioning'));
+  AddOption(AddCheckbox(E,'inlineimage','Use inline images'));
+  AddOption(AddCheckbox(E,'useimgtag','Use IMG tag'));
+  AddOption(AddCheckbox(E,'tocpageframe','Create TOC Frame'));
+  AddOption(AddCheckbox(E,'memoasis','Insert memos as-is (let browser handle layout)'));
+  AddOption(AddCheckbox(E,'externaljs','Use external file for JS'));
+  AddOption(AddNumber(E,'DPI','DPI (resolution)'));
+  AddOption(AddEdit(E,'sequence','Sequence format'));
+  AddOption(AddStyleEmbedding(E));
+  AddOption(AddNumber(E,'offsettop','Fixed positioning, offset from top'));
+  AddOption(AddNumber(E,'offsetleft','Fixed positioning, offset from left'));
+  E:=AddPanel(F,'TOC page',4);
+  I:=I+AddConfigureTOCPage(E,HTMLOptionsArr,I);
+  E:=AddPanel(F,'Frame page',4);
+  I:=I+AddConfigureFramePage(E,HTMLOptionsArr,I);
+  E:=AddPanel(F,'Page Navigator',4);
+  I:=I+AddConfigurePageNavigator(E,HTMLOptionsArr,I);
+  SetLength(HTMLOptionsArr,I);
+end;
+
+procedure TMainForm.AddImageOptions(AParent : TJSHTMLElement);
+
+Var
+  F : TJSHTMLFormElement;
+  E : TJSHTMLElement;
+  I : Integer;
+
+  Procedure AddOption(A : TJSHTMLElement);
+
+  begin
+    ImageOptionsArr[I]:=A;
+    inc(I);
+  end;
+
+begin
+  F:=AddForm(aParent,fsHorizontal);
+  I:=0;
+  SetLength(ImageOptionsArr,100);
+  E:=AddPanel(F,'Image Options',4);
+  Prefix:='image.';
+  AddOption(AddCheckBox(E,'useframes','Use frames'));
+  AddOption(AddCheckBox(E,'externaljs','Use external Javascript file'));
+  AddOption(AddNumber(E,'DPI','Image DPI'));
+  AddOption(AddEdit(E,'sequence','Page number sequence format'));
+  AddStyleEmbedding(E);
+  E:=AddPanel(F,'TOC page',4);
+  I:=I+AddConfigureTOCPage(E,ImageOptionsArr,I);
+  E:=AddPanel(F,'Frame page',4);
+  I:=I+AddConfigureFramePage(E,ImageOptionsArr,I);
+  E:=AddPanel(F,'Page Navigator',4);
+  I:=I+AddConfigurePageNavigator(E,ImageOptionsArr,I);
+  SetLength(ImageOptionsArr,I);
+end;
+
+function TMainForm.DemosLoaded(Event: TEventListenerEvent): boolean;
+
+var
+  i : integer;
+  C,J : TJSObject;
+  A : TJSObjectDynArray;
+  N,D : String;
+  O : TJSHTMLOptionElement;
+  xhr : TJSXMLHttpRequest;
+
+begin
+  xhr:=TJSXMLHttpRequest(Event.Target);
+  console.log('Result of call ',xhr.Status);
+  if (xhr.status = 200) then
+    begin
+    J:=TJSJSON.parse(xhr.responseText);
+    A:=TJSObjectDynArray(J.Properties['data']);
+    for I:=0 to Length(A)-1 do
+      begin
+      C:=A[i];
+      N:=String(C.Properties['name']);
+      D:=String(C.Properties['description']);
+      O:=TJSHTMLOptionElement(Document.CreateElement('option'));
+      O.value:=N;
+      O.text:=D;
+      CBDemo.append(O);
+      end;
+    end
+  else
+    ShowError('Failed to load countries: '+IntToStr(xhr.Status));
+  Result := True;
+end;
+
+procedure TMainForm.GetDemoList;
+
+Var
+  xhr : TJSXMLHttpRequest;
+
+begin
+  xhr:=TJSXMLHttpRequest.New;
+  xhr.addEventListener('load', @DemosLoaded);
+  // Case sensitive
+  xhr.open('GET', ServerURL+'ReportList/', true);
+  xhr.send;
+end;
+
+procedure TMainForm.ShowError(Msg: String);
+
+Var
+  E : TJSHTMLElement;
+
+begin
+  E:=TJSHTMLElement(Document.CreateElement('p'));
+  E.className:='text-danger';
+  E.append(Msg);
+  Ptop.append(E);
+end;
+
+procedure TMainForm.Initialize;
+
+Var
+  frm, cont, PP, BOK : TJSHTMLElement;
+
+begin
+  cont:=TJSHTMLElement(Document.createElement('div'));
+  cont.classname:='container-fluid';
+  Document.Body.Append(cont);
+  PTop:=AddPanel(Cont,'Report',1);
+  frm:=AddForm(PTop ,fsHorizontal);
+  frm.style.cssText:='width: 50%';
+  CBDemo:=AddCombo(frm,'output','Report demo',[]);
+  GetDemoList;
+
+  CBFormat:=AddCombo(frm,'format','Output format',['PDF','PDF','HTML','Plain HTML','FPIMage','Images in HTML pages']);
+  CBNewWindow:=AddCheckBox(frm,'NewWindow','Open in new window');
+  BOK:=TJSHTMLElement(Document.createElement('button'));
+  BOK.Append('Show report');
+  BOK.ClassName:='btn btn-default';
+  BOK.onClick:=@DoClick;
+  PTOP.Append(BOK);
+  POptions:=AddPanel(Cont,'Options',1);
+  FNavs:=AddNav(POptions,['pdf','PDF','html','HTML','fpimage','Image'],'PDF',@DoNavClick);
+  PP:=TJSHTMLElement(Document.CreateElement('div'));
+  PP.classname:='tab-content';
+  POPtions.Append(PP);
+  SetLength(PTabs,3);
+  PPDFOptions:=CreateTabSheet(PP,'pdf',True);
+  AddPDFOptions(PPDFOptions);
+  PHTMLOptions:=CreateTabSheet(PP,'html',False);
+  AddHTMLOptions(PHTMLOptions);
+  PImageOptions:=CreateTabSheet(PP,'image',False);
+  AddImageOptions(PImageOptions);
+  PTabs[0]:=PPDFOptions;
+  PTabs[1]:=PHTMLOptions;
+  PTabs[2]:=PImageOptions;
+end;
+
+end.
+

+ 10 - 0
demo/fpreport/reportdemo.html

@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<HTML>
+<Title>Pas2JS and FPReport demo</Title>
+<script SRC="reportdemo.js" type="application/javascript"></script>
+<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+</body>
+<script>
+    rtl.run();
+</script>
+</HTML>

+ 74 - 0
demo/fpreport/reportdemo.lpi

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="reportdemo"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="2">
+      <Unit0>
+        <Filename Value="reportdemo.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="frmmain.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="reportdemo"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc -Jm -Jminclude $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 8 - 0
demo/fpreport/reportdemo.lpr

@@ -0,0 +1,8 @@
+program reportdemo;
+
+uses SysUtils, frmmain;
+
+begin
+  TMainForm.Create(Nil);
+end.
+

+ 622 - 0
demo/hotreload/dirwatch.pp

@@ -0,0 +1,622 @@
+unit dirwatch;
+{$IFDEF LINUX}
+{$DEFINE USEINOTIFY}
+{$ELSE}
+{$DEFINE USEGENERIC}
+{$ENDIF}
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils,
+{$IFDEF UNIX}
+  baseunix,
+{$IFDEF USEINOTIFY}
+  ctypes,
+  linux,
+{$ENDIF}
+{$ENDIF}
+  contnrs;
+
+
+
+Type
+  TFileEvent = (feModify,feAttrib,feCreate,feDelete);
+  TFileEvents = set of TFileEvent;
+
+  { TDirectoryEntry }
+  TDirectoryEntry = Class(TCollectionItem)
+  private
+    FEvents: TFileEvents;
+    FName: String;
+    FAttributes: Integer;
+    FGroup: gid_t;
+    FMode: mode_t;
+    FOwner: uid_t;
+    FSize: Int64;
+    FTimeStamp: TDateTime;
+  Protected
+{$IFDEF USEGENERIC}
+    procedure InitWatch(ABaseDir: String; AList: TFPStringHashTable);
+{$ENDIF}
+  Public
+    Property TimeStamp : TDateTime Read FTimeStamp Write FTimeStamp;
+    Property Size : Int64 Read FSize Write FSize;
+    Property Attributes : Integer Read FAttributes Write FAttributes;
+{$IFDEF UNIX}
+    Property Mode : mode_t Read FMode Write FMode;
+    Property Owner : uid_t Read FOwner Write FOwner;
+    Property Group : gid_t Read FGroup Write FGroup;
+{$ENDIF}
+  Published
+    Property Name : String Read FName Write FName;
+    Property Events : TFileEvents Read FEvents Write FEvents;
+  end;
+
+  { TDirectoryEntries }
+
+  TDirectoryEntries = Class(TCollection)
+  private
+    function GetE(AIndex : Integer): TDirectoryEntry;
+    procedure SetE(AIndex : Integer; AValue: TDirectoryEntry);
+  Public
+    Function IndexOfEntry(Const AName : String) : Integer;
+    Function EntryByName(Const AName : String) : TDirectoryEntry;
+    Function AddEntry(Const AName : String) : TDirectoryEntry;
+    Property Entries[AIndex : Integer] : TDirectoryEntry Read GetE Write SetE; default;
+  end;
+
+  TFileEventHandler = procedure (Sender : TObject; aEntry : TDirectoryEntry; AEvents : TFileEvents) of Object;
+
+  { TDirwatch }
+
+  TDirwatch = Class(TComponent)
+  private
+    FIdleInterval: Cardinal;
+    FOnIdle: TNotifyEvent;
+    FOnIdleNotify: TNotifyEvent;
+    FTerminated: Boolean;
+    FThreaded: Boolean;
+    FWatches: TDirectoryEntries;
+    FBaseDir: String;
+    FOnChange: TFileEventHandler;
+{$IFDEF USEGENERIC}
+    FReference : TFPStringHashTable;
+    FOldReference : TFPStringHashTable;
+    procedure DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
+    procedure DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
+{$ENDIF}
+{$IFDEF USEINOTIFY}
+    FINotifyFD : Cint;
+{$ENDIF}
+    function DirectoryEntryForFileName(S: String): TDirectoryEntry;
+    procedure DoChangeEvent(Entry: TDirectoryEntry; Events: TFileEvents);
+    procedure SetBaseDir(AValue: String);
+  Protected
+    procedure DoIdle; virtual;
+    procedure Check; virtual;
+    procedure DoneWatch; virtual;
+    procedure DoStartWatch; virtual;
+    procedure InitWatch;virtual;
+  Public
+    Constructor Create(AOWner : TComponent); override;
+    Destructor Destroy; override;
+    Procedure StartWatch;
+    Procedure AddWatch(const aFileName : string; aEvents : TFileEvents);
+    Procedure Terminate;
+    Property Terminated : Boolean Read FTerminated;
+  Published
+    Property BaseDir : String read FBaseDir Write SetBaseDir;
+    Property OnChange : TFileEventHandler Read FOnChange Write FOnChange;
+    Property Threaded : Boolean Read FThreaded Write FThreaded;
+    Property Watches : TDirectoryEntries Read FWatches Write FWatches;
+    Property OnIdle : TNotifyEvent Read FOnIdle Write FOnIdleNotify;
+    Property IdleInterval : Cardinal Read FIdleInterval Write FIdleInterval;
+  end;
+
+Const
+  EventNames : Array[TFileEvent] of string = ('Modify','Attrib','Create','Delete');
+  AllEvents =   [feModify,feAttrib,feCreate,feDelete];
+
+Function FileEventsToStr(Events : TFileEvents) : String;
+
+implementation
+
+
+Function FileEventsToStr(Events : TFileEvents) : String;
+
+Var
+  E : TFileEvent;
+
+begin
+  Result:='';
+  for E in Events do
+    begin
+    if Result<>'' then
+      Result:=Result+',';
+    Result:=Result+EventNames[E];
+    end;
+
+end;
+
+{ TDirwatch }
+Type
+
+  { TDirwatchThread }
+
+  TDirwatchThread = class(TThread)
+  Private
+    FDir:TDirWatch;
+  Public
+    Constructor Create(ADirwatch : TDirWatch);
+    Procedure Execute; override;
+  end;
+
+{ TDirectoryEntry }
+
+Function SearchRecToString(Info : TSearchRec; AEvents : TFileEvents) : String;
+
+begin
+  if feAttrib in AEvents then
+    Result:=IntToStr(Info.Attr)
+  else
+    Result:='';
+  Result:=Result+';'+IntToStr(Info.Size)+';'+IntToStr(Info.Time);
+end;
+
+{$IFDEF USEGENERIC}
+procedure TDirectoryEntry.InitWatch(ABaseDir: String; AList: TFPStringHashTable);
+
+Var
+  Info : TSearchRec;
+  FN : String;
+
+begin
+  if (ABaseDir<>'') then
+    FN:=IncludeTrailingPathDelimiter(ABaseDir)+Name
+  else
+    FN:=Name;
+  if FindFirst(FN,faAnyFile,Info)=0 then
+    begin
+    if (faDirectory and Info.Attr) = 0 then
+      begin
+      AList.Add(FN,SearchRecToString(Info,Self.Events))
+      end
+    else
+      begin
+      FindClose(Info);
+      FN:=IncludeTrailingPathDelimiter(FN);
+      if FindFirst(FN+AllFilesMask,0,Info)=0  then
+        Repeat
+          if (info.Name<>'.') and (Info.Name<>'..') then
+            AList.Add(FN+Info.Name,SearchRecToString(Info,Self.Events));
+        until (FindNext(Info)<>0)
+      end;
+    FindClose(Info);
+    end
+end;
+
+{$ENDIF}
+{$IFDEF USEINOTIFY}
+
+{$ENDIF}
+{ TDirwatchThread }
+
+constructor TDirwatchThread.Create(ADirwatch: TDirWatch);
+
+begin
+  FDir:=ADirWatch;
+  FreeOnTerminate:=True;
+  inherited create(False);
+end;
+
+procedure TDirwatchThread.Execute;
+begin
+  FDir.DoStartWatch;
+end;
+
+
+procedure TDirwatch.SetBaseDir(AValue: String);
+begin
+  if FBaseDir=AValue then Exit;
+  FBaseDir:=AValue;
+  FWatches.Clear;
+end;
+
+constructor TDirwatch.Create(AOWner: TComponent);
+begin
+  inherited Create(AOWner);
+  FWatches:=TDirectoryEntries.Create(TDirectoryEntry);
+  FidleInterval:=100;
+end;
+
+destructor TDirwatch.Destroy;
+begin
+  FreeAndNil(FWatches);
+  inherited Destroy;
+end;
+
+Type
+  { TDirwatchChange }
+  TDirwatchChange = Class
+    FEntry : TDirectoryEntry;
+    FEvents : TFileEvents;
+    FDirWatch : TDirWatch;
+    Constructor Create(AEntry : TDirectoryEntry;aEvents : TFileEvents;ADirWatch : TDirWatch);
+    Procedure DoEvent;
+ end;
+
+{ TDirwatchChange }
+
+constructor TDirwatchChange.Create(AEntry: TDirectoryEntry; aEvents: TFileEvents; ADirWatch: TDirWatch);
+
+begin
+  FEntry:=AEntry;
+  FEvents:=AEvents;
+  FDirWatch:=ADirWatch;
+end;
+
+procedure TDirwatchChange.DoEvent;
+begin
+  FDirwatch.FonChange(FDirwatch,FEntry,FEvents);
+end;
+
+Procedure TDirwatch.DoChangeEvent(Entry : TDirectoryEntry; Events : TFileEvents);
+
+Var
+  W : TDirWatchChange;
+
+begin
+  try
+    if Assigned(FOnChange) then
+      if Not Threaded then
+        FonChange(Self,Entry,Events)
+      else
+        begin
+        W:=TDirWatchChange.Create(Entry,Events,Self);
+        try
+          TThread.Synchronize(TThread.CurrentThread,@W.DoEvent)
+        finally
+          W.Free;
+        end;
+        end
+  Finally
+    // Specially created
+    if Entry.Collection=Nil then
+      FreeAndNil(Entry);
+  end;
+end;
+
+
+procedure TDirwatch.DoIdle;
+
+begin
+  if Assigned(FOnIdle) then
+    FOnIdle(Self);
+end;
+
+Function TDirwatch.DirectoryEntryForFileName(S : String) : TDirectoryEntry;
+
+begin
+  Result:=FWatches.EntryByName(S);
+  if (Result=Nil) then
+    Result:=FWatches.EntryByName(ExtractFilePath(S));
+  if (Result=Nil) then
+    begin
+    Result:=TDirectoryEntry.Create(Nil);
+    Result.Name:=S;
+    end;
+end;
+
+{$IFDEF USEGENERIC}
+procedure TDirwatch.DoneWatch;
+
+begin
+  FreeAndNil(FReference);
+end;
+
+procedure TDirwatch.InitWatch;
+
+Var
+  I : Integer;
+
+begin
+  FReference:=TFPStringHashTable.Create;
+  For I:=0 to FWatches.Count-1 do
+    FWatches[i].InitWatch(BaseDir,FReference);
+end;
+
+procedure TDirwatch.DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
+
+Var
+  DE : TDirectoryEntry;
+
+begin
+  DE:=FWatches.EntryByName(Key);
+  if (DE=Nil) then
+    DE:=FWatches.EntryByName(ExtractFilePath(Key));
+  if (DE=Nil) then
+    begin
+    DE:=TDirectoryEntry.Create(Nil);
+    DE.Name:=Key;
+    end;
+  DoChangeEvent(DE,[feDelete]);
+  Continue:=False;
+end;
+
+procedure TDirwatch.DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
+
+Var
+  S : String;
+  E : TFileEvents;
+  DE : TDirectoryEntry;
+
+begin
+//  Writeln('check file: ',key,' attrs : ',Item);
+  E:=[];
+  S:=FOldReference[Key];
+  if (S='') then
+    E:=[feCreate]
+  else
+    begin
+    FOldReference.Delete(Key);
+    if (S<>Item) then
+      E:=[feAttrib];
+    end;
+  if E<>[] then
+    begin
+    DE:=DirectoryEntryForFileName(Key);
+    DoChangeEvent(DE,E);
+    Continue:=False;
+    end;
+end;
+
+procedure TDirwatch.Check;
+
+begin
+  FOldReference:=FReference;
+  try
+    FReference:=TFPStringHashTable.Create;
+    InitWatch;
+    FReference.Iterate(@doCheckItem);
+    if FoldReference.Count>0 then
+      FReference.Iterate(@doDeletedItem);
+      // Deleted files
+    Sleep(IdleInterval);
+  finally
+    FreeAndNil(FoldReference);
+  end;
+end;
+{$ENDIF}
+
+{$IFDEF USEINOTIFY}
+Procedure WatchDirectory(d : string);
+
+Const
+  Events = IN_MODIFY or IN_ATTRIB or IN_CREATE or IN_DELETE;
+
+Var
+  fd, wd,fnl,len : cint;
+  fds : tfdset;
+  e : ^inotify_event;
+  buf : Array[0..1023*4] of Byte; // 4K Buffer
+  fn : string;
+  p : pchar;
+
+begin
+  fd:=inotify_init;
+  try
+    wd:=inotify_add_watch(fd,pchar(d),Events);
+    fpFD_Zero(fds);
+    fpFD_SET(fd,fds);
+    While (fpSelect(fd+1,@fds,nil,nil,nil)>=0) do
+      begin
+      len:=fpRead(fd,buf,sizeof(buf));
+      e:=@buf;
+      While ((pchar(e)-@buf)<len) do
+        begin
+        fnl:=e^.len;
+        if (fnl>0) then
+          begin
+          p:=@e^.name+fnl-1;
+          While (p^=#0) do
+            begin
+            dec(p);
+            dec(fnl);
+            end;
+          end;
+        setlength(fn,fnl);
+        if (fnl>0) then
+          move(e^.name,fn[1],fnl);
+        Writeln('Change ',e^.mask,' (',
+//                InotifyEventsToString(e^.mask),
+                ') detected for file "',fn,'"');
+        ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
+        end;
+      end;
+  finally
+    fpClose(fd);
+  end;
+end;
+
+procedure TDirwatch.DoneWatch;
+
+begin
+  fpClose(FInotifyFD);
+end;
+
+procedure TDirwatch.InitWatch;
+
+Const
+  NativeEvents : Array[TFileEvent] of cint = (IN_Modify,IN_Attrib,IN_Create,IN_Delete);
+
+Var
+  WD,I,NEvents : Integer;
+  E : TFileEvent;
+  BD,FN : String;
+
+begin
+  BD:=BaseDir;
+  if BD<>'' then
+    BD:=IncludeTrailingPathDelimiter(BD);
+  FINotifyFD:=inotify_init;
+  For I:=0 to FWatches.Count-1 do
+    begin
+    NEvents:=0;
+    for E in FWatches[i].Events do
+      NEvents:=NEvents OR NativeEvents[E];
+    FN:=BD+FWatches[i].Name;
+    wd:=inotify_add_watch(FINotifyFD,PChar(FN),NEvents);
+    end;
+end;
+
+Function NativeEventsToEvents(Native : cint) : TFileEvents;
+
+  Procedure MA(C : cint; AEvent : TFileEvent);
+
+  begin
+    if (Native and C)<>0 then
+      Include(Result,AEvent);
+  end;
+
+begin
+  Result:=[];
+  MA(IN_ACCESS,feAttrib);
+  MA(IN_MODIFY,feModify);
+  MA(IN_ATTRIB,feAttrib);
+  MA(IN_CLOSE_WRITE,feAttrib);
+  MA(IN_CLOSE_NOWRITE,feAttrib);
+  MA(IN_OPEN,feAttrib);
+  MA(IN_MOVED_FROM,feCreate);
+  MA(IN_MOVED_TO,feDelete);
+  MA(IN_CREATE,feCreate);
+  Ma(IN_DELETE,feDelete);
+  Ma(IN_DELETE_SELF,feDelete);
+  Ma(IN_MOVE_SELF,feDelete);
+  Ma(IN_UNMOUNT,feDelete);
+  // IN_Q_OVERFLOW
+  // IN_IGNORED
+
+end;
+
+procedure TDirwatch.Check;
+
+Var
+  fnl,len : cint;
+  e : ^inotify_event;
+  buf : Array[0..1023*4] of Byte; // 4K Buffer
+  fn : string;
+  p : pchar;
+  fds : tfdset;
+  Timeout : ttimeval;
+
+begin
+  fpFD_Zero(fds);
+  fpFD_SET(FINotifyFD,fds);
+  timeout.tv_sec:=FIdleInterval div 1000;
+  timeout.tv_usec:=(FIdleInterval mod 1000)*1000;
+  if (fpSelect(FINotifyFD+1,@fds,nil,nil,@Timeout)<=0) then
+    exit;
+  len:=fpRead(FINotifyFD,buf,sizeof(buf));
+  e:=@buf;
+  While ((pchar(e)-@buf)<len) do
+    begin
+    fnl:=e^.len;
+    if (fnl>0) then
+      begin
+      p:=@e^.name+fnl-1;
+      While (p^=#0) do
+        begin
+        dec(p);
+        dec(fnl);
+        end;
+      end;
+    setlength(fn,fnl);
+    if (fnl>0) then
+      move(e^.name,fn[1],fnl);
+    DoChangeEvent(DirectoryEntryForFileName(FN),NativeEventsToEvents(E^ .mask));
+    ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
+    end;
+end;
+{$ENDIF}
+
+procedure TDirwatch.DoStartWatch;
+
+begin
+  InitWatch;
+  try
+    While not Terminated do
+      begin
+      Check;
+      if Threaded then
+        TThread.Synchronize(TThread.CurrentThread,@DoIdle)
+      else
+        DoIdle;
+      end;
+  Finally
+    DoneWatch;
+  end;
+end;
+
+procedure TDirwatch.StartWatch;
+
+begin
+  If Threaded then
+    TDirwatchThread.Create(Self).WaitFor
+  else
+    DoStartWatch;
+end;
+
+procedure TDirwatch.AddWatch(const aFileName: string; aEvents: TFileEvents);
+begin
+  FWatches.AddEntry(AFileName).Events:=AEvents;
+end;
+
+procedure TDirwatch.Terminate;
+begin
+  FTerminated:=True;
+end;
+
+{ TDirectoryEntries }
+
+function TDirectoryEntries.GetE(AIndex : Integer): TDirectoryEntry;
+begin
+  Result:=TDirectoryEntry(Items[AIndex]);
+end;
+
+procedure TDirectoryEntries.SetE(AIndex : Integer; AValue: TDirectoryEntry);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TDirectoryEntries.IndexOfEntry(const AName: String): Integer;
+
+begin
+  Result:=Count-1;
+  While (Result>=0) and (GetE(Result).Name<>AName) do
+    Dec(Result);
+end;
+
+function TDirectoryEntries.EntryByName(const AName: String): TDirectoryEntry;
+
+Var
+  I : Integer;
+
+begin
+  I:=IndexOfEntry(AName);
+  If (I=-1) then
+    Result:=Nil
+  else
+    Result:=GetE(I);
+end;
+
+function TDirectoryEntries.AddEntry(Const AName: String): TDirectoryEntry;
+begin
+  Result:=Add as TDirectoryEntry;
+  Result.Name:=AName;
+end;
+
+end.
+

+ 2 - 0
demo/hotreload/hotreload.cfg

@@ -0,0 +1,2 @@
+-Jirtl.js
+-Jc

+ 9 - 0
demo/hotreload/hotreload.html

@@ -0,0 +1,9 @@
+<HTML>
+<Title>Pas2JS HotReloadD web demo</Title>
+<script SRC="hotreload.js" type="application/javascript"></script>
+<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+</body>
+<script>
+    rtl.run();
+</script>
+</HTML>

+ 70 - 0
demo/hotreload/hotreload.lpi

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="hotreload"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="hotreload.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="hotreload"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$makeexe(pas2js) -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 116 - 0
demo/hotreload/hotreload.lpr

@@ -0,0 +1,116 @@
+program hotreload;
+
+uses sysutils,js,web, hotreloadclient;
+
+Type
+
+  { TForm }
+
+  TForm = Class
+    Button1 : TJSElement;
+    Edit : TJSHTMLInputElement;
+    FLastReq : TJSXMLHttpRequest;
+    function RecompileClick(Event: TJSMouseEvent): boolean;
+    Constructor create; reintroduce;
+  private
+    function DoReloadChange(Event: TEventListenerEvent): boolean;
+    function doResponse(Event: TEventListenerEvent): boolean;
+  end;
+
+function TForm.doResponse(Event: TEventListenerEvent): boolean;
+
+Var
+  Data : TJSObject;
+  S : String;
+
+begin
+  Result:=True;
+  console.warn('Compile request response received');
+  try
+     Data:=TJSJSON.parse(FLastReq.responseText);
+     if data['success'] then
+       begin
+       if isInteger(data['compileID']) then
+         begin
+         S:=String(data['compileID']);
+         Edit.value:=S;
+         end;
+       end
+     else
+       Edit.value:=''
+  except
+    console.error('Compile request response received non-json response: '+FLastReq.ResponseText);
+  end;
+  FLastReq:=nil;
+end;
+
+function TForm.RecompileClick(Event: TJSMouseEvent): boolean;
+
+Var
+  Req : TJSXMLHttpRequest;
+
+begin
+   Result:=True;
+   console.info('Recompile requested');
+   Req:=TJSXMLHttpRequest.new;
+   Req.addEventListener('load',@DoResponse);
+   Req.open('POST','$sys/compile');
+   Req.send;
+   FLastReq:=Req;
+end;
+
+constructor TForm.Create;
+
+Var
+  D,Panel,PanelContent : TJSElement;
+  CB : TJSHTMLInputElement;
+
+begin
+   Panel:=document.createElement('div');
+   // attrs are default array property...
+   Panel['class']:='panel panel-default';
+   PanelContent:=document.createElement('div');
+   PanelContent['class']:='panel-body';
+   Button1:=document.createElement('input');
+   Button1['id']:='Button1';
+   Button1['type']:='submit';
+   Button1['class']:='btn btn-default';
+   Button1['value']:='Recompile';
+   TJSHTMLElement(Button1).onclick:=@RecompileClick;
+   document.body.appendChild(Panel);
+   Edit:=TJSHTMLInputElement(document.createElement('input'));
+   Edit['id']:='recompileID';
+   Edit.readOnly:=true;
+   cb:=TJSHTMLInputElement(document.createElement('input'));
+   cb['id']:='autoreload';
+   cb['type']:='checkbox';
+   cb.onchange:=@DoReloadChange;
+   cb.checked:=THotReload.getglobal.options.Reload;
+   Panel.appendChild(PanelContent);
+   PanelContent.appendChild(Button1);
+   D:=Document.createElement('span');
+   D.id:='buildlabel';
+   D.appendChild(Document.createTextNode('Build ID'));
+   PanelContent.appendChild(D);
+   PanelContent.appendChild(Edit);
+   PanelContent.appendChild(cb);
+   D:=Document.createElement('span');
+   D.id:='reloadhint';
+   D.appendChild(Document.createTextNode('Reload page on build/sync'));
+   PanelContent.appendChild(D);
+end;
+
+function TForm.DoReloadChange(Event: TEventListenerEvent): boolean;
+begin
+  Result:=True;
+  THotReload.getglobal.options.Reload:=not THotReload.getglobal.options.Reload;
+end;
+
+begin
+  THotReload.StartHotReload;
+  TForm.Create;
+
+//  THotReload.getglobal.options.Path:='status.json';
+//  THotReload.getglobal.options.log:=True;
+end.
+

+ 62 - 0
demo/hotreload/server.lpi

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="server"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <Units Count="2">
+      <Unit0>
+        <Filename Value="server.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="dirwatch.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="server"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 366 - 0
demo/hotreload/server.lpr

@@ -0,0 +1,366 @@
+program server;
+
+uses
+  {$IFDEF UNIX}cthreads,{$ENDIF}
+  sysutils, classes, fpjson, contnrs, syncobjs, custhttpapp, fpwebfile,
+  httproute, dirwatch, httpdefs;
+
+Type
+  TDirWatcher = Class;
+  THTTPApplication = Class;
+
+  { TCompileItem }
+
+  TCompileItem = Class(TCollectionItem)
+  private
+    FCommandLine: string;
+    FFileName: string;
+    FOutput : TStrings;
+    FThread: TThread;
+    function GetOutput: TStrings;
+  Public
+    Property FileName : string Read FFileName Write FFileName;
+    Property CommandLine : string Read FCommandLine Write FCommandLine;
+    Property Output : TStrings Read GetOutput;
+    Property Thread : TThread Read FThread;
+  end;
+
+  { TCompiles }
+
+  TCompiles = Class(TCollection)
+  private
+    function GetC(AIndex : Integer): TCompileItem;
+  Public
+     Property Compiles[AIndex : Integer] : TCompileItem Read GetC; default;
+  end;
+
+
+  { TCompileThread }
+
+  TCompileThread = class(TThread)
+  private
+    FItem: TCompileItem;
+    procedure SetItem(AValue: TCompileItem);
+  Public
+    Constructor create(aItem : TCompileItem);
+    Procedure Execute; override;
+    Property Item : TCompileItem read FItem write SetItem;
+  end;
+
+  { TDirWatcher }
+
+  TDirWatcher = Class(TComponent)
+  Private
+    FApp : THTTPApplication;
+    FDW : TDirWatch;
+    procedure DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
+  Public
+    Constructor Create(App : THTTPApplication; ADir : String); reintroduce;
+    Destructor Destroy; override;
+  end;
+
+  { THTTPApplication }
+
+  THTTPApplication = Class(TCustomHTTPApplication)
+  private
+    FProjectFile: String;
+    FStatusLock : TCriticalSection;
+    FQuiet: Boolean;
+    FWatch: Boolean;
+    FDW : TDirWatcher;
+    FStatusList : TFPObjectList;
+    FCompiles : TCompiles;
+    Procedure AddToStatus(AEntry : TDirectoryEntry; AEvents : TFileEvents);
+    procedure DoStatusRequest(ARequest: TRequest; AResponse: TResponse);
+    procedure DoRecompile(ARequest: TRequest; AResponse: TResponse);
+    function ScheduleCompile(const aProjectFile: String; ACommandLine : String = ''): Integer;
+    procedure StartWatch(ADir: String);
+    procedure Usage(Msg: String);
+  public
+    Constructor Create(AOWner : TComponent); override;
+    Destructor Destroy; override;
+    procedure DoLog(EventType: TEventType; const Msg: String); override;
+    Procedure DoRun; override;
+    property Quiet : Boolean read FQuiet Write FQuiet;
+    Property Watch : Boolean Read FWatch Write FWatch;
+    Property ProjectFile : String Read FProjectFile Write FProjectFile;
+  end;
+
+
+  { TCompileThread }
+
+procedure TCompileThread.SetItem(AValue: TCompileItem);
+begin
+  if FItem=AValue then Exit;
+  FItem:=AValue;
+end;
+
+constructor TCompileThread.create(aItem: TCompileItem);
+begin
+
+end;
+
+  procedure TCompileThread.Execute;
+begin
+
+end;
+
+{ TCompiles }
+
+function TCompiles.GetC(AIndex : Integer): TCompileItem;
+begin
+  Result:=Items[Aindex] as TCompileItem;
+end;
+
+{ TCompileItem }
+
+function TCompileItem.GetOutput: TStrings;
+begin
+  If (FOutput=Nil) then
+    FOutput:=TStringList.Create;
+  Result:=FOutput;
+end;
+
+
+{ TDirWatcher }
+
+procedure TDirWatcher.DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
+begin
+  if Assigned(FApp) then
+    FApp.AddToStatus(AEntry,AEvents);
+end;
+
+constructor TDirWatcher.Create(App: THTTPApplication; ADir: String);
+begin
+ Inherited create(APP);
+ FApp:=App;
+ FDW:=TDirwatch.Create(Self);
+ FDW.AddWatch(ADir,allEvents);
+ FDW.OnChange:=@DoChange;
+ TThread.ExecuteInThread(@FDW.StartWatch);
+end;
+
+destructor TDirWatcher.Destroy;
+begin
+  FApp:=Nil;
+  FDW.Terminate;
+  FreeAndNil(FDW);
+  inherited Destroy;
+end;
+
+{ THTTPApplication }
+
+procedure THTTPApplication.DoLog(EventType: TEventType; const Msg: String);
+begin
+ if Quiet then
+   exit;
+ if IsConsole then
+   Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
+ else
+   inherited DoLog(EventType, Msg);
+end;
+
+procedure THTTPApplication.Usage(Msg : String);
+
+begin
+  if (Msg<>'') then
+    Writeln('Error: ',Msg);
+  Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
+  Writeln('Where options is one or more of : ');
+  Writeln('-d --directory=dir  Base directory from which to serve files.');
+  Writeln('                    Default is current working directory: ',GetCurrentDir);
+  Writeln('-h --help           This help text');
+  Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
+  Writeln('-n --noindexpage    Do not allow index page.');
+  Writeln('-p --port=NNNN      TCP/IP port to listen on (default is 3000)');
+  Writeln('-q --quiet          Do not write diagnostic messages');
+  Writeln('-w --watch          Watch directory for changes');
+  Halt(Ord(Msg<>''));
+end;
+
+constructor THTTPApplication.Create(AOWner: TComponent);
+begin
+  inherited Create(AOWner);
+  FStatusLock:=TCriticalSection.Create;
+  FStatusList:=TFPObjectList.Create(False);
+  FCompiles:=TCompiles.Create(TCompileItem);
+end;
+
+destructor THTTPApplication.Destroy;
+begin
+  FStatusLock.Enter;
+  try
+    FreeAndNil(FCompiles);
+    FreeAndNil(FStatusList);
+  finally
+    FStatusLock.Leave;
+  end;
+  FreeAndNil(FStatusLock);
+  inherited Destroy;
+end;
+
+procedure THTTPApplication.StartWatch(ADir : String);
+
+begin
+  FDW:=TDirWatcher.Create(Self,ADir);
+end;
+
+procedure THTTPApplication.AddToStatus(AEntry: TDirectoryEntry; AEvents: TFileEvents);
+begin
+  Log(etDebug,'File change detected: %s (%s)',[AEntry.name,FileEventsToStr(AEvents)]);
+  FStatusLock.Enter;
+  try
+    FStatusList.Add(TJSONObject.Create(['action','file','name',AEntry.name,'events',FileEventsToStr(AEvents)]));
+  finally
+    FStatusLock.Leave;
+  end;
+end;
+
+procedure THTTPApplication.DoStatusRequest(ARequest : TRequest; AResponse : TResponse);
+
+Var
+  R,O : TJSONObject;
+  A : TJSONArray;
+  I : integer;
+begin
+  Log(etDebug,'Status request from: %s',[ARequest.RemoteAddress]);
+  R:=Nil;
+  try
+    FStatusLock.Enter;
+    try
+      if (FStatusList.Count=0) then
+        R:=TJSONObject.Create(['ping',True])
+      else
+        begin
+        O:=FStatusList[0] as TJSONObject;
+        FStatusList.Delete(0);
+        if O.Get('action','')<>'file' then
+          R:=O
+        else
+          begin
+          // If first event is file event, then add and delete all file events in list.
+          A:=TJSONArray.Create([O]);
+          O.Delete('action');
+          R:=TJSONObject.Create(['action','sync','files',A]);
+          For I:=FStatusList.Count-1 downto 0 do
+            begin
+            O:=FStatusList[0] as TJSONObject;
+            if (O.Get('action','')='file') then
+              begin
+              A.Add(O);
+              O.Delete('action');
+              FStatusList.Delete(I);
+              end;
+            end;
+          end
+        end;
+    finally
+      FStatusLock.Leave;
+    end;
+    AResponse.ContentType:='application/json';
+    AResponse.Content:=R.AsJSON;
+    AResponse.SendResponse;
+  finally
+    R.Free;
+  end;
+end;
+
+Function THTTPApplication.ScheduleCompile(const aProjectFile : String; ACommandLine : String = '') : Integer;
+
+Var
+  CI : TCompileItem;
+
+begin
+  CI:=FCompiles.Add as TCompileItem;
+  CI.FileName:=AProjectFile;
+  CI.FThread:=TCompileThread.Create(CI);
+  Result:=CI.ID;
+end;
+
+procedure THTTPApplication.DoRecompile(ARequest: TRequest; AResponse: TResponse);
+
+Var
+  ID : Integer;
+  PF,CL : String;
+
+begin
+  PF:=ARequest.ContentFields.Values['ProjectFile'];
+  CL:=ARequest.ContentFields.Values['CommandLine'];
+  if PF='' then
+    PF:=ProjectFile;
+  If (PF='') then
+    begin
+    AResponse.Code:=404;
+    AResponse.CodeText:='No project file';
+    AResponse.ContentType:='application/json';
+    AResponse.Content:='{ "success" : false, "message": "no project file set or provided" }';
+    end
+  else
+    begin
+    ID:=ScheduleCompile(PF,CL);
+    AResponse.Code:=200;
+    AResponse.ContentType:='application/json';
+    AResponse.Content:=Format('{ "success" : true, "file": "%s", "commandLine" : "%s", "compileID": %d }',[StringToJSONString(PF),StringToJSONString(CL),ID]);
+    end;
+end;
+
+procedure THTTPApplication.DoRun;
+
+Var
+  S,IndexPage,D : String;
+
+begin
+  S:=Checkoptions('hqd:ni:p:wc::',['help','quiet','noindexpage','directory:','port:','indexpage:','watch','compile::']);
+  if (S<>'') or HasOption('h','help') then
+    usage(S);
+  Quiet:=HasOption('q','quiet');
+  Watch:=HasOption('w','watch');
+
+  Port:=StrToIntDef(GetOptionValue('p','port'),3000);
+  D:=GetOptionValue('d','directory');
+  if D='' then
+    D:=GetCurrentDir;
+  Log(etInfo,'Listening on port %d, serving files from directory: %s',[Port,D]);
+  {$ifdef unix}
+  {$ifdef darwin}
+  MimeTypesFile:='/private/etc/apache2/mime.types';
+  {$else}
+  MimeTypesFile:='/etc/mime.types';
+  {$endif}
+  {$endif}
+  if Watch then
+    StartWatch(D);
+  httprouter.RegisterRoute('$sys/status',rmGet,@DoStatusRequest);
+  if Hasoption('c','compile') then
+    begin
+    ProjectFile:=GetOptionValue('c','compile');
+    if ProjectFile='' then
+      ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
+    If Not FileExists(ProjectFile) then
+      ProjectFile:=IncludeTrailingPathDelimiter(D)+'server.lpr';
+    httprouter.RegisterRoute('$sys/compile',rmPost,@DoRecompile);
+    end;
+  TSimpleFileModule.BaseDir:=IncludeTrailingPathDelimiter(D);
+  TSimpleFileModule.OnLog:=@Log;
+  If not HasOption('n','noindexpage') then
+    begin
+    IndexPage:=GetOptionValue('i','indexpage');
+    if IndexPage='' then
+      IndexPage:='index.html';
+    Log(etInfo,'Using index page %s',[IndexPage]);
+    TSimpleFileModule.IndexPageName:=IndexPage;
+    end;
+  TSimpleFileModule.RegisterDefaultRoute;
+  inherited;
+end;
+
+Var
+  Application : THTTPApplication;
+
+begin
+  Application:=THTTPApplication.Create(Nil);
+  Application.Initialize;
+  Application.Run;
+  Application.Free;
+end.
+

+ 1 - 0
demo/hotreload/status.json

@@ -0,0 +1 @@
+{ "ping" : true }

+ 41 - 0
demo/jquery/demoadd.html

@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>add demo</title>
+  <style>
+  div {
+    width: 60px;
+    height: 60px;
+    margin: 10px;
+    float: left;
+  }
+  p {
+    clear: left;
+    font-weight: bold;
+    font-size: 16px;
+    color: blue;
+    margin: 0 10px;
+    padding: 2px;
+  }
+  </style>
+  <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
+  <script src="demoadd.js"></script>
+</head>
+<body>
+ 
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+ 
+<p>Added this... (notice no border)</p>
+ 
+<script>
+  rtl.run();
+</script>
+ 
+</body>
+</html>

+ 73 - 0
demo/jquery/demoadd.lpi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <UseDefaultCompilerOptions Value="True"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demoadd"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <VersionInfo>
+      <StringTable ProductVersion=""/>
+    </VersionInfo>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demoadd.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demoadd"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile)) -O-"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 5 - 0
demo/jquery/demoadd.pas

@@ -0,0 +1,5 @@
+uses libjquery;
+
+begin
+  JQuery('div').css('border','2px solid red').add('p').css('background','yellow');
+end.

+ 32 - 0
demo/jquery/demoaddclass.html

@@ -0,0 +1,32 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>addClass demo</title>
+  <style>
+  p {
+    margin: 8px;
+    font-size: 16px;
+  }
+  .selected {
+    color: red;
+  }
+  .highlight {
+    background: yellow;
+  }
+  </style>
+  <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
+  <script src="demoaddclass.js" type="application/javascript"></script>
+</head>
+<body>
+ 
+<p>Hello</p>
+<p>and</p>
+<p>Goodbye</p>
+ 
+<script>
+  rtl.run();
+</script>
+ 
+</body>
+</html>

+ 5 - 0
demo/jquery/demoaddclass.pas

@@ -0,0 +1,5 @@
+uses libjquery;
+
+begin
+  jQuery('p:last').addClass('selected highlight');
+end.

+ 33 - 0
demo/jquery/demoaddclass2.html

@@ -0,0 +1,33 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>addClass demo</title>
+  <style>
+  div {
+    background: white;
+  }
+  .red {
+    background: red;
+  }
+  .red.green {
+    background: green;
+  }
+  </style>
+  <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
+  <script src="demoaddclass2.js" type="application/javascript"></script>
+</head>
+<body>
+ 
+ <div>This div should be white</div>
+ <div class="red">This div will be green because it now has the "green" and "red" classes.
+   It would be red if the addClass function failed.</div>
+ <div>This div should be white</div>
+ <p>There are zero green divs</p>
+ 
+<script>
+  rtl.run();
+</script>
+ 
+</body>
+</html>

+ 16 - 0
demo/jquery/demoaddclass2.pas

@@ -0,0 +1,16 @@
+uses libjquery;
+
+function greenonred(index : Integer; currentclass:string) : string;
+
+begin
+  result:='';
+  if (currentClass='red') then
+    begin
+    result:='green';
+    jQuery('p').text('There is one green div');
+    end;
+end;
+
+begin
+  jQuery('div').addClass(@greenonred);
+end.

+ 51 - 0
demo/rtl/README.md

@@ -0,0 +1,51 @@
+### Simple demos for the RTL units
+
+### Compiling for running in node.js:
+
+```
+pas2js -Tnodejs -Jirtl.js demojsstring.pas
+pas2js -Tnodejs -Jirtl.js demojsregexp.pas
+pas2js -Tnodejs -Jirtl.js democollection.pas
+pas2js -Tnodejs -Jirtl.js democomponents.lpr
+pas2js -Tnodejs -Jirtl.js demostringlist.pas
+pas2js -Tnodejs -Jirtl.js demo_njsprocess.pas
+```
+
+### Compiling for running in a browser:
+```
+pas2js -Jc -Jirtl.js demodombuttonevent.pas
+pas2js -Jc -Jirtl.js demodocument1.pas
+pas2js -Jc -Jirtl.js democollection.pas
+pas2js -Jc -Jirtl.js democomponents.lpr
+pas2js -Jc -Jirtl.js demoajax.pas
+pas2js -Jc -Jirtl.js democanvas2d.pas
+pas2js -Jc -Jirtl.js demonew.pas
+pas2js -Jc -Jirtl.js democlasstopas.pas
+pas2js -Jc -Jirtl.js demodocument1.pas
+pas2js -Jc -Jirtl.js demouncaughtexception.pas
+pas2js -Jc -Jirtl.js demoxhr.lpr
+pas2js -Jc -Jirtl.js dembrowserconsole.lpr
+```
+When using lazarus, you can also open the respective .lpi files,
+and compile your project.
+Make sure pas2js is in your path, or the IDE will not find it.
+
+### Run in node.js
+
+To run the code, you need to run
+```
+nodejs demojsstring.js
+nodejs demojsregexp.js
+```
+etc.
+
+### Run/Show in a browser.
+Some of the projects can be run straight from file.
+Just open the file in the explorer using your favourite browser.
+
+The ajax demo needs to be run from a webserver, just as the demoxhr demo.
+
+One way to do so, is to compile the simpleserver example program from
+the fcl-web examples, and run it in this directory.
+Then point your browser to
+```http://localhost:3000/ajaxdemo.html```.

+ 52 - 0
demo/rtl/ajax.pas

@@ -0,0 +1,52 @@
+unit ajax;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, Web;
+
+type
+
+  { TAjax }
+
+  TAjax = class
+  private
+    FOnLoad: TJSEventHandler;
+    FXmlHttpRequest: TJSXMLHttpRequest;
+    procedure SetOnLoad(AValue: TJSEventHandler);
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure Open(AMethod, AUrl: string);
+    property OnLoad: TJSEventHandler write SetOnLoad;
+  end;
+
+implementation
+
+{ TAjax }
+
+procedure TAjax.SetOnLoad(AValue: TJSEventHandler);
+begin
+  FXmlHttpRequest.addEventListener('load', AValue);
+end;
+
+constructor TAjax.Create;
+begin
+  FXmlHttpRequest := TJSXMLHttpRequest.new;
+end;
+
+destructor TAjax.Destroy;
+begin
+//  FXmlHttpRequest.Free;
+end;
+
+procedure TAjax.Open(AMethod, AUrl: string);
+begin
+  FXmlHttpRequest.open(AMethod, AUrl, true);
+  FXmlHttpRequest.send;
+end;
+
+end.
+

+ 637 - 0
demo/rtl/countries.json

@@ -0,0 +1,637 @@
+{
+  "metaData" : { "fields" : [ 
+    { "name": "Name", "type": "string"}, 
+    { "name" : "Population", "type": "int" }
+    ]
+  },
+  "Data" : [
+    {
+      "Name" : "Afghanistan",
+      "Population" : 31628000
+    },
+    {
+      "Name" : "Albania",
+      "Population" : 2894000
+    },
+    {
+      "Name" : "Algeria",
+      "Population" : 38934000
+    },
+    {
+      "Name" : "Angola",
+      "Population" : 24228000
+    },
+    {
+      "Name" : "Argentina",
+      "Population" : 42980000
+    },
+    {
+      "Name" : "Armenia",
+      "Population" : 3006000
+    },
+    {
+      "Name" : "Australia",
+      "Population" : 23491000
+    },
+    {
+      "Name" : "Austria",
+      "Population" : 8534000
+    },
+    {
+      "Name" : "Azerbaijan",
+      "Population" : 9538000
+    },
+    {
+      "Name" : "Bahrain",
+      "Population" : 1362000
+    },
+    {
+      "Name" : "Bangladesh",
+      "Population" : 159078000
+    },
+    {
+      "Name" : "Belarus",
+      "Population" : 9470000
+    },
+    {
+      "Name" : "Belgium",
+      "Population" : 11225000
+    },
+    {
+      "Name" : "Benin",
+      "Population" : 10598000
+    },
+    {
+      "Name" : "Bolivia",
+      "Population" : 10562000
+    },
+    {
+      "Name" : "Bosnia and Herzegovina",
+      "Population" : 3818000
+    },
+    {
+      "Name" : "Botswana",
+      "Population" : 2220000
+    },
+    {
+      "Name" : "Brazil",
+      "Population" : 206078000
+    },
+    {
+      "Name" : "Bulgaria",
+      "Population" : 7226000
+    },
+    {
+      "Name" : "Burkina Faso",
+      "Population" : 17589000
+    },
+    {
+      "Name" : "Burundi",
+      "Population" : 10817000
+    },
+    {
+      "Name" : "Cambodia",
+      "Population" : 15328000
+    },
+    {
+      "Name" : "Cameroon",
+      "Population" : 22773000
+    },
+    {
+      "Name" : "Canada",
+      "Population" : 35540000
+    },
+    {
+      "Name" : "Central African Republic",
+      "Population" : 4804000
+    },
+    {
+      "Name" : "Chad",
+      "Population" : 13587000
+    },
+    {
+      "Name" : "Chile",
+      "Population" : 17763000
+    },
+    {
+      "Name" : "China",
+      "Population" : 1364270000
+    },
+    {
+      "Name" : "Colombia",
+      "Population" : 47791000
+    },
+    {
+      "Name" : "Congo Dem. Rep.",
+      "Population" : 74877000
+    },
+    {
+      "Name" : "Congo Rep.",
+      "Population" : 4505000
+    },
+    {
+      "Name" : "Costa Rica",
+      "Population" : 4758000
+    },
+    {
+      "Name" : "Croatia",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Cuba",
+      "Population" : 11379000
+    },
+    {
+      "Name" : "Cyprus",
+      "Population" : 1154000
+    },
+    {
+      "Name" : "Czech Republic",
+      "Population" : 10511000
+    },
+    {
+      "Name" : "Denmark",
+      "Population" : 5640000
+    },
+    {
+      "Name" : "Dominican Republic",
+      "Population" : 10406000
+    },
+    {
+      "Name" : "Ecuador",
+      "Population" : 15903000
+    },
+    {
+      "Name" : "Egypt Arab Rep.",
+      "Population" : 89580000
+    },
+    {
+      "Name" : "El Salvador",
+      "Population" : 6108000
+    },
+    {
+      "Name" : "Eritrea",
+      "Population" : 5110000
+    },
+    {
+      "Name" : "Estonia",
+      "Population" : 1314000
+    },
+    {
+      "Name" : "Ethiopia",
+      "Population" : 96959000
+    },
+    {
+      "Name" : "Finland",
+      "Population" : 5464000
+    },
+    {
+      "Name" : "France",
+      "Population" : 66207000
+    },
+    {
+      "Name" : "Gabon",
+      "Population" : 1688000
+    },
+    {
+      "Name" : "Gambia The",
+      "Population" : 1928000
+    },
+    {
+      "Name" : "Georgia",
+      "Population" : 4504000
+    },
+    {
+      "Name" : "Germany",
+      "Population" : 80890000
+    },
+    {
+      "Name" : "Ghana",
+      "Population" : 26787000
+    },
+    {
+      "Name" : "Greece",
+      "Population" : 10958000
+    },
+    {
+      "Name" : "Guatemala",
+      "Population" : 16015000
+    },
+    {
+      "Name" : "Guinea-Bissau",
+      "Population" : 1801000
+    },
+    {
+      "Name" : "Guinea",
+      "Population" : 12276000
+    },
+    {
+      "Name" : "Haiti",
+      "Population" : 10572000
+    },
+    {
+      "Name" : "Honduras",
+      "Population" : 7962000
+    },
+    {
+      "Name" : "Hong Kong SAR China",
+      "Population" : 7242000
+    },
+    {
+      "Name" : "Hungary",
+      "Population" : 9862000
+    },
+    {
+      "Name" : "India",
+      "Population" : 1295292000
+    },
+    {
+      "Name" : "Indonesia",
+      "Population" : 254455000
+    },
+    {
+      "Name" : "Iran Islamic Rep.",
+      "Population" : 78144000
+    },
+    {
+      "Name" : "Iraq",
+      "Population" : 34812000
+    },
+    {
+      "Name" : "Ireland",
+      "Population" : 4613000
+    },
+    {
+      "Name" : "Israel",
+      "Population" : 8215000
+    },
+    {
+      "Name" : "Italy",
+      "Population" : 61336000
+    },
+    {
+      "Name" : "Jamaica",
+      "Population" : 2721000
+    },
+    {
+      "Name" : "Japan",
+      "Population" : 127132000
+    },
+    {
+      "Name" : "Jordan",
+      "Population" : 6607000
+    },
+    {
+      "Name" : "Kazakhstan",
+      "Population" : 17289000
+    },
+    {
+      "Name" : "Kenya",
+      "Population" : 44864000
+    },
+    {
+      "Name" : "Korea Dem. Rep.",
+      "Population" : 25027000
+    },
+    {
+      "Name" : "Korea Rep.",
+      "Population" : 50424000
+    },
+    {
+      "Name" : "Kosovo",
+      "Population" : 1823000
+    },
+    {
+      "Name" : "Kuwait",
+      "Population" : 3753000
+    },
+    {
+      "Name" : "Kyrgyz Republic",
+      "Population" : 5834000
+    },
+    {
+      "Name" : "Lao PDR",
+      "Population" : 6689000
+    },
+    {
+      "Name" : "Latvia",
+      "Population" : 1990000
+    },
+    {
+      "Name" : "Lebanon",
+      "Population" : 4547000
+    },
+    {
+      "Name" : "Lesotho",
+      "Population" : 2109000
+    },
+    {
+      "Name" : "Liberia",
+      "Population" : 4397000
+    },
+    {
+      "Name" : "Libya",
+      "Population" : 6259000
+    },
+    {
+      "Name" : "Lithuania",
+      "Population" : 2929000
+    },
+    {
+      "Name" : "Macedonia FYR",
+      "Population" : 2076000
+    },
+    {
+      "Name" : "Madagascar",
+      "Population" : 23572000
+    },
+    {
+      "Name" : "Malawi",
+      "Population" : 16695000
+    },
+    {
+      "Name" : "Malaysia",
+      "Population" : 29902000
+    },
+    {
+      "Name" : "Mali",
+      "Population" : 17086000
+    },
+    {
+      "Name" : "Mauritania",
+      "Population" : 3970000
+    },
+    {
+      "Name" : "Mauritius",
+      "Population" : 1261000
+    },
+    {
+      "Name" : "Mexico",
+      "Population" : 125386000
+    },
+    {
+      "Name" : "Moldova",
+      "Population" : 3556000
+    },
+    {
+      "Name" : "Mongolia",
+      "Population" : 2910000
+    },
+    {
+      "Name" : "Morocco",
+      "Population" : 33921000
+    },
+    {
+      "Name" : "Mozambique",
+      "Population" : 27216000
+    },
+    {
+      "Name" : "Myanmar",
+      "Population" : 53437000
+    },
+    {
+      "Name" : "Namibia",
+      "Population" : 2403000
+    },
+    {
+      "Name" : "Nepal",
+      "Population" : 28175000
+    },
+    {
+      "Name" : "Netherlands",
+      "Population" : 16854000
+    },
+    {
+      "Name" : "New Zealand",
+      "Population" : 4510000
+    },
+    {
+      "Name" : "Nicaragua",
+      "Population" : 6014000
+    },
+    {
+      "Name" : "Niger",
+      "Population" : 19114000
+    },
+    {
+      "Name" : "Nigeria",
+      "Population" : 177476000
+    },
+    {
+      "Name" : "Norway",
+      "Population" : 5136000
+    },
+    {
+      "Name" : "Oman",
+      "Population" : 4236000
+    },
+    {
+      "Name" : "Pakistan",
+      "Population" : 185044000
+    },
+    {
+      "Name" : "Panama",
+      "Population" : 3868000
+    },
+    {
+      "Name" : "Papua New Guinea",
+      "Population" : 7464000
+    },
+    {
+      "Name" : "Paraguay",
+      "Population" : 6553000
+    },
+    {
+      "Name" : "Peru",
+      "Population" : 30973000
+    },
+    {
+      "Name" : "Philippines",
+      "Population" : 99139000
+    },
+    {
+      "Name" : "Poland",
+      "Population" : 37996000
+    },
+    {
+      "Name" : "Portugal",
+      "Population" : 10397000
+    },
+    {
+      "Name" : "Puerto Rico",
+      "Population" : 3548000
+    },
+    {
+      "Name" : "Qatar",
+      "Population" : 2172000
+    },
+    {
+      "Name" : "Romania",
+      "Population" : 19911000
+    },
+    {
+      "Name" : "Russian Federation",
+      "Population" : 143820000
+    },
+    {
+      "Name" : "Rwanda",
+      "Population" : 11342000
+    },
+    {
+      "Name" : "Saudi Arabia",
+      "Population" : 30887000
+    },
+    {
+      "Name" : "Senegal",
+      "Population" : 14673000
+    },
+    {
+      "Name" : "Serbia",
+      "Population" : 7129000
+    },
+    {
+      "Name" : "Sierra Leone",
+      "Population" : 6316000
+    },
+    {
+      "Name" : "Singapore",
+      "Population" : 5470000
+    },
+    {
+      "Name" : "Slovak Republic",
+      "Population" : 5419000
+    },
+    {
+      "Name" : "Slovenia",
+      "Population" : 2062000
+    },
+    {
+      "Name" : "Somalia",
+      "Population" : 10518000
+    },
+    {
+      "Name" : "South Africa",
+      "Population" : 54002000
+    },
+    {
+      "Name" : "South Sudan",
+      "Population" : 11911000
+    },
+    {
+      "Name" : "Spain",
+      "Population" : 46405000
+    },
+    {
+      "Name" : "Sri Lanka",
+      "Population" : 20639000
+    },
+    {
+      "Name" : "Sudan",
+      "Population" : 39350000
+    },
+    {
+      "Name" : "Swaziland",
+      "Population" : 1269000
+    },
+    {
+      "Name" : "Sweden",
+      "Population" : 9690000
+    },
+    {
+      "Name" : "Switzerland",
+      "Population" : 8190000
+    },
+    {
+      "Name" : "Syrian Arab Republic",
+      "Population" : 22158000
+    },
+    {
+      "Name" : "Tajikistan",
+      "Population" : 8296000
+    },
+    {
+      "Name" : "Tanzania",
+      "Population" : 51823000
+    },
+    {
+      "Name" : "Thailand",
+      "Population" : 67726000
+    },
+    {
+      "Name" : "Timor-Leste",
+      "Population" : 1212000
+    },
+    {
+      "Name" : "Togo",
+      "Population" : 7115000
+    },
+    {
+      "Name" : "Trinidad and Tobago",
+      "Population" : 1354000
+    },
+    {
+      "Name" : "Tunisia",
+      "Population" : 10997000
+    },
+    {
+      "Name" : "Turkey",
+      "Population" : 75932000
+    },
+    {
+      "Name" : "Turkmenistan",
+      "Population" : 5307000
+    },
+    {
+      "Name" : "Uganda",
+      "Population" : 37783000
+    },
+    {
+      "Name" : "Ukraine",
+      "Population" : 45363000
+    },
+    {
+      "Name" : "United Arab Emirates",
+      "Population" : 9086000
+    },
+    {
+      "Name" : "United Kingdom",
+      "Population" : 64510000
+    },
+    {
+      "Name" : "United States",
+      "Population" : 318857000
+    },
+    {
+      "Name" : "Uruguay",
+      "Population" : 3420000
+    },
+    {
+      "Name" : "Uzbekistan",
+      "Population" : 30743000
+    },
+    {
+      "Name" : "Venezuela RB",
+      "Population" : 30694000
+    },
+    {
+      "Name" : "Vietnam",
+      "Population" : 90730000
+    },
+    {
+      "Name" : "West Bank and Gaza",
+      "Population" : 4295000
+    },
+    {
+      "Name" : "Yemen Rep.",
+      "Population" : 26184000
+    },
+    {
+      "Name" : "Zambia",
+      "Population" : 15721000
+    },
+    {
+      "Name" : "Zimbabwe",
+      "Population" : 15246000
+    }
+  ]
+}

+ 39 - 0
demo/rtl/demo_njsprocess.pas

@@ -0,0 +1,39 @@
+program demo_njsprocess;
+
+uses
+  SysUtils, JS, NodeJS;
+
+//procedure List(s: jsvalue); assembler;
+//asm
+//  for (var key in s) if (s.hasOwnProperty(key)) console.log('prop="'+key+'"');
+//end;
+
+var
+  i: Integer;
+begin
+  //List(TNJSProcess);
+  writeln('argv: ',TNJSProcess.argv);
+  for i:=0 to length(TNJSProcess.argv)-1 do
+    writeln(i,'/',length(TNJSProcess.argv),' ',TNJSProcess.argv[i]);
+  writeln('arch=',TNJSProcess.arch);
+  writeln('config=',TNJSProcess.config);
+  writeln('cwd=',TNJSProcess.cwd);
+  writeln('env=',TNJSProcess.env);
+  writeln('execArgv=',TNJSProcess.execArgv);
+  writeln('execPath=',TNJSProcess.execPath);
+  writeln('getegid=',TNJSProcess.getegid);
+  writeln('geteuid=',TNJSProcess.geteuid);
+  writeln('getgid=',TNJSProcess.getgid);
+  writeln('getgroups=',TNJSProcess.getgroups);
+  writeln('getuid=',TNJSProcess.getuid);
+  writeln('memoryUsage=',TNJSProcess.memoryUsage);
+  writeln('pid=',TNJSProcess.pid);
+  writeln('platform=',TNJSProcess.platform);
+  writeln('release=',TNJSProcess.release);
+  writeln('title=',TNJSProcess.title);
+  writeln('umask=',TNJSProcess.umask);
+  writeln('uptime=',TNJSProcess.uptime);
+  writeln('version=',TNJSProcess.version);
+  writeln('versions=',TNJSProcess.versions);
+end.
+

+ 13 - 0
demo/rtl/demoajax.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <script type="application/javascript" src="demoajax.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 71 - 0
demo/rtl/demoajax.lpi

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demoajax"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+        <LaunchingApplication Use="True" PathPlusParams="/usr/bin/xdg-open $MakeDir($(ProjPath))$NameOnly($(ProjFile)).html"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demoajax.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demoajax"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jc -Jirtl.js -Tbrowser -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 45 - 0
demo/rtl/demoajax.lpr

@@ -0,0 +1,45 @@
+program demoajax;
+
+uses SysUtils, Web, ajax;
+
+Type
+
+  { TForm }
+
+  TForm = Class
+    function onLoad(Event: TEventListenerEvent): boolean;
+    Constructor Create;
+  end;
+
+
+function TForm.onLoad(Event: TEventListenerEvent): boolean;
+var
+  lPanel: TJSElement;
+  lStatus: longint;
+begin
+  lStatus := TJSXMLHttpRequest(event.target).Status;
+  lPanel := document.createElement('div');
+  if(lStatus = 404) then
+    lPanel['style'] := 'width: 100px; height: 100px; border: 4px solid red;'
+  else
+    lPanel['style'] := 'width: 100px; height: 100px; border: 4px solid green;';
+  document.body.appendChild(lPanel);
+  console.log(TJSXMLHttpRequest(event.target).Status);
+  Result := True;
+end;
+
+constructor TForm.Create;
+
+var
+  lAjax: TAjax;
+
+begin
+  lAjax := TAjax.Create;
+  lAjax.OnLoad := @onLoad;
+  lAjax.Open('GET','demoajax2.html');
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 16 - 0
demo/rtl/demobrowserconsole.html

@@ -0,0 +1,16 @@
+<html>
+  <head>
+    <title>Browser console output emulation demo</title>
+    <meta charset="utf-8"/>
+    <script type="application/javascript" src="demobrowserconsole.js"></script>
+  </head>
+  <body>
+    <div id="pasjsconsole" style="width: 640px; height: 480px;">
+    </div>
+    <script type="application/javascript">
+     rtl.showUncaughtExceptions=true;
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 73 - 0
demo/rtl/demobrowserconsole.lpi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <SaveOnlyProjectUnits Value="True"/>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <SaveJumpHistory Value="False"/>
+        <SaveFoldState Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demobrowserconsole"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demobrowserconsole.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="devweb"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="/home/michael/source/pas2js/src/compiler/pas2js -Tbrowser  -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 16 - 0
demo/rtl/demobrowserconsole.lpr

@@ -0,0 +1,16 @@
+program demobrowserconsole;
+
+uses browserconsole;
+
+
+Var
+  I : Integer;
+
+begin
+  For I:=30 downto 1 do
+    Writeln(I,', Hello, world!');
+  ConsoleStyle:=DefaultCrtConsoleStyle;
+  InitConsole;
+  For I:=1 to 30 do
+    Writeln(I,', Hello, world ?');
+end.

+ 17 - 0
demo/rtl/democanvas2d.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<HTML>
+  <head>
+    <meta charset="utf-8"/>
+    <Title>Pas2JS web demo</Title>
+    <script src="democanvas2d.js" type="application/javascript"></script>
+    <link href="../fpreport/bootstrap.min.css" rel="stylesheet">
+    <link href="css/bootstrap.min.css" rel="stylesheet">
+  </head>
+<body>
+<script>
+  document.addEventListener("DOMContentLoaded", function(event) {
+    rtl.run();
+  });
+</script>
+</body>
+</HTML>

+ 69 - 0
demo/rtl/democanvas2d.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="democanvas2d"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="democanvas2d.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="democanvas2d"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -O- -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 93 - 0
demo/rtl/democanvas2d.pas

@@ -0,0 +1,93 @@
+program democanvas2d;
+
+uses Web, Classes, JS, SysUtils;
+
+Type
+  TForm = Class
+    Canvas2D : TJSCanvasRenderingContext2D;
+    Ex : TJSHTMLInputElement;
+    Ey : TJSHTMLInputElement;
+    EHeight : TJSHTMLInputElement;
+    EWidth  : TJSHTMLInputElement;
+    ELineWidth  : TJSHTMLInputElement;
+    function ButtonClick(Event{%H-}: TJSMouseEvent): boolean;
+    Constructor Create; reintroduce;
+  end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+
+Var
+  X,Y,W,H : Double;
+
+begin
+  writeln('Drawing rectangle');
+  X:=StrToFloat(EX.value);
+  Y:=StrToFloat(Ey.value);
+  W:=StrToFloat(EWidth.value);
+  H:=StrToFloat(EHeight.value);
+  Canvas2D.lineWidth:=ParseFloat(ELineWidth.value);
+  Canvas2D.rect(X,Y, W,H);
+  Canvas2D.stroke;
+  Result:=true;
+end;
+
+constructor TForm.Create;
+
+  Function CreateNumberEdit (aName : String) : TJSHTMLInputElement;
+
+  begin
+    Result:=TJSHTMLInputElement(document.createElement('input'));
+    Result['type']:='text';
+    Result.value:='100';
+    Result.name:=aName;
+    Result['style']:='width: 80px;';
+  end;
+Var
+  Panel,PanelContent : TJSElement;
+  Button1:TJSElement;
+  Canvas : TJSHTMLCanvasElement;
+
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button1:=document.createElement('input');
+  Button1['id']:='Button1';
+  Button1['type']:='submit';
+  Button1['class']:='btn btn-default';
+  Button1['value']:='Draw rectangle';
+  TJSHTMLElement(Button1).onclick:=@ButtonClick;
+  EHeight:=CreateNumberEdit('Height');
+  EWidth:=CreateNumberEdit('Width');
+  ELineWidth:=CreateNumberEdit('LineWidth');
+  ELineWidth['type']:='text';
+  ELineWidth.Value:='0.5';
+  EX:=CreateNumberEdit('X');
+  EY:=CreateNumberEdit('Y');
+  document.body.appendChild(Panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button1);
+  PanelContent.appendChild(document.createTextNode('X'));
+  PanelContent.appendChild(EX);
+  PanelContent.appendChild(document.createTextNode('Y'));
+  PanelContent.appendChild(EY);
+  PanelContent.appendChild(document.createTextNode('Width'));
+  PanelContent.appendChild(EWidth);
+  PanelContent.appendChild(document.createTextNode('Height'));
+  PanelContent.appendChild(EHeight);
+  PanelContent.appendChild(document.createTextNode('LineWidth'));
+  PanelContent.appendChild(ELineWidth);
+  Panel.appendChild(PanelContent);
+  Canvas:=TJSHTMLCanvasElement(document.createElement('canvas'));
+  Canvas.width:=640;
+  Canvas.height:=480;
+  PanelContent.appendChild(canvas);
+  Canvas2D:=Canvas.getContextAs2DContext('2d');
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 13 - 0
demo/rtl/democlasstopas.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <script type="application/javascript" src="democlasstopas.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 69 - 0
demo/rtl/democlasstopas.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="democlasstopas"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="democlasstopas.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="democlasstopas"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jc -Jirtl.js -Jminclude $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 65 - 0
demo/rtl/democlasstopas.pas

@@ -0,0 +1,65 @@
+program democlasstopas;
+
+uses JS, Types, SysUtils;
+
+function ClassToPas(Obj: TJSObject): string;
+var
+  Names: TStringDynArray;
+  i, j: Integer;
+  t: String;
+  p: TJSArray;
+  f: TJSFunction;
+  Value: JSValue;
+begin
+  Result:='';
+  p:=TJSArray.new;
+  while Obj<>nil do
+    begin
+    Names:=TJSObject.getOwnPropertyNames(Obj);
+    for i:=0 to length(Names)-1 do
+      begin
+      try
+        Value:=Obj[Names[i]];
+      except
+        Result:=Result+'// not readable property "'+Names[i]+'"'+sLineBreak;
+      end;
+      if jsTypeOf(Value)='function' then
+        begin
+        f:=TJSFunction(Value);
+        t:='function '+f.name+'(';
+        for j:=1 to NativeInt(f['length']) do
+          begin
+          if j>1 then t:=t+';';
+          t:=t+'arg'+IntToStr(j)+' : argtype'+IntToStr(j);
+          end;
+        t:=t+') : returntype;';
+        end
+      else
+        t:=Names[i]+' : vartype;';
+      if p.indexOf(t)<0 then
+        begin
+        p.push(t);
+        Result:=Result+t+sLineBreak;
+        end;
+      end;
+    Obj:=TJSObject.getPrototypeOf(Obj);
+    if Obj<>nil then
+      Result:=Result+'// next getPrototypeOf ...'+sLineBreak;
+    end;
+end;
+
+procedure ShowRTLProps;
+var
+  o: TJSObject;
+begin
+  // get the new JavaScript object:
+  asm
+  o = window.localStorage; // rtl
+  end;
+  writeln(ClassToPas(o));
+end;
+
+begin
+  ShowRTLProps;
+end.
+

+ 13 - 0
demo/rtl/democollection.html

@@ -0,0 +1,13 @@
+<html>
+  <head>
+    <title>Collection demo</title>
+    <script type="application/javascript" src="democollection.js"></script>
+  </head>
+  <body>
+    <div id="pasjsconsole"></div>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>
+  

+ 69 - 0
demo/rtl/democollection.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="democollection"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="democollection.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="democollection"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jc -Jirtl.js -Tbrowser -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 110 - 0
demo/rtl/democollection.pas

@@ -0,0 +1,110 @@
+program democollection;
+
+uses browserconsole, sysutils, classes;
+
+Type
+
+  { TMyCollection }
+
+  TMyCollectionItem = class(TCollectionItem)
+  private
+    FMyName: String;
+  Published
+    Property MyName : String Read FMyName Write FMyName;
+  end;
+
+  { TMyCollectionItems }
+
+  TMyCollectionItems = Class(TCollection)
+  private
+    function GetMI(AIndex : Integer): TMyCollectionItem;
+    procedure SetMI(AIndex : Integer; AValue: TMyCollectionItem);
+  Public
+    Function AddItem : TMyCollectionItem;
+    Property MyItems[AIndex : Integer] : TMyCollectionItem Read GetMI Write SetMI;default;
+  end;
+
+{ TMyCollectionItems }
+
+function TMyCollectionItems.GetMI(AIndex : Integer): TMyCollectionItem;
+begin
+  Result:=Items[AIndex] as TMyCollectionItem;
+end;
+
+procedure TMyCollectionItems.SetMI(AIndex : Integer; AValue: TMyCollectionItem);
+begin
+  Items[AIndex]:=AValue;
+end;
+
+function TMyCollectionItems.AddItem: TMyCollectionItem;
+begin
+  Result:=Add as TMyCollectionItem;
+end;
+
+Procedure DumpCollection(C : TMyCollectionItems);
+
+Var
+  S : String;
+  I : integer;
+
+begin
+  S:='';
+  For I:=0 to C.Count-1 do
+    begin
+    If S<>'' then
+      S:=S+', ';
+    S:=S+'['+IntToStr(I)+'] : '+C[i].MyName;
+    end;
+  Writeln('Fruit collection: ',s);
+end;
+
+{
+  Const
+  MyNames : Array [1..10] of string
+          = ('apple','pear','banana','orange','lemon','prune',
+             'pineapple','strawberry','raspberry','cherry');
+}
+
+Function GetName(I : integer) : String;
+
+begin
+  Case I of
+   1 : Result:='apple';
+   2 : Result:='pear';
+   3 : Result:='banana';
+   4 : Result:='orange';
+   5 : Result:='lemon';
+   6 : Result:='prune';
+   7 : Result:='pineapple';
+   8 : Result:='strawberry';
+   9 : Result:='raspberry';
+   10 : Result:='cherry';
+ end;
+end;
+
+
+
+Var
+  MyC : TMyCollectionItems;
+  C : TMyCollectionItem;
+  I : Integer;
+
+begin
+  MyC:=TMyCollectionItems.Create(TMyCollectionItem);
+  For I:=1 to 10 do
+    begin
+    C:=MyC.AddItem;
+    C.MyName:=GetName(i);
+    end;
+  DumpCollection(MyC);
+  Writeln('pruning prune');
+  MyC.Delete(5);
+  DumpCollection(MyC);
+  Writeln('Prefer banana over pear');
+  MyC.Exchange(1,2);
+  DumpCollection(MyC);
+  Writeln('Indigestion, no more fruit');
+  MyC.Clear;
+  DumpCollection(MyC);
+end.
+

+ 68 - 0
demo/rtl/democomponents.lpi

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="democomponents"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="democomponents.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="democomponents"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 58 - 0
demo/rtl/democomponents.lpr

@@ -0,0 +1,58 @@
+program democomponents;
+
+uses browserconsole, Classes;
+
+Type
+   TMyGeneration = (first,second,third);
+
+   { TMyParentComponent }
+
+   TMyParentComponent = Class(TComponent)
+   private
+     FMyProperty: TMyGeneration;
+   Published
+     Property MyProperty : TMyGeneration Read FMyProperty Write FMyProperty;
+   end;
+
+   { TMyChildComponent }
+
+   TMyChildComponent = Class(TMyParentComponent)
+   Public
+     destructor Destroy; override;
+   end;
+
+Var
+  DestroyCount : Integer;
+
+{ TMyChildComponent }
+
+destructor TMyChildComponent.Destroy;
+begin
+  DestroyCount:=DestroyCount+1;
+  Writeln('Destroying child "',Name,'", current count : ',DestroyCount);
+  inherited Destroy;
+end;
+
+Var
+  P : TMyParentComponent;
+  C : TMyChildComponent;
+
+begin
+  P:=TMyParentComponent.Create(Nil);
+  try
+    P.Name:='Parent1';
+    P.MyProperty:=First;
+    C:=TMyChildComponent.Create(P);
+    C.Name:='Child1';
+    C.MyProperty:=Second;
+    C:=TMyChildComponent.Create(C);
+    C.Name:='Child2';
+    C.MyProperty:=Third;
+    C:=TMyChildComponent.Create(P);
+    C.Name:='Child3';
+    C.MyProperty:=Second;
+  finally
+    P.Destroy;
+  end;
+end.
+

+ 69 - 0
demo/rtl/demodatetime.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demodatetime"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demodatetime.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demodatetime"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 61 - 0
demo/rtl/demodatetime.pas

@@ -0,0 +1,61 @@
+program demodatetime;
+
+uses sysutils;
+
+Procedure DumpDate(Msg : String; Dt : TDateTime);
+
+Var
+  Y,M,D : Word;
+
+begin
+  DecodeDate(Dt,Y,M,D);
+  Writeln(Msg,' : ',Y,'-',M,'-',D,' (',Dt,')');
+end;
+
+Procedure DumpTime(Msg : String; Dt : TDateTime);
+
+Var
+  H,M,S,Z : Word;
+
+begin
+  DecodeTime(Frac(Dt),H,M,S,z);
+  if z<>0 then
+    Writeln(Msg,' : ',H,':',M,':',S,'.',z,' (',Frac(Dt),')')
+  else
+    Writeln(Msg,' : ',H,':',M,':',S,' (',Frac(Dt),')')
+end;
+
+Procedure DumpDateTime(Msg : String; Dt : TDateTime);
+
+Var
+  Y,Mo,Da,H,M,S,Z : Word;
+
+begin
+  DecodeDate(Dt,Y,Mo,Da);
+  DecodeTime(Frac(Dt),H,M,S,z);
+  if z<>0 then
+    Writeln(Msg,' : ',Y,'-',Mo,'-',Da,' ',H,':',M,':',S,'.',z,' (',Dt,')')
+  else
+    Writeln(Msg,' : ',Y,'-',Mo,'-',Da,' ',H,':',M,':',S,' (',Dt,')')
+end;
+
+Var
+  Dt : TDateTime;
+
+begin
+  Dt:=Date;
+  DumpDate('Date',Dt);
+  Dt:=Time;
+  DumpTime('Time',dt);
+  Dt:=Now;
+  DumpDateTime('Now',Dt);
+  Writeln('DateToStr : ',DateToStr(Dt));
+  Writeln('TimeToStr : ',TimeToStr(Dt));
+  DumpTime('StrToTime',StrToTime('14:15:16'));
+  DumpDate('StrToDate (yyyy-mm-dd)',StrToDate('2016-10-12'));
+  ShortDateFormat:='mm-dd-yyyy';
+  DumpDate('StrToDate (mm-dd-yyyy)',StrToDate('10-16-2016'));
+  ShortDateFormat:='dd-mm-yyyy';
+  DumpDate('StrToDate (dd-mm-yyyy)',StrToDate('17-10-2016'));
+end.
+

+ 13 - 0
demo/rtl/demodocument1.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello, world!</title>
+    <script type="application/javascript" src="demodocument1.js" ></script>
+  </head>
+  <body> 
+    <script type="application/javascript" >
+     rtl.run();
+    </script>
+  </body>
+</html>
+  

+ 7 - 0
demo/rtl/demodocument1.pas

@@ -0,0 +1,7 @@
+program test;
+
+uses web;
+
+begin
+  document.body.innerHTML := 'Hello world!';
+end.

+ 12 - 0
demo/rtl/demodombuttonevent.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<HTML>
+<Title>Pas2JS web demo</Title>
+<script SRC="demodombuttonevent.js" type="application/javascript"></script>
+<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+</body>
+<script>
+  document.addEventListener("DOMContentLoaded", function(event) {
+    rtl.run();
+  });
+</script>
+</HTML>

+ 69 - 0
demo/rtl/demodombuttonevent.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demodombuttonevent"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demodombuttonevent.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demodombuttonevent"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -O- -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 42 - 0
demo/rtl/demodombuttonevent.pas

@@ -0,0 +1,42 @@
+program demodombuttonevent;
+
+uses web, classes, libjquery;
+
+Type
+  TForm = Class
+    function ButtonClick(Event: TJSMouseEvent): boolean;
+    Constructor Create;
+  end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+begin
+  writeln('ButtonClick ',Event,' in ',className);
+  window.alert('Hello world from Pascal!');
+  Result:=true;
+end;
+
+constructor TForm.Create;
+Var
+  Panel,PanelContent : TJSElement;
+  Button1:TJSElement;
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button1:=document.createElement('input');
+  Button1['id']:='Button1';
+  Button1['type']:='submit';
+  Button1['class']:='btn btn-default';
+  Button1['value']:='Click me!';
+  TJSHTMLElement(Button1).onclick:=@ButtonClick;
+  document.body.appendChild(Panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button1);
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 69 - 0
demo/rtl/demojsarray.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demojsarray"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+        <MaxVersion Major="1"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demojsarray.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demojsarray"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 47 - 0
demo/rtl/demojsarray.pas

@@ -0,0 +1,47 @@
+uses js;
+
+function FilterOdd(el : JSValue; Index : NativeInt; Arr: TJSArray) : boolean  ;
+
+Var
+  I : Integer;
+begin
+  I:=Integer(el);
+  result:=((i mod 2)=1);
+end;
+
+function showElement(el : JSValue; Index : NativeInt; Arr: TJSArray) : boolean  ;
+
+begin
+  Writeln(Index,':',el);
+  result:=true;
+end;
+
+Procedure ShowArray(Msg : string; a: TJSArray);
+  
+begin
+  writeln(Msg,' : ');
+  a.forEach(@ShowElement);
+end;
+
+
+var
+  a,b : TJSArray;
+
+begin
+  a:=TJSArray._of(5,4,3,2,1,0);
+  ShowArray('init',a);
+  a:=TJSArray.new(5,4,3,2,1,0);
+  ShowArray('init 2',a);
+{$IFDEF ECMAScript6}
+  // Note these change the array itself
+  ShowArray('fill(-1,3)',a.fill(-1,3));
+  ShowArray('fill(-1,1,1)',a.fill(-1,1,1));
+  ShowArray('fill(-1)',a.fill(-1));
+{$ENDIF}
+  a:=TJSArray.new(5,4,3,2,1,2,3);
+  Writeln(a.ToString,'.indexOf(3): ',a.indexOf(3));  
+  Writeln(a.ToString,'.indexOf(2,4): ',a.indexOf(2,4));  
+  ShowArray('Filter(odd)',a.filter(@FilterOdd));
+  a:=TJSArray.new('alpha', 'bravo', 'charlie', 'delta');
+  ShowArray('copyWithin(2,0)',a.copyWithin(2, 0));
+end. 

+ 24 - 0
demo/rtl/demojsdataarray.pas

@@ -0,0 +1,24 @@
+uses js;
+
+Const
+  BSize = 10;
+ 
+var
+  B : TJSArrayBuffer;
+  V : TJSDataView;
+  I : Integer;
+
+begin
+  B:=TJSArrayBuffer.New(BSize);
+  V:=TJSDataView.New(B);
+  for I:=0 to v.byteLength-1 do
+    Writeln('Byte ',I,': ',v.getUInt8(i));
+  for I:=0 to v.byteLength-1 do
+    v.setUInt8(i,i+1);
+  Writeln('Writing bytes');
+  for I:=0 to v.byteLength-1 do
+     Writeln('Byte ',I,': ',v.getUInt8(i));
+  Writeln('Reading as Words');
+  for I:=0 to (v.byteLength-1) div 2 do
+     Writeln('Word ',I,': ',v.getUInt16(i*2));
+end.

+ 26 - 0
demo/rtl/demojsregexp.pas

@@ -0,0 +1,26 @@
+uses JS;
+
+Var
+ R : TJSRegexp;
+ T : TStringDynArray;
+ i : integer;
+ 
+begin
+  r:=TJSRegexp.New('m(.)','g');
+  writeln('source: ',r.source);
+  writeln('toString: ',r.toString);
+  writeln('Multiline: ',r.multiline);
+  writeln('global: ',r.global);
+  writeln('ignoreCase: ',r.ignoreCase);
+{$IFDEF FIREFOX}  
+  writeln('sticky: ',r.sticky);
+{$ENDIF}  
+  t:=r.exec('memamimomu');
+  While t<>nil do
+    begin
+    Writeln(r.toString,' -> exec(''memamimomu'') : length ',length(t),' lastIndex:',r.lastIndex);
+    for I:=0 to Length(t)-1 do
+       Writeln('Match[',i,'] : ',t[i]); 
+    t:=r.exec('memamimomu');
+    end    
+end.

+ 50 - 0
demo/rtl/demojsstring.pas

@@ -0,0 +1,50 @@
+
+{$codepage UTF8}
+
+uses types,JS;
+
+Var
+  S,C,R : TJSString;
+  t : TStringDynArray;
+  i : integer;
+  Q : String;
+  
+begin
+  S:=TJSString.New('memamomu');
+  C:=TJSString.New('你好!');
+  R:=TJSString.New('привет!');
+  writeln(s,' -> upper: ',s.toUpperCase);
+  Writeln(s,' -> lower: ',s.toLowerCase);
+  Writeln(s,' -> startsWith(''me''): ',s.startsWith('me'));
+  Writeln(s,' -> startsWith(''ma''): ',s.startsWith('ma'));
+  For I:=0 to s.length-1 do
+    Writeln('S[',I,']: ',s.charAt(i),
+    ', Charcode S[',I,']: ',s.charCodeAt(i),
+    ', codePoint S[',I,']: ',s.codePointAt(i));
+  For I:=0 to C.length-1 do
+    Writeln('C[',I,']: ',C.charAt(i),
+    ', Charcode C[',I,']: ',C.charCodeAt(i),
+    ', codePoint C[',I,']: ',C.codePointAt(i));
+  For I:=0 to R.length-1 do
+    Writeln('R[',I,']: ',R.charAt(i),
+    ', Charcode R[',I,']: ',R.charCodeAt(i),
+    ', codePoint R[',I,']: ',R.codePointAt(i));
+  Writeln(R,' -> indexOf(''вет''): ',R.indexOf('вет'));
+  Writeln(C,' -> indexOf(''вет''): ',c.indexOf('вет'));
+  Writeln(S,' -> lastIndexOf(''m''): ',S.lastIndexOf('m'));
+  Writeln(S,' -> link(''freepascal.org''): ',S.link('freepascal.org'));
+  t:=S.Split('m');
+  writeln(s,' -> split length: ',length(t),', elements:');
+  for i:=0 to length(t)-1 do
+    writeln(i,' : ',t[i]);
+  Writeln(s,' -> substr(6): ',s.substr(6));
+  Writeln(s,' -> substr(5,2): ',s.substr(5,2));
+  Q:='abcde';
+  writeln(Q,', typecast to TJSString, uppercase: ',TJSString(Q).toUpperCase);
+  t:=S.match(TJSRegexp.new('m.','g'));
+  writeln(s,' -> match(/m./g): ',length(t),', elements:');
+  for i:=0 to length(t)-1 do
+    writeln(i,' : ',t[i]);
+  Writeln(S,' -> replace(/m(.)/g/,''n$1''): ',S.replace(TJSRegexp.new('m(.)','g'),'n$1f'));
+  
+end.

+ 13 - 0
demo/rtl/demonew.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>New demo</title>
+    <script type="application/javascript" src="demonew.js" ></script>
+  </head>
+  <body> 
+    <script type="application/javascript" >
+     rtl.run();
+    </script>
+  </body>
+</html>
+  

+ 27 - 0
demo/rtl/demonew.pas

@@ -0,0 +1,27 @@
+uses types, js;
+
+Var
+  O : TJSObject;
+  S : TStringDynArray;
+  I : integer;
+  V : JSValue;
+    
+begin
+  O:=new(['a','text','b',123,'c',true]);
+  S:=TJSObject.getOwnPropertyNames(O);
+  Writeln('Object has ',Length(S),' own properties');
+  for I:=0 to Length(S)-1 do
+    begin
+    Writeln(i);
+    V:=O.Properties[S[i]];
+    Writeln('Property ',S[i],' : ',V);
+    end;
+  S:=TJSObject.Keys(O);
+  Writeln('Object has ',Length(S),' keys');
+  for I:=0 to Length(S)-1 do
+    Writeln('Property ',S[i],' : ',O.Properties[S[i]]);
+  Writeln('Manual : ');  
+  Writeln('a: ',O['a']);
+  Writeln('b: ',O['b']);
+  Writeln('c: ',O['c']);
+end.

+ 73 - 0
demo/rtl/demorouter.lpi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demorouter"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="2">
+      <Unit0>
+        <Filename Value="demorouter.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+      <Unit1>
+        <Filename Value="webrouter.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit1>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demorouter"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -O- -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 42 - 0
demo/rtl/demorouter.pas

@@ -0,0 +1,42 @@
+program demorouter;
+
+uses web, classes, libjquery, webrouter;
+
+Type
+  TForm = Class
+    function ButtonClick(Event: TJSMouseEvent): boolean;
+    Constructor Create;
+  end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+begin
+  writeln('ButtonClick ',Event,' in ',className);
+  window.alert('Hello world from Pascal!');
+  Result:=true;
+end;
+
+constructor TForm.Create;
+Var
+  Panel,PanelContent : TJSElement;
+  Button1:TJSElement;
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button1:=document.createElement('input');
+  Button1['id']:='Button1';
+  Button1['type']:='submit';
+  Button1['class']:='btn btn-default';
+  Button1['value']:='Click me!';
+  TJSHTMLElement(Button1).onclick:=@ButtonClick;
+  document.body.appendChild(Panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button1);
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 70 - 0
demo/rtl/demortti.lpi

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demortti"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demortti.pas"/>
+        <IsPartOfProject Value="True"/>
+        <UnitName Value="DemoRTTI"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demortti"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 135 - 0
demo/rtl/demortti.pas

@@ -0,0 +1,135 @@
+program DemoRTTI;
+
+uses SysUtils, TypInfo;
+
+type
+
+  { TShrimp }
+
+  TShrimp = class
+  private
+    FSize: integer;
+    procedure SetSize(const AValue: integer);
+  published
+    FName: String;
+    function GetSize: integer;
+    procedure SetName(const AValue: String);
+    property Name: String read FName write SetName;
+    property Size: integer read GetSize write SetSize;
+  end;
+
+{ TShrimp }
+
+function TShrimp.GetSize: integer;
+begin
+  Result:=FSize;
+end;
+
+procedure TShrimp.SetSize(const AValue: integer);
+begin
+  FSize:=AValue;
+end;
+
+procedure TShrimp.SetName(const AValue: String);
+begin
+  if FName=AValue then Exit;
+  FName:=AValue;
+end;
+
+procedure ShowFields(TI: TTypeInfoStruct);
+var
+  i: Integer;
+  f: TTypeMemberField;
+begin
+  writeln('ShowFields of ',TI.Name);
+  for i:=0 to TI.FieldCount-1 do
+  begin
+    f:=TTypeMemberField(TI.Members[TI.Fields[i]]);
+    writeln('  Name="',f.Name,'" Kind=',str(f.Kind),
+      ' Type=',f.TypeInfo.Name,' TypeKind=',str(f.TypeInfo.Kind));
+  end;
+end;
+
+function ProcSignatureToStr(Sig: TProcedureSignature): String;
+var
+  i: Integer;
+  Param: TProcedureParam;
+begin
+  Result:='(';
+  for i:=0 to length(Sig.Params)-1 do
+  begin
+    Param:=Sig.Params[i];
+    if i>0 then Result:=Result+';';
+    Result:=Result+Param.Name;
+    Result:=Result+':'+Param.TypeInfo.Name;
+  end;
+  Result:=Result+')';
+  if Sig.ResultType<>nil then
+    Result:=Result+':'+Sig.ResultType.Name+'/'+str(Sig.ResultType.Kind);
+end;
+
+procedure ShowMethods(TI: TTypeInfoStruct);
+var
+  i: Integer;
+  m: TTypeMemberMethod;
+begin
+  writeln('ShowMethods of ',TI.Name);
+  for i:=0 to TI.MethodCount-1 do
+  begin
+    m:=TTypeMemberMethod(TI.Members[TI.Methods[i]]);
+    writeln('  Name="',m.Name,'" Kind=',str(m.Kind),
+      ' MethodKind=',str(m.MethodKind),' Sig=',ProcSignatureToStr(m.ProcSig));
+  end;
+end;
+
+procedure ShowProperties(Instance: TObject);
+var
+  i: Integer;
+  PropInfo: TTypeMemberProperty;
+  Value: String;
+  TI: TTypeInfoClass;
+begin
+  TI:=TypeInfo(Instance);
+  writeln('ShowProperties of ',TI.Name);
+  for i:=0 to TI.PropCount-1 do
+  begin
+    PropInfo:=TTypeMemberProperty(TI.Members[TI.Properties[i]]);
+    Value:='?';
+    if PropInfo.Getter<>'' then
+    begin
+      case PropInfo.TypeInfo.Kind of
+      tkInteger: Value:=IntToStr(GetNativeIntProp(Instance,PropInfo));
+      tkString: Value:=GetStringProp(Instance,PropInfo);
+      end;
+    end;
+    writeln('  Name="',PropInfo.Name,'"',
+      ' Kind=',str(PropInfo.Kind),
+      ' Type=',PropInfo.TypeInfo.Name,
+      ' TypeKind=',str(PropInfo.TypeInfo.Kind),
+      ' Value=',Value);
+  end;
+end;
+
+procedure ShowObjectRTTI(Obj: TObject);
+var
+  ObjTI: TTypeInfoClass;
+begin
+  ObjTI:=TypeInfo(Obj);
+  ShowFields(ObjTI);
+  ShowMethods(ObjTI);
+  ShowProperties(Obj);
+end;
+
+var
+  Shrimp: TShrimp;
+begin
+  Shrimp:=TShrimp.Create;
+  Shrimp.Name:='Shrimpy';
+  Shrimp.Size:=42;
+  ShowObjectRTTI(Shrimp);
+
+  writeln('Setting Size to 137:');
+  SetNativeIntProp(Shrimp,'size',137);
+  writeln('Size=',GetNativeIntProp(Shrimp,'size'));
+end.
+

+ 32 - 0
demo/rtl/demostringlist.pas

@@ -0,0 +1,32 @@
+{$mode objfpc}
+{$H+}
+uses sysutils,classes;
+
+Var
+  L : TStringList;
+  I : Integer;
+  //S : TJSString;
+  
+begin
+  L:=TStringList.Create;
+  for I:=0 to 10 do
+    L.Add(IntToStr(I));
+  for I:=0 to L.Count-1 do
+    Writeln(I,' : ',L[i]);
+  writeln('in one swoop: ',L.text);
+  L.Delete(4);
+  Writeln('Index 4 deleted.',L.Text);
+  Writeln('Commatext : ',L.CommaText);
+  Writeln('IndexOf(5) : ',L.INdexOf('5'));
+  L.Clear;
+  Writeln('Clear : "',L.Text,'"');
+  L.CommaText:='3,4,5';
+  Writeln('After set commatext:  ',L.CommaText);
+  L.exchange(2,0);
+  Writeln('After exchange : ',L.CommaText);
+  L.Sort;
+  Writeln('After sort : ',L.CommaText);
+  //S:=TJSString.new('abc');
+  //Writeln(S.toUpperCase);
+  
+end.  

+ 14 - 0
demo/rtl/demouncaughtexception.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <script type="application/javascript" src="demouncaughtexception.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.showUncaughtExceptions=true;
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 69 - 0
demo/rtl/demouncaughtexception.lpi

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="Demo Uncaught exception"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demouncaughtexception.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demouncaughtexception.lpi"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Jirtl.js -Tnodejs -Fu$(ProjUnitPath) -o$NameOnly($(ProjFile)).js $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 11 - 0
demo/rtl/demouncaughtexception.pas

@@ -0,0 +1,11 @@
+program demouncaughtexception;
+
+uses sysutils;
+
+Type
+  EMyError = class(Exception);
+
+begin
+  Raise EMyError.Create('This can be shown in an alert by setting rtl.setUncaughtException=true');
+end.
+

+ 15 - 0
demo/rtl/demoxhr.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>XMLHTTPRequest demo</title>
+    <meta charset="utf-8"/>
+    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
+    <script type="application/javascript" src="demoxhr.js"></script>
+  </head>
+  <body>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>
+

+ 70 - 0
demo/rtl/demoxhr.lpi

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="10"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <MainUnit Value="0"/>
+      <Title Value="demoxhr"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes Count="1">
+      <Item1 Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+    <RunParams>
+      <local>
+        <FormatVersion Value="1"/>
+      </local>
+    </RunParams>
+    <RequiredPackages Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPackages>
+    <Units Count="1">
+      <Unit0>
+        <Filename Value="demoxhr.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit0>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="demoxhr"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Other>
+      <ExecuteBefore>
+        <Command Value="$MakeExe(pas2js) -Tbrowser -Jirtl.js -Jc $Name($(ProjFile))"/>
+        <ScanForFPCMsgs Value="True"/>
+      </ExecuteBefore>
+    </Other>
+    <CompileReasons Compile="False" Build="False" Run="False"/>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions Count="3">
+      <Item1>
+        <Name Value="EAbort"/>
+      </Item1>
+      <Item2>
+        <Name Value="ECodetoolError"/>
+      </Item2>
+      <Item3>
+        <Name Value="EFOpenError"/>
+      </Item3>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 128 - 0
demo/rtl/demoxhr.lpr

@@ -0,0 +1,128 @@
+program demoxhr;
+
+uses sysutils, js, web, ajax;
+
+Type
+
+  { TForm }
+
+  TForm = Class
+    XHR : TJSXMLHttpRequest;
+    Table,
+    Panel,
+    PanelContent,
+    Button : TJSElement;
+    function onLoad(Event: TEventListenerEvent): boolean;
+    Constructor create;
+    function CreateTable: TJSElement;
+  private
+    function ButtonClick(Event: TJSMouseEvent): boolean;
+    function CreateRow(AName: String; APopulation: NativeInt): TJSElement;
+  end;
+
+
+function TForm.CreateRow(AName : String; APopulation : NativeInt) : TJSElement;
+
+Var
+  C : TJSElement;
+
+begin
+  Result:=document.createElement('TR');
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.appendChild(Document.createTextNode(AName));
+  C:=document.createElement('TD');
+  Result.Append(C);
+  C.AppendChild(document.createTextNode(IntToStr(APopulation)));
+end;
+
+function TForm.CreateTable : TJSElement;
+
+Var
+  TH,R,H : TJSElement;
+
+begin
+  Result:=document.createElement('TABLE');
+  Result.className:='table table-striped table-bordered table-hover table-condensed';
+  TH:=document.createElement('THEAD');
+  Result.Append(TH);
+  R:=document.createElement('TR');
+  TH.Append(R);
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Name'));
+  H:=document.createElement('TH');
+  R.Append(H);
+  H.AppendChild(document.createTextNode('Population'));
+end;
+
+function TForm.onLoad(Event: TEventListenerEvent): boolean;
+
+var
+  i : integer;
+  C,J : TJSObject;
+  A : TJSObjectDynArray;
+  N,TB : TJSElement;
+
+begin
+  console.log('Result of call ',xhr.Status);
+{  While (PanelContent.childNodes.length>0) do
+    PanelContent.removeChild(PanelContent.childNodes.item(PanelContent.childNodes.length-1));}
+  if (xhr.status = 200) then
+    begin
+    J:=TJSJSON.parse(xhr.responseText);
+    A:=TJSObjectDynArray(J.Properties['Data']);
+    Table:=CreateTable;
+    Document.Body.append(Table);
+    TB:=document.createElement('TBODY');
+    Table.Append(TB);
+    for I:=0 to Length(A)-1 do
+      begin
+      C:=A[i];
+      TB.Append(CreateRow(String(C.Properties['Name']),Integer(C.Properties['Population'])));
+      end;
+    end
+  else
+    begin
+    N:=Document.CreateElement('div');
+    N.appendChild(Document.createTextNode('Failed to load countries: '+IntToStr(xhr.Status)));
+    PanelContent.append(N);
+    end;
+  Result := True;
+end;
+
+function TForm.ButtonClick(Event: TJSMouseEvent): boolean;
+
+begin
+  xhr:=TJSXMLHttpRequest.New;
+  xhr.addEventListener('load', @OnLoad);
+  xhr.open('GET', 'countries.json', true);
+  xhr.send;
+  Result:=true;
+end;
+
+constructor TForm.create;
+
+
+begin
+  Panel:=document.createElement('div');
+  // attrs are default array property...
+  Panel['class']:='panel panel-default';
+  PanelContent:=document.createElement('div');
+  PanelContent['class']:='panel-body';
+  Button:=document.createElement('input');
+  Button['id']:='Button1';
+  Button['type']:='submit';
+  Button.className:='btn btn-default';
+  Button['value']:='Fetch countries';
+  TJSHTMLElement(Button).onclick:=@ButtonClick;
+  document.body.appendChild(panel);
+  Panel.appendChild(PanelContent);
+  PanelContent.appendChild(Button);
+
+end;
+
+begin
+  TForm.Create;
+end.
+

+ 154 - 0
src/packages/fcl-base/browserapp.pas

@@ -0,0 +1,154 @@
+unit browserapp;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Types, JS, web, CustApp;
+
+type
+
+  { TBrowserApplication }
+
+  TBrowserApplication = class(TCustomApplication)
+  protected
+    procedure DoRun; override;
+    function GetConsoleApplication: boolean; override;
+    function GetLocation: String; override;
+  public
+    procedure GetEnvironmentList(List: TStrings; NamesOnly: Boolean); override;
+    procedure ShowException(E: Exception); override;
+    procedure HandleException(Sender: TObject); override;
+  end;
+
+procedure ReloadEnvironmentStrings;
+
+implementation
+
+var
+  EnvNames: TJSObject;
+  Params : TStringDynArray;
+
+procedure ReloadEnvironmentStrings;
+
+var
+  I : Integer;
+  S : String;
+  A,P : TStringDynArray;
+begin
+  if Assigned(EnvNames) then
+    FreeAndNil(EnvNames);
+  EnvNames:=TJSObject.new;
+  S:=Window.Location.search;
+  S:=Copy(S,2,Length(S)-1);
+  A:=TJSString(S).split('&');
+  for I:=0 to Length(A)-1 do
+    begin
+    P:=TJSString(A[i]).split('=');
+    if Length(P)=2 then
+      EnvNames[decodeURIComponent(P[0])]:=decodeURIComponent(P[1])
+    else if Length(P)=1 then
+      EnvNames[decodeURIComponent(P[0])]:=''
+    end;
+end;
+
+procedure ReloadParamStrings;
+
+begin
+  SetLength(Params,1);
+  Params[0]:=Window.location.pathname;
+end;
+
+
+function GetParamCount: longint;
+begin
+  Result:=Length(Params)-1;
+end;
+
+function GetParamStr(Index: longint): String;
+begin
+  Result:=Params[Index]
+end;
+
+function MyGetEnvironmentVariable(Const EnvVar: String): String;
+
+begin
+  Result:=String(EnvNames[envvar]);
+end;
+
+function MyGetEnvironmentVariableCount: Integer;
+begin
+  Result:=length(TJSOBject.getOwnPropertyNames(envNames));
+end;
+
+function MyGetEnvironmentString(Index: Integer): String;
+begin
+  Result:=String(EnvNames[TJSOBject.getOwnPropertyNames(envNames)[Index]]);
+end;
+
+{ TBrowserApplication }
+
+procedure TBrowserApplication.DoRun;
+begin
+  // Override in descendent classes.
+end;
+
+function TBrowserApplication.GetConsoleApplication: boolean;
+begin
+  Result:=true;
+end;
+
+function TBrowserApplication.GetLocation: String;
+begin
+  Result:=''; // ToDo ExtractFilePath(GetExeName);
+end;
+
+procedure TBrowserApplication.GetEnvironmentList(List: TStrings;
+  NamesOnly: Boolean);
+var
+  Names: TStringDynArray;
+  i: Integer;
+begin
+  Names:=TJSObject.getOwnPropertyNames(EnvNames);
+  for i:=0 to length(Names)-1 do
+  begin
+    if NamesOnly then
+      List.Add(Names[i])
+    else
+      List.Add(Names[i]+'='+String(EnvNames[Names[i]]));
+  end;
+end;
+
+procedure TBrowserApplication.ShowException(E: Exception);
+
+Var
+  S : String;
+
+begin
+  if (E<>nil) then
+    S:=E.ClassName+': '+E.Message
+  else if ExceptObjectJS then
+    s:=TJSObject(ExceptObjectJS).toString;
+  window.alert('Unhandled exception caught:'+S);
+end;
+
+procedure TBrowserApplication.HandleException(Sender: TObject);
+begin
+  if ExceptObject is Exception then
+    ShowException(ExceptObject);
+  inherited HandleException(Sender);
+end;
+
+initialization
+  IsConsole:=true;
+  OnParamCount:=@GetParamCount;
+  OnParamStr:=@GetParamStr;
+  ReloadEnvironmentStrings;
+  ReloadParamStrings;
+  OnGetEnvironmentVariable:=@MyGetEnvironmentVariable;
+  OnGetEnvironmentVariableCount:=@MyGetEnvironmentVariableCount;
+  OnGetEnvironmentString:=@MyGetEnvironmentString;
+
+end.
+

+ 632 - 0
src/packages/fcl-base/custapp.pas

@@ -0,0 +1,632 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2003 by the Free Pascal development team
+
+    CustomApplication class.
+
+    Port to pas2js by Mattias Gaertner [email protected]
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit CustApp;
+
+{$mode objfpc}
+
+interface
+
+uses
+  Classes, SysUtils, Types, JS;
+
+Const
+  SErrInvalidOption: String = 'Invalid option at position %s: "%s"';
+  SErrNoOptionAllowed: String = 'Option at position %s does not allow an argument: %s';
+  SErrOptionNeeded: String = 'Option at position %s needs an argument : %s';
+
+Type
+  TExceptionEvent = procedure (Sender : TObject; E : Exception) of object;
+  TEventLogTypes = set of TEventType;
+
+  { TCustomApplication }
+
+  TCustomApplication = Class(TComponent)
+  Private
+    FEventLogFilter: TEventLogTypes;
+    FExceptObjectJS: JSValue;
+    FOnException: TExceptionEvent;
+    FTerminated: Boolean;
+    FTitle: String;
+    FOptionChar: Char;
+    FCaseSensitiveOptions: Boolean;
+    FStopOnException: Boolean;
+    FExceptionExitCode: Integer;
+    FExceptObject: Exception;
+  Protected
+    function GetEnvironmentVar(VarName: String): String; virtual;
+    function GetExeName: string; virtual;
+    function GetLocation: String; virtual; abstract;
+    function GetOptionAtIndex(AIndex: Integer; IsLong: Boolean): String;
+    procedure SetTitle(const AValue: string); virtual;
+    function GetConsoleApplication: boolean; virtual; abstract;
+    procedure DoRun; virtual; abstract;
+    function GetParams(Index: Integer): String; virtual;
+    function GetParamCount: Integer; virtual;
+    procedure DoLog(EventType: TEventType; const Msg: String); virtual;
+  Public
+    constructor Create(AOwner: TComponent); override;
+    // Some Delphi methods.
+    procedure HandleException(Sender: TObject); virtual;
+    procedure Initialize; virtual;
+    procedure Run;
+    procedure ShowException(E: Exception); virtual; abstract;
+    procedure Terminate; virtual;
+    procedure Terminate(AExitCode: Integer); virtual;
+    // Extra methods.
+    function FindOptionIndex(Const S: String; var Longopt: Boolean; StartAt: Integer = -1): Integer;
+    function GetOptionValue(Const S: String): String;
+    function GetOptionValue(Const C: Char; Const S: String): String;
+    function GetOptionValues(Const C: Char; Const S: String): TStringDynArray;
+    function HasOption(Const S: String) : Boolean;
+    function HasOption(Const C: Char; Const S: String): Boolean;
+    function CheckOptions(Const ShortOptions: String; Const Longopts: TStrings;
+      Opts,NonOpts: TStrings; AllErrors: Boolean = False): String;
+    function CheckOptions(Const ShortOptions: String; Const Longopts: Array of string;
+      Opts,NonOpts: TStrings; AllErrors: Boolean = False): String;
+    function CheckOptions(Const ShortOptions: String; Const Longopts: TStrings;
+      AllErrors: Boolean = False): String;
+    function CheckOptions(Const ShortOptions: String; Const LongOpts: Array of string;
+      AllErrors: Boolean = False): String;
+    function CheckOptions(Const ShortOptions: String; Const LongOpts: String;
+      AllErrors: Boolean = False): String;
+    function GetNonOptions(Const ShortOptions: String; Const Longopts: Array of string): TStringDynArray;
+    procedure GetNonOptions(Const ShortOptions: String; Const Longopts: Array of string;
+      NonOptions: TStrings);
+    procedure GetEnvironmentList(List: TStrings; NamesOnly: Boolean); virtual; abstract;
+    procedure GetEnvironmentList(List: TStrings); virtual;
+    procedure Log(EventType: TEventType; const Msg: String);
+    procedure Log(EventType: TEventType; const Fmt: String; const Args: Array of string);
+    // Delphi properties
+    property ExeName: string read GetExeName;
+    property Terminated: Boolean read FTerminated;
+    property Title: string read FTitle write SetTitle;
+    property OnException: TExceptionEvent read FOnException write FOnException;
+    // Extra properties
+    property ConsoleApplication: Boolean Read GetConsoleApplication;
+    property Location: String Read GetLocation;
+    property Params[Index: integer]: String Read GetParams;
+    property ParamCount: Integer Read GetParamCount;
+    property EnvironmentVariable[EnvName: String]: String Read GetEnvironmentVar;
+    property OptionChar: Char Read FoptionChar Write FOptionChar;
+    property CaseSensitiveOptions: Boolean Read FCaseSensitiveOptions Write FCaseSensitiveOptions;
+    property StopOnException: Boolean Read FStopOnException Write FStopOnException;
+    property ExceptionExitCode: Longint Read FExceptionExitCode Write FExceptionExitCode;
+    property ExceptObject: Exception read FExceptObject write FExceptObject;
+    property ExceptObjectJS: JSValue read FExceptObjectJS write FExceptObjectJS;
+    property EventLogFilter: TEventLogTypes Read FEventLogFilter Write FEventLogFilter;
+  end;
+
+var CustomApplication: TCustomApplication = nil;
+
+implementation
+
+{ TCustomApplication }
+
+function TCustomApplication.GetEnvironmentVar(VarName: String): String;
+begin
+  Result:=GetEnvironmentVariable(VarName);
+end;
+
+function TCustomApplication.GetExeName: string;
+begin
+  Result:=ParamStr(0);
+end;
+
+function TCustomApplication.GetOptionAtIndex(AIndex: Integer; IsLong: Boolean
+  ): String;
+
+Var
+  P : Integer;
+  O : String;
+
+begin
+  Result:='';
+  If AIndex=-1 then
+    Exit;
+  If IsLong then
+    begin // Long options have form --option=value
+    O:=Params[AIndex];
+    P:=Pos('=',O);
+    If P=0 then
+      P:=Length(O);
+    Delete(O,1,P);
+    Result:=O;
+    end
+  else
+    begin // short options have form '-o value'
+    If AIndex<ParamCount then
+      if Copy(Params[AIndex+1],1,1)<>'-' then
+        Result:=Params[AIndex+1];
+    end;
+end;
+
+procedure TCustomApplication.SetTitle(const AValue: string);
+begin
+  FTitle:=AValue;
+end;
+
+function TCustomApplication.GetParams(Index: Integer): String;
+begin
+  Result:=ParamStr(Index);
+end;
+
+function TCustomApplication.GetParamCount: Integer;
+begin
+  Result:=System.ParamCount;
+end;
+
+procedure TCustomApplication.DoLog(EventType: TEventType; const Msg: String);
+begin
+  // Do nothing, override in descendants
+  if EventType=etCustom then ;
+  if Msg='' then ;
+end;
+
+constructor TCustomApplication.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FOptionChar:='-';
+  FCaseSensitiveOptions:=True;
+  FStopOnException:=False;
+end;
+
+procedure TCustomApplication.HandleException(Sender: TObject);
+begin
+  ShowException(ExceptObject);
+  if FStopOnException then
+    Terminate(ExceptionExitCode);
+end;
+
+procedure TCustomApplication.Initialize;
+begin
+  FTerminated:=False;
+end;
+
+procedure TCustomApplication.Run;
+begin
+  Repeat
+    ExceptObject:=nil;
+    ExceptObjectJS:=nil;
+    Try
+      DoRun;
+    except
+      on E: Exception do
+      begin
+        ExceptObject:=E;
+        ExceptObjectJS:=E;
+        HandleException(Self);
+      end
+      else begin
+        ExceptObject:=nil;
+        ExceptObjectJS := JS.JSExceptValue;
+      end;
+    end;
+    break;
+  Until FTerminated;
+end;
+
+procedure TCustomApplication.Terminate;
+begin
+  Terminate(ExitCode);
+end;
+
+procedure TCustomApplication.Terminate(AExitCode: Integer);
+begin
+  FTerminated:=True;
+  ExitCode:=AExitCode;
+end;
+
+function TCustomApplication.FindOptionIndex(const S: String;
+  var Longopt: Boolean; StartAt: Integer): Integer;
+
+Var
+  SO,O : String;
+  I,P : Integer;
+
+begin
+  If Not CaseSensitiveOptions then
+    SO:=UpperCase(S)
+  else
+    SO:=S;
+  Result:=-1;
+  I:=StartAt;
+  if I=-1 then
+    I:=ParamCount;
+  While (Result=-1) and (I>0) do
+    begin
+    O:=Params[i];
+    // - must be seen as an option value
+    If (Length(O)>1) and (O[1]=FOptionChar) then
+      begin
+      Delete(O,1,1);
+      LongOpt:=(Length(O)>0) and (O[1]=FOptionChar);
+      If LongOpt then
+        begin
+        Delete(O,1,1);
+        P:=Pos('=',O);
+        If (P<>0) then
+          O:=Copy(O,1,P-1);
+        end;
+      If Not CaseSensitiveOptions then
+        O:=UpperCase(O);
+      If (O=SO) then
+        Result:=i;
+      end;
+    Dec(i);
+    end;
+end;
+
+function TCustomApplication.GetOptionValue(const S: String): String;
+begin
+  Result:=GetOptionValue(' ',S);
+end;
+
+function TCustomApplication.GetOptionValue(const C: Char; const S: String
+  ): String;
+
+Var
+  B : Boolean;
+  I : integer;
+
+begin
+  Result:='';
+  I:=FindOptionIndex(C,B);
+  If I=-1 then
+    I:=FindOptionIndex(S,B);
+  If I<>-1 then
+    Result:=GetOptionAtIndex(I,B);
+end;
+
+function TCustomApplication.GetOptionValues(const C: Char; const S: String
+  ): TStringDynArray;
+
+Var
+  I,Cnt : Integer;
+  B : Boolean;
+
+begin
+  SetLength(Result,ParamCount);
+  Cnt:=0;
+  Repeat
+    I:=FindOptionIndex(C,B,I);
+    If I<>-1 then
+      begin
+      Inc(Cnt);
+      Dec(I);
+      end;
+  Until I=-1;
+  Repeat
+    I:=FindOptionIndex(S,B,I);
+    If I<>-1 then
+      begin
+      Inc(Cnt);
+      Dec(I);
+      end;
+  Until I=-1;
+  SetLength(Result,Cnt);
+  Cnt:=0;
+  I:=-1;
+  Repeat
+    I:=FindOptionIndex(C,B,I);
+    If (I<>-1) then
+      begin
+      Result[Cnt]:=GetOptionAtIndex(I,False);
+      Inc(Cnt);
+      Dec(i);
+      end;
+  Until (I=-1);
+  I:=-1;
+  Repeat
+    I:=FindOptionIndex(S,B,I);
+    If I<>-1 then
+      begin
+      Result[Cnt]:=GetOptionAtIndex(I,True);
+      Inc(Cnt);
+      Dec(i);
+      end;
+  Until (I=-1);
+end;
+
+function TCustomApplication.HasOption(const S: String): Boolean;
+
+Var
+  B : Boolean;
+
+begin
+  Result:=FindOptionIndex(S,B)<>-1;
+end;
+
+function TCustomApplication.HasOption(const C: Char; const S: String): Boolean;
+
+Var
+  B : Boolean;
+
+begin
+  Result:=(FindOptionIndex(C,B)<>-1) or (FindOptionIndex(S,B)<>-1);
+end;
+
+function TCustomApplication.CheckOptions(const ShortOptions: String;
+  const Longopts: TStrings; Opts, NonOpts: TStrings; AllErrors: Boolean
+  ): String;
+
+Var
+  I,J,L,P : Integer;
+  O,OV,SO : String;
+  UsedArg,HaveArg : Boolean;
+
+  Function FindLongOpt(S : String) : boolean;
+
+  Var
+    I : integer;
+
+  begin
+    Result:=Assigned(LongOpts);
+    if Not Result then
+      exit;
+    If CaseSensitiveOptions then
+      begin
+      I:=LongOpts.Count-1;
+      While (I>=0) and (LongOpts[i]<>S) do
+        Dec(i);
+      end
+    else
+      begin
+      S:=UpperCase(S);
+      I:=LongOpts.Count-1;
+      While (I>=0) and (UpperCase(LongOpts[i])<>S) do
+        Dec(i);
+      end;
+    Result:=(I<>-1);
+  end;
+
+  Procedure AddToResult(Const Msg : string);
+
+  begin
+    If (Result<>'') then
+      Result:=Result+sLineBreak;
+    Result:=Result+Msg;
+  end;
+
+begin
+  If CaseSensitiveOptions then
+    SO:=Shortoptions
+  else
+    SO:=LowerCase(Shortoptions);
+  Result:='';
+  I:=1;
+  While (I<=ParamCount) and ((Result='') or AllErrors) do
+    begin
+    O:=Paramstr(I);
+    If (Length(O)=0) or (O[1]<>FOptionChar) then
+      begin
+      If Assigned(NonOpts) then
+        NonOpts.Add(O);
+      end
+    else
+      begin
+      If (Length(O)<2) then
+        AddToResult(Format(SErrInvalidOption,[IntToStr(I),O]))
+      else
+        begin
+        HaveArg:=False;
+        OV:='';
+        // Long option ?
+        If (O[2]=FOptionChar) then
+          begin
+          Delete(O,1,2);
+          J:=Pos('=',O);
+          If J<>0 then
+            begin
+            HaveArg:=true;
+            OV:=O;
+            Delete(OV,1,J);
+            O:=Copy(O,1,J-1);
+            end;
+          // Switch Option
+          If FindLongopt(O) then
+            begin
+            If HaveArg then
+              AddToResult(Format(SErrNoOptionAllowed,[IntToStr(I),O]));
+            end
+          else
+            begin // Required argument
+            If FindLongOpt(O+':') then
+              begin
+              If Not HaveArg then
+                AddToResult(Format(SErrOptionNeeded,[IntToStr(I),O]));
+              end
+            else
+              begin // Optional Argument.
+              If not FindLongOpt(O+'::') then
+                AddToResult(Format(SErrInvalidOption,[IntToStr(I),O]));
+              end;
+            end;
+          end
+        else // Short Option.
+          begin
+          HaveArg:=(I<ParamCount) and (Length(ParamStr(I+1))>0) and (ParamStr(I+1)[1]<>FOptionChar);
+          UsedArg:=False;
+          If Not CaseSensitiveOptions then
+            O:=LowerCase(O);
+          L:=Length(O);
+          J:=2;
+          While ((Result='') or AllErrors) and (J<=L) do
+            begin
+            P:=Pos(O[J],SO);
+            If (P=0) or (O[j]=':') then
+              AddToResult(Format(SErrInvalidOption,[IntToStr(I),O[J]]))
+            else
+              begin
+              If (P<Length(SO)) and (SO[P+1]=':') then
+                begin
+                // Required argument
+                If ((P+1)=Length(SO)) or (SO[P+2]<>':') Then
+                  If (J<L) or not haveArg then // Must be last in multi-opt !!
+                    begin
+                    AddToResult(Format(SErrOptionNeeded,[IntToStr(I),O[J]]));
+                    end;
+                O:=O[j]; // O is added to arguments.
+                UsedArg:=True;
+                end;
+              end;
+            Inc(J);
+            end;
+          HaveArg:=HaveArg and UsedArg;
+          If HaveArg then
+            begin
+            Inc(I); // Skip argument.
+            OV:=Paramstr(I);
+            end;
+          end;
+        If HaveArg and ((Result='') or AllErrors) then
+          If Assigned(Opts) then
+            Opts.Add(O+'='+OV);
+        end;
+      end;
+    Inc(I);
+    end;
+end;
+
+function TCustomApplication.CheckOptions(const ShortOptions: String;
+  const Longopts: array of string; Opts, NonOpts: TStrings; AllErrors: Boolean
+  ): String;
+Var
+  L : TStringList;
+  I : Integer;
+begin
+  L:=TStringList.Create;
+  try
+    For I:=0 to High(LongOpts) do
+      L.Add(LongOpts[i]);
+    Result:=CheckOptions(ShortOptions,L,Opts,NonOpts,AllErrors);
+  finally
+    L.Destroy;
+  end;
+end;
+
+function TCustomApplication.CheckOptions(const ShortOptions: String;
+  const Longopts: TStrings; AllErrors: Boolean): String;
+begin
+  Result:=CheckOptions(ShortOptions,LongOpts,Nil,Nil,AllErrors);
+end;
+
+function TCustomApplication.CheckOptions(const ShortOptions: String;
+  const LongOpts: array of string; AllErrors: Boolean): String;
+
+Var
+  L : TStringList;
+  I : Integer;
+
+begin
+  L:=TStringList.Create;
+  Try
+    For I:=0 to High(LongOpts) do
+      L.Add(LongOpts[i]);
+    Result:=CheckOptions(ShortOptions,L,AllErrors);
+  Finally
+    L.Destroy;
+  end;
+end;
+
+function TCustomApplication.CheckOptions(const ShortOptions: String;
+  const LongOpts: String; AllErrors: Boolean): String;
+
+Const
+  SepChars = ' '#10#13#9;
+
+Var
+  L : TStringList;
+  Len,I,J : Integer;
+
+begin
+  L:=TStringList.Create;
+  Try
+    I:=1;
+    Len:=Length(LongOpts);
+    While I<=Len do
+      begin
+      While Isdelimiter(SepChars,LongOpts,I) do
+        Inc(I);
+      J:=I;
+      While (J<=Len) and Not IsDelimiter(SepChars,LongOpts,J) do
+        Inc(J);
+      If (I<=J) then
+        L.Add(Copy(LongOpts,I,(J-I)));
+      I:=J+1;
+      end;
+    Result:=CheckOptions(Shortoptions,L,AllErrors);
+  Finally
+    L.Destroy;
+  end;
+end;
+
+function TCustomApplication.GetNonOptions(const ShortOptions: String;
+  const Longopts: array of string): TStringDynArray;
+
+Var
+  NO : TStrings;
+  I : Integer;
+
+begin
+  No:=TStringList.Create;
+  try
+    GetNonOptions(ShortOptions,LongOpts,No);
+    SetLength(Result,NO.Count);
+    For I:=0 to NO.Count-1 do
+      Result[I]:=NO[i];
+  finally
+    NO.Destroy;
+  end;
+end;
+
+procedure TCustomApplication.GetNonOptions(const ShortOptions: String;
+  const Longopts: array of string; NonOptions: TStrings);
+
+Var
+  S : String;
+
+begin
+  S:=CheckOptions(ShortOptions,LongOpts,Nil,NonOptions,true);
+  if (S<>'') then
+    Raise EListError.Create(S);
+end;
+
+procedure TCustomApplication.GetEnvironmentList(List: TStrings);
+begin
+  GetEnvironmentList(List,False);
+end;
+
+procedure TCustomApplication.Log(EventType: TEventType; const Msg: String);
+begin
+  If (FEventLogFilter=[]) or (EventType in FEventLogFilter) then
+    DoLog(EventType,Msg);
+end;
+
+procedure TCustomApplication.Log(EventType: TEventType; const Fmt: String;
+  const Args: array of string);
+begin
+  try
+    Log(EventType, Format(Fmt, Args));
+  except
+    On E: Exception do
+      Log(etError,Format('Error formatting message "%s" with %d arguments: %s',
+        [Fmt,IntToStr(Length(Args)),E.Message]));
+  end
+end;
+
+end.
+

+ 51 - 0
src/packages/fcl-base/fcl_base_pas2js.lpk

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <Package Version="4">
+    <Name Value="fcl_base_pas2js"/>
+    <Type Value="RunTimeOnly"/>
+    <Author Value="Mattias Gaertner"/>
+    <AutoUpdate Value="Manually"/>
+    <CompilerOptions>
+      <Version Value="11"/>
+      <SearchPaths>
+        <UnitOutputDirectory Value="."/>
+      </SearchPaths>
+      <Other>
+        <ExecuteBefore>
+          <Command Value="$MakeExe(pas2js) -O- fcl_base_pas2js.pas"/>
+          <ScanForFPCMsgs Value="True"/>
+        </ExecuteBefore>
+      </Other>
+      <SkipCompiler Value="True"/>
+    </CompilerOptions>
+    <Description Value="Free Pascal Component Library - Base
+Port to pas2js."/>
+    <License Value="Modified LGPL-2"/>
+    <Version Major="1"/>
+    <Files Count="3">
+      <Item1>
+        <Filename Value="custapp.pas"/>
+        <UnitName Value="custapp"/>
+      </Item1>
+      <Item2>
+        <Filename Value="nodejsapp.pas"/>
+        <UnitName Value="nodejsapp"/>
+      </Item2>
+      <Item3>
+        <Filename Value="browserapp.pas"/>
+        <UnitName Value="browserapp"/>
+      </Item3>
+    </Files>
+    <RequiredPkgs Count="1">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+    </RequiredPkgs>
+    <UsageOptions>
+      <UnitPath Value="$(PkgOutDir)"/>
+    </UsageOptions>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+  </Package>
+</CONFIG>

+ 15 - 0
src/packages/fcl-base/fcl_base_pas2js.pas

@@ -0,0 +1,15 @@
+{ This file was automatically created by Lazarus. Do not edit!
+  This source is only used to compile and install the package.
+ }
+
+unit fcl_base_pas2js;
+
+{$warn 5023 off : no warning about unused units}
+interface
+
+uses
+  CustApp, NodeJSApp, browserapp;
+
+implementation
+
+end.

+ 138 - 0
src/packages/fcl-base/nodejsapp.pas

@@ -0,0 +1,138 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2017 by the Free Pascal development team
+
+    TNodeJSApplication class.
+
+    Initial implementation by Mattias Gaertner [email protected]
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit NodeJSApp;
+
+{$mode objfpc}
+
+interface
+
+uses
+  NodeJS, Classes, SysUtils, Types, JS, CustApp;
+
+type
+  
+  { TNodeJSApplication }
+
+  TNodeJSApplication = class(TCustomApplication)
+  protected
+    procedure DoRun; override;
+    function GetConsoleApplication: boolean; override;
+    function GetLocation: String; override;
+  public
+    procedure GetEnvironmentList(List: TStrings; NamesOnly: Boolean); override;
+    procedure ShowException(E: Exception); override;
+    procedure HandleException(Sender: TObject); override;
+  end;
+
+procedure ReloadEnvironmentStrings;
+
+implementation
+
+var
+  EnvNames: TStringDynArray;
+
+procedure ReloadEnvironmentStrings;
+begin
+  EnvNames:=TJSObject.getOwnPropertyNames(TNJSProcess.env);
+end;
+
+function GetParamCount: longint;
+begin
+  // argv contains as first element nodejs, and second the src.js
+  Result:=length(TNJSProcess.argv)-2;
+end;
+
+function GetParamStr(Index: longint): String;
+begin
+  // ParamStr(0) is the exename = the js file, which is the second element argv[1]
+  Result:=TNJSProcess.argv[Index+1];
+end;
+
+function MyGetEnvironmentVariable(Const EnvVar: String): String;
+begin
+  Result:=String(TNJSProcess.env[EnvVar]);
+end;
+
+function MyGetEnvironmentVariableCount: Integer;
+begin
+  Result:=length(EnvNames);
+end;
+
+function MyGetEnvironmentString(Index: Integer): String;
+begin
+  Result:=EnvNames[Index];
+end;
+
+{ TNodeJSApplication }
+
+procedure TNodeJSApplication.DoRun;
+begin
+  // Override in descendent classes.
+end;
+
+function TNodeJSApplication.GetConsoleApplication: boolean;
+begin
+  Result:=true;
+end;
+
+function TNodeJSApplication.GetLocation: String;
+begin
+  Result:=''; // ToDo ExtractFilePath(GetExeName);
+end;
+
+procedure TNodeJSApplication.GetEnvironmentList(List: TStrings;
+  NamesOnly: Boolean);
+var
+  Names: TStringDynArray;
+  i: Integer;
+begin
+  Names:=TJSObject.getOwnPropertyNames(TNJSProcess.env);
+  for i:=0 to length(Names)-1 do
+  begin
+    if NamesOnly then
+      List.Add(Names[i])
+    else
+      List.Add(Names[i]+'='+String(TNJSProcess.env[Names[i]]));
+  end;
+end;
+
+procedure TNodeJSApplication.ShowException(E: Exception);
+begin
+  if E<>nil then
+    writeln(E.ClassName,': ',E.Message)
+  else if ExceptObjectJS then
+    writeln(ExceptObjectJS);
+end;
+
+procedure TNodeJSApplication.HandleException(Sender: TObject);
+begin
+  if ExceptObject is Exception then
+    ShowException(ExceptObject);
+  inherited HandleException(Sender);
+end;
+
+initialization
+  IsConsole:=true;
+  OnParamCount:=@GetParamCount;
+  OnParamStr:=@GetParamStr;
+  ReloadEnvironmentStrings;
+  OnGetEnvironmentVariable:=@MyGetEnvironmentVariable;
+  OnGetEnvironmentVariableCount:=@MyGetEnvironmentVariableCount;
+  OnGetEnvironmentString:=@MyGetEnvironmentString;
+
+end.
+

+ 8780 - 0
src/packages/fcl-db/db.pas

@@ -0,0 +1,8780 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 1999-2017 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    DB database unit
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit DB;
+
+{$mode objfpc}
+{$h+}
+{ $define dsdebug}
+interface
+
+uses Classes, SysUtils, JS, Types, DateUtils;
+
+const
+
+  dsMaxBufferCount = MAXINT div 8;
+  dsMaxStringSize = 8192;
+
+  // Used in AsBoolean for string fields to determine
+  // whether it's true or false.
+  YesNoChars : Array[Boolean] of char = ('N', 'Y');
+
+  SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
+
+type
+
+{ Misc Dataset types }
+
+  TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert, dsSetKey,
+    dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue, dsBlockRead,
+    dsInternalCalc, dsOpening, dsRefreshFields);
+
+  TDataEvent = (deFieldChange, deRecordChange, deDataSetChange,
+    deDataSetScroll, deLayoutChange, deUpdateRecord, deUpdateState,
+    deCheckBrowseMode, dePropertyChange, deFieldListChange, deFocusControl,
+    deParentScroll,deConnectChange,deReconcileError,deDisabledStateChange);
+
+  TUpdateStatus = (usUnmodified, usModified, usInserted, usDeleted, usResolved, usResolveFailed);
+  TUpdateStatusSet = Set of TUpdateStatus;
+
+  TUpdateMode = (upWhereAll, upWhereChanged, upWhereKeyOnly);
+  TResolverResponse = (rrSkip, rrAbort, rrMerge, rrApply, rrIgnore);
+
+  TProviderFlag = (pfInUpdate, pfInWhere, pfInKey, pfHidden, pfRefreshOnInsert,pfRefreshOnUpdate);
+  TProviderFlags = set of TProviderFlag;
+
+{ Forward declarations }
+
+  TFieldDef = class;
+  TFieldDefs = class;
+  TField = class;
+  TFields = Class;
+  TDataSet = class;
+  TDataSource = Class;
+  TDataLink = Class;
+  TDataProxy = Class;
+  TDataRequest = class;
+  TRecordUpdateDescriptor = class;
+  TRecordUpdateDescriptorList = class;
+  TRecordUpdateBatch = class;
+
+{ Exception classes }
+
+  EDatabaseError = class(Exception);
+
+  EUpdateError   = class(EDatabaseError)
+  private
+    FContext           : String;
+    FErrorCode         : integer;
+    FOriginalException : Exception;
+    FPreviousError     : Integer;
+  public
+    constructor Create(NativeError, Context : String;
+      ErrCode, PrevError : integer; E: Exception);
+    Destructor Destroy; override;
+    property Context : String read FContext;
+    property ErrorCode : integer read FErrorcode;
+    property OriginalException : Exception read FOriginalException;
+    property PreviousError : Integer read FPreviousError;
+  end;
+  
+
+{ TFieldDef }
+
+  TFieldClass = class of TField;
+
+  // Data type for field.
+  TFieldType = (
+    ftUnknown, ftString, ftInteger, ftLargeInt, ftBoolean, ftFloat, ftDate,
+    ftTime, ftDateTime,  ftAutoInc, ftBlob, ftMemo, ftFixedChar,
+    ftVariant
+  );
+
+{ TDateTimeRec }
+
+  TFieldAttribute = (faHiddenCol, faReadonly, faRequired, faLink, faUnNamed, faFixed);
+  TFieldAttributes = set of TFieldAttribute;
+
+{ TNamedItem }
+
+  TNamedItem = class(TCollectionItem)
+  private
+    FName: string;
+  protected
+    function GetDisplayName: string; override;
+    procedure SetDisplayName(const Value: string); override;
+  Public  
+    property DisplayName : string read GetDisplayName write SetDisplayName;
+  published
+    property Name : string read FName write SetDisplayName;
+  end;
+
+{ TDefCollection }
+
+  TDefCollection = class(TOwnedCollection)
+  private
+    FDataset: TDataset;
+    FUpdated: boolean;
+  protected
+    procedure SetItemName(Item: TCollectionItem); override;
+  public
+    constructor create(ADataset: TDataset; AOwner: TPersistent; AClass: TCollectionItemClass);
+    function Find(const AName: string): TNamedItem;
+    procedure GetItemNames(List: TStrings);
+    function IndexOf(const AName: string): Longint;
+    property Dataset: TDataset read FDataset;
+    property Updated: boolean read FUpdated write FUpdated;
+  end;
+
+{ TFieldDef }
+
+  TFieldDef = class(TNamedItem)
+  Private
+    FAttributes : TFieldAttributes;
+    FDataType : TFieldType;
+    FFieldNo : Longint;
+    FInternalCalcField : Boolean;
+    FPrecision : Longint;
+    FRequired : Boolean;
+    FSize : Integer;
+    Function GetFieldClass : TFieldClass;
+    procedure SetAttributes(AValue: TFieldAttributes);
+    procedure SetDataType(AValue: TFieldType);
+    procedure SetPrecision(const AValue: Longint);
+    procedure SetSize(const AValue: Integer);
+    procedure SetRequired(const AValue: Boolean);
+  public
+    constructor Create(ACollection : TCollection); override;
+    constructor Create(AOwner: TFieldDefs; const AName: string;  ADataType: TFieldType; ASize: Integer; ARequired: Boolean; AFieldNo: Longint); overload;
+    destructor Destroy; override;
+    procedure Assign(Source: TPersistent); override;
+    function CreateField(AOwner: TComponent): TField;
+    property FieldClass: TFieldClass read GetFieldClass;
+    property FieldNo: Longint read FFieldNo;
+    property InternalCalcField: Boolean read FInternalCalcField write FInternalCalcField;
+    property Required: Boolean read FRequired write SetRequired;
+  Published
+    property Attributes: TFieldAttributes read FAttributes write SetAttributes default [];
+    property DataType: TFieldType read FDataType write SetDataType;
+    property Precision: Longint read FPrecision write SetPrecision default 0;
+    property Size: Integer read FSize write SetSize default 0;
+  end;
+  TFieldDefClass = Class of TFieldDef;
+
+{ TFieldDefs }
+
+  TFieldDefs = class(TDefCollection)
+  private
+    FHiddenFields : Boolean;
+    function GetItem(Index: Longint): TFieldDef;
+    procedure SetItem(Index: Longint; const AValue: TFieldDef);
+  Protected
+    Class Function FieldDefClass : TFieldDefClass; virtual;
+  public
+    constructor Create(ADataSet: TDataSet);
+//    destructor Destroy; override;
+    Function Add(const AName: string; ADataType: TFieldType; ASize, APrecision: Integer; ARequired, AReadOnly: Boolean; AFieldNo : Integer) : TFieldDef; overload;
+    Function Add(const AName: string; ADataType: TFieldType; ASize: Word; ARequired: Boolean; AFieldNo : Integer) : TFieldDef; overload;
+    procedure Add(const AName: string; ADataType: TFieldType; ASize: Word; ARequired: Boolean); overload;
+    procedure Add(const AName: string; ADataType: TFieldType; ASize: Word); overload;
+    procedure Add(const AName: string; ADataType: TFieldType); overload;
+    Function AddFieldDef : TFieldDef;
+    procedure Assign(FieldDefs: TFieldDefs); overload;
+    function Find(const AName: string): TFieldDef;
+//    procedure Clear;
+//    procedure Delete(Index: Longint);
+    procedure Update; overload;
+    Function MakeNameUnique(const AName : String) : string; virtual;
+    Property HiddenFields : Boolean Read FHiddenFields Write FHiddenFields;
+    property Items[Index: Longint]: TFieldDef read GetItem write SetItem; default;
+  end;
+  TFieldDefsClass = Class of TFieldDefs;
+
+{ TField }
+
+  TFieldKind = (fkData, fkCalculated, fkLookup, fkInternalCalc);
+  TFieldKinds = Set of TFieldKind;
+
+  TFieldNotifyEvent = procedure(Sender: TField) of object;
+  TFieldGetTextEvent = procedure(Sender: TField; var aText: string;
+    DisplayText: Boolean) of object;
+  TFieldSetTextEvent = procedure(Sender: TField; const aText: string) of object;
+  TFieldChars = Array of Char;
+
+{ TLookupList }
+
+  TLookupList = class(TObject)
+  private
+    FList: TFPList;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure Add(const AKey, AValue: JSValue);
+    procedure Clear;
+    function FirstKeyByValue(const AValue: JSValue): JSValue;
+    function ValueOfKey(const AKey: JSValue): JSValue;
+    procedure ValuesToStrings(AStrings: TStrings);
+  end;
+
+{ TField }
+
+  TField = class(TComponent)
+  private
+    FAlignment : TAlignment;
+    FAttributeSet : String;
+    FCalculated : Boolean;
+    FConstraintErrorMessage : String;
+    FCustomConstraint : String;
+    FDataSet : TDataSet;
+//    FDataSize : Word;
+    FDataType : TFieldType;
+    FDefaultExpression : String;
+    FDisplayLabel : String;
+    FDisplayWidth : Longint;
+//    FEditMask: TEditMask;
+    FFieldDef: TFieldDef;
+    FFieldKind : TFieldKind;
+    FFieldName : String;
+    FFieldNo : Longint;
+    FFields : TFields;
+    FHasConstraints : Boolean;
+    FImportedConstraint : String;
+    FIsIndexField : Boolean;
+    FKeyFields : String;
+    FLookupCache : Boolean;
+    FLookupDataSet : TDataSet;
+    FLookupKeyfields : String;
+    FLookupresultField : String;
+    FLookupList: TLookupList;
+    FOffset : Word;
+    FOnChange : TFieldNotifyEvent;
+    FOnGetText: TFieldGetTextEvent;
+    FOnSetText: TFieldSetTextEvent;
+    FOnValidate: TFieldNotifyEvent;
+    FOrigin : String;
+    FReadOnly : Boolean;
+    FRequired : Boolean;
+    FSize : integer;
+    FValidChars : TFieldChars;
+    FValueBuffer : JSValue;
+    FValidating : Boolean;
+    FVisible : Boolean;
+    FProviderFlags : TProviderFlags;
+    function GetIndex : longint;
+    function GetLookup: Boolean;
+    procedure SetAlignment(const AValue: TAlignMent);
+    procedure SetIndex(const AValue: Longint);
+    function GetDisplayText: String;
+    function GetEditText: String;
+    procedure SetEditText(const AValue: string);
+    procedure SetDisplayLabel(const AValue: string);
+    procedure SetDisplayWidth(const AValue: Longint);
+    function GetDisplayWidth: integer;
+    procedure SetLookup(const AValue: Boolean);
+    procedure SetReadOnly(const AValue: Boolean);
+    procedure SetVisible(const AValue: Boolean);
+    function IsDisplayLabelStored : Boolean;
+    function IsDisplayWidthStored: Boolean;
+    function GetLookupList: TLookupList;
+    procedure CalcLookupValue;
+  protected
+    Procedure RaiseAccessError(const TypeName: string);
+    function AccessError(const TypeName: string): EDatabaseError;
+    procedure CheckInactive;
+    class procedure CheckTypeSize(AValue: Longint); virtual;
+    procedure Change; virtual;
+    procedure Bind(Binding: Boolean); virtual;
+    procedure DataChanged;
+    function GetAsBoolean: Boolean; virtual;
+    function GetAsBytes: TBytes; virtual;
+    function GetAsLargeInt: NativeInt; virtual;
+    function GetAsDateTime: TDateTime; virtual;
+    function GetAsFloat: Double; virtual;
+    function GetAsLongint: Longint; virtual;
+    function GetAsInteger: Longint; virtual;
+    function GetAsJSValue: JSValue; virtual;
+    function GetOldValue: JSValue; virtual;
+    function GetAsString: string; virtual;
+    function GetCanModify: Boolean; virtual;
+    function GetClassDesc: String; virtual;
+    function GetDataSize: Integer; virtual;
+    function GetDefaultWidth: Longint; virtual;
+    function GetDisplayName : String;
+    function GetCurValue: JSValue; virtual;
+    function GetNewValue: JSValue; virtual;
+    function GetIsNull: Boolean; virtual;
+    procedure GetText(var AText: string; ADisplayText: Boolean); virtual;
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    procedure PropertyChanged(LayoutAffected: Boolean);
+    procedure SetAsBoolean(AValue: Boolean); virtual;
+    procedure SetAsDateTime(AValue: TDateTime); virtual;
+    procedure SetAsFloat(AValue: Double); virtual;
+    procedure SetAsLongint(AValue: Longint); virtual;
+    procedure SetAsInteger(AValue: Longint); virtual;
+    procedure SetAsLargeInt(AValue: NativeInt); virtual;
+    procedure SetAsJSValue(const AValue: JSValue); virtual;
+    procedure SetAsString(const AValue: string); virtual;
+    procedure SetDataset(AValue : TDataset); virtual;
+    procedure SetDataType(AValue: TFieldType);
+    procedure SetNewValue(const AValue: JSValue);
+    procedure SetSize(AValue: Integer); virtual;
+    procedure SetParentComponent(Value: TComponent); override;
+    procedure SetText(const AValue: string); virtual;
+    procedure SetVarValue(const AValue: JSValue); virtual;
+    procedure SetAsBytes(const AValue: TBytes); virtual;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    function GetParentComponent: TComponent; override;
+    function HasParent: Boolean; override;
+    procedure Assign(Source: TPersistent); override;
+    procedure AssignValue(const AValue: JSValue);
+    procedure Clear; virtual;
+    procedure FocusControl;
+    function GetData : JSValue;
+    class function IsBlob: Boolean; virtual;
+    function IsValidChar(InputChar: Char): Boolean; virtual;
+    procedure RefreshLookupList;
+    procedure SetData(Buffer: JSValue); overload;
+    procedure SetFieldType(AValue: TFieldType); virtual;
+    procedure Validate(Buffer: Pointer);
+    property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean;
+    property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime;
+    property AsFloat: Double read GetAsFloat write SetAsFloat;
+    property AsLongint: Longint read GetAsLongint write SetAsLongint;
+    property AsLargeInt: NativeInt read GetAsLargeInt write SetAsLargeInt;
+    property AsInteger: Longint read GetAsInteger write SetAsInteger;
+    property AsString: string read GetAsString write SetAsString;
+    property AsJSValue: JSValue read GetAsJSValue write SetAsJSValue;
+    property AttributeSet: string read FAttributeSet write FAttributeSet;
+    property Calculated: Boolean read FCalculated write FCalculated;
+    property CanModify: Boolean read GetCanModify;
+    property CurValue: JSValue read GetCurValue;
+    property DataSet: TDataSet read FDataSet write SetDataSet;
+    property DataSize: Integer read GetDataSize;
+    property DataType: TFieldType read FDataType;
+    property DisplayName: String Read GetDisplayName;
+    property DisplayText: String read GetDisplayText;
+    property FieldNo: Longint read FFieldNo;
+    property IsIndexField: Boolean read FIsIndexField;
+    property IsNull: Boolean read GetIsNull;
+    property Lookup: Boolean read GetLookup write SetLookup; deprecated;
+    property NewValue: JSValue read GetNewValue write SetNewValue;
+    property Offset: word read FOffset;
+    property Size: Integer read FSize write SetSize;
+    property Text: string read GetEditText write SetEditText;
+    property ValidChars : TFieldChars read FValidChars write FValidChars;
+    property Value: JSValue read GetAsJSValue write SetAsJSValue;
+    property OldValue: JSValue read GetOldValue;
+    property LookupList: TLookupList read GetLookupList;
+    Property FieldDef : TFieldDef Read FFieldDef;
+  published
+    property Alignment : TAlignment read FAlignment write SetAlignment default taLeftJustify;
+    property CustomConstraint: string read FCustomConstraint write FCustomConstraint;
+    property ConstraintErrorMessage: string read FConstraintErrorMessage write FConstraintErrorMessage;
+    property DefaultExpression: string read FDefaultExpression write FDefaultExpression;
+    property DisplayLabel : string read GetDisplayName write SetDisplayLabel stored IsDisplayLabelStored;
+    property DisplayWidth: Longint read GetDisplayWidth write SetDisplayWidth stored IsDisplayWidthStored;
+    property FieldKind: TFieldKind read FFieldKind write FFieldKind;
+    property FieldName: string read FFieldName write FFieldName;
+    property HasConstraints: Boolean read FHasConstraints;
+    property Index: Longint read GetIndex write SetIndex;
+    property ImportedConstraint: string read FImportedConstraint write FImportedConstraint;
+    property KeyFields: string read FKeyFields write FKeyFields;
+    property LookupCache: Boolean read FLookupCache write FLookupCache;
+    property LookupDataSet: TDataSet read FLookupDataSet write FLookupDataSet;
+    property LookupKeyFields: string read FLookupKeyFields write FLookupKeyFields;
+    property LookupResultField: string read FLookupResultField write FLookupResultField;
+    property Origin: string read FOrigin write FOrigin;
+    property ProviderFlags : TProviderFlags read FProviderFlags write FProviderFlags;
+    property ReadOnly: Boolean read FReadOnly write SetReadOnly;
+    property Required: Boolean read FRequired write FRequired;
+    property Visible: Boolean read FVisible write SetVisible default True;
+    property OnChange: TFieldNotifyEvent read FOnChange write FOnChange;
+    property OnGetText: TFieldGetTextEvent read FOnGetText write FOnGetText;
+    property OnSetText: TFieldSetTextEvent read FOnSetText write FOnSetText;
+    property OnValidate: TFieldNotifyEvent read FOnValidate write FOnValidate;
+  end;
+
+{ TStringField }
+
+  TStringField = class(TField)
+  private
+    FFixedChar     : boolean;
+    FTransliterate : Boolean;
+  protected
+    class procedure CheckTypeSize(AValue: Longint); override;
+    function GetAsBoolean: Boolean; override;
+    function GetAsDateTime: TDateTime; override;
+    function GetAsFloat: Double; override;
+    function GetAsInteger: Longint; override;
+    function GetAsLargeInt: NativeInt; override;
+    function GetAsString: String; override;
+    function GetAsJSValue: JSValue; override;
+    function GetDefaultWidth: Longint; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+    procedure SetAsBoolean(AValue: Boolean); override;
+    procedure SetAsDateTime(AValue: TDateTime); override;
+    procedure SetAsFloat(AValue: Double); override;
+    procedure SetAsInteger(AValue: Longint); override;
+    procedure SetAsLargeInt(AValue: NativeInt); override;
+    procedure SetAsString(const AValue: String); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure SetFieldType(AValue: TFieldType); override;
+    property FixedChar : Boolean read FFixedChar write FFixedChar;
+    property Transliterate: Boolean read FTransliterate write FTransliterate;
+    property Value: String read GetAsString write SetAsString;
+  published
+    property Size default 20;
+  end;
+
+
+{ TNumericField }
+
+  TNumericField = class(TField)
+  Private
+    FDisplayFormat : String;
+    FEditFormat : String;
+  protected
+    class procedure CheckTypeSize(AValue: Longint); override;
+    procedure RangeError(AValue, Min, Max: Double);
+    procedure SetDisplayFormat(const AValue: string);
+    procedure SetEditFormat(const AValue: string);
+    function  GetAsBoolean: Boolean; override;
+    Procedure SetAsBoolean(AValue: Boolean); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  published
+    property Alignment default taRightJustify;
+    property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
+    property EditFormat: string read FEditFormat write SetEditFormat;
+  end;
+
+{ TLongintField }
+
+  TIntegerField = class(TNumericField)
+  private
+    FMinValue,
+    FMaxValue,
+    FMinRange,
+    FMaxRange  : Longint;
+    Procedure SetMinValue (AValue : longint);
+    Procedure SetMaxValue (AValue : longint);
+  protected
+    function GetAsFloat: Double; override;
+    function GetAsInteger: Longint; override;
+    function GetAsString: string; override;
+    function GetAsJSValue: JSValue; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+    function GetValue(var AValue: Longint): Boolean;
+    procedure SetAsFloat(AValue: Double); override;
+    procedure SetAsInteger(AValue: Longint); override;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+    function GetAsLargeInt: NativeInt; override;
+    procedure SetAsLargeInt(AValue: NativeInt); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    Function CheckRange(AValue : Longint) : Boolean;
+    property Value: Longint read GetAsInteger write SetAsInteger;
+  published
+    property MaxValue: Longint read FMaxValue write SetMaxValue default 0;
+    property MinValue: Longint read FMinValue write SetMinValue default 0;
+  end;
+
+{ TLargeintField }
+
+  TLargeintField = class(TNumericField)
+  private
+    FMinValue,
+    FMaxValue,
+    FMinRange,
+    FMaxRange  : NativeInt;
+    Procedure SetMinValue (AValue : NativeInt);
+    Procedure SetMaxValue (AValue : NativeInt);
+  protected
+    function GetAsFloat: Double; override;
+    function GetAsInteger: Longint; override;
+    function GetAsLargeInt: NativeInt; override;
+    function GetAsString: string; override;
+    function GetAsJSValue: JSValue; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+    function GetValue(var AValue: NativeInt): Boolean;
+    procedure SetAsFloat(AValue: Double); override;
+    procedure SetAsInteger(AValue: Longint); override;
+    procedure SetAsLargeInt(AValue: NativeInt); override;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    Function CheckRange(AValue : NativeInt) : Boolean;
+    property Value: NativeInt read GetAsLargeInt write SetAsLargeInt;
+  published
+    property MaxValue: NativeInt read FMaxValue write SetMaxValue default 0;
+    property MinValue: NativeInt read FMinValue write SetMinValue default 0;
+  end;
+
+{ TAutoIncField }
+
+  TAutoIncField = class(TIntegerField)
+  Protected
+    procedure SetAsInteger(AValue: Longint); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+{ TFloatField }
+
+  TFloatField = class(TNumericField)
+  private
+    FCurrency: Boolean;
+    FMaxValue : Double;
+    FMinValue : Double;
+    FPrecision : Longint;
+    procedure SetCurrency(const AValue: Boolean);
+    procedure SetPrecision(const AValue: Longint);
+  protected
+    function GetAsFloat: Double; override;
+    function GetAsLargeInt: NativeInt; override;
+    function GetAsInteger: Longint; override;
+    function GetAsJSValue: JSValue; override;
+    function GetAsString: string; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+    procedure SetAsFloat(AValue: Double); override;
+    procedure SetAsLargeInt(AValue: NativeInt); override;
+    procedure SetAsInteger(AValue: Longint); override;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    Function CheckRange(AValue : Double) : Boolean;
+    property Value: Double read GetAsFloat write SetAsFloat;
+
+  published
+    property Currency: Boolean read FCurrency write SetCurrency default False;
+    property MaxValue: Double read FMaxValue write FMaxValue;
+    property MinValue: Double read FMinValue write FMinValue;
+    property Precision: Longint read FPrecision write SetPrecision default 15; // min 2 instellen, delphi compat
+  end;
+
+
+{ TBooleanField }
+
+  TBooleanField = class(TField)
+  private
+    FDisplayValues : String;
+    // First byte indicates uppercase or not.
+    FDisplays : Array[Boolean,Boolean] of string;
+    Procedure SetDisplayValues(const AValue : String);
+  protected
+    function GetAsBoolean: Boolean; override;
+    function GetAsString: string; override;
+    function GetAsJSValue: JSValue; override;
+    function GetAsInteger: Longint; override;
+    function GetDefaultWidth: Longint; override;
+    procedure SetAsBoolean(AValue: Boolean); override;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetAsInteger(AValue: Longint); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    property Value: Boolean read GetAsBoolean write SetAsBoolean;
+  published
+    property DisplayValues: string read FDisplayValues write SetDisplayValues;
+  end;
+
+{ TDateTimeField }
+
+  TDateTimeField = class(TField)
+  private
+    FDisplayFormat : String;
+    procedure SetDisplayFormat(const AValue: string);
+  protected
+    Function ConvertToDateTime(aValue : JSValue; aRaiseError : Boolean) : TDateTime; virtual;
+    Function DateTimeToNativeDateTime(aValue : TDateTime) : JSValue; virtual;
+    function GetAsDateTime: TDateTime; override;
+    function GetAsFloat: Double; override;
+    function GetAsString: string; override;
+    function GetAsJSValue: JSValue; override;
+    function GetDataSize: Integer; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+    procedure SetAsDateTime(AValue: TDateTime); override;
+    procedure SetAsFloat(AValue: Double); override;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    property Value: TDateTime read GetAsDateTime write SetAsDateTime;
+  published
+    property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
+  end;
+
+{ TDateField }
+
+  TDateField = class(TDateTimeField)
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+{ TTimeField }
+
+  TTimeField = class(TDateTimeField)
+  protected
+    procedure SetAsString(const AValue: string); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+{ TBinaryField }
+
+  TBinaryField = class(TField)
+  protected
+    class procedure CheckTypeSize(AValue: Longint); override;
+    Function BlobToBytes(aValue : JSValue) : TBytes; virtual;
+    Function BytesToBlob(aValue : TBytes) : JSValue; virtual;
+    function GetAsString: string; override;
+    function GetAsJSValue: JSValue; override;
+    function GetValue(var AValue: TBytes): Boolean;
+    procedure SetAsString(const AValue: string); override;
+    procedure SetVarValue(const AValue: JSValue); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  published
+    property Size default 16;
+  end;
+
+{ TBytesField }
+
+
+{ TBlobField }
+  TBlobStreamMode = (bmRead, bmWrite, bmReadWrite);
+//  TBlobType = ftBlob..ftMemo;
+
+  TBlobField = class(TBinaryField)
+  private
+    FModified : Boolean;
+    // Wrapper that retrieves FDataType as a TBlobType
+    //   function GetBlobType: TBlobType;
+    // Wrapper that calls SetFieldType
+    //   procedure SetBlobType(AValue: TBlobType);
+  protected
+    function GetBlobSize: Longint; virtual;
+    function GetIsNull: Boolean; override;
+    procedure GetText(var AText: string; ADisplayText: Boolean); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure Clear; override;
+    class function IsBlob: Boolean; override;
+    procedure SetFieldType(AValue: TFieldType); override;
+    property BlobSize: Longint read GetBlobSize;
+    property Modified: Boolean read FModified write FModified;
+    property Value: string read GetAsString write SetAsString;
+  published
+   // property BlobType: TBlobType read GetBlobType write SetBlobType; // default ftBlob;
+    property Size default 0;
+  end;
+
+{ TMemoField }
+
+  TMemoField = class(TBlobField)
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+
+{ TVariantField }
+
+  TVariantField = class(TField)
+  protected
+    class procedure CheckTypeSize(aValue: Integer); override;
+
+    function GetAsBoolean: Boolean; override;
+    procedure SetAsBoolean(aValue: Boolean); override;
+
+    function GetAsDateTime: TDateTime; override;
+    procedure SetAsDateTime(aValue: TDateTime); override;
+
+    function GetAsFloat: Double; override;
+    procedure SetAsFloat(aValue: Double); override;
+
+    function GetAsInteger: Longint; override;
+    procedure SetAsInteger(AValue: Longint); override;
+
+    function GetAsString: string; override;
+    procedure SetAsString(const aValue: string); override;
+
+
+    function GetAsJSValue: JSValue; override;
+    procedure SetVarValue(const aValue: JSValue); override;
+
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
+{ TIndexDef }
+
+  TIndexDefs = class;
+
+  TIndexOption = (ixPrimary, ixUnique, ixDescending, ixCaseInsensitive,
+    ixExpression, ixNonMaintained);
+  TIndexOptions = set of TIndexOption;
+
+  TIndexDef = class(TNamedItem)
+  Private
+    FCaseinsFields: string;
+    FDescFields: string;
+    FExpression : String;
+    FFields : String;
+    FOptions : TIndexOptions;
+    FSource : String;
+  protected
+    function GetExpression: string;
+    procedure SetCaseInsFields(const AValue: string); virtual;
+    procedure SetDescFields(const AValue: string);
+    procedure SetExpression(const AValue: string);
+  public
+    constructor Create(Owner: TIndexDefs; const AName, TheFields: string;
+      TheOptions: TIndexOptions); overload;
+    procedure Assign(Source: TPersistent); override;
+  published
+    property Expression: string read GetExpression write SetExpression;
+    property Fields: string read FFields write FFields;
+    property CaseInsFields: string read FCaseinsFields write SetCaseInsFields;
+    property DescFields: string read FDescFields write SetDescFields;
+    property Options: TIndexOptions read FOptions write FOptions;
+    property Source: string read FSource write FSource;
+  end;
+
+{ TIndexDefs }
+
+  TIndexDefs = class(TDefCollection)
+  Private
+    Function  GetItem(Index: Integer): TIndexDef;
+    Procedure SetItem(Index: Integer; Value: TIndexDef);
+  public
+    constructor Create(ADataSet: TDataSet); virtual; overload;
+    procedure Add(const Name, Fields: string; Options: TIndexOptions);
+    Function AddIndexDef: TIndexDef;
+    function Find(const IndexName: string): TIndexDef;
+    function FindIndexForFields(const Fields: string): TIndexDef;
+    function GetIndexForFields(const Fields: string;
+      CaseInsensitive: Boolean): TIndexDef;
+    procedure Update; overload; virtual;
+    Property Items[Index: Integer] : TIndexDef read GetItem write SetItem; default;
+  end;
+
+{ TCheckConstraint }
+
+  TCheckConstraint = class(TCollectionItem)
+  Private
+    FCustomConstraint : String;
+    FErrorMessage : String;
+    FFromDictionary : Boolean;
+    FImportedConstraint : String;
+  public
+    procedure Assign(Source: TPersistent); override;
+  //  function GetDisplayName: string; override;
+  published
+    property CustomConstraint: string read FCustomConstraint write FCustomConstraint;
+    property ErrorMessage: string read FErrorMessage write FErrorMessage;
+    property FromDictionary: Boolean read FFromDictionary write FFromDictionary;
+    property ImportedConstraint: string read FImportedConstraint write FImportedConstraint;
+  end;
+
+{ TCheckConstraints }
+
+  TCheckConstraints = class(TCollection)
+  Private
+   Function GetItem(Index : Longint) : TCheckConstraint;
+   Procedure SetItem(index : Longint; Value : TCheckConstraint);
+  protected
+    function GetOwner: TPersistent; override;
+  public
+    constructor Create(AOwner: TPersistent);
+    function Add: TCheckConstraint;
+    property Items[Index: Longint]: TCheckConstraint read GetItem write SetItem; default;
+  end;
+
+  { TFieldsEnumerator }
+
+  TFieldsEnumerator = class
+  private
+    FPosition: Integer;
+    FFields: TFields;
+    function GetCurrent: TField;
+  public
+    constructor Create(AFields: TFields);
+    function MoveNext: Boolean;
+    property Current: TField read GetCurrent;
+  end;
+
+{ TFields }
+
+  TFields = Class(TObject)
+  Private
+    FDataset : TDataset;
+    FFieldList : TFpList;
+    FOnChange : TNotifyEvent;
+    FValidFieldKinds : TFieldKinds;
+  Protected
+    Procedure ClearFieldDefs;
+    Procedure Changed;
+    Procedure CheckfieldKind(Fieldkind : TFieldKind; Field : TField);
+    Function GetCount : Longint;
+    Function GetField (Index : Integer) : TField;
+    Procedure SetField(Index: Integer; Value: TField);
+    Procedure SetFieldIndex (Field : TField;Value : Integer);
+    Property OnChange : TNotifyEvent Read FOnChange Write FOnChange;
+    Property ValidFieldKinds : TFieldKinds Read FValidFieldKinds;
+  Public
+    Constructor Create(ADataset : TDataset);
+    Destructor Destroy;override;
+    Procedure Add(Field : TField);
+    Procedure CheckFieldName (Const Value : String);
+    Procedure CheckFieldNames (Const Value : String);
+    Procedure Clear;
+    Function FindField (Const Value : String) : TField;
+    Function FieldByName (Const Value : String) : TField;
+    Function FieldByNumber(FieldNo : Integer) : TField;
+    Function GetEnumerator: TFieldsEnumerator;
+    Procedure GetFieldNames (Values : TStrings);
+    Function IndexOf(Field : TField) : Longint;
+    procedure Remove(Value : TField);
+    Property Count : Integer Read GetCount;
+    Property Dataset : TDataset Read FDataset;
+    Property Fields [Index : Integer] : TField Read GetField Write SetField; default;
+  end;
+  TFieldsClass = Class of TFields;
+
+{ TParam }
+
+  TBlobData = TBytes;  // Delphi defines it as alias to TBytes
+
+  TParamBinding = array of integer;
+
+  TParamType = (ptUnknown, ptInput, ptOutput, ptInputOutput, ptResult);
+  TParamTypes = set of TParamType;
+
+  TParamStyle = (psInterbase,psPostgreSQL,psSimulated);
+
+  TParams = class;
+
+  TParam = class(TCollectionItem)
+  private
+    FNativeStr: string;
+    FValue: JSValue;
+    FPrecision: Integer;
+    FNumericScale: Integer;
+    FName: string;
+    FDataType: TFieldType;
+    FBound: Boolean;
+    FParamType: TParamType;
+    FSize: Integer;
+    Function GetDataSet: TDataSet;
+    Function IsParamStored: Boolean;
+    procedure SetAsBytes(AValue: TBlobData);
+  protected
+    Procedure AssignParam(Param: TParam);
+    Procedure AssignTo(Dest: TPersistent); override;
+    Function GetAsBoolean: Boolean;
+    Function GetAsBytes: TBytes;
+    Function GetAsDateTime: TDateTime;
+    Function GetAsFloat: Double;
+    Function GetAsInteger: Longint;
+    Function GetAsLargeInt: NativeInt;
+    Function GetAsMemo: string;
+    Function GetAsString: string;
+    Function GetAsJSValue: JSValue;
+    Function GetDisplayName: string; override;
+    Function GetIsNull: Boolean;
+    Function IsEqual(AValue: TParam): Boolean;
+    Procedure SetAsBlob(const AValue: TBlobData);
+    Procedure SetAsBoolean(AValue: Boolean);
+    Procedure SetAsBytes(const AValue: TBytes);
+    Procedure SetAsDate(const AValue: TDateTime);
+    Procedure SetAsDateTime(const AValue: TDateTime);
+    Procedure SetAsFloat(const AValue: Double);
+    Procedure SetAsInteger(AValue: Longint);
+    Procedure SetAsLargeInt(AValue: NativeInt);
+    Procedure SetAsMemo(const AValue: string);
+    Procedure SetAsString(const AValue: string);
+    Procedure SetAsTime(const AValue: TDateTime);
+    Procedure SetAsJSValue(const AValue: JSValue);
+    Procedure SetDataType(AValue: TFieldType);
+    Procedure SetText(const AValue: string);
+  public
+    constructor Create(ACollection: TCollection); overload; override;
+    constructor Create(AParams: TParams; AParamType: TParamType); reintroduce; overload;
+    Procedure Assign(Source: TPersistent); override;
+    Procedure AssignField(Field: TField);
+    Procedure AssignToField(Field: TField);
+    Procedure AssignFieldValue(Field: TField; const AValue: JSValue);
+    Procedure AssignFromField(Field : TField);
+    Procedure Clear;
+    Property AsBlob : TBlobData read GetAsBytes  write SetAsBytes;
+    Property AsBoolean : Boolean read GetAsBoolean write SetAsBoolean;
+    Property AsBytes : TBytes read GetAsBytes write SetAsBytes;
+    Property AsDate : TDateTime read GetAsDateTime write SetAsDate;
+    Property AsDateTime : TDateTime read GetAsDateTime write SetAsDateTime;
+    Property AsFloat : Double read GetAsFloat write SetAsFloat;
+    Property AsInteger : LongInt read GetAsInteger write SetAsInteger;
+    Property AsLargeInt : NativeInt read GetAsLargeInt write SetAsLargeInt;
+    Property AsMemo : string read GetAsMemo write SetAsMemo;
+    Property AsSmallInt : LongInt read GetAsInteger write SetAsInteger;
+    Property AsString : string read GetAsString write SetAsString;
+    Property AsTime : TDateTime read GetAsDateTime write SetAsTime;
+    Property Bound : Boolean read FBound write FBound;
+    Property Dataset : TDataset Read GetDataset;
+    Property IsNull : Boolean read GetIsNull;
+    Property Text : string read GetAsString write SetText;
+  published
+    Property DataType : TFieldType read FDataType write SetDataType;
+    Property Name : string read FName write FName;
+    Property NumericScale : Integer read FNumericScale write FNumericScale default 0;
+    Property ParamType : TParamType read FParamType write FParamType;
+    Property Precision : Integer read FPrecision write FPrecision default 0;
+    Property Size : Integer read FSize write FSize default 0;
+    Property Value : JSValue read GetAsJSValue write SetAsJSValue stored IsParamStored;
+  end;
+  TParamClass = Class of TParam;
+
+{ TParams }
+
+  TParams = class(TCollection)
+  private
+    FOwner: TPersistent;
+    Function  GetItem(Index: Integer): TParam;
+    Function  GetParamValue(const ParamName: string): JSValue;
+    Procedure SetItem(Index: Integer; Value: TParam);
+    Procedure SetParamValue(const ParamName: string; const Value: JSValue);
+  protected
+    Procedure AssignTo(Dest: TPersistent); override;
+    Function  GetDataSet: TDataSet;
+    Function  GetOwner: TPersistent; override;
+    Class Function ParamClass : TParamClass; virtual;
+  public
+    Constructor Create(AOwner: TPersistent; AItemClass : TCollectionItemClass); overload;
+    Constructor Create(AOwner: TPersistent); overload;
+    Constructor Create; overload;
+    Procedure AddParam(Value: TParam);
+    Procedure AssignValues(Value: TParams);
+    Function  CreateParam(FldType: TFieldType; const ParamName: string; ParamType: TParamType): TParam;
+    Function  FindParam(const Value: string): TParam;
+    Procedure GetParamList(List: TList; const ParamNames: string);
+    Function  IsEqual(Value: TParams): Boolean;
+    Function  ParamByName(const Value: string): TParam;
+    Function  ParseSQL(SQL: String; DoCreate: Boolean): String; overload;
+    Function  ParseSQL(SQL: String; DoCreate, EscapeSlash, EscapeRepeat : Boolean; ParameterStyle : TParamStyle): String; overload;
+    Function  ParseSQL(SQL: String; DoCreate, EscapeSlash, EscapeRepeat : Boolean; ParameterStyle : TParamStyle; out ParamBinding: TParambinding): String; overload;
+    Function  ParseSQL(SQL: String; DoCreate, EscapeSlash, EscapeRepeat : Boolean; ParameterStyle : TParamStyle; out ParamBinding: TParambinding; out ReplaceString : string): String; overload;
+    Procedure RemoveParam(Value: TParam);
+    Procedure CopyParamValuesFromDataset(ADataset : TDataset; CopyBound : Boolean);
+    Property Dataset : TDataset Read GetDataset;
+    Property Items[Index: Integer] : TParam read GetItem write SetItem; default;
+    Property ParamValues[const ParamName: string] : JSValue read GetParamValue write SetParamValue;
+  end;
+
+{ TDataSet }
+  
+  TBookmarkFlag = (bfCurrent, bfBOF, bfEOF, bfInserted);
+  TBookmark = record
+    Data : JSValue;
+    Flag : TBookmarkFlag;
+  end; // Bookmark is always the index in the data array.
+  TBookmarkStr = string; // JSON encoded version of the above
+
+  TGetMode = (gmCurrent, gmNext, gmPrior);
+  TGetResult = (grOK, grBOF, grEOF, grError);
+
+  TResyncMode = set of (rmExact, rmCenter);
+  TDataAction = (daFail, daAbort, daRetry);
+  TUpdateAction = (uaFail, uaAbort, uaSkip, uaRetry, uaApplied);
+  TUpdateKind = (ukModify, ukInsert, ukDelete);
+
+  TLocateOption = (loCaseInsensitive, loPartialKey);
+  TLocateOptions = set of TLocateOption;
+  TDataOperation = procedure of object;
+
+  TDataSetNotifyEvent = procedure(DataSet: TDataSet) of object;
+  TDataSetErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;
+    var DataAction: TDataAction) of object;
+
+  TFilterOption = (foCaseInsensitive, foNoPartialCompare);
+  TFilterOptions = set of TFilterOption;
+
+  TLoadOption = (loNoOpen,loNoEvents,loAtEOF);
+  TLoadOptions = Set of TLoadOption;
+  TDatasetLoadEvent = procedure(DataSet: TDataSet; Data : JSValue) of object;
+  TDatasetLoadFailEvent = procedure(DataSet: TDataSet; ID : Integer; Const ErrorMsg : String) of object;
+
+  TFilterRecordEvent = procedure(DataSet: TDataSet;
+    var Accept: Boolean) of object;
+  TDatasetClass = Class of TDataset;
+
+  TRecordState = (rsNew,rsClean,rsUpdate,rsDelete);
+  TDataRecord = record
+    data : JSValue;
+    state : TRecordState;
+    bookmark : JSValue;
+    bookmarkFlag : TBookmarkFlag;
+  end;
+  TBuffers = Array of TDataRecord;
+
+{------------------------------------------------------------------------------}
+
+  TDataSet = class(TComponent)
+  Private
+    FAfterApplyUpdates: TDatasetNotifyEvent;
+    FAfterLoad: TDatasetNotifyEvent;
+    FBeforeApplyUpdates: TDatasetNotifyEvent;
+    FBeforeLoad: TDatasetNotifyEvent;
+    FBlockReadSize: Integer;
+    FCalcBuffer: TDataRecord;
+    FCalcFieldsSize: Longint;
+    FOnLoadFail: TDatasetLoadFailEvent;
+    FOpenAfterRead : boolean;
+    FActiveRecord: Longint;
+    FAfterCancel: TDataSetNotifyEvent;
+    FAfterClose: TDataSetNotifyEvent;
+    FAfterDelete: TDataSetNotifyEvent;
+    FAfterEdit: TDataSetNotifyEvent;
+    FAfterInsert: TDataSetNotifyEvent;
+    FAfterOpen: TDataSetNotifyEvent;
+    FAfterPost: TDataSetNotifyEvent;
+    FAfterRefresh: TDataSetNotifyEvent;
+    FAfterScroll: TDataSetNotifyEvent;
+    FAutoCalcFields: Boolean;
+    FBOF: Boolean;
+    FBeforeCancel: TDataSetNotifyEvent;
+    FBeforeClose: TDataSetNotifyEvent;
+    FBeforeDelete: TDataSetNotifyEvent;
+    FBeforeEdit: TDataSetNotifyEvent;
+    FBeforeInsert: TDataSetNotifyEvent;
+    FBeforeOpen: TDataSetNotifyEvent;
+    FBeforePost: TDataSetNotifyEvent;
+    FBeforeRefresh: TDataSetNotifyEvent;
+    FBeforeScroll: TDataSetNotifyEvent;
+    FBlobFieldCount: Longint;
+    FBuffers : TBuffers;
+    FBufferCount: Longint;
+    FConstraints: TCheckConstraints;
+    FDisableControlsCount : Integer;
+    FDisableControlsState : TDatasetState;
+    FCurrentRecord: Longint;
+    FDataSources : TFPList;
+    FDefaultFields: Boolean;
+    FEOF: Boolean;
+    FEnableControlsEvent : TDataEvent;
+    FFieldList : TFields;
+    FFieldDefs: TFieldDefs;
+    FFilterOptions: TFilterOptions;
+    FFilterText: string;
+    FFiltered: Boolean;
+    FFound: Boolean;
+    FInternalCalcFields: Boolean;
+    FModified: Boolean;
+    FOnCalcFields: TDataSetNotifyEvent;
+    FOnDeleteError: TDataSetErrorEvent;
+    FOnEditError: TDataSetErrorEvent;
+    FOnFilterRecord: TFilterRecordEvent;
+    FOnNewRecord: TDataSetNotifyEvent;
+    FOnPostError: TDataSetErrorEvent;
+    FRecordCount: Longint;
+    FIsUniDirectional: Boolean;
+    FState : TDataSetState;
+    FInternalOpenComplete: Boolean;
+    FDataProxy : TDataProxy;
+    FDataRequestID : Integer;
+    FUpdateBatchID : Integer;
+    FChangeList : TFPList;
+    FBatchList : TFPList;
+    Procedure DoInsertAppend(DoAppend : Boolean);
+    Procedure DoInternalOpen;
+    Function  GetBuffer (Index : longint) : TDataRecord;
+    function GetBufferCount: Longint;
+    function GetDataProxy: TDataProxy;
+    Procedure RegisterDataSource(ADataSource : TDataSource);
+    procedure SetConstraints(Value: TCheckConstraints);
+    procedure SetDataProxy(AValue: TDataProxy);
+    Procedure ShiftBuffersForward;
+    Procedure ShiftBuffersBackward;
+    Function  TryDoing (P : TDataOperation; Ev : TDatasetErrorEvent) : Boolean;
+    Function GetActive : boolean;
+    Procedure UnRegisterDataSource(ADataSource : TDataSource);
+    procedure SetBlockReadSize(AValue: Integer); virtual;
+    Procedure SetFieldDefs(AFieldDefs: TFieldDefs);
+    procedure DoInsertAppendRecord(const Values: array of jsValue; DoAppend : boolean);
+    // Callback for Tdataproxy.DoGetData;
+    function ResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
+    procedure HandleRequestresponse(ARequest: TDataRequest);
+  protected
+    // Proxy methods
+    // Override this to integrate package in local data
+    function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; virtual;
+    Function GetRecordUpdates(AList: TRecordUpdateDescriptorList) : Integer; virtual;
+    procedure ResolveUpdateBatch(Sender: TObject; aBatch: TRecordUpdateBatch); virtual;
+    Function DataPacketReceived(ARequest: TDataRequest) : Boolean; virtual;
+    function DoLoad(aOptions: TLoadOptions; aAfterLoad: TDatasetLoadEvent): Boolean; virtual;
+    function DoGetDataProxy: TDataProxy; virtual;
+    Procedure InitChangeList; virtual;
+    Procedure DoneChangeList; virtual;
+    Procedure ClearChangeList;
+    Function IndexInChangeList(aBookmark: TBookmark): Integer; virtual;
+    Function AddToChangeList(aChange : TUpdateStatus) : TRecordUpdateDescriptor ; virtual;
+    Procedure RemoveFromChangeList(R : TRecordUpdateDescriptor); virtual;
+    Procedure DoApplyUpdates;
+    procedure RecalcBufListSize;
+    procedure ActivateBuffers; virtual;
+    procedure BindFields(Binding: Boolean);
+    procedure BlockReadNext; virtual;
+    function  BookmarkAvailable: Boolean;
+    procedure CalculateFields(Var Buffer: TDataRecord); virtual;
+    procedure CheckActive; virtual;
+    procedure CheckInactive; virtual;
+    procedure CheckBiDirectional;
+    procedure Loaded; override;
+    procedure ClearBuffers; virtual;
+    procedure ClearCalcFields(var Buffer: TDataRecord); virtual;
+    procedure CloseBlob(Field: TField); virtual;
+    procedure CloseCursor; virtual;
+    procedure CreateFields; virtual;
+    procedure DataEvent(Event: TDataEvent; Info: JSValue); virtual;
+    procedure DestroyFields; virtual;
+    procedure DoAfterCancel; virtual;
+    procedure DoAfterClose; virtual;
+    procedure DoAfterDelete; virtual;
+    procedure DoAfterEdit; virtual;
+    procedure DoAfterInsert; virtual;
+    procedure DoAfterOpen; virtual;
+    procedure DoAfterPost; virtual;
+    procedure DoAfterScroll; virtual;
+    procedure DoAfterRefresh; virtual;
+    procedure DoBeforeCancel; virtual;
+    procedure DoBeforeClose; virtual;
+    procedure DoBeforeDelete; virtual;
+    procedure DoBeforeEdit; virtual;
+    procedure DoBeforeInsert; virtual;
+    procedure DoBeforeOpen; virtual;
+    procedure DoBeforePost; virtual;
+    procedure DoBeforeScroll; virtual;
+    procedure DoBeforeRefresh; virtual;
+    procedure DoOnCalcFields; virtual;
+    procedure DoOnNewRecord; virtual;
+    procedure DoBeforeLoad; virtual;
+    procedure DoAfterLoad; virtual;
+    procedure DoBeforeApplyUpdates; virtual;
+    procedure DoAfterApplyUpdates;virtual;
+    function  FieldByNumber(FieldNo: Longint): TField;
+    function  FindRecord(Restart, GoForward: Boolean): Boolean; virtual;
+    function  GetBookmarkStr: TBookmarkStr; virtual;
+    procedure GetCalcFields(Var Buffer: TDataRecord); virtual;
+    function  GetCanModify: Boolean; virtual;
+    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
+    function  GetFieldClass(FieldType: TFieldType): TFieldClass; virtual;
+    Function  GetfieldCount : Integer;
+    function  GetFieldValues(const FieldName : string) : JSValue; virtual;
+    function  GetIsIndexField(Field: TField): Boolean; virtual;
+    function  GetIndexDefs(IndexDefs : TIndexDefs; IndexTypes : TIndexOptions) : TIndexDefs;
+    function  GetNextRecords: Longint; virtual;
+    function  GetNextRecord: Boolean; virtual;
+    function  GetPriorRecords: Longint; virtual;
+    function  GetPriorRecord: Boolean; virtual;
+    function  GetRecordCount: Longint; virtual;
+    function  GetRecNo: Longint; virtual;
+    procedure InitFieldDefs; virtual;
+    procedure InitFieldDefsFromfields;
+    procedure InitRecord(var Buffer: TDataRecord); virtual;
+    procedure InternalCancel; virtual;
+    procedure InternalEdit; virtual;
+    procedure InternalInsert; virtual;
+    procedure InternalRefresh; virtual;
+    procedure OpenCursor(InfoQuery: Boolean); virtual;
+    procedure OpenCursorcomplete; virtual;
+    procedure RefreshInternalCalcFields(Var Buffer: TDataRecord); virtual;
+    procedure RestoreState(const Value: TDataSetState);
+    Procedure SetActive (Value : Boolean); virtual;
+    procedure SetBookmarkStr(const Value: TBookmarkStr); virtual;
+    procedure SetBufListSize(Value: Longint); virtual;
+    procedure SetChildOrder(Child: TComponent; Order: Longint); override;
+    procedure SetCurrentRecord(Index: Longint); virtual;
+    procedure SetDefaultFields(const Value: Boolean);
+    procedure SetFiltered(Value: Boolean); virtual;
+    procedure SetFilterOptions(Value: TFilterOptions); virtual;
+    procedure SetFilterText(const Value: string); virtual;
+    procedure SetFieldValues(const FieldName: string; Value: JSValue); virtual;
+    procedure SetFound(const Value: Boolean); virtual;
+    procedure SetModified(Value: Boolean);
+    procedure SetName(const NewName: TComponentName); override;
+    procedure SetOnFilterRecord(const Value: TFilterRecordEvent); virtual;
+    procedure SetRecNo(Value: Longint); virtual;
+    procedure SetState(Value: TDataSetState);
+    function SetTempState(const Value: TDataSetState): TDataSetState;
+    Function TempBuffer: TDataRecord;
+    procedure UpdateIndexDefs; virtual;
+    property ActiveRecord: Longint read FActiveRecord;
+    property CurrentRecord: Longint read FCurrentRecord;
+    property BlobFieldCount: Longint read FBlobFieldCount;
+    property Buffers[Index: Longint]: TDataRecord read GetBuffer;
+    property BufferCount: Longint read GetBufferCount;
+    property CalcBuffer: TDataRecord read FCalcBuffer;
+    property CalcFieldsSize: Longint read FCalcFieldsSize;
+    property InternalCalcFields: Boolean read FInternalCalcFields;
+    property Constraints: TCheckConstraints read FConstraints write SetConstraints;
+    function AllocRecordBuffer: TDataRecord; virtual;
+    procedure FreeRecordBuffer(var Buffer: TDataRecord); virtual;
+    procedure GetBookmarkData(Buffer: TDataRecord; var Data: TBookmark); virtual;
+    function GetBookmarkFlag(Buffer: TDataRecord): TBookmarkFlag; virtual;
+    function GetDataSource: TDataSource; virtual;
+    function GetRecordSize: Word; virtual;
+    procedure InternalAddRecord(Buffer: Pointer; AAppend: Boolean); virtual;
+    procedure InternalDelete; virtual;
+    procedure InternalFirst; virtual;
+    procedure InternalGotoBookmark(ABookmark: TBookmark); virtual;
+    procedure InternalHandleException(E: Exception); virtual;
+    procedure InternalInitRecord(var Buffer: TDataRecord); virtual;
+    procedure InternalLast; virtual;
+    procedure InternalPost; virtual;
+    procedure InternalSetToRecord(Buffer: TDataRecord); virtual;
+    procedure SetBookmarkFlag(Var Buffer: TDataRecord; Value: TBookmarkFlag); virtual;
+    procedure SetBookmarkData(Var Buffer: TDataRecord; Data: TBookmark); virtual;
+    procedure SetUniDirectional(const Value: Boolean);
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    // These use the active buffer
+    function GetFieldData(Field: TField): JSValue;  virtual; overload;
+    procedure SetFieldData(Field: TField; AValue : JSValue);  virtual; overload;
+    function GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;  virtual; overload;
+    procedure SetFieldData(Field: TField; var Buffer: TDatarecord; AValue : JSValue);  virtual; overload;
+    class function FieldDefsClass : TFieldDefsClass; virtual;
+    class function FieldsClass : TFieldsClass; virtual;
+  protected { abstract methods }
+    function GetRecord(var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult; virtual; abstract;
+    procedure InternalClose; virtual; abstract;
+    procedure InternalOpen; virtual; abstract;
+    procedure InternalInitFieldDefs; virtual; abstract;
+    function IsCursorOpen: Boolean; virtual; abstract;
+    property DataProxy : TDataProxy Read GetDataProxy Write SetDataProxy;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    function ActiveBuffer: TDataRecord;
+    procedure Append;
+    procedure AppendRecord(const Values: array of jsValue);
+    function BookmarkValid(ABookmark: TBookmark): Boolean; virtual;
+    function ConvertToDateTime(aValue : JSValue; ARaiseException : Boolean) : TDateTime; virtual;
+    function ConvertDateTimeToNative(aValue : TDateTime) : JSValue; virtual;
+    Class function DefaultConvertToDateTime(aValue : JSValue; ARaiseException : Boolean) : TDateTime; virtual;
+    Class function DefaultConvertDateTimeToNative(aValue : TDateTime) : JSValue; virtual;
+    Function BlobDataToBytes(aValue : JSValue) : TBytes; virtual;
+    Class Function DefaultBlobDataToBytes(aValue : JSValue) : TBytes; virtual;
+    Function BytesToBlobData(aValue : TBytes) : JSValue ; virtual;
+    Class Function DefaultBytesToBlobData(aValue : TBytes) : JSValue; virtual;
+    procedure Cancel; virtual;
+    procedure CheckBrowseMode;
+    procedure ClearFields;
+    procedure Close;
+    Procedure ApplyUpdates;
+    function  ControlsDisabled: Boolean;
+    function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Longint; virtual;
+    procedure CursorPosChanged;
+    procedure Delete; virtual;
+    procedure DisableControls;
+    procedure Edit;
+    procedure EnableControls;
+    function FieldByName(const FieldName: string): TField;
+    function FindField(const FieldName: string): TField;
+    function FindFirst: Boolean; virtual;
+    function FindLast: Boolean; virtual;
+    function FindNext: Boolean; virtual;
+    function FindPrior: Boolean; virtual;
+    procedure First;
+    procedure FreeBookmark(ABookmark: TBookmark); virtual;
+    function GetBookmark: TBookmark; virtual;
+    function GetCurrentRecord(Buffer: TDataRecord): Boolean; virtual;
+    procedure GetFieldList(List: TList; const FieldNames: string);
+    procedure GetFieldNames(List: TStrings);
+    procedure GotoBookmark(const ABookmark: TBookmark);
+    procedure Insert;
+    procedure InsertRecord(const Values: array of JSValue);
+    function IsEmpty: Boolean;
+    function IsLinkedTo(ADataSource: TDataSource): Boolean;
+    function IsSequenced: Boolean; virtual;
+    procedure Last;
+    Function Load(aOptions : TLoadOptions; aAfterLoad : TDatasetLoadEvent) : Boolean;
+    function Locate(const KeyFields: string; const KeyValues: JSValue; Options: TLocateOptions) : boolean; virtual;
+    function Lookup(const KeyFields: string; const KeyValues: JSValue; const ResultFields: string): JSValue; virtual;
+    function MoveBy(Distance: Longint): Longint;
+    procedure Next;
+    procedure Open;
+    procedure Post; virtual;
+    procedure Prior;
+    procedure Refresh;
+    procedure Resync(Mode: TResyncMode); virtual;
+    procedure SetFields(const Values: array of JSValue);
+    procedure UpdateCursorPos;
+    procedure UpdateRecord;
+    function UpdateStatus: TUpdateStatus; virtual;
+    property BlockReadSize: Integer read FBlockReadSize write SetBlockReadSize;
+    property BOF: Boolean read FBOF;
+    property Bookmark: TBookmark read GetBookmark write GotoBookmark;
+    property CanModify: Boolean read GetCanModify;
+    property DataSource: TDataSource read GetDataSource;
+    property DefaultFields: Boolean read FDefaultFields;
+    property EOF: Boolean read FEOF;
+    property FieldCount: Longint read GetFieldCount;
+    property FieldDefs: TFieldDefs read FFieldDefs write SetFieldDefs;
+    property Found: Boolean read FFound;
+    property Modified: Boolean read FModified;
+    property IsUniDirectional: Boolean read FIsUniDirectional default False;
+    property RecordCount: Longint read GetRecordCount;
+    property RecNo: Longint read GetRecNo write SetRecNo;
+    property RecordSize: Word read GetRecordSize;
+    property State: TDataSetState read FState;
+    property Fields : TFields read FFieldList;
+//    property FieldValues[FieldName : string] : JSValue read GetFieldValues write SetFieldValues; default;
+    property Filter: string read FFilterText write SetFilterText;
+    property Filtered: Boolean read FFiltered write SetFiltered default False;
+    property FilterOptions: TFilterOptions read FFilterOptions write SetFilterOptions;
+    property Active: Boolean read GetActive write SetActive default False;
+    property AutoCalcFields: Boolean read FAutoCalcFields write FAutoCalcFields default true;
+    property BeforeOpen: TDataSetNotifyEvent read FBeforeOpen write FBeforeOpen;
+    property AfterOpen: TDataSetNotifyEvent read FAfterOpen write FAfterOpen;
+    property BeforeClose: TDataSetNotifyEvent read FBeforeClose write FBeforeClose;
+    property AfterClose: TDataSetNotifyEvent read FAfterClose write FAfterClose;
+    property BeforeInsert: TDataSetNotifyEvent read FBeforeInsert write FBeforeInsert;
+    property AfterInsert: TDataSetNotifyEvent read FAfterInsert write FAfterInsert;
+    property BeforeEdit: TDataSetNotifyEvent read FBeforeEdit write FBeforeEdit;
+    property AfterEdit: TDataSetNotifyEvent read FAfterEdit write FAfterEdit;
+    property BeforePost: TDataSetNotifyEvent read FBeforePost write FBeforePost;
+    property AfterPost: TDataSetNotifyEvent read FAfterPost write FAfterPost;
+    property BeforeCancel: TDataSetNotifyEvent read FBeforeCancel write FBeforeCancel;
+    property AfterCancel: TDataSetNotifyEvent read FAfterCancel write FAfterCancel;
+    property BeforeDelete: TDataSetNotifyEvent read FBeforeDelete write FBeforeDelete;
+    property AfterDelete: TDataSetNotifyEvent read FAfterDelete write FAfterDelete;
+    property BeforeScroll: TDataSetNotifyEvent read FBeforeScroll write FBeforeScroll;
+    property AfterScroll: TDataSetNotifyEvent read FAfterScroll write FAfterScroll;
+    property BeforeRefresh: TDataSetNotifyEvent read FBeforeRefresh write FBeforeRefresh;
+    property BeforeLoad : TDatasetNotifyEvent Read FBeforeLoad Write FBeforeLoad;
+    Property AfterLoad : TDatasetNotifyEvent Read FAfterLoad Write FAfterLoad;
+    Property BeforeApplyUpdates : TDatasetNotifyEvent Read FBeforeApplyUpdates Write FBeforeApplyUpdates;
+    Property AfterApplyUpdates : TDatasetNotifyEvent Read FAfterApplyUpdates Write FAfterApplyUpdates;
+    property AfterRefresh: TDataSetNotifyEvent read FAfterRefresh write FAfterRefresh;
+    property OnCalcFields: TDataSetNotifyEvent read FOnCalcFields write FOnCalcFields;
+    property OnDeleteError: TDataSetErrorEvent read FOnDeleteError write FOnDeleteError;
+    property OnEditError: TDataSetErrorEvent read FOnEditError write FOnEditError;
+    property OnFilterRecord: TFilterRecordEvent read FOnFilterRecord write SetOnFilterRecord;
+    property OnNewRecord: TDataSetNotifyEvent read FOnNewRecord write FOnNewRecord;
+    property OnPostError: TDataSetErrorEvent read FOnPostError write FOnPostError;
+    property OnLoadFail : TDatasetLoadFailEvent Read FOnLoadFail Write FOnLoadFail;
+  end;
+
+{ TDataLink }
+
+  TDataLink = class(TPersistent)
+  private
+    FFirstRecord,
+    FBufferCount : Integer;
+    FActive,
+    FDataSourceFixed,
+    FEditing,
+    FReadOnly,
+    FUpdatingRecord,
+    FVisualControl : Boolean;
+    FDataSource : TDataSource;
+    Function  CalcFirstRecord(Index : Integer) : Integer;
+    Procedure CalcRange;
+    Procedure CheckActiveAndEditing;
+    Function  GetDataset : TDataset;
+    procedure SetActive(AActive: Boolean);
+    procedure SetDataSource(Value: TDataSource);
+    Procedure SetReadOnly(Value : Boolean);
+  protected
+    procedure ActiveChanged; virtual;
+    procedure CheckBrowseMode; virtual;
+    procedure DataEvent(Event: TDataEvent; Info: JSValue); virtual;
+    procedure DataSetChanged; virtual;
+    procedure DataSetScrolled(Distance: Integer); virtual;
+    procedure EditingChanged; virtual;
+    procedure FocusControl(Field: JSValue); virtual;
+    function  GetActiveRecord: Integer; virtual;
+    function  GetBOF: Boolean; virtual;
+    function  GetBufferCount: Integer; virtual;
+    function  GetEOF: Boolean; virtual;
+    function  GetRecordCount: Integer; virtual;
+    procedure LayoutChanged; virtual;
+    function  MoveBy(Distance: Integer): Integer; virtual;
+    procedure RecordChanged(Field: TField); virtual;
+    procedure SetActiveRecord(Value: Integer); virtual;
+    procedure SetBufferCount(Value: Integer); virtual;
+    procedure UpdateData; virtual;
+    property VisualControl: Boolean read FVisualControl write FVisualControl;
+    property FirstRecord: Integer read FFirstRecord write FFirstRecord;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    function  Edit: Boolean;
+    procedure UpdateRecord;
+    property Active: Boolean read FActive;
+    property ActiveRecord: Integer read GetActiveRecord write SetActiveRecord;
+    property BOF: Boolean read GetBOF;
+    property BufferCount: Integer read GetBufferCount write SetBufferCount;
+    property DataSet: TDataSet read GetDataSet;
+    property DataSource: TDataSource read FDataSource write SetDataSource;
+    property DataSourceFixed: Boolean read FDataSourceFixed write FDataSourceFixed;
+    property Editing: Boolean read FEditing;
+    property Eof: Boolean read GetEOF;
+    property ReadOnly: Boolean read FReadOnly write SetReadOnly;
+    property RecordCount: Integer read GetRecordCount;
+  end;
+
+{ TDetailDataLink }
+
+  TDetailDataLink = class(TDataLink)
+  protected
+    function GetDetailDataSet: TDataSet; virtual;
+  public
+    property DetailDataSet: TDataSet read GetDetailDataSet;
+  end;
+
+{ TMasterDataLink }
+
+  TMasterDataLink = class(TDetailDataLink)
+  private
+    FDetailDataSet: TDataSet;
+    FFieldNames: string;
+    FFields: TList;
+    FOnMasterChange: TNotifyEvent;
+    FOnMasterDisable: TNotifyEvent;
+    procedure SetFieldNames(const Value: string);
+  protected
+    procedure ActiveChanged; override;
+    procedure CheckBrowseMode; override;
+    function GetDetailDataSet: TDataSet; override;
+    procedure LayoutChanged; override;
+    procedure RecordChanged(Field: TField); override;
+    Procedure DoMasterDisable; virtual;
+    Procedure DoMasterChange; virtual;
+  public
+    constructor Create(ADataSet: TDataSet);virtual;
+    destructor Destroy; override;
+    property FieldNames: string read FFieldNames write SetFieldNames;
+    property Fields: TList read FFields;
+    property OnMasterChange: TNotifyEvent read FOnMasterChange write FOnMasterChange;
+    property OnMasterDisable: TNotifyEvent read FOnMasterDisable write FOnMasterDisable;
+  end;
+
+{ TMasterParamsDataLink }
+
+  TMasterParamsDataLink = Class(TMasterDataLink)
+  Private
+    FParams : TParams;
+    Procedure SetParams(AValue : TParams);
+  Protected
+    Procedure DoMasterDisable; override;
+    Procedure DoMasterChange; override;
+  Public
+    constructor Create(ADataSet: TDataSet); override;
+    Procedure RefreshParamNames; virtual;
+    Procedure CopyParamsFromMaster(CopyBound : Boolean); virtual;
+    Property Params : TParams Read FParams Write SetParams;
+  end;
+
+{ TDataSource }
+
+  TDataChangeEvent = procedure(Sender: TObject; Field: TField) of object;
+
+  TDataSource = class(TComponent)
+  private
+    FDataSet: TDataSet;
+    FDataLinks: TList;
+    FEnabled: Boolean;
+    FAutoEdit: Boolean;
+    FState: TDataSetState;
+    FOnStateChange: TNotifyEvent;
+    FOnDataChange: TDataChangeEvent;
+    FOnUpdateData: TNotifyEvent;
+    procedure DistributeEvent(Event: TDataEvent; Info: JSValue);
+    procedure RegisterDataLink(DataLink: TDataLink);
+    Procedure ProcessEvent(Event : TDataEvent; Info : JSValue);
+    procedure SetDataSet(ADataSet: TDataSet);
+    procedure SetEnabled(Value: Boolean);
+    procedure UnregisterDataLink(DataLink: TDataLink);
+  protected
+    Procedure DoDataChange (Info : Pointer);virtual;
+    Procedure DoStateChange; virtual;
+    Procedure DoUpdateData;
+    property DataLinks: TList read FDataLinks;
+  public
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure Edit;
+    function IsLinkedTo(ADataSet: TDataSet): Boolean;
+    property State: TDataSetState read FState;
+  published
+    property AutoEdit: Boolean read FAutoEdit write FAutoEdit default True;
+    property DataSet: TDataSet read FDataSet write SetDataSet;
+    property Enabled: Boolean read FEnabled write SetEnabled default True;
+    property OnStateChange: TNotifyEvent read FOnStateChange write FOnStateChange;
+    property OnDataChange: TDataChangeEvent read FOnDataChange write FOnDataChange;
+    property OnUpdateData: TNotifyEvent read FOnUpdateData write FOnUpdateData;
+  end;
+
+
+  { TDataRequest }
+  TDataRequestResult = (rrFail,rrEOF,rrOK);
+  TDataRequestEvent = Procedure (ARequest : TDataRequest) of object;
+
+  TDataRequest = Class(TObject)
+  private
+    FBookmark: TBookMark;
+    FCurrent: TBookMark;
+    FDataset: TDataset;
+    FErrorMsg: String;
+    FEvent: TDatasetLoadEvent;
+    FLoadOptions: TLoadOptions;
+    FRequestID: Integer;
+    FSuccess: TDataRequestResult;
+    FData : JSValue;
+    FAfterRequest : TDataRequestEvent;
+    FDataProxy : TDataProxy;
+  Protected
+    Procedure DoAfterRequest;
+  Public
+    Constructor Create(aDataProxy : TDataProxy; aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent); virtual;
+    property DataProxy : TDataProxy Read FDataProxy;
+    Property Dataset : TDataset Read FDataset;
+    Property Bookmark : TBookMark Read FBookmark;
+    Property RequestID : Integer Read FRequestID;
+    Property LoadOptions : TLoadOptions Read FLoadOptions;
+    Property Current : TBookMark Read FCurrent;
+    Property Success : TDataRequestResult Read FSuccess Write FSuccess;
+    Property Event : TDatasetLoadEvent Read FEvent;
+    Property ErrorMsg : String Read FErrorMsg Write FErrorMsg;
+    Property Data : JSValue read FData Write FData;
+  end;
+  TDataRequestClass = Class of TDataRequest;
+
+  { TRecordUpdateDescriptor }
+
+  TRecordUpdateDescriptor = Class(TObject)
+  private
+    FBookmark: TBookmark;
+    FData: JSValue;
+    FDataset: TDataset;
+    FProxy: TDataProxy;
+    FResolveError: String;
+    FServerData: JSValue;
+    FStatus: TUpdateStatus;
+    FOriginalStatus : TUpdateStatus;
+  Protected
+    Procedure SetStatus(aValue : TUpdateStatus); virtual;
+  Public
+    Constructor Create(aProxy : TDataProxy; aDataset : TDataset; aBookmark : TBookMark; AData : JSValue; AStatus : TUpdateStatus);
+    Procedure Resolve(aData : JSValue);
+    Procedure ResolveFailed(aError : String);
+    Property Proxy : TDataProxy read FProxy;
+    Property Dataset : TDataset Read FDataset;
+    Property OriginalStatus : TUpdateStatus Read FOriginalStatus;
+    Property Status : TUpdateStatus Read FStatus;
+    Property ServerData : JSValue Read FServerData;
+    Property Data : JSValue Read FData;
+    Property Bookmark : TBookmark Read FBookmark;
+    Property ResolveError : String Read FResolveError ;
+  end;
+  TRecordUpdateDescriptorClass = Class of TRecordUpdateDescriptor;
+
+  { TRecordUpdateDescriptorList }
+
+  TRecordUpdateDescriptorList = Class(TFPList)
+  private
+    function GetUpdate(AIndex : Integer): TRecordUpdateDescriptor;
+  Public
+    Property UpdateDescriptors[AIndex : Integer] : TRecordUpdateDescriptor Read GetUpdate; Default;
+  end;
+
+  { TRecordUpdateBatch }
+  TUpdateBatchStatus = (ubsPending,ubsProcessing,ubsProcessed,ubsResolved);
+  TResolveBatchEvent = Procedure (Sender : TObject; ARequest : TRecordUpdateBatch) of object;
+
+  TRecordUpdateBatch = class(TObject)
+  private
+    FBatchID: Integer;
+    FDataset: TDataset;
+    FLastChangeIndex: Integer;
+    FList: TRecordUpdateDescriptorList;
+    FOnResolve: TResolveBatchEvent;
+    FOwnsList: Boolean;
+    FStatus: TUpdateBatchStatus;
+  Protected
+    Property LastChangeIndex : Integer Read FLastChangeIndex;
+  Public
+    Constructor Create (aBatchID : Integer; AList : TRecordUpdateDescriptorList; AOwnsList : Boolean);
+    Destructor Destroy; override;
+    Procedure FreeList;
+    Property Dataset : TDataset Read FDataset Write FDataset;
+    Property OnResolve : TResolveBatchEvent Read FOnResolve Write FOnResolve;
+    Property OwnsList : Boolean Read FOwnsList;
+    property BatchID : Integer Read FBatchID;
+    Property Status : TUpdateBatchStatus Read FStatus Write FStatus;
+    Property List : TRecordUpdateDescriptorList Read FList;
+  end;
+  TRecordUpdateBatchClass = Class of TRecordUpdateBatch;
+
+  { TDataProxy }
+
+  TDataProxy = Class(TComponent)
+  Protected
+    Function GetDataRequestClass : TDataRequestClass; virtual;
+    Function GetUpdateDescriptorClass : TRecordUpdateDescriptorClass; virtual;
+    Function GetUpdateBatchClass : TRecordUpdateBatchClass; virtual;
+    // Use this to call resolve event, and free the batch.
+    Procedure ResolveBatch(aBatch : TRecordUpdateBatch);
+  Public
+    Function GetDataRequest(aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent) : TDataRequest; virtual;
+    Function GetUpdateDescriptor(aDataset : TDataset; aBookmark : TBookMark; AData : JSValue; AStatus : TUpdateStatus) : TRecordUpdateDescriptor; virtual;
+    function GetRecordUpdateBatch(aBatchID: Integer; AList: TRecordUpdateDescriptorList; AOwnsList: Boolean=True): TRecordUpdateBatch; virtual;
+    // actual calls to do the work. Dataset wi
+    Function DoGetData(aRequest : TDataRequest) : Boolean; virtual; abstract;
+    // TDataProxy is responsible for calling OnResolve and if not, Freeing the batch.
+    Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; virtual; abstract;
+  end;
+
+const
+  {
+  TFieldType = (
+    ftUnknown, ftString, ftInteger, ftLargeInt, ftBoolean, ftFloat, ftDate,
+    ftTime, ftDateTime,  ftAutoInc, ftBlob, ftMemo, ftFixedChar,
+    ftVariant
+  );
+  }
+
+Const
+  Fieldtypenames : Array [TFieldType] of String =
+    (
+      {ftUnknown} 'Unknown',
+      {ftString} 'String',
+      {ftInteger} 'Integer',
+      {ftLargeint} 'NativeInt',
+      {ftBoolean} 'Boolean',
+      {ftFloat} 'Float',
+      {ftDate} 'Date',
+      {ftTime} 'Time',
+      {ftDateTime} 'DateTime',
+      {ftAutoInc} 'AutoInc',
+      {ftBlob} 'Blob',
+      {ftMemo} 'Memo',
+      {ftFixedChar} 'FixedChar',
+      {ftVariant} 'Variant'
+    );
+
+  DefaultFieldClasses : Array [TFieldType] of TFieldClass =
+    (
+      { ftUnknown} Tfield,
+      { ftString} TStringField,
+      { ftInteger} TIntegerField,
+      { ftLargeint} TLargeIntField,
+      { ftBoolean} TBooleanField,
+      { ftFloat} TFloatField,
+      { ftDate} TDateField,
+      { ftTime} TTimeField,
+      { ftDateTime} TDateTimeField,
+      { ftAutoInc} TAutoIncField,
+      { ftBlob} TBlobField,
+      { ftMemo} TMemoField,
+      { ftFixedChar} TStringField,
+      { ftVariant} TVariantField
+    );
+
+  dsEditModes = [dsEdit, dsInsert, dsSetKey];
+  dsWriteModes = [dsEdit, dsInsert, dsSetKey, dsCalcFields, dsFilter,
+                  dsNewValue, dsInternalCalc, dsRefreshFields];
+  // Correct list of all field types that are BLOB types.
+  // Please use this instead of checking TBlobType which will give
+  // incorrect results
+  ftBlobTypes = [ftBlob, ftMemo];
+
+
+{ Auxiliary functions }
+
+Procedure DatabaseError (Const Msg : String); overload;
+Procedure DatabaseError (Const Msg : String; Comp : TComponent); overload;
+Procedure DatabaseErrorFmt (Const Fmt : String; Const Args : Array Of JSValue); overload;
+Procedure DatabaseErrorFmt (Const Fmt : String; Const Args : Array Of JSValue; Comp : TComponent); overload;
+Function ExtractFieldName(Const Fields: String; var Pos: Integer): String;
+
+// function SkipComments(var p: PChar; EscapeSlash, EscapeRepeat : Boolean) : boolean;
+
+// operator Enumerator(ADataSet: TDataSet): TDataSetEnumerator;
+ 
+implementation
+
+uses DBConst,TypInfo;
+
+{ ---------------------------------------------------------------------
+    Auxiliary functions
+  ---------------------------------------------------------------------}
+
+Procedure DatabaseError (Const Msg : String);
+
+begin
+  Raise EDataBaseError.Create(Msg);
+end;
+
+Procedure DatabaseError (Const Msg : String; Comp : TComponent);
+
+begin
+  if assigned(Comp) and (Comp.Name <> '') then
+    Raise EDatabaseError.CreateFmt('%s : %s',[Comp.Name,Msg])
+  else
+    DatabaseError(Msg);
+end;
+
+Procedure DatabaseErrorFmt (Const Fmt : String; Const Args : Array Of JSValue);
+
+begin
+  Raise EDatabaseError.CreateFmt(Fmt,Args);
+end;
+
+Procedure DatabaseErrorFmt (Const Fmt : String; Const Args : Array Of JSValue;
+                            Comp : TComponent);
+begin
+  if assigned(comp) then
+    Raise EDatabaseError.CreateFmt(Format('%s : %s',[Comp.Name,Fmt]),Args)
+  else
+    DatabaseErrorFmt(Fmt, Args);
+end;
+
+function ExtractFieldName(const Fields: string; var Pos: Integer): string;
+var
+  i: Integer;
+  FieldsLength: Integer;
+begin
+  i:=Pos;
+  FieldsLength:=Length(Fields);
+  while (i<=FieldsLength) and (Fields[i]<>';') do Inc(i);
+  Result:=Trim(Copy(Fields,Pos,i-Pos));
+  if (i<=FieldsLength) and (Fields[i]=';') then Inc(i);
+  Pos:=i;
+end;
+
+{ TRecordUpdateBatch }
+
+constructor TRecordUpdateBatch.Create(aBatchID: Integer; AList: TRecordUpdateDescriptorList; AOwnsList : Boolean);
+begin
+  FBatchID:=aBatchID;
+  FList:=AList;
+  FOwnsList:=AOwnsList;
+  FStatus:=ubsPending;
+end;
+
+destructor TRecordUpdateBatch.Destroy;
+begin
+  if OwnsList then
+    FreeList;
+  inherited Destroy;
+end;
+
+procedure TRecordUpdateBatch.FreeList;
+begin
+  FreeAndNil(FList);
+end;
+
+{ TRecordUpdateDescriptorList }
+
+function TRecordUpdateDescriptorList.GetUpdate(AIndex : Integer): TRecordUpdateDescriptor;
+begin
+  Result:=TRecordUpdateDescriptor(Items[AIndex]);
+end;
+
+{ TRecordUpdateDescriptor }
+
+procedure TRecordUpdateDescriptor.SetStatus(aValue: TUpdateStatus);
+begin
+  FStatus:=AValue;
+end;
+
+constructor TRecordUpdateDescriptor.Create(aProxy: TDataProxy; aDataset: TDataset; aBookmark: TBookMark; AData: JSValue;
+  AStatus: TUpdateStatus);
+begin
+  FDataset:=aDataset;
+  FBookmark:=aBookmark;
+  FData:=AData;
+  FStatus:=AStatus;
+  FOriginalStatus:=AStatus;
+  FProxy:=aProxy;
+end;
+
+
+procedure TRecordUpdateDescriptor.Resolve(aData: JSValue);
+begin
+  FStatus:=usResolved;
+  FServerData:=AData;
+end;
+
+procedure TRecordUpdateDescriptor.ResolveFailed(aError: String);
+begin
+  SetStatus(usResolveFailed);
+  FResolveError:=AError;
+end;
+
+{ TDataRequest }
+
+procedure TDataRequest.DoAfterRequest;
+begin
+  if Assigned(FAfterRequest) then
+    FAfterRequest(Self);
+end;
+
+constructor TDataRequest.Create(aDataProxy : TDataProxy; aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent);
+begin
+  FDataProxy:=aDataProxy;
+  FLoadOptions:=aOptions;
+  FEvent:=aAfterLoad;
+  FAfterRequest:=aAfterRequest;
+end;
+
+{ TDataProxy }
+
+function TDataProxy.GetDataRequestClass: TDataRequestClass;
+begin
+  Result:=TDataRequest;
+end;
+
+function TDataProxy.GetUpdateDescriptorClass: TRecordUpdateDescriptorClass;
+begin
+  Result:=TRecordUpdateDescriptor;
+end;
+
+function TDataProxy.GetUpdateBatchClass: TRecordUpdateBatchClass;
+begin
+  Result:=TRecordUpdateBatch;
+end;
+
+procedure TDataProxy.ResolveBatch(aBatch: TRecordUpdateBatch);
+begin
+  try
+    If Assigned(ABatch.FOnResolve) then
+      ABatch.FOnResolve(Self,ABatch);
+  finally
+    aBatch.Free;
+  end;
+end;
+
+function TDataProxy.GetDataRequest(aOptions: TLoadOptions; aAfterRequest : TDataRequestEvent; aAfterLoad: TDatasetLoadEvent): TDataRequest;
+begin
+  Result:=GetDataRequestClass.Create(Self,aOptions,aAfterRequest,aAfterLoad);
+end;
+
+function TDataProxy.GetUpdateDescriptor(aDataset : TDataset; aBookmark: TBookMark; AData: JSValue; AStatus: TUpdateStatus): TRecordUpdateDescriptor;
+begin
+  Result:=GetUpdateDescriptorClass.Create(Self,aDataset, aBookmark,AData,AStatus);
+end;
+
+function TDataProxy.GetRecordUpdateBatch(aBatchID: Integer; AList: TRecordUpdateDescriptorList; AOwnsList : Boolean = True): TRecordUpdateBatch;
+begin
+  Result:=GetUpdateBatchClass.Create(aBatchID,AList,AOwnsList);
+end;
+
+
+{ EUpdateError }
+constructor EUpdateError.Create(NativeError, Context : String;
+                                ErrCode, PrevError : integer; E: Exception);
+                                
+begin
+  Inherited CreateFmt(NativeError,[Context]);
+  FContext := Context;
+  FErrorCode := ErrCode;
+  FPreviousError := PrevError;
+  FOriginalException := E;
+end;
+
+Destructor EUpdateError.Destroy;
+
+begin
+  FOriginalException.Free;
+  Inherited;
+end;
+
+{ TNamedItem }
+
+function TNamedItem.GetDisplayName: string;
+begin
+  Result := FName;
+end;
+
+procedure TNamedItem.SetDisplayName(const Value: string);
+Var TmpInd : Integer;
+begin
+  if FName=Value then exit;
+  if (Value <> '') and (Collection is TFieldDefs ) then
+    begin
+    TmpInd :=  (TDefCollection(Collection).IndexOf(Value));
+    if (TmpInd >= 0) and (TmpInd <> Index) then
+      DatabaseErrorFmt(SDuplicateName, [Value, Collection.ClassName]);
+    end;
+  FName:=Value;
+  inherited SetDisplayName(Value);
+end;
+
+{ TDefCollection }
+
+procedure TDefCollection.SetItemName(Item: TCollectionItem);
+
+Var
+  N : TNamedItem;
+  TN : String;
+
+begin
+  N:=Item as TNamedItem;
+  if N.Name = '' then
+    begin
+    TN:=Copy(ClassName, 2, 5) + IntToStr(N.ID+1);
+    if assigned(Dataset) then
+      TN:=Dataset.Name+TN;
+    N.Name:=TN;
+    end
+  else
+    inherited SetItemName(Item);
+end;
+
+constructor TDefCollection.create(ADataset: TDataset; AOwner: TPersistent;
+  AClass: TCollectionItemClass);
+begin
+  inherited Create(AOwner,AClass);
+  FDataset := ADataset;
+end;
+
+function TDefCollection.Find(const AName: string): TNamedItem;
+var i: integer;
+begin
+  Result := Nil;
+  for i := 0 to Count - 1 do
+    if AnsiSameText(TNamedItem(Items[i]).Name, AName) then
+    begin
+    Result := TNamedItem(Items[i]);
+    Break;
+    end;
+end;
+
+procedure TDefCollection.GetItemNames(List: TStrings);
+var i: LongInt;
+begin
+  for i := 0 to Count - 1 do
+    List.Add(TNamedItem(Items[i]).Name);
+end;
+
+function TDefCollection.IndexOf(const AName: string): Longint;
+var i: LongInt;
+begin
+  Result := -1;
+  for i := 0 to Count - 1 do
+    if AnsiSameText(TNamedItem(Items[i]).Name, AName) then
+    begin
+    Result := i;
+    Break;
+    end;
+end;
+
+{ TIndexDef }
+
+procedure TIndexDef.SetDescFields(const AValue: string);
+begin
+  if FDescFields=AValue then exit;
+  if AValue <> '' then FOptions:=FOptions + [ixDescending];
+  FDescFields:=AValue;
+end;
+
+procedure TIndexDef.Assign(Source: TPersistent);
+var idef : TIndexDef;
+begin
+  idef := nil;
+  if Source is TIndexDef then
+    idef := Source as TIndexDef;
+  if Assigned(idef) then
+     begin
+     FName := idef.Name;
+     FFields := idef.Fields;
+     FOptions := idef.Options;
+     FCaseinsFields := idef.CaseInsFields;
+     FDescFields := idef.DescFields;
+     FSource := idef.Source;
+     FExpression := idef.Expression;
+     end
+  else
+    inherited Assign(Source);
+end;
+
+function TIndexDef.GetExpression: string;
+begin
+  Result := FExpression;
+end;
+
+procedure TIndexDef.SetExpression(const AValue: string);
+begin
+  FExpression := AValue;
+end;
+
+procedure TIndexDef.SetCaseInsFields(const AValue: string);
+begin
+  if FCaseinsFields=AValue then exit;
+  if AValue <> '' then FOptions:=FOptions + [ixCaseInsensitive];
+  FCaseinsFields:=AValue;
+end;
+
+constructor TIndexDef.Create(Owner: TIndexDefs; const AName, TheFields: string;
+      TheOptions: TIndexOptions);
+
+begin
+  FName := aname;
+  inherited create(Owner);
+  FFields := TheFields;
+  FOptions := TheOptions;
+end;
+
+
+{ TIndexDefs }
+
+Function TIndexDefs.GetItem (Index : integer) : TIndexDef;
+
+begin
+  Result:=(Inherited GetItem(Index)) as TIndexDef;
+end;
+
+Procedure TIndexDefs.SetItem(Index: Integer; Value: TIndexDef);
+begin
+  Inherited SetItem(Index,Value);
+end;
+
+constructor TIndexDefs.Create(ADataSet: TDataSet);
+
+begin
+  inherited create(ADataset, Owner, TIndexDef);
+end;
+
+
+Function TIndexDefs.AddIndexDef: TIndexDef;
+
+begin
+//  Result := inherited add as TIndexDef;
+  Result:=TIndexDef.Create(Self,'','',[]);
+end;
+
+procedure TIndexDefs.Add(const Name, Fields: string; Options: TIndexOptions);
+
+begin
+  TIndexDef.Create(Self,Name,Fields,Options);
+end;
+
+function TIndexDefs.Find(const IndexName: string): TIndexDef;
+begin
+  Result := (inherited Find(IndexName)) as TIndexDef;
+  if (Result=Nil) Then
+    DatabaseErrorFmt(SIndexNotFound, [IndexName], FDataSet);
+end;
+
+function TIndexDefs.FindIndexForFields(const Fields: string): TIndexDef;
+
+begin
+  //!! To be implemented
+end;
+
+
+function TIndexDefs.GetIndexForFields(const Fields: string;
+  CaseInsensitive: Boolean): TIndexDef;
+
+var
+  i, FieldsLen: integer;
+  Last: TIndexDef;
+begin
+  Last := nil;
+  FieldsLen := Length(Fields);
+  for i := 0 to Count - 1 do
+  begin
+    Result := Items[I];
+    if (Result.Options * [ixDescending, ixExpression] = []) and
+       (not CaseInsensitive or (ixCaseInsensitive in Result.Options)) and
+       AnsiSameText(Fields, Result.Fields) then
+    begin
+      Exit;
+    end else
+    if AnsiSameText(Fields, Copy(Result.Fields, 1, FieldsLen)) and
+       ((Length(Result.Fields) = FieldsLen) or
+       (Result.Fields[FieldsLen + 1] = ';')) then
+    begin
+      if (Last = nil) or
+         ((Last <> nil) And (Length(Last.Fields) > Length(Result.Fields))) then
+           Last := Result;
+    end;
+  end;
+  Result := Last;
+end;
+
+procedure TIndexDefs.Update;
+
+begin
+  if (not updated) and assigned(Dataset) then
+    begin
+    Dataset.UpdateIndexDefs;
+    updated := True;
+    end;
+end;
+
+{ TCheckConstraint }
+
+procedure TCheckConstraint.Assign(Source: TPersistent);
+
+begin
+  //!! To be implemented
+end;
+
+
+
+{ TCheckConstraints }
+
+Function TCheckConstraints.GetItem(Index : Longint) : TCheckConstraint;
+
+begin
+  //!! To be implemented
+  Result := nil;
+end;
+
+
+Procedure TCheckConstraints.SetItem(index : Longint; Value : TCheckConstraint);
+
+begin
+  //!! To be implemented
+end;
+
+
+function TCheckConstraints.GetOwner: TPersistent;
+
+begin
+  //!! To be implemented
+  Result := nil;
+end;
+
+
+constructor TCheckConstraints.Create(AOwner: TPersistent);
+
+begin
+  //!! To be implemented
+  inherited Create(TCheckConstraint);
+end;
+
+
+function TCheckConstraints.Add: TCheckConstraint;
+
+begin
+  //!! To be implemented
+  Result := nil;
+end;
+
+{ TLookupList }
+
+constructor TLookupList.Create;
+
+begin
+  FList := TFPList.Create;
+end;
+
+destructor TLookupList.Destroy;
+
+begin
+  Clear;
+  FList.Destroy;
+  inherited Destroy;
+end;
+
+procedure TLookupList.Add(const AKey, AValue: JSValue);
+
+var LookupRec: TJSObject;
+
+begin
+  LookupRec:=New(['Key',AKey,'Value',AValue]);
+  FList.Add(LookupRec);
+end;
+
+procedure TLookupList.Clear;
+
+begin
+  FList.Clear;
+end;
+
+function TLookupList.FirstKeyByValue(const AValue: JSValue): JSValue;
+var
+  i: Integer;
+begin
+  for i := 0 to FList.Count - 1 do
+    with TJSObject(FList[i]) do
+      if Properties['Value'] = AValue then
+        begin
+        Result := Properties['Key'];
+        exit;
+        end;
+  Result := Null;
+end;
+
+function TLookupList.ValueOfKey(const AKey: JSValue): JSValue;
+
+  Function VarArraySameValues(VarArray1,VarArray2 : TJSValueDynArray) : Boolean;
+  // This only works for one-dimensional vararrays with a lower bound of 0
+  // and equal higher bounds wich only contains JSValues.
+  // The vararrays returned by GetFieldValues do apply.
+  var i : integer;
+  begin
+    Result := True;
+    if (Length(VarArray1)<>Length(VarArray2)) then
+      exit;
+    for i := 0 to Length(VarArray1) do
+      begin
+      if VarArray1[i]<>VarArray2[i] then
+        begin
+        Result := false;
+        Exit;
+        end;
+    end;
+  end;
+
+var I: Integer;
+begin
+  Result := Null;
+  if IsNull(AKey) then Exit;
+  i := FList.Count - 1;
+  if IsArray(AKey) then
+    while (i >= 0) And not VarArraySameValues(TJSValueDynArray(TJSOBject(FList.Items[I]).Properties['Key']),TJSValueDynArray(AKey)) do Dec(i)
+  else
+    while (i >= 0) And (TJSObject(FList[I]).Properties['Key'] <> AKey) do Dec(i);
+  if i >= 0 then Result := TJSObject(FList[I]).Properties['Value'];
+end;
+
+procedure TLookupList.ValuesToStrings(AStrings: TStrings);
+
+var
+  i: Integer;
+  p: TJSObject;
+
+begin
+  AStrings.Clear;
+  for i := 0 to FList.Count - 1 do
+    begin
+    p := TJSObject(FList[i]);
+    AStrings.AddObject(String(p.properties['Value']), TObject(p));
+    end;
+end;
+
+{ ---------------------------------------------------------------------
+    TDataSet
+  ---------------------------------------------------------------------}
+
+Const
+  DefaultBufferCount = 10;
+
+constructor TDataSet.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  FFieldDefs:=FieldDefsClass.Create(Self);
+  FFieldList:=FieldsClass.Create(Self);
+  FDataSources:=TFPList.Create;
+  FConstraints:=TCheckConstraints.Create(Self);
+  SetLength(FBuffers,1);
+  FActiveRecord := 0;
+  FEOF := True;
+  FBOF := True;
+  FIsUniDirectional := False;
+  FAutoCalcFields := True;
+  FDataRequestID:=0;
+end;
+
+
+
+destructor TDataSet.Destroy;
+
+var
+  i: Integer;
+
+begin
+  Active:=False;
+  FFieldDefs.Free;
+  FFieldList.Free;
+  With FDataSources do
+    begin
+    While Count>0 do
+      TDataSource(Items[Count - 1]).DataSet:=Nil;
+    Destroy;
+    end;
+  for i := 0 to FBufferCount do
+    FreeRecordBuffer(FBuffers[i]);
+  FConstraints.Free;
+  SetLength(FBuffers,1);
+  Inherited Destroy;
+end;
+
+// This procedure must be called when the first record is made/read
+procedure TDataSet.ActivateBuffers;
+
+begin
+  FBOF:=False;
+  FEOF:=False;
+  FActiveRecord:=0;
+end;
+
+procedure TDataSet.BindFields(Binding: Boolean);
+
+var i, FieldIndex: Integer;
+    FieldDef: TFieldDef;
+    Field: TField;
+
+begin
+  { FieldNo is set to -1 for calculated/lookup fields, to 0 for unbound field
+    and for bound fields it is set to FieldDef.FieldNo }
+  FCalcFieldsSize := 0;
+  FBlobFieldCount := 0;
+  for i := 0 to Fields.Count - 1 do
+    begin
+    Field := Fields[i];
+    Field.FFieldDef := Nil;
+    if not Binding then
+      Field.FFieldNo := 0
+    else if Field.FieldKind in [fkCalculated, fkLookup] then
+      begin
+      Field.FFieldNo := -1;
+      Field.FOffset := FCalcFieldsSize;
+      Inc(FCalcFieldsSize, Field.DataSize + 1);
+      end
+    else
+      begin
+      FieldIndex := FieldDefs.IndexOf(Field.FieldName);
+      if FieldIndex = -1 then
+        DatabaseErrorFmt(SFieldNotFound,[Field.FieldName],Self)
+      else
+        begin
+        FieldDef := FieldDefs[FieldIndex];
+        Field.FFieldDef := FieldDef;
+        Field.FFieldNo := FieldDef.FieldNo;
+        if FieldDef.InternalCalcField then
+          FInternalCalcFields := True;
+        if Field.IsBlob then
+          begin
+          Field.FSize := FieldDef.Size;
+          Field.FOffset := FBlobFieldCount;
+          Inc(FBlobFieldCount);
+          end;
+        // synchronize CodePage between TFieldDef and TField
+        // character data in record buffer and field buffer should have same CodePage
+        end;
+      end;
+    Field.Bind(Binding);
+    end;
+end;
+
+function TDataSet.BookmarkAvailable: Boolean;
+
+Const BookmarkStates = [dsBrowse,dsEdit,dsInsert];
+
+begin
+  Result:=(Not IsEmpty) and  not FIsUniDirectional and (State in BookmarkStates)
+          and (getBookMarkFlag(ActiveBuffer)=bfCurrent);
+end;
+
+procedure TDataSet.CalculateFields(var Buffer: TDataRecord);
+var
+  i: Integer;
+  OldState: TDatasetState;
+begin
+  FCalcBuffer := Buffer; 
+  if FState <> dsInternalCalc then
+  begin
+    OldState := FState;
+    FState := dsCalcFields;
+    try
+      ClearCalcFields(FCalcBuffer);
+      if not IsUniDirectional then
+        for i := 0 to FFieldList.Count - 1 do
+          if FFieldList[i].FieldKind = fkLookup then
+            FFieldList[i].CalcLookupValue;
+    finally
+      DoOnCalcFields;
+      FState := OldState;
+    end;
+  end;
+end;
+
+procedure TDataSet.CheckActive;
+
+begin
+  If Not Active then
+    DataBaseError(SInactiveDataset,Self);
+end;
+
+procedure TDataSet.CheckInactive;
+
+begin
+  If Active then
+    DataBaseError(SActiveDataset,Self);
+end;
+
+procedure TDataSet.ClearBuffers;
+
+begin
+  FRecordCount:=0;
+  FActiveRecord:=0;
+  FCurrentRecord:=-1;
+  FBOF:=True;
+  FEOF:=True;
+end;
+
+procedure TDataSet.ClearCalcFields(var Buffer: TDataRecord);
+
+begin
+  // Empty
+end;
+
+procedure TDataSet.CloseBlob(Field: TField);
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.CloseCursor;
+
+begin
+  ClearBuffers;
+  SetBufListSize(0);
+  Fields.ClearFieldDefs;
+  InternalClose;
+  FInternalOpenComplete := False;
+end;
+
+procedure TDataSet.CreateFields;
+
+Var I : longint;
+
+begin
+{$ifdef DSDebug}
+  Writeln ('Creating fields');
+  Writeln ('Count : ',fielddefs.Count);
+  For I:=0 to FieldDefs.Count-1 do
+    Writeln('Def ',I,' : ',Fielddefs.items[i].Name,'(',Fielddefs.items[i].FieldNo,')');
+{$endif}
+  For I:=0 to FieldDefs.Count-1 do
+    With FieldDefs.Items[I] do
+      If DataType<>ftUnknown then
+        begin
+        {$ifdef DSDebug}
+        Writeln('About to create field ',FieldDefs.Items[i].Name);
+        {$endif}
+        CreateField(self);
+        end;
+end;
+
+procedure TDataSet.DataEvent(Event: TDataEvent; Info: JSValue);
+
+  procedure HandleFieldChange(aField: TField);
+  begin
+    if aField.FieldKind in [fkData, fkInternalCalc] then
+      SetModified(True);
+      
+    if State <> dsSetKey then begin
+      if aField.FieldKind = fkData then begin
+        if FInternalCalcFields then
+          RefreshInternalCalcFields(FBuffers[FActiveRecord])
+        else if FAutoCalcFields and (FCalcFieldsSize <> 0) then
+          CalculateFields(FBuffers[FActiveRecord]);
+      end;
+      
+      aField.Change;
+    end;
+  end;
+  
+  procedure HandleScrollOrChange;
+  begin
+    if State <> dsInsert then
+      UpdateCursorPos;
+  end;
+
+var
+  i: Integer;
+begin
+  case Event of
+    deFieldChange   : HandleFieldChange(TField(Info));
+    deDataSetChange,
+    deDataSetScroll : HandleScrollOrChange;
+    deLayoutChange  : FEnableControlsEvent:=deLayoutChange;    
+  end;
+
+  if not ControlsDisabled and (FState <> dsBlockRead) then begin
+    for i := 0 to FDataSources.Count - 1 do
+      TDataSource(FDataSources[i]).ProcessEvent(Event, Info);
+  end;
+end;
+
+procedure TDataSet.DestroyFields;
+
+begin
+  FFieldList.Clear;
+end;
+
+procedure TDataSet.DoAfterCancel;
+
+begin
+ If assigned(FAfterCancel) then
+   FAfterCancel(Self);
+end;
+
+procedure TDataSet.DoAfterClose;
+
+begin
+ If assigned(FAfterClose) and not (csDestroying in ComponentState) then
+   FAfterClose(Self);
+end;
+
+procedure TDataSet.DoAfterDelete;
+
+begin
+ If assigned(FAfterDelete) then
+   FAfterDelete(Self);
+end;
+
+procedure TDataSet.DoAfterEdit;
+
+begin
+ If assigned(FAfterEdit) then
+   FAfterEdit(Self);
+end;
+
+procedure TDataSet.DoAfterInsert;
+
+begin
+ If assigned(FAfterInsert) then
+   FAfterInsert(Self);
+end;
+
+procedure TDataSet.DoAfterOpen;
+
+begin
+ If assigned(FAfterOpen) then
+   FAfterOpen(Self);
+end;
+
+procedure TDataSet.DoAfterPost;
+
+begin
+ If assigned(FAfterPost) then
+   FAfterPost(Self);
+end;
+
+procedure TDataSet.DoAfterScroll;
+
+begin
+ If assigned(FAfterScroll) then
+   FAfterScroll(Self);
+end;
+
+procedure TDataSet.DoAfterRefresh;
+
+begin
+ If assigned(FAfterRefresh) then
+   FAfterRefresh(Self);
+end;
+
+procedure TDataSet.DoBeforeCancel;
+
+begin
+ If assigned(FBeforeCancel) then
+   FBeforeCancel(Self);
+end;
+
+procedure TDataSet.DoBeforeClose;
+
+begin
+ If assigned(FBeforeClose) and not (csDestroying in ComponentState) then
+   FBeforeClose(Self);
+end;
+
+procedure TDataSet.DoBeforeDelete;
+
+begin
+ If assigned(FBeforeDelete) then
+   FBeforeDelete(Self);
+end;
+
+procedure TDataSet.DoBeforeEdit;
+
+begin
+ If assigned(FBeforeEdit) then
+   FBeforeEdit(Self);
+end;
+
+procedure TDataSet.DoBeforeInsert;
+
+begin
+ If assigned(FBeforeInsert) then
+   FBeforeInsert(Self);
+end;
+
+procedure TDataSet.DoBeforeOpen;
+
+begin
+ If assigned(FBeforeOpen) then
+   FBeforeOpen(Self);
+end;
+
+procedure TDataSet.DoBeforePost;
+
+begin
+ If assigned(FBeforePost) then
+   FBeforePost(Self);
+end;
+
+procedure TDataSet.DoBeforeScroll;
+
+begin
+ If assigned(FBeforeScroll) then
+   FBeforeScroll(Self);
+end;
+
+procedure TDataSet.DoBeforeRefresh;
+
+begin
+ If assigned(FBeforeRefresh) then
+   FBeforeRefresh(Self);
+end;
+
+procedure TDataSet.DoInternalOpen;
+
+begin
+  InternalOpen;
+  FInternalOpenComplete := True;
+{$ifdef dsdebug}
+  Writeln ('Calling internal open');
+{$endif}
+{$ifdef dsdebug}
+  Writeln ('Calling RecalcBufListSize');
+{$endif}
+  FRecordCount := 0;
+  RecalcBufListSize;
+  FBOF := True;
+  FEOF := (FRecordCount = 0);
+  if Assigned(DataProxy) then
+    InitChangeList;
+end;
+
+procedure TDataSet.DoOnCalcFields;
+
+begin
+ If Assigned(FOnCalcfields) then
+   FOnCalcFields(Self);
+end;
+
+procedure TDataSet.DoOnNewRecord;
+
+begin
+ If assigned(FOnNewRecord) then
+   FOnNewRecord(Self);
+end;
+
+procedure TDataSet.DoBeforeLoad;
+begin
+  If Assigned(FBeforeLoad) then
+    FBeforeLoad(Self);
+end;
+
+procedure TDataSet.DoAfterLoad;
+begin
+  if Assigned(FAfterLoad) then
+    FAfterLoad(Self);
+end;
+
+procedure TDataSet.DoBeforeApplyUpdates;
+
+begin
+  If Assigned(FBeforeApplyUpdates) then
+    FBeforeApplyUpdates(Self);
+end;
+
+procedure TDataSet.DoAfterApplyUpdates;
+
+begin
+  If Assigned(FAfterApplyUpdates) then
+    FAfterApplyUpdates(Self);
+end;
+
+function TDataSet.FieldByNumber(FieldNo: Longint): TField;
+
+begin
+  Result:=FFieldList.FieldByNumber(FieldNo);
+end;
+
+function TDataSet.FindRecord(Restart, GoForward: Boolean): Boolean;
+
+begin
+  //!! To be implemented
+end;
+
+
+function TDataSet.GetBookmarkStr: TBookmarkStr;
+
+Var
+  B : TBookMark;
+
+begin
+  Result:='';
+  If BookMarkAvailable then
+    begin
+    GetBookMarkData(ActiveBuffer,B);
+    Result:=TJSJSON.stringify(B);
+    end
+end;
+
+function TDataSet.GetBuffer(Index: longint): TDataRecord;
+
+begin
+  Result:=FBuffers[Index];
+end;
+
+function TDataSet.GetBufferCount: Longint;
+begin
+  Result:=Length(FBuffers);
+end;
+
+function TDataSet.DoGetDataProxy: TDataProxy;
+
+begin
+  Result:=nil;
+end;
+
+procedure TDataSet.InitChangeList;
+
+begin
+  DoneChangeList;
+  FChangeList:=TFPList.Create;
+end;
+
+procedure TDataSet.ClearChangeList;
+
+Var
+  I : integer;
+
+begin
+  If not Assigned(FChangeList) then
+    exit;
+  For I:=0 to FChangeList.Count-1 do
+    begin
+    TObject(FChangeList[i]).Destroy;
+    FChangeList[i]:=Nil;
+    end;
+end;
+
+Function TDataSet.IndexInChangeList(aBookmark : TBookmark) : Integer;
+
+begin
+  Result:=-1;
+  if Not assigned(FChangeList) then
+    exit;
+  Result:=FChangeList.Count-1;
+  While (Result>=0) and (CompareBookmarks(aBookMark,TRecordUpdateDescriptor(FChangeList[Result]).Bookmark)<>0) do
+    Dec(Result);
+end;
+
+Function TDataSet.AddToChangeList(aChange: TUpdateStatus) : TRecordUpdateDescriptor;
+
+Var
+  B : TBookmark;
+  I : Integer;
+
+begin
+  if Not Assigned(FChangeList) then
+    Exit;
+  B:=GetBookmark;
+  I:=IndexInChangeList(B);
+  if (I=-1) then
+    begin
+    if Assigned(DataProxy) then
+      Result:=DataProxy.GetUpdateDescriptor(Self,B,ActiveBuffer.data,aChange)
+    else
+      Result:=TRecordUpdateDescriptor.Create(Nil,Self,B,ActiveBuffer.data,aChange);
+    FChangeList.Add(Result);
+    end
+  else
+    begin
+    Result:=TRecordUpdateDescriptor(FChangeList[i]);
+    Case aChange of
+      usDeleted : Result.FStatus:=usDeleted;
+      usInserted : DatabaseError(SErrInsertingSameRecordtwice,Self);
+      usModified : Result.FData:=ActiveBuffer.Data;
+    end
+    end;
+end;
+
+procedure TDataSet.RemoveFromChangeList(R: TRecordUpdateDescriptor);
+
+begin
+  if Not (Assigned(R) and Assigned(FChangeList)) then
+    Exit;
+end;
+
+Function TDataSet.GetRecordUpdates(AList: TRecordUpdateDescriptorList) : Integer;
+
+Var
+  I,MinIndex : integer;
+
+begin
+  MinIndex:=0; // Check batch list for minimal index ?
+  For I:=MinIndex to FChangeList.Count-1 do
+    Alist.Add(FChangeList[i]);
+  Result:=FChangeList.Count;
+end;
+
+Function TDataSet.ResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor) : Boolean;
+
+begin
+  try
+    Result:=DoResolveRecordUpdate(anUpdate);
+    If not Result then
+      anUpdate.FStatus:=usResolveFailed;
+  except
+    On E : Exception do
+      anUpdate.ResolveFailed(E.Classname+': '+E.Message);
+  end;
+end;
+
+procedure TDataSet.ResolveUpdateBatch(Sender: TObject; aBatch : TRecordUpdateBatch);
+
+Var
+  BI,RI,Idx: integer;
+  RUD : TRecordUpdateDescriptor;
+  doRemove : Boolean;
+
+begin
+  if Assigned(FBatchList) and (aBatch.Dataset=Self) then
+    BI:=FBatchList.IndexOf(aBatch)
+  else
+    BI:=-1;
+  if (BI=-1) then
+    Exit;
+  FBatchList.Delete(Bi);
+  For RI:=0 to aBatch.List.Count-1 do
+    begin
+    RUD:=aBatch.List[RI];
+    aBatch.List.Items[RI]:=Nil;
+    Idx:=IndexInChangeList(RUD.Bookmark);
+    if (Idx<>-1) then
+      begin
+      doRemove:=False;
+      if (RUD.Status=usResolved) then
+        DoRemove:=ResolveRecordUpdate(RUD)
+      else
+        DoRemove:=(RUD.Status in [usUnmodified,usResolveFailed]);
+      // What if not resolvable.. ?
+      If DoRemove then
+        begin
+        RUD.Free;
+        FChangeList.Delete(Idx);
+        end;
+      end;
+    end;
+  if (FBatchList.Count=0) then
+    FreeAndNil(FBatchList);
+  DoAfterApplyUpdates;
+end;
+
+procedure TDataSet.DoApplyUpdates;
+
+Var
+  B : TRecordUpdateBatch;
+  l : TRecordUpdateDescriptorList;
+  I : integer;
+
+begin
+  if Not Assigned(DataProxy) then
+    DatabaseError(SErrDoApplyUpdatesNeedsProxy,Self);
+  if Not (Assigned(FChangeList) and (FChangeList.Count>0)) then
+    Exit;
+  L:=TRecordUpdateDescriptorList.Create;
+  try
+    I:=GetRecordUpdates(L);
+  except
+    L.Free;
+    Raise;
+  end;
+  Inc(FUpdateBatchID);
+  B:=DataProxy.GetRecordUpdateBatch(FUpdateBatchID,L,True);
+  B.FDataset:=Self;
+  B.FLastChangeIndex:=I;
+  B.OnResolve:=@ResolveUpdateBatch;
+  If not Assigned(FBatchlist) then
+    FBatchlist:=TFPList.Create;
+  FBatchList.Add(B);
+  DataProxy.ProcessUpdateBatch(B);
+end;
+
+procedure TDataSet.DoneChangeList;
+
+begin
+  ClearChangeList;
+  FreeAndNil(FChangeList);
+end;
+
+function TDataSet.GetDataProxy: TDataProxy;
+
+begin
+  If (FDataProxy=Nil) then
+    DataProxy:=DoGetDataProxy;
+  Result:=FDataProxy;
+end;
+
+function TDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
+
+begin
+  Result:=False;
+end;
+
+procedure TDataSet.HandleRequestresponse(ARequest: TDataRequest);
+
+Var
+  DataAdded : Boolean;
+
+begin
+  if Not Assigned(ARequest) then
+    exit;
+  Case ARequest.Success of
+  rrFail:
+    begin
+    if Assigned(FOnLoadFail) then
+      FOnLoadFail(Self,aRequest.RequestID,aRequest.ErrorMsg);
+    end;
+  rrEOF,
+  rrOK :
+    begin
+    DataAdded:=False;
+    // Notify caller
+    if Assigned(ARequest.Event) then
+      ARequest.Event(Self,aRequest.Data);
+    // allow descendent to integrate data.
+    // Must be done before user is notified or dataset is opened...
+    if (ARequest.Success<>rrEOF) then
+      DataAdded:=DataPacketReceived(aRequest);
+    // Open if needed.
+    if Not (Active or (loNoOpen in aRequest.LoadOptions)) then
+      begin
+      // Notify user
+      if not (loNoEvents in aRequest.LoadOptions) then
+        DoAfterLoad;
+      Open
+      end
+    else
+      begin
+      if (loAtEOF in aRequest.LoadOptions) and DataAdded then
+        FEOF:=False;
+      if not (loNoEvents in aRequest.LoadOptions) then
+        DoAfterLoad;
+      end;
+    end;
+  end;
+  aRequest.Destroy;
+end;
+
+function TDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
+begin
+  Result:=True;
+end;
+
+procedure TDataSet.GetCalcFields(var Buffer: TDataRecord);
+
+begin
+  if (FCalcFieldsSize > 0) or FInternalCalcFields then
+    CalculateFields(Buffer);
+end;
+
+function TDataSet.GetCanModify: Boolean;
+
+begin
+  Result:= not FIsUnidirectional;
+end;
+
+procedure TDataSet.GetChildren(Proc: TGetChildProc; Root: TComponent);
+
+var
+ I: Integer;
+ Field: TField;
+
+begin
+ for I := 0 to Fields.Count - 1 do begin
+   Field := Fields[I];
+   if (Field.Owner = Root) then
+     Proc(Field);
+ end;
+end;
+
+function TDataSet.GetDataSource: TDataSource;
+begin
+  Result:=nil;
+end;
+
+function TDataSet.GetRecordSize: Word;
+begin
+  Result := 0;
+end;
+
+procedure TDataSet.InternalAddRecord(Buffer: Pointer; AAppend: Boolean);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.InternalDelete;
+begin
+  // empty stub
+end;
+
+procedure TDataSet.InternalFirst;
+begin
+  // empty stub
+end;
+
+procedure TDataSet.InternalGotoBookmark(ABookmark: TBookMark);
+begin
+  // empty stub
+end;
+
+
+function TDataset.GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;
+
+begin
+  Result:=TJSObject(buffer.data).Properties[Field.FieldName];
+end;
+
+
+procedure TDataSet.SetFieldData(Field: TField; var Buffer: TDataRecord; AValue : JSValue);
+
+begin
+  TJSObject(buffer.data).Properties[Field.FieldName]:=AValue;
+end;
+
+
+function TDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
+
+begin
+  Result := DefaultFieldClasses[FieldType];
+end;
+
+function TDataSet.GetIsIndexField(Field: TField): Boolean;
+
+begin
+  Result:=False;
+end;
+
+function TDataSet.GetIndexDefs(IndexDefs: TIndexDefs; IndexTypes: TIndexOptions
+  ): TIndexDefs;
+  
+var i,f : integer;
+    IndexFields : TStrings;
+    
+begin
+  IndexDefs.Update;
+  Result := TIndexDefs.Create(Self);
+  Result.Assign(IndexDefs);
+  i := 0;
+  IndexFields := TStringList.Create;
+  while i < result.Count do
+    begin
+    if (not ((IndexTypes = []) and (result[i].Options = []))) and
+       ((IndexTypes * result[i].Options) = []) then
+      begin
+      result.Delete(i);
+      dec(i);
+      end
+    else
+      begin
+//      ExtractStrings([';'],[' '],result[i].Fields,Indexfields);
+      for f := 0 to IndexFields.Count-1 do
+        if FindField(Indexfields[f]) = nil  then
+         begin
+        result.Delete(i);
+        dec(i);
+        break;
+        end;
+      end;
+    inc(i);
+    end;
+  IndexFields.Free;
+end;
+
+function TDataSet.GetNextRecord: Boolean;
+
+Var
+   T : TDataRecord;
+
+begin
+{$ifdef dsdebug}
+  Writeln ('Getting next record. Internal RecordCount : ',FRecordCount);
+  Writeln ('Getting next record. Internal buffercount : ',FBufferCount);
+{$endif}
+  If FRecordCount>0 Then
+    SetCurrentRecord(FRecordCount-1);
+  Result:=GetRecord(FBuffers[FBufferCount],gmNext,True)=grOK;
+  if Result then
+    begin
+    If FRecordCount=0 then ActivateBuffers;
+    if FRecordCount=FBufferCount then
+      ShiftBuffersBackward
+    else
+      begin
+      Inc(FRecordCount);
+      FCurrentRecord:=FRecordCount - 1;
+      T:=FBuffers[FCurrentRecord];
+      FBuffers[FCurrentRecord]:=FBuffers[FBufferCount];
+      FBuffers[FBufferCount]:=T;
+      end;
+    end
+  else
+    CursorPosChanged;
+{$ifdef dsdebug}
+  Writeln ('Result getting next record : ',Result);
+{$endif}
+end;
+
+function TDataSet.GetNextRecords: Longint;
+
+begin
+  Result:=0;
+{$ifdef dsdebug}
+  Writeln ('Getting next record(s), need :',FBufferCount);
+{$endif}
+  While (FRecordCount<FBufferCount) and GetNextRecord do
+    Inc(Result);
+{$ifdef dsdebug}
+  Writeln ('Result Getting next record(S), GOT :',RESULT);
+{$endif}
+end;
+
+function TDataSet.GetPriorRecord: Boolean;
+
+begin
+{$ifdef dsdebug}
+  Writeln ('GetPriorRecord: Getting previous record');
+{$endif}
+  CheckBiDirectional;
+  If FRecordCount>0 Then SetCurrentRecord(0);
+  Result:=GetRecord(FBuffers[FBufferCount],gmPrior,True)=grOK;
+  if Result then
+    begin
+      If FRecordCount=0 then ActivateBuffers;
+      ShiftBuffersForward;
+
+      if FRecordCount<FBufferCount then
+        Inc(FRecordCount);
+    end
+  else
+    CursorPosChanged;
+{$ifdef dsdebug}
+  Writeln ('Result getting prior record : ',Result);
+{$endif}
+end;
+
+function TDataSet.GetPriorRecords: Longint;
+
+begin
+  Result:=0;
+{$ifdef dsdebug}
+  Writeln ('Getting previous record(s), need :',FBufferCount);
+{$endif}
+  While (FRecordCount<FBufferCount) and GetPriorRecord do
+    Inc(Result);
+end;
+
+function TDataSet.GetRecNo: Longint;
+
+begin
+  Result := -1;
+end;
+
+function TDataSet.GetRecordCount: Longint;
+
+begin
+  Result := -1;
+end;
+
+procedure TDataSet.InitFieldDefs;
+
+begin
+  if IsCursorOpen then
+    InternalInitFieldDefs
+  else
+    begin
+    try
+      OpenCursor(True);
+    finally
+      CloseCursor;
+      end;
+    end;
+end;
+
+procedure TDataSet.SetBlockReadSize(AValue: Integer);
+begin
+  // the state is changed even when setting the same BlockReadSize (follows Delphi behavior)
+  // e.g., state is dsBrowse and BlockReadSize is 1. Setting BlockReadSize to 1 will change state to dsBlockRead
+  FBlockReadSize := AValue;
+  if AValue > 0 then
+  begin
+    CheckActive; 
+    SetState(dsBlockRead);
+  end	
+  else
+  begin
+    //update state only when in dsBlockRead 
+    if FState = dsBlockRead then
+      SetState(dsBrowse);
+  end;	
+end;
+
+procedure TDataSet.SetFieldDefs(AFieldDefs: TFieldDefs);
+
+begin
+  Fields.ClearFieldDefs;
+  FFieldDefs.Assign(AFieldDefs);
+end;
+
+procedure TDataSet.DoInsertAppendRecord(const Values: array of JSValue; DoAppend : boolean);
+var i : integer;
+    ValuesSize : integer;
+begin
+  ValuesSize:=Length(Values);
+  if ValuesSize>FieldCount then DatabaseError(STooManyFields,self);
+  if DoAppend then
+    Append
+  else
+    Insert;
+  for i := 0 to ValuesSize-1 do
+    Fields[i].AssignValue(Values[i]);
+  Post;
+end;
+
+procedure TDataSet.InitFieldDefsFromFields;
+var i : integer;
+
+begin
+  if FieldDefs.Count = 0 then
+    begin
+    FieldDefs.BeginUpdate;
+    try
+      for i := 0 to Fields.Count-1 do with Fields[i] do
+        if not (FieldKind in [fkCalculated,fkLookup]) then // Do not add fielddefs for calculated/lookup fields.
+          begin
+          FFieldDef:=FieldDefs.FieldDefClass.Create(FieldDefs,FieldName,DataType,Size,Required,FieldDefs.Count+1);
+          with FFieldDef do
+            begin
+            if Required then Attributes := Attributes + [faRequired];
+            if ReadOnly then Attributes := Attributes + [faReadOnly];
+            end;
+          end;
+    finally
+      FieldDefs.EndUpdate;
+      end;
+    end;
+end;
+
+procedure TDataSet.InitRecord(var Buffer: TDataRecord);
+
+begin
+  InternalInitRecord(Buffer);
+  ClearCalcFields(Buffer);
+end;
+
+procedure TDataSet.InternalCancel;
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.InternalEdit;
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.InternalRefresh;
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.OpenCursor(InfoQuery: Boolean);
+
+begin
+  if InfoQuery then
+    InternalInitFieldDefs
+  else if State <> dsOpening then
+    DoInternalOpen;
+end;
+
+procedure TDataSet.OpenCursorcomplete;
+begin
+  try
+    if FState = dsOpening then DoInternalOpen
+  finally
+    if FInternalOpenComplete then
+      begin
+      SetState(dsBrowse);
+      DoAfterOpen;
+      if not IsEmpty then
+        DoAfterScroll;
+      end
+    else
+      begin
+      SetState(dsInactive);
+      CloseCursor;
+      end;
+  end;
+end;
+
+procedure TDataSet.RefreshInternalCalcFields(Var Buffer: TDataRecord);
+
+begin
+  //!! To be implemented
+end;
+
+function TDataSet.SetTempState(const Value: TDataSetState): TDataSetState;
+
+begin
+  result := FState;
+  FState := value;
+  inc(FDisableControlsCount);
+end;
+
+procedure TDataSet.RestoreState(const Value: TDataSetState);
+
+begin
+  FState := value;
+  dec(FDisableControlsCount);
+end;
+
+function TDataSet.GetActive: boolean;
+
+begin
+  result := (FState <> dsInactive) and (FState <> dsOpening);
+end;
+
+procedure TDataSet.InternalHandleException(E :Exception);
+
+begin
+  ShowException(E,Nil);
+end;
+
+procedure TDataSet.InternalInitRecord(var Buffer: TDataRecord);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.InternalLast;
+begin
+  // empty stub
+end;
+
+procedure TDataSet.InternalPost;
+
+  Procedure CheckRequiredFields;
+
+  Var I : longint;
+
+  begin
+    For I:=0 to FFieldList.Count-1 do
+      With FFieldList[i] do
+        // Required fields that are NOT autoinc !! Autoinc cannot be set !!
+        if Required and not ReadOnly and
+           (FieldKind=fkData) and Not (DataType=ftAutoInc) and IsNull then
+          DatabaseErrorFmt(SNeedField,[DisplayName],Self);
+  end;
+
+begin
+  CheckRequiredFields;
+end;
+
+procedure TDataSet.InternalSetToRecord(Buffer: TDataRecord);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.SetBookmarkFlag(Var Buffer: TDataRecord; Value: TBookmarkFlag);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.SetBookmarkData(Var Buffer: TDataRecord; Data: TBookmark);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.SetUniDirectional(const Value: Boolean);
+begin
+  FIsUniDirectional := Value;
+end;
+
+procedure TDataSet.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if (Operation=opRemove) and (AComponent=FDataProxy) then
+    FDataProxy:=Nil;
+end;
+
+class function TDataSet.FieldDefsClass: TFieldDefsClass;
+begin
+  Result:=TFieldDefs;
+end;
+
+class function TDataSet.FieldsClass: TFieldsClass;
+begin
+  Result:=TFields;
+end;
+
+procedure TDataSet.SetActive(Value: Boolean);
+
+begin
+  if value and (Fstate = dsInactive) then
+    begin
+    if csLoading in ComponentState then
+      begin
+      FOpenAfterRead := true;
+      exit;
+      end
+    else
+      begin
+      DoBeforeOpen;
+      FEnableControlsEvent:=deLayoutChange;
+      FInternalCalcFields:=False;
+      try
+        FDefaultFields:=FieldCount=0;
+        OpenCursor(False);
+      finally
+        if FState <> dsOpening then OpenCursorComplete;
+        end;
+      end;
+    FModified:=False;
+    end
+  else if not value and (Fstate <> dsinactive) then
+    begin
+    DoBeforeClose;
+    SetState(dsInactive);
+    FDataRequestID:=0;
+    DoneChangeList;
+    CloseCursor;
+    DoAfterClose;
+    FModified:=False;
+    end
+end;
+
+procedure TDataSet.Loaded;
+
+begin
+  inherited;
+  try
+    if FOpenAfterRead then SetActive(true);
+  except
+    on E : Exception do
+      if csDesigning in Componentstate then
+        InternalHandleException(E);
+      else
+        raise;
+  end;
+end;
+
+
+procedure TDataSet.RecalcBufListSize;
+
+var
+  i, j, ABufferCount: Integer;
+  DataLink: TDataLink;
+
+begin
+{$ifdef dsdebug}
+  Writeln('Recalculating buffer list size - check cursor');
+{$endif}
+  If Not IsCursorOpen Then
+    Exit;
+{$ifdef dsdebug}
+  Writeln('Recalculating buffer list size');
+{$endif}
+  if IsUniDirectional then
+    ABufferCount := 1
+  else
+    ABufferCount := DefaultBufferCount;
+
+  for i := 0 to FDataSources.Count - 1 do
+    for j := 0 to TDataSource(FDataSources[i]).DataLinks.Count - 1 do
+      begin
+      DataLink:=TDataLink(TDataSource(FDataSources[i]).DataLinks[j]);
+      if ABufferCount<DataLink.BufferCount then
+        ABufferCount:=DataLink.BufferCount;
+      end;
+
+  If (FBufferCount=ABufferCount) Then
+    exit;
+
+{$ifdef dsdebug}
+  Writeln('Setting buffer list size');
+{$endif}
+
+  SetBufListSize(ABufferCount);
+{$ifdef dsdebug}
+  Writeln('Getting next buffers');
+{$endif}
+  GetNextRecords;
+  if (FRecordCount < FBufferCount) and not IsUniDirectional then
+    begin
+    FActiveRecord := FActiveRecord + GetPriorRecords;
+    CursorPosChanged;
+    end;
+{$Ifdef dsDebug}
+  WriteLn(
+    'SetBufferCount: FActiveRecord=',FActiveRecord,
+    ' FCurrentRecord=',FCurrentRecord,
+    ' FBufferCount= ',FBufferCount,
+    ' FRecordCount=',FRecordCount);
+{$Endif}
+end;
+
+procedure TDataSet.SetBookmarkStr(const Value: TBookmarkStr);
+
+Var
+  O: TJSObject;
+  B : TBookmark;
+
+begin
+  O:=TJSJSON.parse(Value);
+  B.Flag:=TBookmarkFlag(O.Properties['flag']);
+  B.Data:=O.Properties['Index'];
+  GotoBookMark(B)
+end;
+
+procedure TDataSet.SetBufListSize(Value: Longint);
+
+begin
+  if Value < 0 then Value := 0;
+  If Value=FBufferCount Then
+    exit;
+  SetLength(FBuffers,Value+1); // FBuffers[FBufferCount] is used as a temp buffer
+  FBufferCount:=Value;
+  if FRecordCount > FBufferCount then
+    FRecordCount := FBufferCount;
+end;
+
+procedure TDataSet.SetChildOrder(Child: TComponent; Order: Longint);
+
+var
+  Field: TField;
+begin
+  Field := Child as TField;
+  if Fields.IndexOf(Field) >= 0 then
+    Field.Index := Order;
+end;
+
+procedure TDataSet.SetCurrentRecord(Index: Longint);
+
+begin
+  If FCurrentRecord<>Index then
+    begin
+{$ifdef DSdebug}
+    Writeln ('Setting current record to: ',index);
+{$endif}
+    if not FIsUniDirectional then Case GetBookMarkFlag(FBuffers[Index]) of
+      bfCurrent : InternalSetToRecord(FBuffers[Index]);
+      bfBOF : InternalFirst;
+      bfEOF : InternalLast;
+      end;
+    FCurrentRecord:=Index;
+    end;
+end;
+
+procedure TDataSet.SetDefaultFields(const Value: Boolean);
+begin
+  FDefaultFields := Value;
+end;
+
+procedure TDataSet.CheckBiDirectional;
+
+begin
+  if FIsUniDirectional then DataBaseError(SUniDirectional,Self);
+end;
+
+procedure TDataSet.SetFilterOptions(Value: TFilterOptions);
+
+begin
+  CheckBiDirectional;
+  FFilterOptions := Value;
+end;
+
+procedure TDataSet.SetFilterText(const Value: string);
+
+begin
+  FFilterText := value;
+end;
+
+procedure TDataSet.SetFiltered(Value: Boolean);
+
+begin
+  if Value then CheckBiDirectional;
+  FFiltered := value;
+end;
+
+procedure TDataSet.SetFound(const Value: Boolean);
+begin
+  FFound := Value;
+end;
+
+procedure TDataSet.SetModified(Value: Boolean);
+
+begin
+  FModified := value;
+end;
+
+procedure TDataSet.SetName(const NewName: TComponentName);
+
+  function CheckName(const FieldName: string): string;
+  var i,j: integer;
+  begin
+    Result := FieldName;
+    i := 0;
+    j := 0;
+    while (i < Fields.Count) do begin
+      if Result = Fields[i].FieldName then begin
+        inc(j);
+        Result := FieldName + IntToStr(j);
+      end else Inc(i);
+    end;
+  end;
+
+var
+  i: integer;
+  nm: string;
+  old: string;
+
+begin
+  if Self.Name = NewName then Exit;
+  old := Self.Name;
+  inherited SetName(NewName);
+  if (csDesigning in ComponentState) then
+    for i := 0 to Fields.Count - 1 do begin
+      nm := old + Fields[i].FieldName;
+      if Copy(Fields[i].Name, 1, Length(nm)) = nm then
+        Fields[i].Name := CheckName(NewName + Fields[i].FieldName);
+    end;
+end;
+
+procedure TDataSet.SetOnFilterRecord(const Value: TFilterRecordEvent);
+
+begin
+  CheckBiDirectional;
+  FOnFilterRecord := Value;
+end;
+
+procedure TDataSet.SetRecNo(Value: Longint);
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.SetState(Value: TDataSetState);
+
+begin
+  If Value<>FState then
+    begin
+    FState:=Value;
+    if Value=dsBrowse then
+      FModified:=false;
+    DataEvent(deUpdateState,0);
+    end;
+end;
+
+function TDataSet.TempBuffer: TDataRecord;
+
+begin
+  Result := FBuffers[FRecordCount];
+end;
+
+procedure TDataSet.UpdateIndexDefs;
+
+begin
+  // Empty Abstract
+end;
+
+function TDataSet.AllocRecordBuffer: TDataRecord;
+begin
+  Result.data:=Null;
+  Result.state:=rsNew;
+//  Result := nil;
+end;
+
+procedure TDataSet.FreeRecordBuffer(var Buffer: TDataRecord);
+begin
+  // empty stub
+end;
+
+procedure TDataSet.GetBookmarkData(Buffer: TDataRecord; var Data: TBookmark);
+begin
+
+end;
+
+
+function TDataSet.GetBookmarkFlag(Buffer: TDataRecord): TBookmarkFlag;
+begin
+  Result := bfCurrent;
+end;
+
+function TDataSet.ControlsDisabled: Boolean;
+
+begin
+  Result := (FDisableControlsCount > 0);
+end;
+
+function TDataSet.ActiveBuffer: TDataRecord;
+
+begin
+{$ifdef dsdebug}
+  Writeln ('Active buffer requested. Returning record number: ',ActiveRecord);
+{$endif}
+  Result:=FBuffers[FActiveRecord];
+end;
+
+function TDataSet.GetFieldData(Field: TField): JSValue;
+begin
+  Result:=GetFieldData(Field,ActiveBuffer);
+end;
+
+procedure TDataSet.SetFieldData(Field: TField; AValue: JSValue);
+begin
+  SetFieldData(Field,FBuffers[FActiveRecord],AValue);
+end;
+
+procedure TDataSet.Append;
+
+begin
+  DoInsertAppend(True);
+end;
+
+procedure TDataSet.InternalInsert;
+
+begin
+  //!! To be implemented
+end;
+
+procedure TDataSet.AppendRecord(const Values: array of JSValue);
+
+begin
+  DoInsertAppendRecord(Values,True);
+end;
+
+function TDataSet.BookmarkValid(ABookmark: TBookmark): Boolean;
+{
+  Should be overridden by descendant objects.
+}
+begin
+  Result:=False
+end;
+
+
+
+function TDataSet.ConvertToDateTime(aValue: JSValue; ARaiseException: Boolean): TDateTime;
+begin
+  Result:=DefaultConvertToDateTime(aValue,ARaiseException);
+end;
+
+class function TDataSet.DefaultConvertToDateTime(aValue: JSValue; ARaiseException: Boolean): TDateTime;
+begin
+  Result:=0;
+  if IsString(aValue) then
+    begin
+    if not TryRFC3339ToDateTime(String(AValue),Result) then
+      Raise EConvertError.CreateFmt(SErrInvalidDateTime,[String(aValue)])
+    end
+  else if IsNumber(aValue) then
+    Result:=TDateTime(AValue)
+end;
+
+function TDataSet.ConvertDateTimeToNative(aValue : TDateTime) : JSValue;
+
+begin
+  Result:=DefaultConvertDateTimeToNative(aValue);
+end;
+
+Class function TDataSet.DefaultConvertDateTimeToNative(aValue : TDateTime) : JSValue;
+
+begin
+  Result:=DateTimeToRFC3339(aValue);
+end;
+
+function TDataSet.BlobDataToBytes(aValue: JSValue): TBytes;
+begin
+  Result:=DefaultBlobDataToBytes(aValue);
+end;
+
+class function TDataSet.DefaultBlobDataToBytes(aValue: JSValue): TBytes;
+
+Var
+  S : String;
+  I,J,L : Integer;
+
+begin
+  SetLength(Result,0);
+  // We assume a string, hex-encoded.
+  if isString(AValue) then
+    begin
+    S:=String(Avalue);
+    L:=Length(S);
+    SetLength(Result,(L+1) div 2);
+    I:=1;
+    J:=0;
+    While (I<L) do
+      begin
+      Result[J]:=StrToInt('$'+Copy(S,I,2));
+      Inc(I,2);
+      Inc(J,1);
+      end;
+    end;
+end;
+
+Function TDataSet.BytesToBlobData(aValue : TBytes) : JSValue ;
+
+begin
+  Result:=DefaultBytesToBlobData(aValue);
+end;
+
+Class Function TDataSet.DefaultBytesToBlobData(aValue : TBytes) : JSValue;
+
+Var
+  S : String;
+  I : Integer;
+
+begin
+  if Length(AValue)=0 then
+    Result:=Null
+  else
+    begin
+    S:='';
+    For I:=0 to Length(AValue) do
+      TJSString(S).Concat(IntToHex(aValue[i],2));
+    end;
+end;
+
+procedure TDataSet.Cancel;
+
+begin
+  If State in [dsEdit,dsInsert] then
+    begin
+    DataEvent(deCheckBrowseMode,0);
+    DoBeforeCancel;
+    UpdateCursorPos;
+    InternalCancel;
+    if (State = dsInsert) and (FRecordCount = 1) then
+      begin
+      FEOF := true;
+      FBOF := true;
+      FRecordCount := 0;
+      InitRecord(FBuffers[FActiveRecord]);
+      SetState(dsBrowse);
+      DataEvent(deDatasetChange,0);
+      end
+    else
+      begin
+      SetState(dsBrowse);
+      SetCurrentRecord(FActiveRecord);
+      resync([]);
+      end;
+    DoAfterCancel;
+    end;
+end;
+
+procedure TDataSet.CheckBrowseMode;
+
+begin
+  CheckActive;
+  DataEvent(deCheckBrowseMode,0);
+  Case State of
+    dsEdit,dsInsert:
+      begin
+      UpdateRecord;
+      If Modified then
+        Post
+      else
+        Cancel;
+      end;
+    dsSetKey: Post;
+  end;
+end;
+
+procedure TDataSet.ClearFields;
+
+
+begin
+  DataEvent(deCheckBrowseMode, 0);
+  InternalInitRecord(FBuffers[FActiveRecord]);
+  if State <> dsSetKey then
+    GetCalcFields(FBuffers[FActiveRecord]);
+  DataEvent(deRecordChange, 0);
+end;
+
+procedure TDataSet.Close;
+
+begin
+  Active:=False;
+end;
+
+procedure TDataSet.ApplyUpdates;
+begin
+  DoBeforeApplyUpdates;
+  DoApplyUpdates;
+end;
+
+function TDataSet.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Longint;
+
+begin
+  Result:=0;
+end;
+
+procedure TDataSet.CursorPosChanged;
+
+
+begin
+  FCurrentRecord:=-1;
+end;
+
+procedure TDataSet.Delete;
+
+Var
+  R : TRecordUpdateDescriptor;
+
+begin
+  If Not CanModify then
+    DatabaseError(SDatasetReadOnly,Self);
+  If IsEmpty then
+    DatabaseError(SDatasetEmpty,Self);
+  if State in [dsInsert] then
+  begin
+    Cancel;
+  end else begin
+    DataEvent(deCheckBrowseMode,0);
+{$ifdef dsdebug}
+    writeln ('Delete: checking required fields');
+{$endif}
+    DoBeforeDelete;
+    DoBeforeScroll;
+    R:=AddToChangeList(usDeleted);
+    If Not TryDoing(@InternalDelete,OnDeleteError) then
+      begin
+      RemoveFromChangeList(R);
+      exit;
+      end;
+{$ifdef dsdebug}
+    writeln ('Delete: Internaldelete succeeded');
+{$endif}
+    SetState(dsBrowse);
+{$ifdef dsdebug}
+    writeln ('Delete: Browse mode set');
+{$endif}
+    SetCurrentRecord(FActiveRecord);
+    Resync([]);
+    DoAfterDelete;
+    DoAfterScroll;
+  end;
+end;
+
+procedure TDataSet.DisableControls;
+
+
+begin
+  If FDisableControlsCount=0 then
+    begin
+    { Save current state,
+      needed to detect change of state when enabling controls.
+    }
+    FDisableControlsState:=FState;
+    FEnableControlsEvent:=deDatasetChange;
+    end;
+  Inc(FDisableControlsCount);
+end;
+
+procedure TDataSet.DoInsertAppend(DoAppend: Boolean);
+
+
+  procedure DoInsert(DoAppend : Boolean);
+
+  Var
+    BookBeforeInsert : TBookmark;
+    TempBuf : TDataRecord;
+    I : integer;
+
+  begin
+  // need to scroll up al buffers after current one,
+  // but copy current bookmark to insert buffer.
+  If FRecordCount > 0 then
+    BookBeforeInsert:=Bookmark;
+
+  if not DoAppend then
+    begin
+    if FRecordCount > 0 then
+      begin
+      TempBuf := FBuffers[FBufferCount];
+      for I:=FBufferCount downto FActiveRecord+1 do
+        FBuffers[I]:=FBuffers[I-1];
+      FBuffers[FActiveRecord]:=TempBuf;
+      end;
+    end
+  else if FRecordCount=FBufferCount then
+    ShiftBuffersBackward
+  else
+    begin
+    if FRecordCount>0 then
+      inc(FActiveRecord);
+    end;
+
+  // Active buffer is now edit buffer. Initialize.
+  InitRecord(FBuffers[FActiveRecord]);
+  CursorPosChanged;
+
+  // Put bookmark in edit buffer.
+  if FRecordCount=0 then
+    SetBookmarkFlag(FBuffers[FActiveRecord],bfEOF)
+  else
+    begin
+    fBOF := false;
+    // 29:01:05, JvdS: Why is this here?!? It can result in records with the same bookmark-data?
+    // I would say that the 'internalinsert' should do this. But I don't know how Tdbf handles it
+
+    // 1-apr-06, JvdS: It just sets the bookmark of the newly inserted record to the place
+    // where the record should be inserted. So it is ok.
+    if FRecordCount > 0 then
+      begin
+      SetBookMarkData(FBuffers[FActiveRecord],BookBeforeInsert);
+      FreeBookmark(BookBeforeInsert);
+      end;
+    end;
+
+  InternalInsert;
+
+  // update buffer count.
+  If FRecordCount<FBufferCount then
+    Inc(FRecordCount);
+  end;
+
+begin
+  CheckBrowseMode;
+  If Not CanModify then
+    DatabaseError(SDatasetReadOnly,Self);
+  DoBeforeInsert;
+  DoBeforeScroll;
+  If Not DoAppend then
+    begin
+{$ifdef dsdebug}
+    Writeln ('going to insert mode');
+{$endif}
+    DoInsert(false);
+    end
+  else
+    begin
+{$ifdef dsdebug}
+    Writeln ('going to append mode');
+{$endif}
+    ClearBuffers;
+    InternalLast;
+    GetPriorRecords;
+    if FRecordCount>0 then
+      FActiveRecord:=FRecordCount-1;
+    DoInsert(True);
+    SetBookmarkFlag(FBuffers[FActiveRecord],bfEOF);
+    FBOF :=False;
+    FEOF := true;
+    end;
+  SetState(dsInsert);
+  try
+    DoOnNewRecord;
+  except
+    SetCurrentRecord(FActiveRecord);
+    resync([]);
+    raise;
+  end;
+  // mark as not modified.
+  FModified:=False;
+  // Final events.
+  DataEvent(deDatasetChange,0);
+  DoAfterInsert;
+  DoAfterScroll;
+{$ifdef dsdebug}
+  Writeln ('Done with append');
+{$endif}
+end;
+
+procedure TDataSet.Edit;
+
+begin
+  If State in [dsEdit,dsInsert] then exit;
+  CheckBrowseMode;
+  If Not CanModify then
+    DatabaseError(SDatasetReadOnly,Self);
+  If FRecordCount = 0 then
+    begin
+    Append;
+    Exit;
+    end;
+  DoBeforeEdit;
+  If Not TryDoing(@InternalEdit,OnEditError) then exit;
+  GetCalcFields(FBuffers[FActiveRecord]);
+  SetState(dsEdit);
+  DataEvent(deRecordChange,0);
+  DoAfterEdit;
+end;
+
+procedure TDataSet.EnableControls;
+
+
+begin
+  if FDisableControlsCount > 0 then
+    Dec(FDisableControlsCount);
+
+  if FDisableControlsCount = 0 then begin
+    if FState <> FDisableControlsState then
+      DataEvent(deUpdateState, 0);
+
+    if (FState <> dsInactive) and (FDisableControlsState <> dsInactive) then
+      DataEvent(FEnableControlsEvent, 0);
+  end;
+end;
+
+function TDataSet.FieldByName(const FieldName: string): TField;
+
+
+begin
+  Result:=FindField(FieldName);
+  If Result=Nil then
+    DatabaseErrorFmt(SFieldNotFound,[FieldName],Self);
+end;
+
+function TDataSet.FindField(const FieldName: string): TField;
+
+
+begin
+  Result:=FFieldList.FindField(FieldName);
+end;
+
+function TDataSet.FindFirst: Boolean;
+
+
+begin
+  Result:=False;
+end;
+
+function TDataSet.FindLast: Boolean;
+
+
+begin
+  Result:=False;
+end;
+
+function TDataSet.FindNext: Boolean;
+
+
+begin
+  Result:=False;
+end;
+
+function TDataSet.FindPrior: Boolean;
+
+
+begin
+  Result:=False;
+end;
+
+procedure TDataSet.First;
+
+
+begin
+  CheckBrowseMode;
+  DoBeforeScroll;
+  if not FIsUniDirectional then
+    ClearBuffers
+  else if not FBof then
+    begin
+    Active := False;
+    Active := True;
+    end;
+  try
+    InternalFirst;
+    if not FIsUniDirectional then GetNextRecords;
+  finally
+    FBOF:=True;
+    DataEvent(deDatasetChange,0);
+    DoAfterScroll;
+    end;
+end;
+
+procedure TDataSet.FreeBookmark(ABookmark: TBookmark);
+
+
+begin
+  {$ifdef noautomatedbookmark}
+   FreeMem(ABookMark,FBookMarkSize);
+  {$endif}
+end;
+
+function TDataSet.GetBookmark: TBookmark;
+
+
+begin
+  if BookmarkAvailable then
+    GetBookMarkdata(ActiveBuffer,Result)
+  else
+    Result.Data:=Null;
+end;
+
+function TDataSet.GetCurrentRecord(Buffer: TDataRecord): Boolean;
+
+
+begin
+  Result:=False;
+end;
+
+procedure TDataSet.GetFieldList(List: TList; const FieldNames: string);
+
+var
+  F: TField;
+  N: String;
+  StrPos: Integer;
+
+begin
+  if (FieldNames = '') or (List = nil) then
+    Exit;
+  StrPos := 1;
+  repeat
+    N := ExtractFieldName(FieldNames, StrPos);
+    F := FieldByName(N);
+    List.Add(F);
+  until StrPos > Length(FieldNames);
+end;
+
+procedure TDataSet.GetFieldNames(List: TStrings);
+
+
+begin
+  FFieldList.GetFieldNames(List);
+end;
+
+procedure TDataSet.GotoBookmark(const ABookmark: TBookmark);
+
+
+begin
+  If Assigned(ABookMark) then
+    begin
+    CheckBrowseMode;
+    DoBeforeScroll;
+{$ifdef dsdebug}
+    Writeln('Gotobookmark: ',ABookMark.Data);
+{$endif}
+    InternalGotoBookMark(ABookMark);
+    Resync([rmExact,rmCenter]);
+    DoAfterScroll;
+    end;
+end;
+
+procedure TDataSet.Insert;
+
+begin
+  DoInsertAppend(False);
+end;
+
+procedure TDataSet.InsertRecord(const Values: array of JSValue);
+
+begin
+  DoInsertAppendRecord(Values,False);
+end;
+
+function TDataSet.IsEmpty: Boolean;
+
+begin
+  Result:=(fBof and fEof) and
+          (not (State = dsInsert)); // After an insert on an empty dataset, both fBof and fEof are true
+end;
+
+function TDataSet.IsLinkedTo(ADataSource: TDataSource): Boolean;
+
+begin
+//!! Not tested, I never used nested DS
+  if (ADataSource = nil) or (ADataSource.Dataset = nil) then begin
+    Result := False
+  end else if ADataSource.Dataset = Self then begin
+    Result := True;
+  end else begin
+    Result := ADataSource.Dataset.IsLinkedTo(ADataSource.Dataset.DataSource);
+  end;
+//!! DataSetField not implemented
+end;
+
+function TDataSet.IsSequenced: Boolean;
+
+begin
+  Result := True;
+end;
+
+procedure TDataSet.Last;
+
+begin
+  CheckBiDirectional;
+  CheckBrowseMode;
+  DoBeforeScroll;
+  ClearBuffers;
+  try
+    Writeln('FActiveRecord before last',FActiveRecord);
+    InternalLast;
+    Writeln('FActiveRecord after last',FActiveRecord);
+    GetPriorRecords;
+    Writeln('FRecordCount: ',FRecordCount);
+    if FRecordCount>0 then
+      FActiveRecord:=FRecordCount-1;
+    Writeln('FActiveRecord ',FActiveRecord);
+  finally
+    FEOF:=true;
+    DataEvent(deDataSetChange, 0);
+    DoAfterScroll;
+    end;
+end;
+
+function TDataSet.DoLoad(aOptions: TLoadOptions; aAfterLoad: TDatasetLoadEvent): Boolean;
+
+Var
+  Request : TDataRequest;
+
+begin
+  if not (loNoEvents in aOptions) then
+    DoBeforeLoad;
+  Result:=DataProxy<>Nil;
+  if Not Result then
+    Exit;
+  Request:=DataProxy.GetDataRequest(aOptions,@HandleRequestResponse,aAfterLoad);
+  Request.FDataset:=Self;
+  If Active then
+    Request.FBookmark:=GetBookmark;
+  Inc(FDataRequestID);
+  Request.FRequestID:=FDataRequestID;
+  DataProxy.DoGetData(Request);
+end;
+
+
+function TDataSet.Load(aOptions: TLoadOptions; aAfterLoad: TDatasetLoadEvent): Boolean;
+
+begin
+  if loAtEOF in aOptions then
+    DatabaseError(SatEOFInternalOnly,Self);
+  Result:=DoLoad(aOptions,aAfterLoad);
+end;
+
+function TDataSet.MoveBy(Distance: Longint): Longint;
+Var
+  TheResult: Integer;
+
+  Function ScrollForward : Integer;
+  begin
+    Result:=0;
+{$ifdef dsdebug}
+    Writeln('Scrolling forward : ',Distance);
+    Writeln('Active buffer : ',FActiveRecord);
+    Writeln('RecordCount   : ',FRecordCount);
+    WriteLn('BufferCount   : ',FBufferCount);
+{$endif}
+    FBOF:=False;
+    While (Distance>0) and not FEOF do
+      begin
+      If FActiveRecord<FRecordCount-1 then
+        begin
+        Inc(FActiveRecord);
+        Dec(Distance);
+        Inc(TheResult); //Inc(Result);
+        end
+      else
+        begin
+{$ifdef dsdebug}
+       Writeln('Moveby : need next record');
+{$endif}
+        If GetNextRecord then
+          begin
+          Dec(Distance);
+          Dec(Result);
+          Inc(TheResult); //Inc(Result);
+          end
+        else
+          begin
+          FEOF:=true;
+          // Allow to load more records.
+          DoLoad([loNoOpen,loAtEOF],Nil);
+          end;
+        end;
+      end
+  end;
+
+  Function ScrollBackward : Integer;
+  begin
+    CheckBiDirectional;
+    Result:=0;
+{$ifdef dsdebug}
+    Writeln('Scrolling backward : ',Abs(Distance));
+    Writeln('Active buffer : ',FActiveRecord);
+    Writeln('RecordCunt    : ',FRecordCount);
+    WriteLn('BufferCount   : ',FBufferCount);
+{$endif}
+    FEOF:=False;
+    While (Distance<0) and not FBOF do
+      begin
+      If FActiveRecord>0 then
+        begin
+        Dec(FActiveRecord);
+        Inc(Distance);
+        Dec(TheResult); //Dec(Result);
+        end
+      else
+        begin
+       {$ifdef dsdebug}
+       Writeln('Moveby : need next record');
+       {$endif}
+        If GetPriorRecord then
+          begin
+          Inc(Distance);
+          Inc(Result);
+          Dec(TheResult); //Dec(Result);
+          end
+        else
+          FBOF:=true;
+        end;
+      end
+  end;
+
+Var
+  Scrolled : Integer;
+
+begin
+  CheckBrowseMode;
+  Result:=0; TheResult:=0;
+  DoBeforeScroll;
+  If (Distance = 0) or
+     ((Distance>0) and FEOF) or
+     ((Distance<0) and FBOF) then
+    exit;
+  Try
+    Scrolled := 0;
+    If Distance>0 then
+      Scrolled:=ScrollForward
+    else
+      Scrolled:=ScrollBackward;
+  finally
+{$ifdef dsdebug}
+    WriteLn('ActiveRecord=', FActiveRecord,' FEOF=',FEOF,' FBOF=',FBOF);
+{$Endif}
+    DataEvent(deDatasetScroll,Scrolled);
+    DoAfterScroll;
+    Result:=TheResult;
+  end;
+end;
+
+procedure TDataSet.Next;
+
+begin
+  if BlockReadSize>0 then
+    BlockReadNext
+  else
+    MoveBy(1);
+end;
+
+procedure TDataSet.BlockReadNext;
+begin
+  MoveBy(1);
+end;
+
+procedure TDataSet.Open;
+
+begin
+  Active:=True;
+end;
+
+procedure TDataSet.Post;
+
+Const
+  UpdateStates : Array[Boolean] of TUpdateStatus = (usModified,usInserted);
+
+Var
+  R : TRecordUpdateDescriptor;
+  WasInsert : Boolean;
+
+begin
+  UpdateRecord;
+  if State in [dsEdit,dsInsert] then
+    begin
+    DataEvent(deCheckBrowseMode,0);
+{$ifdef dsdebug}
+    writeln ('Post: checking required fields');
+{$endif}
+    DoBeforePost;
+    WasInsert:=State=dsInsert;
+    If Not TryDoing(@InternalPost,OnPostError) then exit;
+    CursorPosChanged;
+{$ifdef dsdebug}
+    writeln ('Post: Internalpost succeeded');
+{$endif}
+// First set the state to dsBrowse, then the Resync, to prevent the calling of
+// the deDatasetChange event, while the state is still 'editable', while the db isn't
+    SetState(dsBrowse);
+    Resync([]);
+    // We get the new values here, since the bookmark should now be correct to find the record later on when doing applyupdates.
+    R:=AddToChangeList(UpdateStates[wasInsert]);
+    R.FBookmark:=BookMark;
+{$ifdef dsdebug}
+    writeln ('Post: Browse mode set');
+{$endif}
+    DoAfterPost;
+    end
+  else if State<>dsSetKey then
+    DatabaseErrorFmt(SNotEditing, [Name], Self);
+end;
+
+procedure TDataSet.Prior;
+
+begin
+  MoveBy(-1);
+end;
+
+procedure TDataSet.Refresh;
+
+begin
+  CheckbrowseMode;
+  DoBeforeRefresh;
+  UpdateCursorPos;
+  InternalRefresh;
+{ SetCurrentRecord is called by UpdateCursorPos already, so as long as
+  InternalRefresh doesn't do strange things this should be ok. }
+//  SetCurrentRecord(FActiveRecord);
+  Resync([]);
+  DoAfterRefresh;
+end;
+
+procedure TDataSet.RegisterDataSource(ADataSource: TDataSource);
+
+begin
+  FDataSources.Add(ADataSource);
+  RecalcBufListSize;
+end;
+
+
+procedure TDataSet.Resync(Mode: TResyncMode);
+
+var i,count : integer;
+
+begin
+  // See if we can find the requested record.
+{$ifdef dsdebug}
+    Writeln ('Resync called');
+{$endif}
+  if FIsUnidirectional then Exit;
+// place the cursor of the underlying dataset to the active record
+//  SetCurrentRecord(FActiveRecord);
+
+// Now look if the data on the current cursor of the underlying dataset is still available
+  If GetRecord(FBuffers[0],gmCurrent,False)<>grOk Then
+// If that fails and rmExact is set, then raise an exception
+    If rmExact in Mode then
+      DatabaseError(SNoSuchRecord,Self)
+// else, if rmexact is not set, try to fetch the next  or prior record in the underlying dataset
+    else if (GetRecord(FBuffers[0],gmNext,True)<>grOk) and
+            (GetRecord(FBuffers[0],gmPrior,True)<>grOk) then
+      begin
+{$ifdef dsdebug}
+      Writeln ('Resync: fuzzy resync');
+{$endif}
+      // nothing found, invalidate buffer and bail out.
+      ClearBuffers;
+      // Make sure that the active record is 'empty', ie: that all fields are null
+      InternalInitRecord(FBuffers[FActiveRecord]);
+      DataEvent(deDatasetChange,0);
+      exit;
+      end;
+  FCurrentRecord := 0;
+  FEOF := false;
+  FBOF := false;
+
+// If we've arrived here, FBuffer[0] is the current record
+  If (rmCenter in Mode) then
+    count := (FRecordCount div 2)
+  else
+    count := FActiveRecord;
+  i := 0;
+  FRecordCount := 1;
+  FActiveRecord := 0;
+
+// Fill the buffers before the active record
+  while (i < count) and GetPriorRecord do
+    inc(i);
+  FActiveRecord := i;
+// Fill the rest of the buffer
+  GetNextRecords;
+// If the buffer is not full yet, try to fetch some more prior records
+  if FRecordCount < FBufferCount then FActiveRecord:=FActiveRecord+getpriorrecords;
+// That's all folks!
+  DataEvent(deDatasetChange,0);
+end;
+
+procedure TDataSet.SetFields(const Values: array of JSValue);
+
+Var I  : longint;
+begin
+  For I:=0 to high(Values) do
+    Fields[I].AssignValue(Values[I]);
+end;
+
+function TDataSet.TryDoing(P: TDataOperation; Ev: TDatasetErrorEvent): Boolean;
+
+Var Retry : TDataAction;
+
+begin
+{$ifdef dsdebug}
+  Writeln ('Trying to do');
+  If P=Nil then writeln ('Procedure to call is nil !!!');
+{$endif dsdebug}
+  Result:=True;
+  Retry:=daRetry;
+  while Retry=daRetry do
+    Try
+{$ifdef dsdebug}
+      Writeln ('Trying : updatecursorpos');
+{$endif dsdebug}
+      UpdateCursorPos;
+{$ifdef dsdebug}
+      Writeln ('Trying to do it');
+{$endif dsdebug}
+      P;
+      exit;
+    except
+      On E : EDatabaseError do
+        begin
+        retry:=daFail;
+        If Assigned(Ev) then
+          Ev(Self,E,Retry);
+        Case Retry of
+          daFail : Raise;
+          daAbort : Abort;
+        end;
+        end;
+    else
+      Raise;
+    end;
+{$ifdef dsdebug}
+  Writeln ('Exit Trying to do');
+{$endif dsdebug}
+end;
+
+procedure TDataSet.UpdateCursorPos;
+
+begin
+  If FRecordCount>0 then
+    SetCurrentRecord(FActiveRecord);
+end;
+
+procedure TDataSet.UpdateRecord;
+
+begin
+  if not (State in dsEditModes) then
+    DatabaseErrorFmt(SNotEditing, [Name], Self);
+  DataEvent(deUpdateRecord, 0);
+end;
+
+function TDataSet.UpdateStatus: TUpdateStatus;
+
+begin
+  Result:=usUnmodified;
+end;
+
+procedure TDataSet.SetConstraints(Value: TCheckConstraints);
+begin
+  FConstraints.Assign(Value);
+end;
+
+procedure TDataSet.SetDataProxy(AValue: TDataProxy);
+begin
+  If AValue=FDataProxy then
+    exit;
+  if Assigned(FDataProxy) then
+    FDataProxy.RemoveFreeNotification(Self)
+  FDataProxy:=AValue;
+  if Assigned(FDataProxy) then
+    FDataProxy.FreeNotification(Self)
+end;
+
+function TDataSet.GetfieldCount: Integer;
+
+begin
+  Result:=FFieldList.Count;
+end;
+
+procedure TDataSet.ShiftBuffersBackward;
+
+var
+  TempBuf : TDataRecord;
+  I : Integer;
+
+begin
+  TempBuf := FBuffers[0];
+  For I:=1 to FBufferCount do
+    FBuffers[I-1]:=FBuffers[i];
+  FBuffers[BufferCount]:=TempBuf;
+end;
+
+procedure TDataSet.ShiftBuffersForward;
+
+var
+  TempBuf : TDataRecord;
+  I : Integer;
+
+begin
+  TempBuf := FBuffers[FBufferCount];
+  For I:=FBufferCount downto 1 do
+    FBuffers[I]:=FBuffers[i-1];
+  FBuffers[0]:=TempBuf;
+end;
+
+function TDataSet.GetFieldValues(const FieldName: string): JSValue;
+
+var
+  i: Integer;
+  FieldList: TList;
+  A : TJSValueDynArray;
+
+begin
+  FieldList := TList.Create;
+  try
+    GetFieldList(FieldList, FieldName);
+    if FieldList.Count>1 then
+      begin
+      SetLength(A,FieldList.Count);
+      for i := 0 to FieldList.Count - 1 do
+        A[i] := TField(FieldList[i]).Value;
+      Result:=A;
+      end
+    else
+      Result := FieldByName(FieldName).Value;
+  finally
+    FieldList.Free;
+  end;
+end;
+
+procedure TDataSet.SetFieldValues(const FieldName: string; Value: JSValue);
+
+var
+  i : Integer;
+  FieldList: TList;
+  A : TJSValueDynArray;
+
+begin
+  if IsArray(Value) then
+    begin
+    FieldList := TList.Create;
+    try
+      GetFieldList(FieldList, FieldName);
+      A:=TJSValueDynArray(Value);
+      if (FieldList.Count = 1) and (Length(A)>0) then
+        // Allow for a field type that can deal with an array
+        FieldByName(FieldName).Value := Value
+      else
+        for i := 0 to FieldList.Count - 1 do
+          TField(FieldList[i]).Value := A[i];
+    finally
+      FieldList.Free;
+    end;
+    end
+  else
+    FieldByName(FieldName).Value := Value;
+end;
+
+function TDataSet.Locate(const KeyFields: string; const KeyValues: JSValue;
+  Options: TLocateOptions): boolean;
+
+begin
+  CheckBiDirectional;
+  Result := False;
+end;
+
+function TDataSet.Lookup(const KeyFields: string; const KeyValues: JSValue;
+  const ResultFields: string): JSValue;
+
+begin
+  CheckBiDirectional;
+  Result := Null;
+end;
+
+
+procedure TDataSet.UnRegisterDataSource(ADataSource: TDataSource);
+
+begin
+  FDataSources.Remove(ADataSource);
+end;
+
+{ ---------------------------------------------------------------------
+    TFieldDef
+  ---------------------------------------------------------------------}
+
+constructor TFieldDef.Create(ACollection: TCollection);
+
+begin
+  Inherited Create(ACollection);
+  FFieldNo:=Index+1;
+end;
+
+constructor TFieldDef.Create(AOwner: TFieldDefs; const AName: string; ADataType: TFieldType; ASize: Integer; ARequired: Boolean;
+  AFieldNo: Longint);
+begin
+{$ifdef dsdebug }
+  Writeln('TFieldDef.Create : ',Aname,'(',AFieldNo,')');
+{$endif}
+  Inherited Create(AOwner);
+  Name:=Aname;
+  FDatatype:=ADatatype;
+  FSize:=ASize;
+  FRequired:=ARequired;
+  FPrecision:=-1;
+  FFieldNo:=AFieldNo;
+end;
+
+destructor TFieldDef.Destroy;
+
+begin
+  Inherited destroy;
+end;
+
+procedure TFieldDef.Assign(Source: TPersistent);
+var fd: TFieldDef;
+begin
+  fd := nil;
+  if Source is TFieldDef then
+    fd := Source as TFieldDef;
+  if Assigned(fd) then begin
+    Collection.BeginUpdate;
+    try
+      Name := fd.Name;
+      DataType := fd.DataType;
+      Size := fd.Size;
+      Precision := fd.Precision;
+      FRequired := fd.Required;
+    finally
+      Collection.EndUpdate;
+    end;
+  end
+  else
+    inherited Assign(Source);
+end;
+
+function TFieldDef.CreateField(AOwner: TComponent): TField;
+
+var TheField : TFieldClass;
+
+begin
+{$ifdef dsdebug}
+  Writeln ('Creating field '+FNAME);
+{$endif dsdebug}
+  TheField:=GetFieldClass;
+  if TheField=Nil then
+    DatabaseErrorFmt(SUnknownFieldType,[FName]);
+  Result:=TheField.Create(AOwner);
+  Try
+    Result.FFieldDef:=Self;
+    Result.Size:=FSize;
+    Result.Required:=FRequired;
+    Result.FFieldName:=FName;
+    Result.FDisplayLabel:=DisplayName;
+    Result.FFieldNo:=Self.FieldNo;
+    Result.SetFieldType(DataType);
+    Result.FReadOnly:=(faReadOnly in Attributes);
+{$ifdef dsdebug}
+    Writeln ('TFieldDef.CreateField : Result Fieldno : ',Result.FieldNo,'; Self : ',FieldNo);
+    Writeln ('TFieldDef.CreateField : Trying to set dataset');
+{$endif dsdebug}
+    Result.Dataset:=TFieldDefs(Collection).Dataset;
+   if (Result is TFloatField) then
+      TFloatField(Result).Precision := FPrecision;
+  except
+    Result.Free;
+    Raise;
+  end;
+end;
+
+procedure TFieldDef.SetAttributes(AValue: TFieldAttributes);
+begin
+  FAttributes := AValue;
+  Changed(False);
+end;
+
+procedure TFieldDef.SetDataType(AValue: TFieldType);
+begin
+  FDataType := AValue;
+  Changed(False);
+end;
+
+procedure TFieldDef.SetPrecision(const AValue: Longint);
+begin
+  FPrecision := AValue;
+  Changed(False);
+end;
+
+procedure TFieldDef.SetSize(const AValue: Integer);
+begin
+  FSize := AValue;
+  Changed(False);
+end;
+
+procedure TFieldDef.SetRequired(const AValue: Boolean);
+begin
+  FRequired := AValue;
+  Changed(False);
+end;
+
+function TFieldDef.GetFieldClass: TFieldClass;
+
+begin
+  //!! Should be owner as tdataset but that doesn't work ??
+
+  If Assigned(Collection) And
+     (Collection is TFieldDefs) And
+     Assigned(TFieldDefs(Collection).Dataset) then
+    Result:=TFieldDefs(Collection).Dataset.GetFieldClass(FDataType)
+  else
+    Result:=Nil;
+end;
+
+
+{ ---------------------------------------------------------------------
+    TFieldDefs
+  ---------------------------------------------------------------------}
+
+{
+destructor TFieldDefs.Destroy;
+
+begin
+  FItems.Free;
+  // This will destroy all fielddefs since we own them...
+  Inherited Destroy;
+end;
+}
+
+procedure TFieldDefs.Add(const AName: string; ADataType: TFieldType);
+
+begin
+  Add(AName,ADatatype,0,False);
+end;
+
+procedure TFieldDefs.Add(const AName: string; ADataType: TFieldType; ASize : Word);
+
+begin
+  Add(AName,ADatatype,ASize,False);
+end;
+
+procedure TFieldDefs.Add(const AName: string; ADataType: TFieldType; ASize: Word;
+  ARequired: Boolean);
+
+begin
+  If Length(AName)=0 Then
+    DatabaseError(SNeedFieldName,Dataset);
+  // the fielddef will register itself here as an owned component.
+  // fieldno is 1 based !
+  BeginUpdate;
+  try
+    Add(AName,ADataType,ASize,ARequired,Count+1);
+  finally
+    EndUpdate;
+  end;
+end;
+
+function TFieldDefs.GetItem(Index: Longint): TFieldDef;
+
+begin
+  Result := TFieldDef(inherited Items[Index]);
+end;
+
+procedure TFieldDefs.SetItem(Index: Longint; const AValue: TFieldDef);
+begin
+  inherited Items[Index] := AValue;
+end;
+
+class function TFieldDefs.FieldDefClass: TFieldDefClass;
+begin
+  Result:=TFieldDef;
+end;
+
+constructor TFieldDefs.Create(ADataSet: TDataSet);
+begin
+  Inherited Create(ADataset, Owner, FieldDefClass);
+end;
+
+function TFieldDefs.Add(const AName: string; ADataType: TFieldType; ASize, APrecision: Integer;
+  ARequired, AReadOnly: Boolean; AFieldNo: Integer): TFieldDef;
+begin
+  Result:=FieldDefClass.Create(Self, MakeNameUnique(AName), ADataType, ASize, ARequired, AFieldNo);
+  if AReadOnly then
+    Result.Attributes := Result.Attributes + [faReadOnly];
+end;
+
+function TFieldDefs.Add(const AName: string; ADataType: TFieldType; ASize: Word; ARequired: Boolean; AFieldNo: Integer): TFieldDef;
+begin
+  Result:=FieldDefClass.Create(Self,AName,ADataType,ASize,ARequired,AFieldNo);
+end;
+
+procedure TFieldDefs.Assign(FieldDefs: TFieldDefs);
+
+var I : longint;
+
+begin
+  Clear;
+  For i:=0 to FieldDefs.Count-1 do
+    With FieldDefs[i] do
+      Add(Name,DataType,Size,Required);
+end;
+
+function TFieldDefs.Find(const AName: string): TFieldDef;
+begin
+  Result := (Inherited Find(AName)) as TFieldDef;
+  if Result=nil then DatabaseErrorFmt(SFieldNotFound,[AName],FDataset);
+end;
+
+{
+procedure TFieldDefs.Clear;
+
+var I : longint;
+
+begin
+  For I:=FItems.Count-1 downto 0 do
+    TFieldDef(Fitems[i]).Free;
+  FItems.Clear;
+end;
+}
+
+procedure TFieldDefs.Update;
+
+begin
+  if not Updated then
+    begin
+    If Assigned(Dataset) then
+      DataSet.InitFieldDefs;
+    Updated := True;
+    end;
+end;
+
+function TFieldDefs.MakeNameUnique(const AName: String): string;
+var DblFieldCount : integer;
+begin
+  DblFieldCount := 0;
+  Result := AName;
+  while assigned(inherited Find(Result)) do
+    begin
+    inc(DblFieldCount);
+    Result := AName + '_' + IntToStr(DblFieldCount);
+    end;
+end;
+
+function TFieldDefs.AddFieldDef: TFieldDef;
+
+begin
+  Result:=FieldDefClass.Create(Self,'',ftUnknown,0,False,Count+1);
+end;
+
+{ ---------------------------------------------------------------------
+    TField
+  ---------------------------------------------------------------------}
+
+Const
+//  SBCD = 'BCD';
+  SBoolean = 'Boolean';
+  SDateTime = 'TDateTime';
+  SFloat = 'Float';
+  SInteger = 'Integer';
+  SLargeInt = 'NativeInt';
+  SJSValue = 'JSValue';
+  SString = 'String';
+  SBytes = 'Bytes';
+
+constructor TField.Create(AOwner: TComponent);
+
+//Var
+//  I : Integer;
+
+begin
+  Inherited Create(AOwner);
+  FVisible:=True;
+  SetLength(FValidChars,255);
+//  For I:=0 to 255 do
+//    FValidChars[i]:=Char(i);
+
+  FProviderFlags := [pfInUpdate,pfInWhere];
+end;
+
+destructor TField.Destroy;
+
+begin
+  IF Assigned(FDataSet) then
+    begin
+    FDataSet.Active:=False;
+    if Assigned(FFields) then
+      FFields.Remove(Self);
+    end;
+  FLookupList.Free;
+  Inherited Destroy;
+end;
+
+Procedure TField.RaiseAccessError(const TypeName: string);
+
+Var
+  E : EDatabaseError;
+
+begin
+  E:=AccessError(TypeName);
+  Raise E;
+end;
+
+function TField.AccessError(const TypeName: string): EDatabaseError;
+
+begin
+  Raise EDatabaseError.CreateFmt(SinvalidTypeConversion,[TypeName,FFieldName]);
+end;
+
+procedure TField.Assign(Source: TPersistent);
+
+begin
+  if Source = nil then Clear
+  else if Source is TField then begin
+    Value := TField(Source).Value;
+  end else
+    inherited Assign(Source);
+end;
+
+procedure TField.AssignValue(const AValue: JSValue);
+
+  procedure Error;
+  begin
+    DatabaseErrorFmt(SFieldValueError, [DisplayName]);
+  end;
+
+begin
+  Case GetValueType(AValue) of
+    jvtNull : Clear;
+    jvtBoolean : AsBoolean:=Boolean(AValue);
+    jvtInteger : AsLargeInt:=NativeInt(AValue);
+    jvtFloat : AsFloat:=Double(AValue);
+    jvtString : AsString:=String(AValue);
+    jvtArray : SetAsBytes(TBytes(AValue));
+  else
+    Error;
+  end;
+end;
+
+procedure TField.Bind(Binding: Boolean);
+
+begin
+  if Binding and (FieldKind=fkLookup) then
+    begin
+    if ((FLookupDataSet = nil) or (FLookupKeyFields = '') or
+       (FLookupResultField = '') or (FKeyFields = '')) then
+      DatabaseErrorFmt(SLookupInfoError, [DisplayName]);
+    FFields.CheckFieldNames(FKeyFields);
+    FLookupDataSet.Open;
+    FLookupDataSet.Fields.CheckFieldNames(FLookupKeyFields);
+    FLookupDataSet.FieldByName(FLookupResultField);
+    if FLookupCache then
+      RefreshLookupList;
+    end;
+end;
+
+procedure TField.Change;
+
+begin
+  If Assigned(FOnChange) Then
+    FOnChange(Self);
+end;
+
+procedure TField.CheckInactive;
+
+begin
+  If Assigned(FDataSet) then
+    FDataset.CheckInactive;
+end;
+
+procedure TField.Clear;
+
+begin
+  SetData(Nil);
+end;
+
+procedure TField.DataChanged;
+
+begin
+  FDataset.DataEvent(deFieldChange,self);
+end;
+
+procedure TField.FocusControl;
+var
+  Field1: TField;
+begin
+  Field1 := Self;
+  FDataSet.DataEvent(deFocusControl,Field1);
+end;
+
+function TField.GetAsBoolean: Boolean;
+begin
+  raiseAccessError(SBoolean);
+end;
+
+function TField.GetAsBytes: TBytes;
+
+begin
+  raiseAccessError(SBytes);
+end;
+
+
+function TField.GetAsDateTime: TDateTime;
+
+begin
+  raiseAccessError(SdateTime);
+end;
+
+function TField.GetAsFloat: Double;
+
+begin
+  raiseAccessError(SDateTime);
+end;
+
+function TField.GetAsLargeInt: NativeInt;
+begin
+  RaiseAccessError(SLargeInt);
+end;
+
+function TField.GetAsLongint: Longint;
+
+begin
+  Result:=GetAsInteger;
+end;
+
+function TField.GetAsInteger: Longint;
+
+begin
+  RaiseAccessError(SInteger);
+end;
+
+function TField.GetAsJSValue: JSValue;
+
+begin
+  Result:=GetData
+end;
+
+
+function TField.GetAsString: string;
+begin
+  Result := GetClassDesc
+end;
+
+function TField.GetOldValue: JSValue;
+
+var SaveState : TDatasetState;
+
+begin
+  SaveState := FDataset.State;
+  try
+    FDataset.SetTempState(dsOldValue);
+    Result := GetAsJSValue;
+  finally
+    FDataset.RestoreState(SaveState);
+  end;
+end;
+
+function TField.GetNewValue: JSValue;
+
+var SaveState : TDatasetState;
+
+begin
+  SaveState := FDataset.State;
+  try
+    FDataset.SetTempState(dsNewValue);
+    Result := GetAsJSValue;
+  finally
+    FDataset.RestoreState(SaveState);
+  end;
+end;
+
+procedure TField.SetNewValue(const AValue: JSValue);
+
+var SaveState : TDatasetState;
+
+begin
+  SaveState := FDataset.State;
+  try
+    FDataset.SetTempState(dsNewValue);
+    SetAsJSValue(AValue);
+  finally
+    FDataset.RestoreState(SaveState);
+  end;
+end;
+
+function TField.GetCurValue: JSValue;
+
+var SaveState : TDatasetState;
+
+begin
+  SaveState := FDataset.State;
+  try
+    FDataset.SetTempState(dsCurValue);
+    Result := GetAsJSValue;
+  finally
+    FDataset.RestoreState(SaveState);
+  end;
+end;
+
+function TField.GetCanModify: Boolean;
+
+begin
+  Result:=Not ReadOnly;
+  If Result then
+    begin
+    Result := FieldKind in [fkData, fkInternalCalc];
+    if Result then
+      begin
+      Result:=Assigned(DataSet) and Dataset.Active;
+      If Result then
+        Result:= DataSet.CanModify;
+      end;
+    end;
+end;
+
+function TField.GetClassDesc: String;
+var ClassN : string;
+begin
+  ClassN := copy(ClassName,2,pos('Field',ClassName)-2);
+  if isNull then
+    result := '(' + LowerCase(ClassN) + ')'
+   else
+    result := '(' + UpperCase(ClassN) + ')';
+end;
+
+
+function TField.GetData : JSValue;
+
+begin
+  IF FDataset=Nil then
+    DatabaseErrorFmt(SNoDataset,[FieldName]);
+  If FValidating then
+    result:=FValueBuffer
+  else
+    Result:=FDataset.GetFieldData(Self);
+end;
+
+function TField.GetDataSize: Integer;
+
+begin
+  Result:=0;
+end;
+
+function TField.GetDefaultWidth: Longint;
+
+begin
+  Result:=10;
+end;
+
+function TField.GetDisplayName  : String;
+
+begin
+  If FDisplayLabel<>'' then
+    result:=FDisplayLabel
+  else
+    Result:=FFieldName;
+end;
+
+function TField.IsDisplayLabelStored: Boolean;
+
+begin
+  Result:=(DisplayLabel<>FieldName);
+end;
+
+function TField.IsDisplayWidthStored: Boolean;
+
+begin
+  Result:=(FDisplayWidth<>0);
+end;
+
+function TField.GetLookupList: TLookupList;
+begin
+  if not Assigned(FLookupList) then
+    FLookupList := TLookupList.Create;
+  Result := FLookupList;
+end;
+
+procedure TField.CalcLookupValue;
+begin
+{ MVC: TODO
+  if FLookupCache then
+    Value := LookupList.ValueOfKey(FDataSet.FieldValues[FKeyFields])
+  else if Assigned(FLookupDataSet) and FDataSet.Active then
+    Value := FLookupDataSet.Lookup(FLookupKeyfields, FDataSet.FieldValues[FKeyFields], FLookupresultField);
+}
+end;
+
+function TField.GetIndex: longint;
+
+begin
+  If Assigned(FDataset) then
+    Result:=FDataset.FFieldList.IndexOf(Self)
+  else
+    Result:=-1;
+end;
+
+function TField.GetLookup: Boolean;
+begin
+  Result := FieldKind = fkLookup;
+end;
+
+procedure TField.SetAlignment(const AValue: TAlignMent);
+begin
+  if FAlignment <> AValue then
+    begin
+    FAlignment := AValue;
+    PropertyChanged(false);
+    end;
+end;
+
+procedure TField.SetIndex(const AValue: Longint);
+begin
+  if FFields <> nil then FFields.SetFieldIndex(Self, AValue)
+end;
+
+function TField.GetIsNull: Boolean;
+
+begin
+  Result:=js.IsNull(GetData);
+end;
+
+function TField.GetParentComponent: TComponent;
+
+begin
+  Result := DataSet;
+end;
+
+procedure TField.GetText(var AText: string; ADisplayText: Boolean);
+
+begin
+  AText:=GetAsString;
+end;
+
+function TField.HasParent: Boolean;
+
+begin
+  HasParent:=True;
+end;
+
+function TField.IsValidChar(InputChar: Char): Boolean;
+
+begin
+  // FValidChars must be set in Create.
+  Result:=CharInset(InputChar,FValidChars);
+end;
+
+procedure TField.RefreshLookupList;
+var
+  tmpActive: Boolean;
+begin
+  if not Assigned(FLookupDataSet) or (Length(FLookupKeyfields) = 0)
+  or (Length(FLookupresultField) = 0) or (Length(FKeyFields) = 0) then
+    Exit;
+    
+  tmpActive := FLookupDataSet.Active;
+  try
+    FLookupDataSet.Active := True;
+    FFields.CheckFieldNames(FKeyFields);
+    FLookupDataSet.Fields.CheckFieldNames(FLookupKeyFields);
+    FLookupDataset.FieldByName(FLookupResultField); // I presume that if it doesn't exist it throws exception, and that a field with null value is still valid
+    LookupList.Clear; // have to be F-less because we might be creating it here with getter!
+
+    FLookupDataSet.DisableControls;
+    try
+      FLookupDataSet.First;
+      while not FLookupDataSet.Eof do
+      begin
+//        FLookupList.Add(FLookupDataSet.FieldValues[FLookupKeyfields], FLookupDataSet.FieldValues[FLookupResultField]);
+        FLookupDataSet.Next;
+      end;
+    finally
+      FLookupDataSet.EnableControls;
+    end;
+  finally
+    FLookupDataSet.Active := tmpActive;
+  end;
+end;
+
+procedure TField.Notification(AComponent: TComponent; Operation: TOperation);
+
+begin
+  Inherited Notification(AComponent,Operation);
+  if (Operation = opRemove) and (AComponent = FLookupDataSet) then
+    FLookupDataSet := nil;
+end;
+
+procedure TField.PropertyChanged(LayoutAffected: Boolean);
+
+begin
+  If (FDataset<>Nil) and (FDataset.Active) then
+    If LayoutAffected then
+      FDataset.DataEvent(deLayoutChange,0)
+    else
+      FDataset.DataEvent(deDatasetchange,0);
+end;
+
+
+procedure TField.SetAsBytes(const AValue: TBytes);
+begin
+  RaiseAccessError(SBytes);
+end;
+
+procedure TField.SetAsBoolean(AValue: Boolean);
+
+begin
+  RaiseAccessError(SBoolean);
+end;
+
+procedure TField.SetAsDateTime(AValue: TDateTime);
+
+begin
+  RaiseAccessError(SDateTime);
+end;
+
+procedure TField.SetAsFloat(AValue: Double);
+
+begin
+  RaiseAccessError(SFloat);
+end;
+
+procedure TField.SetAsJSValue(const AValue: JSValue);
+
+begin
+  if js.IsNull(AValue) then
+    Clear
+  else
+    try
+      SetVarValue(AValue);
+    except
+      on EVariantError do
+        DatabaseErrorFmt(SFieldValueError, [DisplayName]);
+    end;
+end;
+
+
+procedure TField.SetAsLongint(AValue: Longint);
+begin
+  SetAsInteger(AValue);
+end;
+
+procedure TField.SetAsInteger(AValue: Longint);
+begin
+  RaiseAccessError(SInteger);
+end;
+
+procedure TField.SetAsLargeInt(AValue: NativeInt);
+begin
+  RaiseAccessError(SLargeInt);
+end;
+
+procedure TField.SetAsString(const AValue: string);
+begin
+  RaiseAccessError(SString);
+end;
+
+procedure TField.SetData(Buffer: JSValue);
+
+begin
+  If Not Assigned(FDataset) then
+    DatabaseErrorFmt(SNoDataset,[FieldName]);
+  FDataSet.SetFieldData(Self,Buffer);
+end;
+
+procedure TField.SetDataset(AValue: TDataset);
+
+begin
+{$ifdef dsdebug}
+  Writeln ('Setting dataset');
+{$endif}
+  If AValue=FDataset then exit;
+  If Assigned(FDataset) Then
+    begin
+    FDataset.CheckInactive;
+    FDataset.FFieldList.Remove(Self);
+    end;
+  If Assigned(AValue) then
+    begin
+    AValue.CheckInactive;
+    AValue.FFieldList.Add(Self);
+    end;
+  FDataset:=AValue;
+end;
+
+procedure TField.SetDataType(AValue: TFieldType);
+
+begin
+  FDataType := AValue;
+end;
+
+procedure TField.SetFieldType(AValue: TFieldType);
+
+begin
+  { empty }
+end;
+
+procedure TField.SetParentComponent(Value: TComponent);
+
+begin
+  if not (csLoading in ComponentState) then
+    DataSet := Value as TDataSet;
+end;
+
+procedure TField.SetSize(AValue: Integer);
+
+begin
+  CheckInactive;
+  CheckTypeSize(AValue);
+  FSize:=AValue;
+end;
+
+procedure TField.SetText(const AValue: string);
+
+begin
+  SetAsString(AValue);
+end;
+
+procedure TField.SetVarValue(const AValue: JSValue);
+begin
+  RaiseAccessError(SJSValue);
+end;
+
+procedure TField.Validate(Buffer: Pointer);
+
+begin
+  If assigned(OnValidate) Then
+    begin
+    FValueBuffer:=Buffer;
+    FValidating:=True;
+    Try
+      OnValidate(Self);
+    finally
+      FValidating:=False;
+    end;
+    end;
+end;
+
+class function TField.IsBlob: Boolean;
+
+begin
+  Result:=False;
+end;
+
+class procedure TField.CheckTypeSize(AValue: Longint);
+
+begin
+  If (AValue<>0) and Not IsBlob Then
+    DatabaseErrorFmt(SInvalidFieldSize,[AValue]);
+end;
+
+// TField private methods
+
+procedure TField.SetEditText(const AValue: string);
+begin
+  if Assigned(OnSetText) then
+    OnSetText(Self, AValue)
+  else
+    SetText(AValue);
+end;
+
+function TField.GetEditText: String;
+begin
+  SetLength(Result, 0);
+  if Assigned(OnGetText) then
+    OnGetText(Self, Result, False)
+  else
+    GetText(Result, False);
+end;
+
+function TField.GetDisplayText: String;
+begin
+  SetLength(Result, 0);
+  if Assigned(OnGetText) then
+    OnGetText(Self, Result, True)
+  else
+    GetText(Result, True);
+end;
+
+procedure TField.SetDisplayLabel(const AValue: string);
+begin
+  if FDisplayLabel<>AValue then
+    begin
+    FDisplayLabel:=AValue;
+    PropertyChanged(true);
+    end;
+end;
+
+procedure TField.SetDisplayWidth(const AValue: Longint);
+begin
+  if FDisplayWidth<>AValue then
+    begin
+    FDisplayWidth:=AValue;
+    PropertyChanged(True);
+    end;
+end;
+
+function TField.GetDisplayWidth: integer;
+begin
+  if FDisplayWidth=0 then
+    result:=GetDefaultWidth
+  else
+    result:=FDisplayWidth;
+end;
+
+procedure TField.SetLookup(const AValue: Boolean);
+const
+  ValueToLookupMap: array[Boolean] of TFieldKind = (fkData, fkLookup);
+begin
+  FieldKind := ValueToLookupMap[AValue];
+end;
+
+procedure TField.SetReadOnly(const AValue: Boolean);
+begin
+  if (FReadOnly<>AValue) then
+    begin
+    FReadOnly:=AValue;
+    PropertyChanged(True);
+    end;
+end;
+
+procedure TField.SetVisible(const AValue: Boolean);
+begin
+  if FVisible<>AValue then
+    begin
+    FVisible:=AValue;
+    PropertyChanged(True);
+    end;
+end;
+
+
+{ ---------------------------------------------------------------------
+    TStringField
+  ---------------------------------------------------------------------}
+
+
+constructor TStringField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftString);
+  FFixedChar := False;
+  FTransliterate := False;
+  FSize := 20;
+end;
+
+procedure TStringField.SetFieldType(AValue: TFieldType);
+begin
+  if AValue in [ftString, ftFixedChar] then
+    SetDataType(AValue);
+end;
+
+class procedure TStringField.CheckTypeSize(AValue: Longint);
+
+begin
+// A size of 0 is allowed, since for example Firebird allows
+// a query like: 'select '' as fieldname from table' which
+// results in a string with size 0.
+  If (AValue<0) Then
+    DatabaseErrorFmt(SInvalidFieldSize,[AValue])
+end;
+
+function TStringField.GetAsBoolean: Boolean;
+
+var S : String;
+
+begin
+  S:=GetAsString;
+  result := (Length(S)>0) and (Upcase(S[1]) in ['T',YesNoChars[True]]);
+end;
+
+function TStringField.GetAsDateTime: TDateTime;
+
+begin
+  Result:=StrToDateTime(GetAsString);
+end;
+
+function TStringField.GetAsFloat: Double;
+
+begin
+  Result:=StrToFloat(GetAsString);
+end;
+
+function TStringField.GetAsInteger: Longint;
+
+begin
+  Result:=StrToInt(GetAsString);
+end;
+
+function TStringField.GetAsLargeInt: NativeInt;
+
+begin
+  Result:=StrToInt64(GetAsString);
+end;
+
+function TStringField.GetAsString: String;
+
+Var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  if isString(V) then
+    Result := String(V)
+  else
+    Result:='';
+end;
+
+
+function TStringField.GetAsJSValue: JSValue;
+
+begin
+  Result:=GetData
+end;
+
+
+function TStringField.GetDefaultWidth: Longint;
+
+begin
+  result:=Size;
+end;
+
+procedure TStringField.GetText(var AText: string; ADisplayText: Boolean);
+
+begin
+    AText:=GetAsString;
+end;
+
+
+procedure TStringField.SetAsBoolean(AValue: Boolean);
+
+begin
+  If AValue Then
+    SetAsString('T')
+  else
+    SetAsString('F');
+end;
+
+procedure TStringField.SetAsDateTime(AValue: TDateTime);
+
+begin
+  SetAsString(DateTimeToStr(AValue));
+end;
+
+procedure TStringField.SetAsFloat(AValue: Double);
+
+begin
+  SetAsString(FloatToStr(AValue));
+end;
+
+procedure TStringField.SetAsInteger(AValue: Longint);
+
+begin
+  SetAsString(IntToStr(AValue));
+end;
+
+procedure TStringField.SetAsLargeInt(AValue: NativeInt);
+
+begin
+  SetAsString(IntToStr(AValue));
+end;
+
+
+procedure TStringField.SetAsString(const AValue: String);
+begin
+  SetData(AValue);
+end;
+
+
+procedure TStringField.SetVarValue(const AValue: JSValue);
+begin
+  if isString(AVAlue) then
+    SetAsString(String(AValue))
+  else
+    RaiseAccessError(SFieldValueError);
+end;
+
+
+{ ---------------------------------------------------------------------
+    TNumericField
+  ---------------------------------------------------------------------}
+
+
+constructor TNumericField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  AlignMent:=taRightJustify;
+end;
+
+class procedure TNumericField.CheckTypeSize(AValue: Longint);
+begin
+  // This procedure is only added because some TDataset descendents have the
+  // but that they set the Size property as if it is the DataSize property.
+  // To avoid problems with those descendents, allow values <= 16.
+  If (AValue>16) Then
+    DatabaseErrorFmt(SInvalidFieldSize,[AValue]);
+end;
+
+procedure TNumericField.RangeError(AValue, Min, Max: Double);
+
+begin
+  DatabaseErrorFmt(SRangeError,[AValue,Min,Max,FieldName]);
+end;
+
+procedure TNumericField.SetDisplayFormat(const AValue: string);
+
+begin
+ If FDisplayFormat<>AValue then
+   begin
+   FDisplayFormat:=AValue;
+   PropertyChanged(True);
+   end;
+end;
+
+procedure TNumericField.SetEditFormat(const AValue: string);
+
+begin
+  If FEditFormat<>AValue then
+    begin
+    FEditFormat:=AValue;
+    PropertyChanged(True);
+    end;
+end;
+
+function TNumericField.GetAsBoolean: Boolean;
+begin
+  Result:=GetAsInteger<>0;
+end;
+
+procedure TNumericField.SetAsBoolean(AValue: Boolean);
+begin
+  SetAsInteger(ord(AValue));
+end;
+
+{ ---------------------------------------------------------------------
+    TIntegerField
+  ---------------------------------------------------------------------}
+
+
+constructor TIntegerField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftInteger);
+  FMinRange:=Low(LongInt);
+  FMaxRange:=High(LongInt);
+//  MVC : Todo
+//  FValidchars:=['+','-','0'..'9'];
+end;
+
+function TIntegerField.GetAsFloat: Double;
+
+begin
+  Result:=GetAsInteger;
+end;
+
+function TIntegerField.GetAsLargeInt: NativeInt;
+begin
+  Result:=GetAsInteger;
+end;
+
+function TIntegerField.GetAsInteger: Longint;
+
+begin
+  If Not GetValue(Result) then
+    Result:=0;
+end;
+
+function TIntegerField.GetAsJSValue: JSValue;
+
+var L : Longint;
+
+begin
+  If GetValue(L) then
+    Result:=L
+  else
+    Result:=Null;
+end;
+
+function TIntegerField.GetAsString: string;
+
+var L : Longint;
+
+begin
+  If GetValue(L) then
+    Result:=IntTostr(L)
+  else
+    Result:='';
+end;
+
+
+procedure TIntegerField.GetText(var AText: string; ADisplayText: Boolean);
+
+var l : longint;
+    fmt : string;
+
+begin
+  Atext:='';
+  If Not GetValue(l) then exit;
+  If ADisplayText or (FEditFormat='') then
+    fmt:=FDisplayFormat
+  else
+    fmt:=FEditFormat;
+  If length(fmt)<>0 then
+    AText:=FormatFloat(fmt,L)
+  else
+    Str(L,AText);
+end;
+
+function TIntegerField.GetValue(var AValue: Longint): Boolean;
+
+var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  Result:=isInteger(V);
+  if Result then
+    AValue:=Longint(V);
+end;
+
+procedure TIntegerField.SetAsLargeInt(AValue: NativeInt);
+begin
+  if (AValue>=FMinRange) and (AValue<=FMaxRange) then
+    SetAsInteger(AValue)
+  else
+    RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+procedure TIntegerField.SetAsFloat(AValue: Double);
+
+begin
+  SetAsInteger(Round(AValue));
+end;
+
+procedure TIntegerField.SetAsInteger(AValue: Longint);
+begin
+  If CheckRange(AValue) then
+    SetData(AValue)
+  else
+    if (FMinValue<>0) or (FMaxValue<>0) then
+      RangeError(AValue,FMinValue,FMaxValue)
+    else
+      RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+procedure TIntegerField.SetVarValue(const AValue: JSValue);
+begin
+  if IsInteger(aValue) then
+    SetAsInteger(Integer(AValue))
+  else
+    RaiseAccessError(SInteger);
+end;
+
+procedure TIntegerField.SetAsString(const AValue: string);
+
+var L,Code : longint;
+
+begin
+  If length(AValue)=0 then
+    Clear
+  else
+    begin
+    Val(AValue,L,Code);
+    If Code=0 then
+      SetAsInteger(L)
+    else
+      DatabaseErrorFmt(SNotAnInteger,[AValue]);
+    end;
+end;
+
+Function TIntegerField.CheckRange(AValue : longint) : Boolean;
+
+begin
+  if (FMinValue<>0) or (FMaxValue<>0) then
+    Result := (AValue>=FMinValue) and (AValue<=FMaxValue)
+  else
+    Result := (AValue>=FMinRange) and (AValue<=FMaxRange);
+end;
+
+Procedure TIntegerField.SetMaxValue (AValue : longint);
+
+begin
+  If (AValue>=FMinRange) and (AValue<=FMaxRange) then
+    FMaxValue:=AValue
+  else
+    RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+Procedure TIntegerField.SetMinValue (AValue : longint);
+
+begin
+  If (AValue>=FMinRange) and (AValue<=FMaxRange) then
+    FMinValue:=AValue
+  else
+    RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+{ ---------------------------------------------------------------------
+    TLargeintField
+  ---------------------------------------------------------------------}
+
+
+constructor TLargeintField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftLargeint);
+  FMinRange:=Low(NativeInt);
+  FMaxRange:=High(NativeInt);
+// MVC : Todo
+//  FValidchars:=['+','-','0'..'9'];
+end;
+
+function TLargeintField.GetAsFloat: Double;
+
+begin
+  Result:=GetAsLargeInt;
+end;
+
+function TLargeintField.GetAsLargeInt: NativeInt;
+
+begin
+  If Not GetValue(Result) then
+    Result:=0;
+end;
+
+function TLargeIntField.GetAsJSValue: JSValue;
+
+var L : NativeInt;
+
+begin
+  If GetValue(L) then
+    Result:=L
+  else
+    Result:=Null;
+end;
+
+function TLargeintField.GetAsInteger: Longint;
+
+begin
+  Result:=GetAsLargeInt;
+end;
+
+function TLargeintField.GetAsString: string;
+
+var L : NativeInt;
+
+begin
+  If GetValue(L) then
+    Result:=IntTostr(L)
+  else
+    Result:='';
+end;
+
+procedure TLargeintField.GetText(var AText: string; ADisplayText: Boolean);
+
+var l : NativeInt;
+    fmt : string;
+
+begin
+  Atext:='';
+  If Not GetValue(l) then exit;
+  If ADisplayText or (FEditFormat='') then
+    fmt:=FDisplayFormat
+  else
+    fmt:=FEditFormat;
+  If length(fmt)<>0 then
+    AText:=FormatFloat(fmt,L)
+  else
+    Str(L,AText);
+end;
+
+function TLargeintField.GetValue(var AValue: NativeInt): Boolean;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  Result:=isInteger(P);
+  if Result then
+    AValue:=NativeInt(P);
+end;
+
+procedure TLargeintField.SetAsFloat(AValue: Double);
+
+begin
+  SetAsLargeInt(Round(AValue));
+end;
+
+procedure TLargeintField.SetAsLargeInt(AValue: NativeInt);
+
+begin
+  If CheckRange(AValue) then
+    SetData(AValue)
+  else
+    RangeError(AValue,FMinValue,FMaxValue);
+end;
+
+procedure TLargeintField.SetAsInteger(AValue: Longint);
+
+begin
+  SetAsLargeInt(AValue);
+end;
+
+procedure TLargeintField.SetAsString(const AValue: string);
+
+var L     : NativeInt;
+    code  : Longint;
+
+begin
+  If length(AValue)=0 then
+    Clear
+  else
+    begin
+    Val(AValue,L,Code);
+    If Code=0 then
+      SetAsLargeInt(L)
+    else
+      DatabaseErrorFmt(SNotAnInteger,[AValue]);
+    end;
+end;
+
+procedure TLargeintField.SetVarValue(const AValue: JSValue);
+begin
+  if IsInteger(Avalue) then
+    SetAsLargeInt(NativeInt(AValue))
+  else
+    RaiseAccessError(SLargeInt);
+end;
+
+Function TLargeintField.CheckRange(AValue : NativeInt) : Boolean;
+
+begin
+  if (FMinValue<>0) or (FMaxValue<>0) then
+    Result := (AValue>=FMinValue) and (AValue<=FMaxValue)
+  else
+    Result := (AValue>=FMinRange) and (AValue<=FMaxRange);
+end;
+
+Procedure TLargeintField.SetMaxValue (AValue : NativeInt);
+
+begin
+  If (AValue>=FMinRange) and (AValue<=FMaxRange) then
+    FMaxValue:=AValue
+  else
+    RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+Procedure TLargeintField.SetMinValue (AValue : NativeInt);
+
+begin
+  If (AValue>=FMinRange) and (AValue<=FMaxRange) then
+    FMinValue:=AValue
+  else
+    RangeError(AValue,FMinRange,FMaxRange);
+end;
+
+
+{ TAutoIncField }
+
+constructor TAutoIncField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOWner);
+  SetDataType(ftAutoInc);
+end;
+
+Procedure TAutoIncField.SetAsInteger(AValue: Longint);
+
+begin
+  // Some databases allows insertion of explicit values into identity columns
+  // (some of them also allows (some not) updating identity columns)
+  // So allow it at client side and leave check for server side
+  //if not(FDataSet.State in [dsFilter,dsSetKey,dsInsert]) then
+  //  DataBaseError(SCantSetAutoIncFields);
+  inherited;
+end;
+
+{ TFloatField }
+
+procedure TFloatField.SetCurrency(const AValue: Boolean);
+begin
+  if FCurrency=AValue then exit;
+  FCurrency:=AValue;
+end;
+
+procedure TFloatField.SetPrecision(const AValue: Longint);
+begin
+  if (AValue = -1) or (AValue > 1) then
+    FPrecision := AValue
+  else
+    FPrecision := 2;
+end;
+
+function TFloatField.GetAsFloat: Double;
+
+Var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  If IsNumber(P) then
+    Result:=Double(P)
+  else
+    Result:=0.0;
+end;
+
+function TFloatField.GetAsJSValue: JSValue;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  if IsNumber(P) then
+    Result:=P
+  else
+    Result:=Null;
+end;
+
+function TFloatField.GetAsLargeInt: NativeInt;
+begin
+  Result:=Round(GetAsFloat);
+end;
+
+function TFloatField.GetAsInteger: Longint;
+
+begin
+  Result:=Round(GetAsFloat);
+end;
+
+function TFloatField.GetAsString: string;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  if IsNumber(P) then
+    Result:=FloatToStr(Double(P))
+  else
+    Result:='';
+end;
+
+procedure TFloatField.GetText(var AText: string; ADisplayText: Boolean);
+
+Var
+  fmt : string;
+  E : Double;
+  Digits : integer;
+  ff: TFloatFormat;
+  P : JSValue;
+
+begin
+  AText:='';
+  P:=GetData;
+  if Not IsNumber(P) then
+    exit;
+  E:=Double(P);
+  If ADisplayText or (Length(FEditFormat) = 0) Then
+    Fmt:=FDisplayFormat
+  else
+    Fmt:=FEditFormat;
+    
+  Digits := 0;
+  if not FCurrency then
+    ff := ffGeneral
+  else
+    begin
+    Digits := 2;
+    ff := ffFixed;
+    end;
+
+
+  If fmt<>'' then
+    AText:=FormatFloat(fmt,E)
+  else
+    AText:=FloatToStrF(E,ff,FPrecision,Digits);
+end;
+
+procedure TFloatField.SetAsFloat(AValue: Double);
+
+begin
+  If CheckRange(AValue) then
+    SetData(AValue)
+  else
+    RangeError(AValue,FMinValue,FMaxValue);
+end;
+
+procedure TFloatField.SetAsLargeInt(AValue: NativeInt);
+begin
+  SetAsFloat(AValue);
+end;
+
+procedure TFloatField.SetAsInteger(AValue: Longint);
+
+begin
+  SetAsFloat(AValue);
+end;
+
+procedure TFloatField.SetAsString(const AValue: string);
+
+var f : Double;
+
+begin
+  If (AValue='') then
+    Clear
+  else  
+    begin
+    If not TryStrToFloat(AValue,F) then
+      DatabaseErrorFmt(SNotAFloat, [AValue]);
+    SetAsFloat(f);
+    end;
+end;
+
+procedure TFloatField.SetVarValue(const AValue: JSValue);
+begin
+  if IsNumber(aValue) then
+    SetAsFloat(Double(AValue))
+  else
+    RaiseAccessError('Float');
+end;
+
+constructor TFloatField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftFloat);
+  FPrecision:=15;
+// MVC
+//  FValidChars := [DecimalSeparator, '+', '-', '0'..'9', 'E', 'e'];
+end;
+
+Function TFloatField.CheckRange(AValue : Double) : Boolean;
+
+begin
+  If (FMinValue<>0) or (FMaxValue<>0) then
+    Result:=(AValue>=FMinValue) and (AValue<=FMaxValue)
+  else
+    Result:=True;
+end;
+
+{ TBooleanField }
+
+function TBooleanField.GetAsBoolean: Boolean;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  if isBoolean(P) then
+    Result:=Boolean(P)
+  else
+    Result:=False;
+end;
+
+function TBooleanField.GetAsJSValue: JSValue;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  if isBoolean(P) then
+    Result:=Boolean(P)
+  else
+    Result:=Null;
+end;
+
+function TBooleanField.GetAsString: string;
+
+var
+  P : JSValue;
+
+begin
+  P:=GetData;
+  if isBoolean(P) then
+    Result:=FDisplays[False,Boolean(P)]
+  else
+    result:='';
+end;
+
+function TBooleanField.GetDefaultWidth: Longint;
+
+begin
+  Result:=Length(FDisplays[false,false]);
+  If Result<Length(FDisplays[false,True]) then
+    Result:=Length(FDisplays[false,True]);
+end;
+
+function TBooleanField.GetAsInteger: Longint;
+begin
+  Result := ord(GetAsBoolean);
+end;
+
+procedure TBooleanField.SetAsInteger(AValue: Longint);
+begin
+  SetAsBoolean(AValue<>0);
+end;
+
+procedure TBooleanField.SetAsBoolean(AValue: Boolean);
+
+begin
+  SetData(AValue);
+end;
+
+procedure TBooleanField.SetAsString(const AValue: string);
+
+var Temp : string;
+
+begin
+  Temp:=UpperCase(AValue);
+  if Temp='' then
+    Clear
+  else if pos(Temp, FDisplays[True,True])=1 then
+    SetAsBoolean(True)
+  else if pos(Temp, FDisplays[True,False])=1 then
+    SetAsBoolean(False)
+  else
+    DatabaseErrorFmt(SNotABoolean,[AValue]);
+end;
+
+procedure TBooleanField.SetVarValue(const AValue: JSValue);
+begin
+  if isBoolean(aValue) then
+    SetAsBoolean(Boolean(AValue))
+  else if isNumber(aValue) then
+    SetAsBoolean(Double(AValue)<>0)
+end;
+
+constructor TBooleanField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftBoolean);
+  DisplayValues:='True;False';
+end;
+
+Procedure TBooleanField.SetDisplayValues(const AValue : String);
+
+var I : longint;
+
+begin
+  If FDisplayValues<>AValue then
+    begin
+    I:=Pos(';',AValue);
+    If (I<2) or (I=Length(AValue)) then
+      DatabaseErrorFmt(SInvalidDisplayValues,[AValue]);
+    FdisplayValues:=AValue;
+    // Store display values and their uppercase equivalents;
+    FDisplays[False,True]:=Copy(AValue,1,I-1);
+    FDisplays[True,True]:=UpperCase(FDisplays[False,True]);
+    FDisplays[False,False]:=Copy(AValue,I+1,Length(AValue)-i);
+    FDisplays[True,False]:=UpperCase(FDisplays[False,False]);
+    PropertyChanged(True);
+    end;
+end;
+
+{ TDateTimeField }
+
+procedure TDateTimeField.SetDisplayFormat(const AValue: string);
+begin
+  if FDisplayFormat<>AValue then begin
+    FDisplayFormat:=AValue;
+    PropertyChanged(True);
+  end;
+end;
+
+function TDateTimeField.ConvertToDateTime(aValue: JSValue; aRaiseError: Boolean): TDateTime;
+begin
+  if Assigned(Dataset) then
+    Result:=Dataset.ConvertToDateTime(aValue,aRaiseError)
+  else
+    Result:=TDataset.DefaultConvertToDateTime(aValue,aRaiseError);
+end;
+
+function TDateTimeField.DateTimeToNativeDateTime(aValue: TDateTime): JSValue;
+begin
+  if Assigned(Dataset) then
+    Result:=Dataset.ConvertDateTimeToNative(aValue)
+  else
+    Result:=TDataset.DefaultConvertDateTimeToNative(aValue);
+end;
+
+function TDateTimeField.GetAsDateTime: TDateTime;
+
+begin
+  Result:=ConvertToDateTime(GetData,False);
+end;
+
+procedure TDateTimeField.SetVarValue(const AValue: JSValue);
+
+begin
+  SetAsDateTime(ConvertToDateTime(aValue,True));
+end;
+
+function TDateTimeField.GetAsJSValue: JSValue;
+
+begin
+  Result:=GetData;
+  if Not isString(Result) then
+    Result:=Null;
+end;
+
+function TDateTimeField.GetDataSize: Integer;
+begin
+  Result:=inherited GetDataSize;
+end;
+
+function TDateTimeField.GetAsFloat: Double;
+
+begin
+  Result:=GetAsdateTime;
+end;
+
+
+function TDateTimeField.GetAsString: string;
+
+begin
+  GetText(Result,False);
+end;
+
+
+Procedure TDateTimeField.GetText(var AText: string; ADisplayText: Boolean);
+
+var
+  R : TDateTime;
+  F : String;
+
+begin
+  R:=ConvertToDateTime(GetData,false);
+  If (R=0) then
+    AText:=''
+  else
+    begin
+    If (ADisplayText) and (Length(FDisplayFormat)<>0) then
+      F:=FDisplayFormat
+    else
+      Case DataType of
+       ftTime : F:=LongTimeFormat;
+       ftDate : F:=ShortDateFormat;
+      else
+       F:='c'
+      end;
+    AText:=FormatDateTime(F,R);
+    end;
+end;
+
+
+procedure TDateTimeField.SetAsDateTime(AValue: TDateTime);
+
+begin
+  SetData(DateTimeToNativeDateTime(aValue));
+end;
+
+
+procedure TDateTimeField.SetAsFloat(AValue: Double);
+
+begin
+  SetAsDateTime(AValue);
+end;
+
+
+procedure TDateTimeField.SetAsString(const AValue: string);
+
+var R : TDateTime;
+
+begin
+  if AValue<>'' then
+    begin
+    R:=StrToDateTime(AValue);
+    SetData(DateTimeToNativeDateTime(R));
+    end
+  else
+    SetData(Null);
+end;
+
+
+constructor TDateTimeField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftDateTime);
+end;
+
+
+{ TDateField }
+
+constructor TDateField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftDate);
+end;
+
+
+{ TTimeField }
+
+constructor TTimeField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftTime);
+end;
+
+procedure TTimeField.SetAsString(const AValue: string);
+
+var
+  R : TDateTime;
+
+begin
+  if AValue<>'' then
+    begin
+    R:=StrToTime(AValue);
+    SetData(DateTimeToNativeDateTime(R));
+    end
+  else
+    SetData(Null);
+end;
+
+
+
+{ TBinaryField }
+
+class procedure TBinaryField.CheckTypeSize(AValue: Longint);
+
+begin
+  // Just check for really invalid stuff; actual size is
+  // dependent on the record...
+  If AValue<1 then
+    DatabaseErrorFmt(SInvalidFieldSize,[AValue]);
+end;
+
+Function TBinaryField.BlobToBytes(aValue : JSValue) : TBytes;
+
+begin
+  if Assigned(Dataset) then
+    Result:=DataSet.BlobDataToBytes(aValue)
+  else
+    Result:=TDataSet.DefaultBlobDataToBytes(aValue)
+end;
+
+Function TBinaryField.BytesToBlob(aValue : TBytes) : JSValue;
+
+begin
+  if Assigned(Dataset) then
+    Result:=DataSet.BytesToBlobData(aValue)
+  else
+    Result:=TDataSet.DefaultBytesToBlobData(aValue)
+end;
+
+function TBinaryField.GetAsString: string;
+
+var
+  V : JSValue;
+  S : TBytes;
+  I : Integer;
+
+begin
+  Result := '';
+  V:=GetData;
+  if V<>Null then
+    begin
+    S:=BlobToBytes(V);
+    For I:=0 to Length(S) do
+       TJSString(Result).Concat(TJSString.fromCharCode(S[I]));
+    end;
+end;
+
+
+function TBinaryField.GetAsJSValue: JSValue;
+
+begin
+  Result:=GetData;
+end;
+
+
+function TBinaryField.GetValue(var AValue: TBytes): Boolean;
+var
+  V : JSValue;
+begin
+  V:=GetData;
+  Result:=(V<>Null);
+  if Result then
+    AValue:=BlobToBytes(V)
+  else
+    SetLength(AValue,0);
+end;
+
+
+
+procedure TBinaryField.SetAsString(const AValue: string);
+
+var
+  B : TBytes;
+  i : Integer;
+
+begin
+  SetLength(B, Length(aValue));
+  For I:=1 to Length(aValue) do
+    B[i-1]:=Ord(aValue[i]);
+  SetAsBytes(B);
+end;
+
+
+procedure TBinaryField.SetVarValue(const AValue: JSValue);
+
+var
+  B: TBytes;
+  I,Len: integer;
+
+begin
+  if IsArray(AValue) then
+    begin
+    Len:=Length(TJSValueDynArray(AValue));
+    SetLength(B, Len);
+    For I:=1 to Len-1 do
+      B[i]:=TBytes(AValue)[i];
+    SetAsBytes(B);
+    end
+  else if IsString(AValue) then
+    SetAsString(String(AValue))
+  else
+    RaiseAccessError('Blob');
+end;
+
+
+constructor TBinaryField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+end;
+
+
+
+{ TBlobField }
+
+constructor TBlobField.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  SetDataType(ftBlob);
+end;
+
+procedure TBlobField.Clear;
+begin
+  SetData(Null);
+end;
+
+(*
+function TBlobField.GetBlobType: TBlobType;
+begin
+  Result:=ftBlob;
+end;
+
+procedure TBlobField.SetBlobType(AValue: TBlobType);
+begin
+  SetFieldType(TFieldType(AValue));
+end;
+*)
+
+
+
+
+function TBlobField.GetBlobSize: Longint;
+
+var
+  B : TBytes;
+
+begin
+  B:=GetAsBytes;
+  Result:=Length(B);
+end;
+
+
+function TBlobField.GetIsNull: Boolean;
+
+begin
+  if Not Modified then
+    Result:= inherited GetIsNull
+  else
+    Result:=GetBlobSize=0;
+end;
+
+procedure TBlobField.GetText(var AText: string; ADisplayText: Boolean);
+begin
+  AText := inherited GetAsString;
+end;
+
+class function TBlobField.IsBlob: Boolean;
+
+begin
+  Result:=True;
+end;
+
+procedure TBlobField.SetFieldType(AValue: TFieldType);
+begin
+  if AValue in ftBlobTypes then
+    SetDataType(AValue);
+end;
+
+{ TMemoField }
+
+constructor TMemoField.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  SetDataType(ftMemo);
+end;
+
+
+
+{ TVariantField }
+
+constructor TVariantField.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  SetDataType(ftVariant);
+end;
+
+class procedure TVariantField.CheckTypeSize(aValue: Integer);
+begin
+  { empty }
+end;
+
+function TVariantField.GetAsBoolean: Boolean;
+begin
+  Result :=GetAsJSValue=True;
+end;
+
+function TVariantField.GetAsDateTime: TDateTime;
+
+Var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  if Assigned(Dataset) then
+    Result:=Dataset.ConvertToDateTime(V,True)
+  else
+    Result:=TDataset.DefaultConvertToDateTime(V,True)
+end;
+
+function TVariantField.GetAsFloat: Double;
+
+Var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  if isNumber(V) then
+    Result:=Double(V)
+  else if isString(V) then
+    Result:=parsefloat(String(V))
+  else
+    RaiseAccessError('Variant');
+end;
+
+function TVariantField.GetAsInteger: Longint;
+Var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  if isInteger(V) then
+    Result:=Integer(V)
+  else if isString(V) then
+    Result:=parseInt(String(V))
+  else
+    RaiseAccessError('Variant');
+end;
+
+function TVariantField.GetAsString: string;
+Var
+  V : JSValue;
+
+begin
+  V:=GetData;
+  if isInteger(V) then
+    Result:=IntToStr(Integer(V))
+  else if isNumber(V) then
+      Result:=FloatToStr(Double(V))
+  else if isString(V) then
+    Result:=String(V)
+  else
+    RaiseAccessError('Variant');
+end;
+
+
+function TVariantField.GetAsJSValue: JSValue;
+begin
+  Result:=GetData;
+end;
+
+procedure TVariantField.SetAsBoolean(aValue: Boolean);
+begin
+  SetVarValue(aValue);
+end;
+
+procedure TVariantField.SetAsDateTime(aValue: TDateTime);
+begin
+  SetVarValue(aValue);
+end;
+
+procedure TVariantField.SetAsFloat(aValue: Double);
+begin
+  SetVarValue(aValue);
+end;
+
+procedure TVariantField.SetAsInteger(AValue: Longint);
+begin
+  SetVarValue(aValue);
+end;
+
+procedure TVariantField.SetAsString(const aValue: string);
+begin
+  SetVarValue(aValue);
+end;
+
+procedure TVariantField.SetVarValue(const aValue: JSValue);
+begin
+  SetData(aValue);
+end;
+
+{ TFieldsEnumerator }
+
+function TFieldsEnumerator.GetCurrent: TField;
+begin
+  Result := FFields[FPosition];
+end;
+
+constructor TFieldsEnumerator.Create(AFields: TFields);
+begin
+  inherited Create;
+  FFields := AFields;
+  FPosition := -1;
+end;
+
+function TFieldsEnumerator.MoveNext: Boolean;
+begin
+  inc(FPosition);
+  Result := FPosition < FFields.Count;
+end;
+
+{ TFields }
+
+constructor TFields.Create(ADataset: TDataset);
+
+begin
+  FDataSet:=ADataset;
+  FFieldList:=TFpList.Create;
+  FValidFieldKinds:=[fkData..fkInternalcalc];
+end;
+
+destructor TFields.Destroy;
+
+begin
+  if Assigned(FFieldList) then
+    Clear;
+  FreeAndNil(FFieldList);
+  inherited Destroy;
+end;
+
+procedure TFields.ClearFieldDefs;
+
+Var
+  i : Integer;
+
+begin
+  For I:=0 to Count-1 do
+    Fields[i].FFieldDef:=Nil;
+end;
+
+procedure TFields.Changed;
+
+begin
+  // Removed FDataSet.Active check, needed for Persistent fields (see bug ID 30954)
+  if (FDataSet <> nil) and not (csDestroying in FDataSet.ComponentState) then
+    FDataSet.DataEvent(deFieldListChange, 0);
+  If Assigned(FOnChange) then
+    FOnChange(Self);
+end;
+
+procedure TFields.CheckfieldKind(Fieldkind: TFieldKind; Field: TField);
+
+begin
+  If Not (FieldKind in ValidFieldKinds) Then
+    DatabaseErrorFmt(SInvalidFieldKind,[Field.FieldName]);
+end;
+
+function TFields.GetCount: Longint;
+
+begin
+  Result:=FFieldList.Count;
+end;
+
+
+function TFields.GetField(Index: Integer): TField;
+
+begin
+  Result:=Tfield(FFieldList[Index]);
+end;
+
+procedure TFields.SetField(Index: Integer; Value: TField);
+begin
+  Fields[Index].Assign(Value);
+end;
+
+procedure TFields.SetFieldIndex(Field: TField; Value: Integer);
+var Old : Longint;
+begin
+  Old := FFieldList.indexOf(Field);
+  If Old=-1 then
+    Exit;
+  // Check value
+  If Value<0 Then Value:=0;
+  If Value>=Count then Value:=Count-1;
+  If Value<>Old then
+    begin
+    FFieldList.Delete(Old);
+    FFieldList.Insert(Value,Field);
+    Field.PropertyChanged(True);
+    Changed;
+    end;
+end;
+
+procedure TFields.Add(Field: TField);
+
+begin
+  CheckFieldName(Field.FieldName);
+  FFieldList.Add(Field);
+  Field.FFields:=Self;
+  Changed;
+end;
+
+procedure TFields.CheckFieldName(const Value: String);
+
+begin
+  If FindField(Value)<>Nil then
+    DataBaseErrorFmt(SDuplicateFieldName,[Value],FDataset);
+end;
+
+procedure TFields.CheckFieldNames(const Value: String);
+
+var
+  N: String;
+  StrPos: Integer;
+
+begin
+  if Value = '' then
+    Exit;
+  StrPos := 1;
+  repeat
+    N := ExtractFieldName(Value, StrPos);
+    // Will raise an error if no such field...
+    FieldByName(N);
+  until StrPos > Length(Value);
+end;
+
+procedure TFields.Clear;
+var
+  AField: TField;
+begin
+  while FFieldList.Count > 0 do 
+    begin
+    AField := TField(FFieldList.Last);
+    AField.FDataSet := Nil;
+    AField.Free;
+    FFieldList.Delete(FFieldList.Count - 1);
+    end;
+  Changed;
+end;
+
+function TFields.FindField(const Value: String): TField;
+var S : String;
+    I : longint;
+begin
+  S:=UpperCase(Value);
+  For I:=0 To FFieldList.Count-1 do
+  begin
+    Result:=TField(FFieldList[I]);
+    if S=UpperCase(Result.FieldName) then
+    begin
+      {$ifdef dsdebug}
+      Writeln ('Found field ',Value);
+      {$endif}
+      Exit;
+    end;
+  end;
+  Result:=Nil;
+end;
+
+function TFields.FieldByName(const Value: String): TField;
+
+begin
+  Result:=FindField(Value);
+  If result=Nil then
+    DatabaseErrorFmt(SFieldNotFound,[Value],FDataset);
+end;
+
+function TFields.FieldByNumber(FieldNo: Integer): TField;
+var i : Longint;
+begin
+  For I:=0 to FFieldList.Count-1 do
+  begin
+    Result:=TField(FFieldList[I]);
+    if FieldNo=Result.FieldNo then
+      Exit;
+  end;
+  Result:=Nil;
+end;
+
+function TFields.GetEnumerator: TFieldsEnumerator;
+
+begin
+  Result:=TFieldsEnumerator.Create(Self);
+end;
+
+procedure TFields.GetFieldNames(Values: TStrings);
+var i : longint;
+begin
+  Values.Clear;
+  For I:=0 to FFieldList.Count-1 do
+    Values.Add(Tfield(FFieldList[I]).FieldName);
+end;
+
+function TFields.IndexOf(Field: TField): Longint;
+
+begin
+  Result:=FFieldList.IndexOf(Field);
+end;
+
+procedure TFields.Remove(Value : TField);
+
+begin
+  FFieldList.Remove(Value);
+  Value.FFields := nil;
+  Changed;
+end;
+
+{ ---------------------------------------------------------------------
+    TDatalink
+  ---------------------------------------------------------------------}
+
+Constructor TDataLink.Create;
+
+begin
+  Inherited Create;
+  FBufferCount:=1;
+  FFirstRecord := 0;
+  FDataSource := nil;
+  FDatasourceFixed:=False;
+end;
+
+
+Destructor TDataLink.Destroy;
+
+begin
+  Factive:=False;
+  FEditing:=False;
+  FDataSourceFixed:=False;
+  DataSource:=Nil;
+  Inherited Destroy;
+end;
+
+
+Procedure TDataLink.ActiveChanged;
+
+begin
+  FFirstRecord := 0;
+end;
+
+Procedure TDataLink.CheckActiveAndEditing;
+
+Var
+  B : Boolean;
+
+begin
+  B:=Assigned(DataSource) and Not (DataSource.State in [dsInactive,dsOpening]);
+  If B<>FActive then
+    begin
+    FActive:=B;
+    ActiveChanged;
+    end;
+  B:=Assigned(DataSource) and (DataSource.State in dsEditModes) and Not FReadOnly;
+  If B<>FEditing Then
+    begin
+    FEditing:=B;
+    EditingChanged;
+    end;
+end;
+
+
+Procedure TDataLink.CheckBrowseMode;
+
+begin
+end;
+
+
+Function TDataLink.CalcFirstRecord(Index : Integer) : Integer;
+begin
+  if DataSource.DataSet.FActiveRecord > FFirstRecord + Index + FBufferCount - 1 then
+    Result := DataSource.DataSet.FActiveRecord - (FFirstRecord + Index + FBufferCount - 1)
+  else if DataSource.DataSet.FActiveRecord < FFirstRecord + Index then
+    Result := DataSource.DataSet.FActiveRecord - (FFirstRecord + Index)
+  else Result := 0;
+  
+  Inc(FFirstRecord, Index + Result);
+end;
+
+
+Procedure TDataLink.CalcRange;
+var
+    aMax, aMin: integer;
+begin
+  aMin:= DataSet.FActiveRecord - FBufferCount + 1;
+  If aMin < 0 Then aMin:= 0;
+  aMax:= Dataset.FBufferCount - FBufferCount;
+  If aMax < 0 then aMax:= 0;
+
+  If aMax>DataSet.FActiveRecord Then aMax:=DataSet.FActiveRecord;
+
+  If FFirstRecord < aMin Then FFirstRecord:= aMin;
+  If FFirstrecord > aMax Then FFirstRecord:= aMax;
+
+  If (FfirstRecord<>0) And
+     (DataSet.FActiveRecord - FFirstRecord < FBufferCount -1) Then
+    Dec(FFirstRecord, 1);
+
+end;
+
+
+Procedure TDataLink.DataEvent(Event: TDataEvent; Info: JSValue);
+
+
+begin
+  Case Event of
+    deFieldChange, deRecordChange:
+      If Not FUpdatingRecord then
+        RecordChanged(TField(Info));
+    deDataSetChange: begin
+      SetActive(DataSource.DataSet.Active);
+      CalcRange;
+      CalcFirstRecord(Integer(Info));
+      DatasetChanged;
+    end;
+    deDataSetScroll: DatasetScrolled(CalcFirstRecord(Integer(Info)));
+    deLayoutChange: begin
+      CalcFirstRecord(Integer(Info));
+      LayoutChanged;
+    end;
+    deUpdateRecord: UpdateRecord;
+    deUpdateState: CheckActiveAndEditing;
+    deCheckBrowseMode: CheckBrowseMode;
+    deFocusControl:
+      FocusControl(Info);
+  end;
+end;
+
+
+Procedure TDataLink.DataSetChanged;
+
+begin
+  RecordChanged(Nil);
+end;
+
+
+Procedure TDataLink.DataSetScrolled(Distance: Integer);
+
+begin
+  DataSetChanged;
+end;
+
+
+Procedure TDataLink.EditingChanged;
+
+begin
+end;
+
+
+Procedure TDataLink.FocusControl(Field: JSValue);
+
+begin
+end;
+
+
+Function TDataLink.GetActiveRecord: Integer;
+
+begin
+  Result:=Dataset.FActiveRecord - FFirstRecord;
+end;
+
+Function TDatalink.GetDataSet : TDataset;
+
+begin
+  If Assigned(Datasource) then
+    Result:=DataSource.DataSet
+  else
+    Result:=Nil;  
+end;
+
+
+Function TDataLink.GetBOF: Boolean;
+
+begin
+  Result:=DataSet.BOF
+end;
+
+
+Function TDataLink.GetBufferCount: Integer;
+
+begin
+  Result:=FBufferCount;
+end;
+
+
+Function TDataLink.GetEOF: Boolean;
+
+begin
+  Result:=DataSet.EOF
+end;
+
+
+Function TDataLink.GetRecordCount: Integer;
+
+begin
+  Result:=Dataset.FRecordCount;
+  If Result>BufferCount then
+    Result:=BufferCount;
+end;
+
+
+Procedure TDataLink.LayoutChanged;
+
+begin
+  DataSetChanged;
+end;
+
+
+Function TDataLink.MoveBy(Distance: Integer): Integer;
+
+begin
+  Result:=DataSet.MoveBy(Distance);
+end;
+
+
+Procedure TDataLink.RecordChanged(Field: TField);
+
+begin
+end;
+
+
+Procedure TDataLink.SetActiveRecord(Value: Integer);
+
+begin
+{$ifdef dsdebug}
+  Writeln('Datalink. Setting active record to ',Value,' with firstrecord ',ffirstrecord);
+{$endif}
+  Dataset.FActiveRecord:=Value + FFirstRecord;
+end;
+
+
+Procedure TDataLink.SetBufferCount(Value: Integer);
+
+begin
+  If FBufferCount<>Value then
+    begin
+      FBufferCount:=Value;
+      if Active then begin
+        DataSet.RecalcBufListSize;
+        CalcRange;
+      end;
+    end;
+end;
+
+procedure TDataLink.SetActive(AActive: Boolean);
+begin
+  if Active <> AActive then
+  begin
+    FActive := AActive;
+    // !!!: Set internal state
+    ActiveChanged;
+  end;
+end;
+
+Procedure TDataLink.SetDataSource(Value : TDatasource);
+
+begin
+  if FDataSource = Value then
+    Exit;
+  if not FDataSourceFixed then
+    begin
+    if Assigned(DataSource) then
+      Begin
+      DataSource.UnregisterDatalink(Self);
+      FDataSource := nil;
+      CheckActiveAndEditing;
+      End;
+    FDataSource := Value;
+    if Assigned(DataSource) then
+      begin
+      DataSource.RegisterDatalink(Self);
+      CheckActiveAndEditing;
+      End;
+    end;
+end;
+
+Procedure TDatalink.SetReadOnly(Value : Boolean);
+
+begin
+  If FReadOnly<>Value then
+    begin
+    FReadOnly:=Value;
+    CheckActiveAndEditing;
+    end;
+end;
+
+Procedure TDataLink.UpdateData;
+
+begin
+end;
+
+
+
+Function TDataLink.Edit: Boolean;
+
+begin
+  If Not FReadOnly then
+    DataSource.Edit;
+  // Triggered event will set FEditing
+  Result:=FEditing;
+end;
+
+
+Procedure TDataLink.UpdateRecord;
+
+begin
+  FUpdatingRecord:=True;
+  Try
+    UpdateData;
+  finally
+    FUpdatingRecord:=False;
+  end;
+end;
+
+
+{ ---------------------------------------------------------------------
+    TDetailDataLink
+  ---------------------------------------------------------------------}
+
+Function TDetailDataLink.GetDetailDataSet: TDataSet;
+
+begin
+  Result := nil;
+end;
+
+
+{ ---------------------------------------------------------------------
+    TMasterDataLink
+  ---------------------------------------------------------------------}
+
+constructor TMasterDataLink.Create(ADataSet: TDataSet);
+
+begin
+  inherited Create;
+  FDetailDataSet:=ADataSet;
+  FFields:=TList.Create;
+end;
+
+
+destructor TMasterDataLink.Destroy;
+
+begin
+  FFields.Free;
+  inherited Destroy;
+end;
+
+
+Procedure TMasterDataLink.ActiveChanged;
+
+begin
+  FFields.Clear;
+  if Active then
+    try
+      DataSet.GetFieldList(FFields, FFieldNames);
+    except
+      FFields.Clear;
+      raise;
+    end;
+  if FDetailDataSet.Active and not (csDestroying in FDetailDataSet.ComponentState) then
+    if Active and (FFields.Count > 0) then
+      DoMasterChange
+    else
+      DoMasterDisable;  
+end;
+
+
+Procedure TMasterDataLink.CheckBrowseMode;
+
+begin
+  if FDetailDataSet.Active then FDetailDataSet.CheckBrowseMode;
+end;
+
+
+Function TMasterDataLink.GetDetailDataSet: TDataSet;
+
+begin
+  Result := FDetailDataSet;
+end;
+
+
+Procedure TMasterDataLink.LayoutChanged;
+
+begin
+  ActiveChanged;
+end;
+
+
+Procedure TMasterDataLink.RecordChanged(Field: TField);
+
+begin
+  if (DataSource.State <> dsSetKey) and FDetailDataSet.Active and
+     (FFields.Count > 0) and ((Field = nil) or
+     (FFields.IndexOf(Field) >= 0)) then
+    DoMasterChange;  
+end;
+
+procedure TMasterDatalink.SetFieldNames(const Value: string);
+
+begin
+  if FFieldNames <> Value then
+    begin
+    FFieldNames := Value;
+    ActiveChanged;
+    end;
+end;
+
+Procedure TMasterDataLink.DoMasterDisable; 
+
+begin
+  if Assigned(FOnMasterDisable) then 
+    FOnMasterDisable(Self);
+end;
+
+Procedure TMasterDataLink.DoMasterChange; 
+
+begin
+  If Assigned(FOnMasterChange) then
+    FOnMasterChange(Self);
+end;
+
+{ ---------------------------------------------------------------------
+    TMasterParamsDataLink
+  ---------------------------------------------------------------------}
+
+constructor TMasterParamsDataLink.Create(ADataSet: TDataSet);
+
+Var
+  P : TParams;
+
+begin
+  inherited Create(ADataset);
+  If (ADataset<>Nil) then
+    begin
+    P:=TParams(GetObjectProp(ADataset,'Params',TParams));
+    if (P<>Nil) then
+      Params:=P;
+    end;  
+end;
+
+
+Procedure TMasterParamsDataLink.SetParams(AValue : TParams);
+
+begin
+  FParams:=AValue;
+  If (AValue<>Nil) then
+    RefreshParamNames;
+end;
+
+Procedure TMasterParamsDataLink.RefreshParamNames; 
+
+Var
+  FN : String;
+  DS : TDataset;
+  F  : TField;
+  I : Integer;
+  P : TParam;
+
+
+begin
+  FN:='';
+  DS:=Dataset;
+  If Assigned(FParams) then
+    begin
+    F:=Nil;
+    For I:=0 to FParams.Count-1 do
+      begin
+      P:=FParams[i];
+      if not P.Bound then
+        begin
+        If Assigned(DS) then
+          F:=DS.FindField(P.Name);
+        If (Not Assigned(DS)) or (not DS.Active) or (F<>Nil) then
+          begin
+          If (FN<>'') then
+            FN:=FN+';';
+          FN:=FN+P.Name;
+          end;
+        end;
+      end;
+    end;
+  FieldNames:=FN;  
+end;
+
+Procedure TMasterParamsDataLink.CopyParamsFromMaster(CopyBound : Boolean);
+
+begin
+  if Assigned(FParams) then
+    FParams.CopyParamValuesFromDataset(Dataset,CopyBound);
+end;
+
+Procedure TMasterParamsDataLink.DoMasterDisable; 
+
+begin
+  Inherited;
+  // If master dataset is closing, leave detail dataset intact (Delphi compatible behavior)
+  // If master dataset is reopened, relationship will be reestablished
+end;
+
+Procedure TMasterParamsDataLink.DoMasterChange; 
+
+begin
+  Inherited;
+  if Assigned(Params) and Assigned(DetailDataset) and DetailDataset.Active then
+    begin
+    DetailDataSet.CheckBrowseMode;
+    DetailDataset.Close;
+    DetailDataset.Open;
+    end;
+end;
+
+{ ---------------------------------------------------------------------
+    TDatasource
+  ---------------------------------------------------------------------}
+
+Constructor TDataSource.Create(AOwner: TComponent);
+
+begin
+  Inherited Create(AOwner);
+  FDatalinks := TList.Create;
+  FEnabled := True;
+  FAutoEdit := True;
+end;
+
+
+Destructor TDataSource.Destroy;
+
+begin
+  FOnStateCHange:=Nil;
+  Dataset:=Nil;
+  With FDataLinks do
+    While Count>0 do
+      TDatalink(Items[Count - 1]).DataSource:=Nil;
+  FDatalinks.Free;
+  inherited Destroy;
+end;
+
+
+Procedure TDatasource.Edit;
+
+begin
+  If (State=dsBrowse) and AutoEdit Then
+    Dataset.Edit;
+end;
+
+
+Function TDataSource.IsLinkedTo(ADataSet: TDataSet): Boolean;
+
+begin
+  Result:=False;
+end;
+
+
+procedure TDatasource.DistributeEvent(Event: TDataEvent; Info: JSValue);
+
+
+Var
+  i : Longint;
+
+begin
+  With FDatalinks do
+    begin
+    For I:=0 to Count-1 do
+      With TDatalink(Items[i]) do
+        If Not VisualControl Then
+          DataEvent(Event,Info);
+    For I:=0 to Count-1 do
+      With TDatalink(Items[i]) do
+        If VisualControl Then
+          DataEvent(Event,Info);
+    end;
+end;
+
+procedure TDatasource.RegisterDataLink(DataLink: TDataLink);
+
+begin
+  FDatalinks.Add(DataLink);
+  if Assigned(DataSet) then
+    DataSet.RecalcBufListSize;
+end;
+
+
+procedure TDatasource.SetDataSet(ADataSet: TDataSet);
+begin
+  If FDataset<>Nil Then
+    Begin
+    FDataset.UnRegisterDataSource(Self);
+    FDataSet:=nil;
+    ProcessEvent(deUpdateState,0);
+    End;
+  If ADataset<>Nil Then
+    begin
+    ADataset.RegisterDatasource(Self);
+    FDataSet:=ADataset;
+    ProcessEvent(deUpdateState,0);
+    End;
+end;
+
+
+procedure TDatasource.SetEnabled(Value: Boolean);
+
+begin
+  FEnabled:=Value;
+end;
+
+
+Procedure TDatasource.DoDataChange (Info : Pointer);
+
+begin
+  If Assigned(OnDataChange) Then
+    OnDataChange(Self,TField(Info));
+end;
+
+Procedure TDatasource.DoStateChange;
+
+begin
+  If Assigned(OnStateChange) Then
+    OnStateChange(Self);
+end;
+
+
+Procedure TDatasource.DoUpdateData;
+
+begin
+  If Assigned(OnUpdateData) Then
+    OnUpdateData(Self);
+end;
+
+
+procedure TDatasource.UnregisterDataLink(DataLink: TDataLink);
+
+begin
+  FDatalinks.Remove(Datalink);
+  If Dataset<>Nil then
+    DataSet.RecalcBufListSize;
+  //Dataset.SetBufListSize(DataLink.BufferCount);
+end;
+
+
+procedure TDataSource.ProcessEvent(Event : TDataEvent; Info : JSValue);
+
+Const
+    OnDataChangeEvents = [deRecordChange, deDataSetChange, deDataSetScroll,
+                          deLayoutChange,deUpdateState];
+
+Var
+  NeedDataChange : Boolean;
+  FLastState : TdataSetState;
+
+begin
+  // Special UpdateState handling.
+  If Event=deUpdateState then
+    begin
+    NeedDataChange:=(FState=dsInactive);
+    FLastState:=FState;
+    If Assigned(Dataset) then
+      FState:=Dataset.State
+    else
+      FState:=dsInactive;
+    // Don't do events if nothing changed.
+    If FState=FLastState then
+      exit;
+    end
+  else
+    NeedDataChange:=True;
+  DistributeEvent(Event,Info);
+  // Extra handlers
+  If Not (csDestroying in ComponentState) then
+    begin
+    If (Event=deUpdateState) then
+      DoStateChange;
+    If (Event in OnDataChangeEvents) and
+       NeedDataChange Then
+      DoDataChange(Nil);
+    If (Event = deFieldChange) Then
+      DoDataCHange(Pointer(Info));
+    If (Event=deUpdateRecord) then
+      DoUpdateData;
+    end;
+ end;
+
+
+procedure SkipQuotesString(S : String; var p : integer; QuoteChar : char; EscapeSlash, EscapeRepeat : Boolean);
+
+var notRepeatEscaped : boolean;
+begin
+  Inc(p);
+  repeat
+    notRepeatEscaped := True;
+    while not CharInSet(S[p],[#0, QuoteChar]) do
+      begin
+      if EscapeSlash and (S[p]='\') and (P<Length(S)) then
+        Inc(p,2) // make sure we handle \' and \\ correct
+      else
+        Inc(p);
+      end;
+    if S[p]=QuoteChar then
+      begin
+      Inc(p); // skip final '
+      if (S[p]=QuoteChar) and EscapeRepeat then // Handle escaping by ''
+        begin
+        notRepeatEscaped := False;
+        inc(p);
+        end
+      end;
+  until notRepeatEscaped;
+end;
+
+
+{ TParams }
+
+Function TParams.GetItem(Index: Integer): TParam;
+begin
+  Result:=(Inherited GetItem(Index)) as TParam;
+end;
+
+Function TParams.GetParamValue(const ParamName: string): JSValue;
+begin
+  Result:=ParamByName(ParamName).Value;
+end;
+
+Procedure TParams.SetItem(Index: Integer; Value: TParam);
+begin
+  Inherited SetItem(Index,Value);
+end;
+
+Procedure TParams.SetParamValue(const ParamName: string; const Value: JSValue);
+begin
+  ParamByName(ParamName).Value:=Value;
+end;
+
+Procedure TParams.AssignTo(Dest: TPersistent);
+begin
+ if (Dest is TParams) then
+   TParams(Dest).Assign(Self)
+ else
+   inherited AssignTo(Dest);
+end;
+
+Function TParams.GetDataSet: TDataSet;
+begin
+  If (FOwner is TDataset) Then
+    Result:=TDataset(FOwner)
+  else
+    Result:=Nil;
+end;
+
+Function TParams.GetOwner: TPersistent;
+begin
+  Result:=FOwner;
+end;
+
+Class Function TParams.ParamClass: TParamClass;
+begin
+  Result:=TParam;
+end;
+
+Constructor TParams.Create(AOwner: TPersistent; AItemClass: TCollectionItemClass
+  );
+begin
+  Inherited Create(AItemClass);
+  FOwner:=AOwner;
+end;
+
+
+Constructor TParams.Create(AOwner: TPersistent);
+begin
+  Create(AOwner,ParamClass);
+end;
+
+Constructor TParams.Create;
+begin
+  Create(Nil);
+end;
+
+Procedure TParams.AddParam(Value: TParam);
+begin
+  Value.Collection:=Self;
+end;
+
+Procedure TParams.AssignValues(Value: TParams);
+
+Var
+  I : Integer;
+  P,PS : TParam;
+
+begin
+  For I:=0 to Value.Count-1 do
+    begin
+    PS:=Value[i];
+    P:=FindParam(PS.Name);
+    If Assigned(P) then
+      P.Assign(PS);
+    end;
+end;
+
+Function TParams.CreateParam(FldType: TFieldType; const ParamName: string;
+  ParamType: TParamType): TParam;
+
+begin
+  Result:=Add as TParam;
+  Result.Name:=ParamName;
+  Result.DataType:=FldType;
+  Result.ParamType:=ParamType;
+end;
+
+Function TParams.FindParam(const Value: string): TParam;
+
+Var
+  I : Integer;
+
+begin
+  Result:=Nil;
+  I:=Count-1;
+  While (Result=Nil) and (I>=0) do
+    If (CompareText(Value,Items[i].Name)=0) then
+      Result:=Items[i]
+    else
+      Dec(i);
+end;
+
+Procedure TParams.GetParamList(List: TList; const ParamNames: string);
+
+Var
+  P: TParam;
+  N: String;
+  StrPos: Integer;
+
+begin
+  if (ParamNames = '') or (List = nil) then
+    Exit;
+  StrPos := 1;
+  repeat
+    N := ExtractFieldName(ParamNames, StrPos);
+    P := ParamByName(N);
+    List.Add(P);
+  until StrPos > Length(ParamNames);
+end;
+
+Function TParams.IsEqual(Value: TParams): Boolean;
+
+Var
+  I : Integer;
+
+begin
+  Result:=(Value.Count=Count);
+  I:=Count-1;
+  While Result and (I>=0) do
+    begin
+    Result:=Items[I].IsEqual(Value[i]);
+    Dec(I);
+    end;
+end;
+
+Function TParams.ParamByName(const Value: string): TParam;
+begin
+  Result:=FindParam(Value);
+  If (Result=Nil) then
+    DatabaseErrorFmt(SParameterNotFound,[Value],Dataset);
+end;
+
+Function TParams.ParseSQL(SQL: String; DoCreate: Boolean): String;
+
+var pb : TParamBinding;
+    rs : string;
+
+begin
+  Result := ParseSQL(SQL,DoCreate,True,True,psInterbase, pb, rs);
+end;
+
+Function TParams.ParseSQL(SQL: String; DoCreate, EscapeSlash,
+  EscapeRepeat: Boolean; ParameterStyle: TParamStyle): String;
+
+var pb : TParamBinding;
+    rs : string;
+
+begin
+  Result := ParseSQL(SQL,DoCreate,EscapeSlash,EscapeRepeat,ParameterStyle,pb, rs);
+end;
+
+Function TParams.ParseSQL(SQL: String; DoCreate, EscapeSlash,
+  EscapeRepeat: Boolean; ParameterStyle: TParamStyle; out
+  ParamBinding: TParambinding): String;
+
+var rs : string;
+
+begin
+  Result := ParseSQL(SQL,DoCreate,EscapeSlash, EscapeRepeat, ParameterStyle,ParamBinding, rs);
+end;
+
+function SkipComments(S : String; Var p: Integer; EscapeSlash, EscapeRepeat : Boolean) : Boolean;
+
+begin
+  Result := False;
+  case S[P] of
+  '''', '"', '`':
+    begin
+    Result := True;
+    // single quote, double quote or backtick delimited string
+    SkipQuotesString(S,p, S[p], EscapeSlash, EscapeRepeat);
+    end;
+  '-': // possible start of -- comment
+    begin
+    Inc(p);
+    if S[p]='-' then // -- comment
+      begin
+      Result := True;
+      repeat // skip until at end of line
+        Inc(p);
+      until CharInset(S[p],[#10, #13, #0]);
+      while CharInSet(S[p],[#10, #13]) do
+        Inc(p); // newline is part of comment
+      end;
+    end;
+  '/': // possible start of /* */ comment
+    begin
+    Inc(p);
+    if S[p]='*' then // /* */ comment
+      begin
+      Result := True;
+      Inc(p);
+      while p<=Length(S) do
+        begin
+        if S[p]='*' then // possible end of comment
+          begin
+          Inc(p);
+          if S[p]='/' then Break; // end of comment
+          end
+        else
+          Inc(p);
+        end;
+      if (P<=Length(s)) and (S[p]='/') then
+        Inc(p); // skip final /
+      end;
+    end;
+  end; {case}
+end;
+
+Function TParams.ParseSQL(SQL: String; DoCreate, EscapeSlash,
+  EscapeRepeat: Boolean; ParameterStyle: TParamStyle; out
+  ParamBinding: TParambinding; out ReplaceString: string): String;
+
+type
+  // used for ParamPart
+  TStringPart = record
+    Start,Stop:integer;
+  end;
+
+const
+  ParamAllocStepSize = 8;
+  PAramDelimiters : Array of char = (';',',',' ','(',')',#13,#10,#9,#0,'=','+','-','*','\','/','[',']','|');
+
+var
+  IgnorePart:boolean;
+  p,ParamNameStart,BufStart:Integer;
+  ParamName:string;
+  QuestionMarkParamCount,ParameterIndex,NewLength:integer;
+  ParamCount:integer; // actual number of parameters encountered so far;
+                      // always <= Length(ParamPart) = Length(Parambinding)
+                      // Parambinding will have length ParamCount in the end
+  ParamPart:array of TStringPart; // describe which parts of buf are parameters
+  NewQueryLength:integer;
+  NewQuery:string;
+  NewQueryIndex,BufIndex,CopyLen,i:integer;    // Parambinding will have length ParamCount in the end
+  tmpParam:TParam;
+
+begin
+  if DoCreate then Clear;
+  // Parse the SQL and build ParamBinding
+  ParamCount:=0;
+  NewQueryLength:=Length(SQL);
+  SetLength(ParamPart,ParamAllocStepSize);
+  SetLength(ParamBinding,ParamAllocStepSize);
+  QuestionMarkParamCount:=0; // number of ? params found in query so far
+
+  ReplaceString := '$';
+  if ParameterStyle = psSimulated then
+    while pos(ReplaceString,SQL) > 0 do ReplaceString := ReplaceString+'$';
+
+  p:=1;
+  BufStart:=p; // used to calculate ParamPart.Start values
+  repeat
+    while SkipComments(SQL,p,EscapeSlash,EscapeRepeat) do ;
+    case SQL[p] of
+    ':','?': // parameter
+      begin
+      IgnorePart := False;
+      if SQL[p]=':' then
+        begin // find parameter name
+        Inc(p);
+        if charInSet(SQL[p],[':','=',' ']) then  // ignore ::, since some databases uses this as a cast (wb 4813)
+          begin
+          IgnorePart := True;
+          Inc(p);
+          end
+        else
+          begin
+          if (SQL[p]='"') then // Check if the parameter-name is between quotes
+             begin
+             ParamNameStart:=p;
+             SkipQuotesString(SQL,p,'"',EscapeSlash,EscapeRepeat);
+             // Do not include the quotes in ParamName, but they must be included
+             // when the parameter is replaced by some place-holder.
+             ParamName:=Copy(SQL,ParamNameStart+1,p-ParamNameStart-2);
+             end
+          else
+             begin
+             ParamNameStart:=p;
+             while not CharInSet(SQL[p], ParamDelimiters) do
+               Inc(p);
+             ParamName:=Copy(SQL,ParamNameStart,p-ParamNameStart);
+             end;
+          end;
+        end
+      else
+        begin
+        Inc(p);
+        ParamNameStart:=p;
+        ParamName:='';
+        end;
+      if not IgnorePart then
+        begin
+        Inc(ParamCount);
+        if ParamCount>Length(ParamPart) then
+          begin
+          NewLength:=Length(ParamPart)+ParamAllocStepSize;
+          SetLength(ParamPart,NewLength);
+          SetLength(ParamBinding,NewLength);
+          end;
+        if DoCreate then
+          begin
+          // Check if this is the first occurance of the parameter
+          tmpParam := FindParam(ParamName);
+          // If so, create the parameter and assign the Parameterindex
+          if not assigned(tmpParam) then
+            ParameterIndex := CreateParam(ftUnknown, ParamName, ptInput).Index
+          else  // else only assign the ParameterIndex
+            ParameterIndex := tmpParam.Index;
+          end
+        // else find ParameterIndex
+        else
+          begin
+          if ParamName<>'' then
+            ParameterIndex:=ParamByName(ParamName).Index
+          else
+            begin
+            ParameterIndex:=QuestionMarkParamCount;
+            Inc(QuestionMarkParamCount);
+            end;
+          end;
+        if ParameterStyle in [psPostgreSQL,psSimulated] then
+          begin
+          i:=ParameterIndex+1;
+          repeat
+            inc(NewQueryLength);
+            i:=i div 10;
+          until i=0;
+          end;
+        // store ParameterIndex in FParamIndex, ParamPart data
+        ParamBinding[ParamCount-1]:=ParameterIndex;
+        ParamPart[ParamCount-1].Start:=ParamNameStart-BufStart;
+        ParamPart[ParamCount-1].Stop:=p-BufStart+1;
+        // update NewQueryLength
+        Dec(NewQueryLength,p-ParamNameStart);
+        end;
+      end;
+    #0:
+      Break; // end of SQL
+    else
+      Inc(p);
+    end;
+  until false;
+  SetLength(ParamPart,ParamCount);
+  SetLength(ParamBinding,ParamCount);
+  if ParamCount<=0 then
+    NewQuery:=SQL
+  else
+    begin
+    // replace :ParamName by ? for interbase and by $x for postgresql/psSimulated
+    // (using ParamPart array and NewQueryLength)
+    if (ParameterStyle = psSimulated) and (length(ReplaceString) > 1) then
+      inc(NewQueryLength,(paramcount)*(length(ReplaceString)-1));
+
+    SetLength(NewQuery,NewQueryLength);
+    NewQueryIndex:=1;
+    BufIndex:=1;
+    for i:=0 to High(ParamPart) do
+      begin
+      CopyLen:=ParamPart[i].Start-BufIndex;
+      NewQuery:=NewQuery+Copy(SQL,BufIndex,CopyLen);
+      Inc(NewQueryIndex,CopyLen);
+      case ParameterStyle of
+        psInterbase : begin
+                        NewQuery:=NewQuery+'?';
+                        Inc(NewQueryIndex);
+                      end;
+        psPostgreSQL,
+        psSimulated : begin
+                      ParamName := IntToStr(ParamBinding[i]+1);
+                      NewQuery:=StringOfChar('$',Length(ReplaceString));
+                      NewQuery:=NewQuery+ParamName;
+                      end;
+      end;
+      BufIndex:=ParamPart[i].Stop;
+    end;
+    CopyLen:=Length(SQL)+1-BufIndex;
+    if (CopyLen>0) then
+      NewQuery:=NewQuery+Copy(SQL,BufIndex,CopyLen);
+    end;
+  Result:=NewQuery;
+end;
+
+
+Procedure TParams.RemoveParam(Value: TParam);
+begin
+   Value.Collection:=Nil;
+end;
+
+{ TParam }
+
+Function TParam.GetDataSet: TDataSet;
+begin
+  If Assigned(Collection) and (Collection is TParams) then
+    Result:=TParams(Collection).GetDataset
+  else
+    Result:=Nil;
+end;
+
+Function TParam.IsParamStored: Boolean;
+begin
+  Result:=Bound;
+end;
+
+procedure TParam.SetAsBytes(AValue: TBlobData);
+begin
+  FValue:=TDataset.DefaultBytesToBlobData(aValue);
+end;
+
+Procedure TParam.AssignParam(Param: TParam);
+begin
+  if Not Assigned(Param) then
+    begin
+    Clear;
+    FDataType:=ftunknown;
+    FParamType:=ptUnknown;
+    Name:='';
+    Size:=0;
+    Precision:=0;
+    NumericScale:=0;
+    end
+  else
+    begin
+    FDataType:=Param.DataType;
+    if Param.IsNull then
+      Clear
+    else
+      FValue:=Param.FValue;
+    FBound:=Param.Bound;
+    Name:=Param.Name;
+    if (ParamType=ptUnknown) then
+      ParamType:=Param.ParamType;
+    Size:=Param.Size;
+    Precision:=Param.Precision;
+    NumericScale:=Param.NumericScale;
+    end;
+end;
+
+Procedure TParam.AssignTo(Dest: TPersistent);
+begin
+  if (Dest is TField) then
+    AssignToField(TField(Dest))
+  else
+    inherited AssignTo(Dest);
+end;
+
+Function TParam.GetAsBoolean: Boolean;
+begin
+  If IsNull then
+    Result:=False
+  else
+    Result:=FValue=true;
+end;
+
+Function TParam.GetAsBytes: TBytes;
+begin
+  if IsNull then
+    Result:=nil
+  else if isArray(FValue) then
+    Result:=TBytes(FValue)
+end;
+
+Function TParam.GetAsDateTime: TDateTime;
+begin
+  If IsNull then
+    Result:=0.0
+  else
+    Result:=TDateTime(FValue);
+end;
+
+Function TParam.GetAsFloat: Double;
+begin
+  If IsNull then
+    Result:=0.0
+  else
+    Result:=Double(FValue);
+end;
+
+Function TParam.GetAsInteger: Longint;
+begin
+  If IsNull or not IsInteger(FValue) then
+    Result:=0
+  else
+    Result:=Integer(FValue);
+end;
+
+Function TParam.GetAsLargeInt: NativeInt;
+begin
+  If IsNull or not IsInteger(FValue) then
+    Result:=0
+  else
+    Result:=NativeInt(FValue);
+end;
+
+
+Function TParam.GetAsMemo: string;
+begin
+  If IsNull or not IsString(FValue) then
+    Result:=''
+  else
+    Result:=String(FValue);
+end;
+
+Function TParam.GetAsString: string;
+
+begin
+  If IsNull or not IsString(FValue) then
+    Result:=''
+  else
+    Result:=String(FValue);
+end;
+
+Function TParam.GetAsJSValue: JSValue;
+begin
+  if IsNull then
+    Result:=Null
+  else
+    Result:=FValue;
+end;
+Function TParam.GetDisplayName: string;
+begin
+  if (FName<>'') then
+    Result:=FName
+  else
+    Result:=inherited GetDisplayName
+end;
+
+Function TParam.GetIsNull: Boolean;
+begin
+  Result:= JS.IsNull(FValue);
+end;
+
+Function TParam.IsEqual(AValue: TParam): Boolean;
+begin
+  Result:=(Name=AValue.Name)
+          and (IsNull=AValue.IsNull)
+          and (Bound=AValue.Bound)
+          and (DataType=AValue.DataType)
+          and (ParamType=AValue.ParamType)
+          and (GetValueType(FValue)=GetValueType(AValue.FValue))
+          and (FValue=AValue.FValue);
+end;
+
+Procedure TParam.SetAsBlob(const AValue: TBlobData);
+begin
+  FDataType:=ftBlob;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsBoolean(AValue: Boolean);
+begin
+  FDataType:=ftBoolean;
+  Value:=AValue;
+end;
+
+procedure TParam.SetAsBytes(const AValue: TBytes);
+begin
+
+end;
+
+Procedure TParam.SetAsDate(const AValue: TDateTime);
+begin
+  FDataType:=ftDate;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsDateTime(const AValue: TDateTime);
+begin
+  FDataType:=ftDateTime;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsFloat(const AValue: Double);
+begin
+  FDataType:=ftFloat;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsInteger(AValue: Longint);
+begin
+  FDataType:=ftInteger;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsLargeInt(AValue: NativeInt);
+begin
+  FDataType:=ftLargeint;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsMemo(const AValue: string);
+begin
+  FDataType:=ftMemo;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsString(const AValue: string);
+begin
+  if FDataType <> ftFixedChar then
+    FDataType := ftString;
+  Value:=AValue;
+end;
+
+
+Procedure TParam.SetAsTime(const AValue: TDateTime);
+begin
+  FDataType:=ftTime;
+  Value:=AValue;
+end;
+
+Procedure TParam.SetAsJSValue(const AValue: JSValue);
+
+begin
+  FValue:=AValue;
+  FBound:=not JS.IsNull(AValue);
+  if FBound then
+    case GetValueType(aValue) of
+      jvtBoolean : FDataType:=ftBoolean;
+      jvtInteger : FDataType:=ftInteger;
+      jvtFloat : FDataType:=ftFloat;
+      jvtObject,jvtArray : FDataType:=ftBlob;
+    end;
+end;
+
+Procedure TParam.SetDataType(AValue: TFieldType);
+
+
+begin
+  FDataType:=AValue;
+
+end;
+
+Procedure TParam.SetText(const AValue: string);
+begin
+  Value:=AValue;
+end;
+
+constructor TParam.Create(ACollection: TCollection);
+begin
+  inherited Create(ACollection);
+  ParamType:=ptUnknown;
+  DataType:=ftUnknown;
+  FValue:=Null;
+end;
+
+constructor TParam.Create(AParams: TParams; AParamType: TParamType);
+begin
+  Create(AParams);
+  ParamType:=AParamType;
+end;
+
+Procedure TParam.Assign(Source: TPersistent);
+begin
+  if (Source is TParam) then
+    AssignParam(TParam(Source))
+  else if (Source is TField) then
+    AssignField(TField(Source))
+  else if (source is TStrings) then
+    AsMemo:=TStrings(Source).Text
+  else
+    inherited Assign(Source);
+end;
+
+Procedure TParam.AssignField(Field: TField);
+begin
+  if Assigned(Field) then
+    begin
+    // Need TField.Value
+    AssignFieldValue(Field,Field.Value);
+    Name:=Field.FieldName;
+    end
+  else
+    begin
+    Clear;
+    Name:='';
+    end
+end;
+
+Procedure TParam.AssignToField(Field : TField);
+
+begin
+  if Assigned(Field) then
+    case FDataType of
+      ftUnknown  : DatabaseErrorFmt(SUnknownParamFieldType,[Name],DataSet);
+      // Need TField.AsSmallInt
+      // Need TField.AsWord
+      ftInteger,
+      ftAutoInc  : Field.AsInteger:=AsInteger;
+      ftFloat    : Field.AsFloat:=AsFloat;
+      ftBoolean  : Field.AsBoolean:=AsBoolean;
+      ftBlob,
+      ftString,
+      ftMemo,
+      ftFixedChar: Field.AsString:=AsString;
+      ftTime,
+      ftDate,
+      ftDateTime : Field.AsDateTime:=AsDateTime;
+    end;
+end;
+
+Procedure TParam.AssignFromField(Field : TField);
+
+begin
+  if Assigned(Field) then
+    begin
+    FDataType:=Field.DataType;
+    case Field.DataType of
+      ftUnknown  : DatabaseErrorFmt(SUnknownParamFieldType,[Name],DataSet);
+      ftInteger,
+      ftAutoInc  : AsInteger:=Field.AsInteger;
+      ftFloat    : AsFloat:=Field.AsFloat;
+      ftBoolean  : AsBoolean:=Field.AsBoolean;
+      ftBlob,
+      ftString,
+      ftMemo,
+      ftFixedChar: AsString:=Field.AsString;
+      ftTime,
+      ftDate,
+      ftDateTime : AsDateTime:=Field.AsDateTime;
+    end;
+    end;
+end;
+
+Procedure TParam.AssignFieldValue(Field: TField; const AValue: JSValue);
+
+begin
+  If Assigned(Field) then
+    begin
+
+    if (Field.DataType = ftString) and TStringField(Field).FixedChar then
+      FDataType := ftFixedChar
+    else if (Field.DataType = ftMemo) and (Field.Size > 255) then
+      FDataType := ftString
+    else
+      FDataType := Field.DataType;
+    if JS.IsNull(AValue) then
+      Clear
+    else
+      Value:=AValue;
+
+    Size:=Field.DataSize;
+    FBound:=True;
+
+    end;
+end;
+
+Procedure TParam.Clear;
+begin
+  FValue:=Null;
+end;
+
+
+Procedure TParams.CopyParamValuesFromDataset(ADataSet: TDataSet;
+  CopyBound: Boolean);
+
+Var
+  I : Integer;
+  P : TParam;
+  F : TField;
+
+begin
+  If assigned(ADataSet) then
+    For I:=0 to Count-1 do
+     begin
+     P:=Items[i];
+     if CopyBound or (not P.Bound) then
+       begin
+       // Master dataset must be active and unbound parameters must have fields
+       // with same names in master dataset (Delphi compatible behavior)
+       F:=ADataSet.FieldByName(P.Name);
+       P.AssignField(F);
+       If Not CopyBound then
+         P.Bound:=False;
+       end;
+    end;
+end;
+
+initialization
+
+end.

+ 134 - 0
src/packages/fcl-db/dbconst.pas

@@ -0,0 +1,134 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 1999-2000 by Michael Van Canneyt, member of the
+    Free Pascal development team
+
+    Constants used for displaying messages in DB units
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit DBConst;
+
+Interface
+
+Resourcestring
+  SActiveDataset           = 'Operation cannot be performed on an active dataset';
+  SBadParamFieldType       = 'Bad fieldtype for parameter "%s".';
+  SCantSetAutoIncFields    = 'AutoInc Fields are read-only';
+  SConnected               = 'Operation cannot be performed on a connected database';
+  SDatasetReadOnly         = 'Dataset is read-only.';
+  SDatasetRegistered       = 'Dataset already registered : "%s"';
+  SDuplicateFieldName      = 'Duplicate fieldname : "%s"';
+  SErrAssTransaction       = 'Cannot assign transaction while old transaction active!';
+  SErrColumnNotFound       = 'Column "%s" not found.';
+  SErrDatabasenAssigned    = 'Database not assigned!';
+  SErrNoDatabaseAvailable  = 'Invalid operation: Not attached to database';
+  SErrNoDatabaseName       = 'Database connect string (DatabaseName) not filled in!';
+  SErrNoSelectStatement    = 'Cannot open a non-select statement';
+  SErrNoStatement          = 'SQL statement not set';
+  SErrTransAlreadyActive   = 'Transaction already active';
+  SErrTransactionnSet      = 'Transaction not set';
+  SErrIndexResultTooLong   = 'Index result for "%s" too long, >100 characters (%d).';
+  SErrIndexBasedOnInvField = 'Field "%s" has an invalid field type (%s) to base index on.';
+  SErrIndexBasedOnUnkField = 'Index based on unknown field "%s".';
+  SErrConnTransactionnSet  = 'Transaction of connection not set';
+  SErrNotASQLConnection    = '"%s" is not a TSQLConnection';
+  SErrNotASQLQuery         = '"%s" is not a TCustomSQLQuery';
+  STransNotActive          = 'Operation cannot be performed on an inactive transaction';
+  STransActive             = 'Operation cannot be performed on an active transaction';
+  SFieldNotFound           = 'Field not found : "%s"';
+  SInactiveDataset         = 'Operation cannot be performed on an inactive dataset';
+  SInvalidDisplayValues    = '"%s" are not valid boolean displayvalues';
+  SInvalidFieldKind        = '%s : invalid field kind : ';
+  SInvalidBookmark         = 'Invalid bookmark';
+  SInvalidFieldSize        = 'Invalid field size : %d';
+  SInvalidTypeConversion   = 'Invalid type conversion to %s in field %s';
+  SNeedField               = 'Field %s is required, but not supplied.';
+  SNeedFieldName           = 'Field needs a name';
+  SNoDataset               = 'No dataset asssigned for field : "%s"';
+  SNoDatasetRegistered     = 'No such dataset registered : "%s"';
+  SNoDatasets              = 'No datasets are attached to the database';
+  SNoSuchRecord            = 'Could not find the requested record.';
+  SNoTransactionRegistered = 'No such transaction registered : "%s"';
+  SNoTransactions          = 'No transactions are attached to the database';
+  SNotABoolean             = '"%s" is not a valid boolean';
+  SNotAFloat               = '"%s" is not a valid float';
+  SNotAninteger            = '"%s" is not a valid integer';
+  SNotConnected            = 'Operation cannot be performed on an disconnected database';
+  SNotEditing              = 'Operation not allowed, dataset "%s" is not in an edit or insert state.';
+  SParameterNotFound       = 'Parameter "%s" not found';
+  SRangeError              = '%f is not between %f and %f for %s';
+  SReadOnlyField           = 'Field %s cannot be modified, it is read-only.';
+  STransactionRegistered   = 'Transaction already registered : "%s"';
+  SUniDirectional          = 'Operation cannot be performed on an unidirectional dataset';
+  SUnknownField            = 'No field named "%s" was found in dataset "%s"';
+  SUnknownFieldType        = 'Unknown field type : %s';
+  SUnknownParamFieldType   = 'Unknown fieldtype for parameter "%s".';
+  SMetadataUnavailable     = 'The metadata is not available for this type of database.';
+  SDeletedRecord           = 'The record is deleted.';
+  SIndexNotFound           = 'Index ''%s'' not found';
+  SParameterCountIncorrect = 'The number of parameters is incorrect.';
+  SUnsupportedParameter    = 'Parameters of the type ''%s'' are not (yet) supported.';
+  SFieldValueError         = 'Invalid value for field ''%s''';
+  SInvalidCalcType         = 'Field ''%s'' cannot be a calculated or lookup field';
+  SDuplicateName           = 'Duplicate name ''%s'' in %s';
+  SNoParseSQL              = '%s is only possible if ParseSQL is True';
+  SLookupInfoError         = 'Lookup information for field ''%s'' is incomplete';
+  SUnsupportedFieldType    = 'Fieldtype %s is not supported';
+  SInvPacketRecordsValue   = 'PacketRecords has to be larger then 0';
+  SInvPacketRecordsValueFieldNames = 'PacketRecords must be -1 if IndexFieldNames is set';
+  SInvalidSearchFieldType  = 'Searching in fields of type %s is not supported';
+  SDatasetEmpty            = 'The dataset is empty';
+  SFieldIsNull             = 'The field is null';
+  SOnUpdateError           = 'An error occurred while applying the updates in a record: %s';
+  SApplyRecNotSupported    = 'Applying updates is not supported by this TDataset descendent';
+  SNoWhereFields           = 'No %s query specified and failed to generate one. (No fields for inclusion in where statement found)';
+  SNoUpdateFields          = 'No %s query specified and failed to generate one. (No fields found for insert- or update-statement found)';
+  SNotSupported            = 'Operation is not supported by this type of database';
+  SDBCreateDropFailed      = 'Creation or dropping of database failed';
+  SMaxIndexes              = 'The maximum amount of indexes is reached';
+  SMinIndexes              = 'The minimum amount of indexes is 1';
+  STooManyFields           = 'More fields specified then really exist';
+// These are added for Delphi-compatilility, but not used by the fcl:
+  SFieldIndexError         = 'Field index out of range';
+  SIndexFieldMissing       = 'Cannot access index field ''%s''';
+  SNoFieldIndexes          = 'No index currently active';
+  SNotIndexField           = 'Field ''%s'' is not indexed and cannot be modified';
+  SErrUnknownConnectorType = 'Unknown connector type: "%s"';
+  SNoIndexFieldNameGiven   = 'There are no fields selected to base the index on';
+  SStreamNotRecognised     = 'The data-stream format is not recognized';
+  SNoReaderClassRegistered = 'There is no TDatapacketReaderClass registered for this kind of data-stream';
+  SErrCircularDataSourceReferenceNotAllowed = 'Circular datasource references are not allowed.';
+  SCommitting               = 'Committing transaction';
+  SRollingBack              = 'Rolling back transaction';
+  SCommitRetaining          = 'Commit and retaining transaction';
+  SRollBackRetaining        = 'Rollback and retaining transaction';
+  SErrNoFieldsDefined       = 'Can not create a dataset when there are no fielddefinitions or fields defined';
+  SErrApplyUpdBeforeRefresh = 'Must apply updates before refreshing data';
+  SErrNoDataset             = 'Missing (compatible) underlying dataset, can not open';
+  SErrDisconnectedPacketRecords = 'For disconnected TSQLQuery instances, packetrecords must be -1';
+  SErrImplicitNoRollBack      = 'Implicit use of transactions does not allow rollback.';
+  SErrNoImplicitTransaction   = 'Connection %s does not allow implicit transactions.';
+  SErrImplictTransactionStart = 'Error: attempt to implicitly start a transaction on Connection "%s", transaction "%s".';
+  SErrImplicitConnect         = 'Error: attempt to implicitly activate connection "%s".';
+  SErrFailedToUpdateRecord    = 'Failed to apply record updates: %d rows updated.';
+  SErrRefreshNotSingleton     = 'Refresh SQL resulted in multiple records: %d.';
+  SErrRefreshEmptyResult      = 'Refresh SQL resulted in empty result set.';
+  SErrNoKeyFieldForRefreshClause = 'No key field found to construct refresh SQL WHERE clause';
+  SErrFailedToFetchReturningResult = 'Failed to fetch returning result';
+  SLogParamValue              = 'Parameter "%s" value : "%s"';
+  SErrInvalidDateTime         = 'Invalid date/time value : "%s"';
+  SatEOFInternalOnly          = 'loAtEOF is for internal use only.';
+  SErrInsertingSameRecordtwice = 'Attempt to insert the same record twice.';
+  SErrDoApplyUpdatesNeedsProxy = 'Cannot apply updates without Data proxy';
+
+Implementation
+
+end.

+ 986 - 0
src/packages/fcl-db/jsondataset.pas

@@ -0,0 +1,986 @@
+{$mode objfpc}
+{$h+}
+unit JSONDataset;
+
+interface
+
+uses
+  Types, JS, DB, Classes, SysUtils;
+
+type
+
+  { TJSONFieldMapper }
+  // This class is responsible for mapping the field objects of the records.
+  TJSONFieldMapper = Class(TObject)
+  Protected
+    // Return row TJSONData instance with data for field 'FieldName' or 'FieldIndex'.
+    Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : JSValue) : JSValue; virtual; abstract;
+    // Same, but now based on TField.
+    Function GetJSONDataForField(F : TField; Row : JSValue) : JSValue; virtual;
+    // Set data for field 'FieldName' or 'FieldIndex' to supplied TJSONData instance in row
+    procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : JSValue); virtual; abstract;
+    // Set data for field TField to supplied TJSONData instance
+    procedure SetJSONDataForField(F : TField; Row,Data : JSValue); virtual;
+    // Create a new row.
+    Function CreateRow : JSValue; virtual; abstract;
+  end;
+
+  // JSON has no date/time type, so we use a string field.
+  // ExtJS provides the date/time  format in it's field config: 'dateFormat'
+  // The below field classes store this in the NNNFormat field.
+  { TJSONDateField }
+
+  TJSONDateField = Class(TDateField)
+  private
+    FDateFormat: String;
+  Published
+    Property DateFormat : String Read FDateFormat Write FDateFormat;
+  end;
+
+  { TJSONTimeField }
+
+  TJSONTimeField = Class(TTimeField)
+  private
+    FTimeFormat: String;
+  Published
+    Property TimeFormat : String Read FTimeFormat Write FTimeFormat;
+  end;
+
+  { TJSONDateTimeField }
+
+  TJSONDateTimeField = Class(TDateTimeField)
+  private
+    FDateTimeFormat: String;
+  Published
+    Property DateTimeFormat : String Read FDateTimeFormat Write FDateTimeFormat;
+  end;
+
+  { TBaseJSONDataSet }
+
+  // basic JSON dataset. Does nothing ExtJS specific.
+  TBaseJSONDataSet = class (TDataSet)
+  private
+    FMUS: Boolean;
+    FOwnsData : Boolean;
+    FDefaultList : TFPList;
+    FCurrentList: TFPList;
+    FCurrent: Integer;
+    // Possible metadata to configure fields from.
+    FMetaData : TJSObject;
+    // This will contain the rows.
+    FRows : TJSArray;
+    FFieldMapper : TJSONFieldMapper;
+    // When editing, this object is edited.
+    FEditRow : JSValue;
+    procedure SetMetaData(AValue: TJSObject);
+    procedure SetRows(AValue: TJSArray);
+  protected
+    // dataset virtual methods
+    function AllocRecordBuffer: TDataRecord; override;
+    procedure FreeRecordBuffer(var Buffer: TDataRecord); override;
+    procedure InternalInitRecord(var Buffer: TDataRecord); override;
+    procedure GetBookmarkData(Buffer: TDataRecord; var Data: TBookmark); override;
+    function GetBookmarkFlag(Buffer: TDataRecord): TBookmarkFlag; override;
+    function GetRecord(Var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
+    function GetRecordSize: Word; override;
+    procedure AddToRows(AValue: TJSArray);
+    procedure InternalClose; override;
+    procedure InternalDelete; override;
+    procedure InternalFirst; override;
+    procedure InternalGotoBookmark(ABookmark: TBookmark); override;
+    procedure InternalLast; override;
+    procedure InternalOpen; override;
+    procedure InternalPost; override;
+    procedure InternalInsert; override;
+    procedure InternalEdit; override;
+    procedure InternalCancel; override;
+    procedure InternalInitFieldDefs; override;
+    procedure InternalSetToRecord(Buffer: TDataRecord); override;
+    function  GetFieldClass(FieldType: TFieldType): TFieldClass; override;
+    function IsCursorOpen: Boolean; override;
+    procedure SetBookmarkFlag(Var Buffer: TDataRecord; Value: TBookmarkFlag); override;
+    procedure SetBookmarkData(Var Buffer: TDataRecord; Data: TBookmark); override;
+    function GetRecordCount: Integer; override;
+    procedure SetRecNo(Value: Integer); override;
+    function GetRecNo: Integer; override;
+  Protected
+    // New methods.
+    // Called when dataset is closed. If OwnsData is true, metadata and rows are freed.
+    Procedure FreeData; virtual;
+    // Fill default list.
+    procedure AddToList; virtual;
+    Procedure FillList; virtual;
+    // Convert MetaData object to FieldDefs.
+    Procedure MetaDataToFieldDefs; virtual; abstract;
+    // Initialize Date/Time info in all date/time fields. Called during InternalOpen
+    procedure InitDateTimeFields; virtual;
+    // Convert JSON date S to DateTime for Field F
+    function ConvertDateTimeField(S: String; F: TField): TDateTime; virtual;
+    // Format JSON date to from DT for Field F
+    function FormatDateTimeField(DT : TDateTime; F: TField): String; virtual;
+    // Create fieldmapper. A descendent MUST implement this.
+    Function CreateFieldMapper : TJSONFieldMapper; virtual; abstract;
+    // If True, then the dataset will free MetaData and FRows when it is closed.
+    Property OwnsData : Boolean Read FownsData Write FOwnsData;
+    // set to true if unknown field types should be handled as string fields.
+    Property MapUnknownToStringType : Boolean Read FMUS Write FMUS;
+    // Metadata
+    Property MetaData : TJSObject Read FMetaData Write SetMetaData;
+    // Rows
+    Property Rows : TJSArray Read FRows Write SetRows;
+  public
+    constructor Create (AOwner: TComponent); override;
+    destructor Destroy; override;
+    function GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;  override;
+    procedure SetFieldData(Field: TField; var Buffer: TDatarecord; AValue : JSValue);  override;
+  published
+    Property FieldDefs;
+    // redeclared data set properties
+    property Active;
+    property BeforeOpen;
+    property AfterOpen;
+    property BeforeClose;
+    property AfterClose;
+    property BeforeInsert;
+    property AfterInsert;
+    property BeforeEdit;
+    property AfterEdit;
+    property BeforePost;
+    property AfterPost;
+    property BeforeCancel;
+    property AfterCancel;
+    property BeforeDelete;
+    property AfterDelete;
+    property BeforeScroll;
+    property AfterScroll;
+    property OnCalcFields;
+    property OnDeleteError;
+    property OnEditError;
+    property OnFilterRecord;
+    property OnNewRecord;
+    property OnPostError;
+  end;
+
+  { TExtJSJSONDataSet }
+
+  // Base for ExtJS datasets. It handles MetaData conversion.
+  TExtJSJSONDataSet = Class(TBaseJSONDataset)
+  Private
+    FFields : TJSArray;
+    FIDField: String;
+    FRoot: String;
+  Protected
+    // Data proxy support
+    Procedure InternalOpen; override;
+    function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; override;
+    Function DataPacketReceived(ARequest: TDataRequest) : Boolean; override;
+    Function GenerateMetaData : TJSObject;
+    function ConvertDateFormat(S: String): String; virtual;
+    Procedure MetaDataToFieldDefs; override;
+    procedure InitDateTimeFields; override;
+    function StringToFieldType(S: String): TFieldType;virtual;
+    function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer; virtual;
+  Public
+    // Can be set directly if the dataset is closed.
+    Property MetaData;
+    // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
+    Property Rows;
+    // Root of data array in data packet
+    property Root : String Read FRoot Write FRoot;
+    // property IDField
+    property IDField : String Read FIDField Write FIDField;
+  end;
+
+  { TExtJSJSONObjectDataSet }
+  // Use this dataset for data where the data is an array of objects.
+  TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
+  Protected
+    Function CreateFieldMapper : TJSONFieldMapper; override;
+  end;
+
+  { TExtJSJSONArrayDataSet }
+  // Use this dataset for data where the data is an array of arrays.
+  TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
+  Protected
+    Function CreateFieldMapper : TJSONFieldMapper; override;
+  end;
+
+  { TJSONObjectFieldMapper }
+  // Fieldmapper to be used when the data is in an object
+  TJSONObjectFieldMapper = Class(TJSONFieldMapper)
+  Public
+    procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : JSValue); override;
+    Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : JSValue) : JSValue; override;
+    Function CreateRow : JSValue; override;
+  end;
+
+  { TJSONArrayFieldMapper }
+  // Fieldmapper to be used when the data is in an array
+  TJSONArrayFieldMapper = Class(TJSONFieldMapper)
+  Public
+    procedure SetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row,Data : JSValue); override;
+    Function GetJSONDataForField(Const FieldName : String; FieldIndex : Integer; Row : JSValue) : JSValue; override;
+    Function CreateRow : JSValue; override;
+  end;
+
+  EJSONDataset = Class(EDatabaseError);
+  
+implementation
+
+uses DateUtils;
+
+
+{ TJSONFieldMapper }
+
+function TJSONFieldMapper.GetJSONDataForField(F: TField; Row: JSValue  ): JSValue;
+begin
+  // This supposes that Index is correct, i.e. the field positions have not been changed.
+  Result:=GetJSONDataForField(F.FieldName,F.Index,Row);
+end;
+
+procedure TJSONFieldMapper.SetJSONDataForField(F: TField; Row,Data: JSValue);
+begin
+  SetJSONDataForField(F.FieldName,F.Index,Row,Data);
+end;
+
+{ TJSONArrayDataSet }
+
+function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
+begin
+  Result:=TJSONArrayFieldMapper.Create;
+end;
+
+{ TJSONObjectDataSet }
+
+function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
+begin
+  Result:=TJSONObjectFieldMapper.Create;
+end;
+
+{ TJSONArrayFieldMapper }
+
+procedure TJSONArrayFieldMapper.SetJSONDataForField(const FieldName: String;
+  FieldIndex: Integer; Row, Data: JSValue);
+begin
+  TJSValueDynArray(Row)[FieldIndex]:=Data;
+end;
+
+function TJSONArrayFieldMapper.GetJSONDataForField(Const FieldName: String;
+  FieldIndex: Integer; Row: JSValue): JSValue;
+begin
+  Result:=TJSValueDynArray(Row)[FieldIndex];
+end;
+
+function TJSONArrayFieldMapper.CreateRow: JSValue;
+
+begin
+  Result:=TJSArray.New;
+end;
+
+{ TJSONObjectFieldMapper }
+
+procedure TJSONObjectFieldMapper.SetJSONDataForField(const FieldName: String;
+  FieldIndex: Integer; Row, Data: JSValue);
+begin
+  TJSObject(Row).Properties[FieldName]:=Data;
+end;
+
+function TJSONObjectFieldMapper.GetJSONDataForField(const FieldName: String;
+  FieldIndex: Integer; Row: JSValue): JSValue;
+begin
+  Result:=TJSObject(Row).Properties[FieldName];
+end;
+
+function TJSONObjectFieldMapper.CreateRow: JSValue;
+begin
+  Result:=TJSObject.New;
+end;
+
+procedure TBaseJSONDataSet.SetMetaData(AValue: TJSObject);
+begin
+  CheckInActive;
+  FMetaData:=AValue;
+end;
+
+procedure TBaseJSONDataSet.AddToRows(AValue: TJSArray);
+
+begin
+  if FRows=Nil then
+    FRows:=AValue
+  else
+    begin
+    FRows:=FRows.Concat(AValue);
+    AddToList;
+    end;
+end;
+
+procedure TBaseJSONDataSet.SetRows(AValue: TJSArray);
+begin
+  if AValue=FRows then exit;
+  CheckInActive;
+  FRows:=Nil;
+  AddToRows(AValue);
+end;
+
+
+function TBaseJSONDataSet.AllocRecordBuffer: TDataRecord;
+begin
+  Result.data:=TJSObject.New;
+  Result.bookmark:=null;
+  Result.state:=rsNew;
+end;
+
+// the next two are particularly ugly.
+procedure TBaseJSONDataSet.InternalInitRecord(var Buffer: TDataRecord);
+begin
+//  Writeln('TBaseJSONDataSet.InternalInitRecord');
+  Buffer.Data:=FFieldMapper.CreateRow;
+  Buffer.bookmark:=null;
+  Buffer.state:=rsNew;
+end;
+
+procedure TBaseJSONDataSet.FreeRecordBuffer (var Buffer: TDataRecord);
+begin
+  Buffer.Data:=Null;
+  Buffer.bookmark:=null;
+  Buffer.state:=rsNew;
+end;
+
+procedure TBaseJSONDataSet.GetBookmarkData(Buffer: TDataRecord; var Data: TBookmark);
+begin
+//  writeln('Bookmark :',Buffer.bookmark);
+  Data.Data:=Buffer.bookmark;
+end;
+
+function TBaseJSONDataSet.GetBookmarkFlag(Buffer: TDataRecord): TBookmarkFlag;
+begin
+  Result :=Buffer.BookmarkFlag;
+end;
+
+function TBaseJSONDataSet.GetRecNo: Integer;
+begin
+  Result := FCurrent + 1;
+end;
+
+procedure TBaseJSONDataSet.InternalInitFieldDefs;
+begin
+  If Assigned(FMetaData) then
+    MetaDataToFieldDefs;
+  if (FieldDefs.Count=0) then
+    Raise EJSONDataset.Create('No fields found');
+end;
+
+procedure TBaseJSONDataSet.FreeData;
+begin
+  If FOwnsData then
+    begin
+    FreeAndNil(FRows);
+    FreeAndNil(FMetaData);
+    end;
+  if (FCurrentList<>FDefaultList) then
+    FreeAndNil(FCurrentList)
+  else
+    FCurrentList:=Nil;
+  FreeAndNil(FDefaultList);
+  FreeAndNil(FFieldMapper);
+  FCurrentList:=Nil;
+end;
+
+procedure TBaseJSONDataSet.AddToList;
+
+Var
+  I : Integer;
+
+begin
+  For I:=FDefaultList.Count to FRows.Length-1 do
+    FDefaultList.Add(FRows[i]);
+end;
+
+procedure TBaseJSONDataSet.FillList;
+
+
+begin
+  FDefaultList:=TFPList.Create;
+  AddToList;
+  FCurrentList:=FDefaultList;
+end;
+
+Function  TExtJSJSONDataSet.StringToFieldType(S : String) : TFieldType;
+
+begin
+  if (s='int') then
+    Result:=ftLargeInt
+  else if (s='float') then
+    Result:=ftFloat
+  else if (s='boolean') then
+    Result:=ftBoolean
+  else if (s='date') then
+    Result:=ftDateTime
+  else if (s='string') or (s='auto') or (s='') then
+    Result:=ftString
+  else
+    if MapUnknownToStringType then
+      Result:=ftString
+    else
+      Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
+end;
+
+Function  TExtJSJSONDataSet.GetStringFieldLength(F : TJSObject; AName : String; AIndex : Integer) : integer;
+
+Var
+  I,L : Integer;
+  D : JSValue;
+
+begin
+  Result:=0;
+  D:=F.Properties['maxlen'];
+  if Not jsIsNan(toNumber(D)) then
+    begin
+    Result:=Trunc(toNumber(D));
+    if (Result<=0) then
+      Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
+    end
+  else
+    begin
+    For I:=0 to FRows.Length-1 do
+      begin
+      D:=FFieldMapper.GetJSONDataForField(Aname,AIndex,FRows[i]);
+      if isString(D) then
+        begin
+        l:=Length(String(D));
+        if L>Result then
+          Result:=L;
+        end;
+      end;
+    end;
+  if (Result=0) then
+    Result:=20;
+end;
+
+procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
+
+Var
+  A : TJSArray;
+  F : TJSObject;
+  I,J,FS : Integer;
+  N,idf : String;
+  ft: TFieldType;
+  D : JSValue;
+
+begin
+  FieldDefs.Clear;
+  D:=FMetadata.Properties['fields'];
+  if Not IsArray(D) then
+    Raise EJSONDataset.Create('Invalid metadata object');
+  A:=TJSArray(D);
+  For I:=0 to A.Length-1 do
+    begin
+    If Not isObject(A[i]) then
+      Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
+    F:=TJSObject(A[i]);
+    D:=F.Properties['name'];
+    If Not isString(D) then
+      Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
+    N:=String(D);
+    D:=F.Properties['type'];
+    If IsNull(D) or isUndefined(D) then
+      ft:=ftstring
+    else If Not isString(D) then
+      begin
+      Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
+      end
+    else
+      begin
+      ft:=StringToFieldType(String(D));
+      end;
+    if (ft=ftString) then
+      fs:=GetStringFieldLength(F,N,I)
+    else
+      fs:=0;
+    FieldDefs.Add(N,ft,fs);
+    end;
+  FFields:=A;
+end;
+
+procedure TExtJSJSONDataSet.InternalOpen;
+
+Var
+  I : integer;
+
+begin
+  inherited InternalOpen;
+  Writeln('Checking ID field ',IDField, ' as key field');
+  for I:=0 to Fields.Count-1 do
+    If SameText(Fields[i].FieldName,IDField) then
+      begin
+      Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
+      Writeln('Setting ID field ',IDField, ' as key field');
+      end;
+end;
+
+function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
+
+Var
+  D : JSValue;
+  O : TJSObject;
+  A : TJSArray;
+  I,RecordIndex : Integer;
+  FN : String;
+
+begin
+  Result:=True;
+  if anUpdate.OriginalStatus=usDeleted then
+    exit;
+  D:=anUpdate.ServerData;
+  If isNull(D) then
+    exit;
+  if not isNumber(AnUpdate.Bookmark.Data) then
+    exit(False);
+  RecordIndex:=Integer(AnUpdate.Bookmark.Data);
+  If isString(D) then
+    O:=TJSOBject(TJSJSON.Parse(String(D)))
+  else if isObject(D) then
+    O:=TJSOBject(D)
+  else
+    Exit(False);
+  if Not isArray(O[Root]) then
+    exit(False)
+  A:=TJSArray(O[Root]);
+  If A.Length=1 then
+    begin
+    O:=TJSObject(A[0]);
+    For I:=0 to Fields.Count-1 do
+      begin
+      if O.hasOwnProperty(Fields[i].FieldName) then
+        Self.FFieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
+      end;
+    end;
+end;
+
+function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
+
+Var
+  O : TJSObject;
+  A : TJSArray;
+
+begin
+  Result:=False;
+  If isNull(aRequest.Data) then
+    exit;
+  If isString(aRequest.Data) then
+    O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
+  else if isObject(aRequest.Data) then
+    O:=TJSOBject(aRequest.Data)
+  else
+    DatabaseError('Cannot handle data packet');
+  if (Root='') then
+    root:='rows';
+  if (IDField='') then
+    idField:='id';
+  if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
+    begin
+    if not Active then // Load fields from metadata
+      metaData:=TJSObject(o['metaData']);
+    // We must always check this one...
+    if metaData.hasOwnProperty('root') and isString(metaData['root']) then
+      Root:=string(metaData['root']);
+    if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
+      IDField:=string(metaData['idField']);
+    end;
+  if O.hasOwnProperty(Root) and isArray(o[Root]) then
+    begin
+    A:=TJSArray(o[Root]);
+    Result:=A.Length>0;
+    AddToRows(A);
+    end;
+end;
+
+function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
+
+Var
+  F : TJSArray;
+  O : TJSObject;
+  I,M : Integer;
+  T : STring;
+
+begin
+  Result:=TJSObject.New;
+  F:=TJSArray.New;
+  Result.Properties['fields']:=F;
+  For I:=0 to FieldDefs.Count -1 do
+    begin
+    O:=New(['name',FieldDefs[i].name]);
+    F.push(O);
+    M:=0;
+    case FieldDefs[i].DataType of
+      ftfixedchar,
+      ftString:
+        begin
+        T:='string';
+        M:=FieldDefs[i].Size;
+        end;
+      ftBoolean: T:='boolean';
+      ftDate,
+      ftTime,
+      ftDateTime: T:='date';
+      ftFloat: t:='float';
+      ftInteger,
+      ftAutoInc,
+      ftLargeInt : t:='int';
+    else
+      Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
+    end; // case
+    O.Properties['type']:=t;
+    if M<>0 then
+      O.Properties['maxlen']:=M;
+    end;
+  Result.Properties['root']:='rows';
+end;
+
+Function TExtJSJSONDataSet.ConvertDateFormat(S : String) : String;
+
+{ Not handled: N S w z W t L o O P T Z c U MS }
+
+begin
+  Result:=StringReplace(S,'y','yy',[rfReplaceall]);
+  Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
+  Result:=StringReplace(Result,'g','h',[rfReplaceall]);
+  Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
+  Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
+  Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
+  Result:=StringReplace(Result,'n','m',[rfReplaceall]);
+  Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
+  Result:=StringReplace(Result,'j','d',[rfReplaceall]);
+  Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
+  Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
+  Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
+  Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
+  Result:=LowerCase(Result);
+end;
+
+procedure TExtJSJSONDataSet.InitDateTimeFields;
+
+Var
+  F : TJSObject;
+  FF : TField;
+  I,J : Integer;
+  Fmt : String;
+  D : JSValue;
+
+begin
+  If (FFields=Nil) then
+    Exit;
+  For I:=0 to FFields.Length-1 do
+    begin
+    F:=TJSObject(FFields[i]);
+    D:=F.Properties['type'];
+    if isString(D) and (String(D)='date') then
+      begin
+      D:=F.Properties['dateFormat'];
+      if isString(D) then
+         begin
+         FMT:=ConvertDateFormat(String(D));
+         FF:=FindField(String(F.Properties['name']));
+         if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
+           begin
+           if FF is TJSONDateField then
+             TJSONDateField(FF).DateFormat:=Fmt
+           else if FF is TJSONTimeField then
+             TJSONTimeField(FF).TimeFormat:=Fmt
+           else if FF is TJSONDateTimeField then
+             TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
+           end;
+         end;
+      end;
+    end;
+end;
+
+function TBaseJSONDataSet.GetRecord(Var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
+begin
+  Result := grOK; // default
+  case GetMode of
+    gmNext: // move on
+      if fCurrent < fCurrentList.Count - 1 then
+        Inc (fCurrent)
+      else
+        Result := grEOF; // end of file
+    gmPrior: // move back
+      if fCurrent > 0 then
+        Dec (fCurrent)
+      else
+        Result := grBOF; // begin of file
+    gmCurrent: // check if empty
+      if fCurrent >= fCurrentList.Count then
+        Result := grEOF;
+  end;
+  if Result = grOK then // read the data
+    begin
+    Buffer.Data:=FRows[FCurrent];
+    Buffer.BookmarkFlag := bfCurrent;
+    Buffer.Bookmark:=fCurrent;
+    end;
+end;
+
+function TBaseJSONDataSet.GetRecordCount: Integer;
+begin
+  Result := FCurrentList.Count;
+end;
+
+function TBaseJSONDataSet.GetRecordSize: Word;
+begin
+  Result := 0; // actual data without house-keeping
+end;
+
+
+procedure TBaseJSONDataSet.InternalClose;
+begin
+  // disconnet and destroy field objects
+  BindFields (False);
+  if DefaultFields then
+    DestroyFields;
+  FreeData;
+end;
+
+procedure TBaseJSONDataSet.InternalDelete;
+
+Var
+  R : JSValue;
+
+begin
+  R:=JSValue(FCurrentList[FCurrent]);
+  FCurrentList.Delete(FCurrent);
+  if (FCurrent>=FCurrentList.Count) then
+    Dec(FCurrent);
+  FRows:=FRows.Splice(FCurrent,1);
+end;
+
+procedure TBaseJSONDataSet.InternalFirst;
+begin
+  FCurrent := -1;
+end;
+
+procedure TBaseJSONDataSet.InternalGotoBookmark(ABookmark: TBookmark);
+begin
+  if isNumber(ABookmark.Data) then
+    FCurrent:=Integer(ABookmark.Data);
+//  Writeln('Fcurrent', FCurrent,' from ',ABookmark.Data);
+end;
+
+procedure TBaseJSONDataSet.InternalInsert;
+
+Var
+  I : Integer;
+  D : TFieldDef;
+
+begin
+//  Writeln('TBaseJSONDataSet.InternalInsert');
+  FEditRow:=ActiveBuffer.Data;
+  For I:=0 to FieldDefs.Count-1 do
+    begin
+    D:=FieldDefs[i];
+    FFieldMapper.SetJSONDataForField(D.Name,D.Index,FEditRow,Null);
+    end;
+end;
+
+procedure TBaseJSONDataSet.InternalEdit;
+begin
+//  Writeln('TBaseJSONDataSet.InternalEdit:  ');
+  FEditRow:=TJSJSON.parse(TJSJSON.stringify(FRows[FCurrent]));
+//  Writeln('TBaseJSONDataSet.InternalEdit: ',FEditRow);
+end;
+
+procedure TBaseJSONDataSet.InternalCancel;
+begin
+
+end;
+
+procedure TBaseJSONDataSet.InternalLast;
+begin
+  // The first thing that will happen is a GetPrior Record.
+  FCurrent:=FCurrentList.Count;
+end;
+
+procedure TBaseJSONDataSet.InitDateTimeFields;
+
+begin
+  // Do nothing
+end;
+
+procedure TBaseJSONDataSet.InternalOpen;
+begin
+  FreeAndNil(FFieldMapper);
+  FFieldMapper:=CreateFieldMapper;
+  IF (FRows=Nil) then // opening from fielddefs ?
+    begin
+    FRows:=TJSArray.New;
+    OwnsData:=True;
+    end;
+  FillList;
+  InternalInitFieldDefs;
+  if DefaultFields then
+    CreateFields;
+  BindFields (True);
+  InitDateTimeFields;
+  FCurrent := -1;
+end;
+
+procedure TBaseJSONDataSet.InternalPost;
+
+Var
+  RI,I : integer;
+  B : TBookmark;
+
+begin
+  GetBookMarkData(ActiveBuffer,B);
+  if (State=dsInsert) then
+    begin // Insert or Append
+    FRows.push(FEditRow);
+    if GetBookMarkFlag(ActiveBuffer)=bfEOF then
+      begin // Append
+      FDefaultList.Add(FEditRow);
+      if (FCurrentList<>FDefaultList) then
+        FCurrentList.Add(FEditRow);
+      end
+    else  // insert
+      begin
+      FCurrentList.Insert(FCurrent,FEditRow);
+      if (FCurrentList<>FDefaultList) then
+        FDefaultList.Add(FEditRow);
+      end;
+    end
+  else
+    begin // Edit
+    RI:=FRows.IndexOf(JSValue(FCurrentList[FCurrent]));
+    if (RI<>-1) then
+      FRows[RI]:=FEditRow
+    else
+      FRows.push(FEditRow);
+    FCurrentList[FCurrent]:=FEditRow;
+    if (FCurrentList<>FDefaultList) then
+      FDefaultList[FCurrent]:=FEditRow;
+    end;
+  FEditRow:=Nil;
+end;
+
+procedure TBaseJSONDataSet.InternalSetToRecord(Buffer: TDataRecord);
+begin
+  FCurrent := Integer(Bookmark.Data);
+end;
+
+function TBaseJSONDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
+begin
+  case FieldType of
+    ftDate : Result:=TJSONDateField;
+    ftDateTime : Result:=TJSONDateTimeField;
+    ftTime : Result:=TJSONTimeField;
+  else
+    Result:=inherited GetFieldClass(FieldType);
+  end;
+end;
+
+function TBaseJSONDataSet.IsCursorOpen: Boolean;
+begin
+  Result := Assigned(FDefaultList);
+end;
+
+procedure TBaseJSONDataSet.SetBookmarkData(var Buffer: TDataRecord;  Data: TBookmark);
+begin
+  Buffer.Bookmark:=Data.Data;
+//  Writeln('Set Bookmark from: ',Data.Data);
+end;
+
+function TBaseJSONDataSet.ConvertDateTimeField(S : String; F : TField) : TDateTime;
+
+Var
+  Ptrn : string;
+
+begin
+  Result:=0;
+  Case F.DataType of
+    ftDate : Ptrn:=TJSONDateField(F).DateFormat;
+    ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
+    ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
+  end;
+  If (Ptrn='') then
+    Case F.DataType of
+      ftDate : Result:=StrToDate(S);
+      ftTime : Result:=StrToTime(S);
+      ftDateTime : Result:=StrToDateTime(S);
+    end
+  else
+    begin
+    Result:=ScanDateTime(ptrn,S,1);
+    end;
+end;
+
+function TBaseJSONDataSet.FormatDateTimeField(DT: TDateTime; F: TField
+  ): String;
+
+Var
+  Ptrn : string;
+begin
+  Result:='';
+  Case F.DataType of
+    ftDate : Ptrn:=TJSONDateField(F).DateFormat;
+    ftTime : Ptrn:=TJSONTimeField(F).TimeFormat;
+    ftDateTime : Ptrn:=TJSONDateTimeField(F).DateTimeFormat;
+  end;
+  If (Ptrn='') then
+    Case F.DataType of
+      ftDate : Result:=DateToStr(DT);
+      ftTime : Result:=TimeToStr(DT);
+      ftDateTime : Result:=DateTimeToStr(DT);
+    end
+  else
+    Result:=FormatDateTime(ptrn,DT);
+end;
+
+function TBaseJSONDataSet.GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;
+
+var
+  R : JSValue;
+
+begin
+//  Writeln('Getting data for field ',Field.FieldName,'Buffer  ',Buffer);
+  if (FEditRow<>Nil) then
+    R:=FEditRow
+  else
+    R:=Buffer.data;
+  Result:=FFieldMapper.GetJSONDataForField(Field,R);
+end;
+
+procedure TBaseJSONDataSet.SetFieldData(Field: TField; var Buffer: TDatarecord; AValue : JSValue);
+
+begin
+  FFieldMapper.SetJSONDataForField(Field,FEditRow,AValue);
+//  FFieldMapper.SetJSONDataForField(Field,Buffer.Data,AValue);
+end;
+
+procedure TBaseJSONDataSet.SetBookmarkFlag(var Buffer: TDataRecord; Value: TBookmarkFlag);
+
+begin
+  Buffer.BookmarkFlag := Value;
+end;
+
+procedure TBaseJSONDataSet.SetRecNo(Value: Integer);
+begin
+  if (Value < 0) or (Value > FCurrentList.Count) then
+    raise EJSONDataset.CreateFmt('SetRecNo: index %d out of range',[Value]);
+  FCurrent := Value - 1;
+  Resync([]); 
+  DoAfterScroll;
+end;
+
+constructor TBaseJSONDataSet.Create(AOwner: TComponent);
+begin
+  inherited;
+  FownsData:=True;
+end;
+
+destructor TBaseJSONDataSet.Destroy;
+begin
+  FreeData;
+  inherited;
+end;
+
+end.

+ 53 - 0
src/packages/fcl-db/pas2js_fcldb.lpk

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <Package Version="4">
+    <Name Value="pas2js_fcldb"/>
+    <Type Value="RunTimeOnly"/>
+    <AutoUpdate Value="Manually"/>
+    <CompilerOptions>
+      <Version Value="11"/>
+      <SearchPaths>
+        <UnitOutputDirectory Value="."/>
+      </SearchPaths>
+      <Other>
+        <ExecuteBefore>
+          <Command Value="$MakeExe(pas2js) -O- pas2js_fcldb.pas"/>
+          <ScanForFPCMsgs Value="True"/>
+        </ExecuteBefore>
+      </Other>
+      <SkipCompiler Value="True"/>
+    </CompilerOptions>
+    <Files Count="4">
+      <Item1>
+        <Filename Value="db.pas"/>
+        <UnitName Value="db"/>
+      </Item1>
+      <Item2>
+        <Filename Value="dbconst.pas"/>
+        <UnitName Value="dbconst"/>
+      </Item2>
+      <Item3>
+        <Filename Value="jsondataset.pas"/>
+        <UnitName Value="jsondataset"/>
+      </Item3>
+      <Item4>
+        <Filename Value="restconnection.pas"/>
+        <UnitName Value="restconnection"/>
+      </Item4>
+    </Files>
+    <RequiredPkgs Count="2">
+      <Item1>
+        <PackageName Value="pas2js_rtl"/>
+      </Item1>
+      <Item2>
+        <PackageName Value="FCL"/>
+      </Item2>
+    </RequiredPkgs>
+    <UsageOptions>
+      <UnitPath Value="$(PkgOutDir)"/>
+    </UsageOptions>
+    <PublishOptions>
+      <Version Value="2"/>
+    </PublishOptions>
+  </Package>
+</CONFIG>

+ 15 - 0
src/packages/fcl-db/pas2js_fcldb.pas

@@ -0,0 +1,15 @@
+{ This file was automatically created by Lazarus. Do not edit!
+  This source is only used to compile and install the package.
+ }
+
+unit pas2js_fcldb;
+
+{$warn 5023 off : no warning about unused units}
+interface
+
+uses
+  db, dbconst, jsondataset;
+
+implementation
+
+end.

Some files were not shown because too many files changed in this diff