Jump to content

lynkfs@gmail.com

Moderators
  • Content count

    313
  • Joined

  • Last visited

  • Days Won

    68

lynkfs@gmail.com last won the day on December 13 2017

lynkfs@gmail.com had the most liked content!

About lynkfs@gmail.com

  • Rank

Profile Information

  • Gender
    Male
  • Location
    Australia

Recent Profile Visitors

188 profile views
  1. architecture

    For some this is the ultimate in application architecture, a set of completely separated layers with clearly defined tasks. One of the reasons would be that this type of application architecture facilitates client-server applications where these layers can be distributed over one or more servers. Like 'client'-'applicationserver'-'databaseserver'. In practice this is usually not so clear-cut. Business logic can be partly implemented on the client, partly on the server (and/or partly in the database as well). In case of server-side rendering most of the presentation tasks are transferred to the server, which leaves only razor-thin clients. And logical servers can be split over multiple physical servers too. Nevertheless it is a nice way of separating areas of concern. Having these separate layers, it must be possible to establish some kind of communication between them and this post is about some of the available mechanisms in Smart. Business Layer Let's say we have a completely separated business logic layer with a data-structure of sorts and all necessary functions to maintain this data. As an example see below the layer for a "ToDo" list type of application. type JToDoPerson = class id : integer; name : string; end; type JToDoTask = class id : integer; name : string; owner : string; statusidx : integer; end; type JToDoList = class tasks : Array of JToDoTask; persons : Array of JToDoPerson; statuses : array[0..2] of string := ['todo','in progress','done']; task : JToDoTask; person : JToDoPerson; constructor create; virtual; procedure addTask(TaskId: integer; TaskName, Source: string); procedure delTask(TaskId: integer; TaskName, Source: string); procedure updTask(TaskId: integer; owner: string; status: integer; Source: string); function getTask(TaskId: integer) : JToDoTask; procedure addPerson(PersonName, Source: string); procedure delPerson(PersonName, Source: string); end; In this demo, only the 'addPerson' method (adding a team-member to the team), is used. Procedure JToDoList.addPerson(PersonName, Source: string); begin person := JToDoPerson.Create; person.name := PersonName; inc(guid); person.id := guid; persons.add(person); end; Communication between Presentation layer and Business Logic layer. In Smart the presentation layer is the collection of Forms and their visual components. Assuming there is a form with a button somewhere which, when clicked, adds a new team-member, there are various ways in which communication between the business-logic and the presentation layer can happen. 1) simply call the appropriate method from the button onclick handler // 01 add person by method call todolist.addPerson('name01','W3Button1'); 2) use a custom event ('addPerson' event) // 02 add person by sending a custom event; asm @event = new CustomEvent('addPerson', { detail: ['name02','W3Button1'] }); end; browserapi.document.dispatchEvent(event); and in the constructor of the todo-list set up an event listener : // event listener for custom event browserapi.document.addEventListener('addPerson', procedure(e: variant) begin todolist.AddPerson(e.detail[0],e.detail[1]); end, false); 3) use a generic event ('businessLogic' event) and check for event-type // 03 add person by sending a generic event; var msg : variant := TVariant.CreateObject; msg.type := 'addPerson'; msg.payload01 := 'name03'; msg.payload02 := 'W3Button1'; var payload : variant := new JObject; payload.detail := JSON.stringify(msg); asm @event = new CustomEvent('businessLogic', @payload); end; browserapi.document.dispatchEvent(event); and in the constructor of the todo-list set up an event listener : // event listener for generic event browserapi.document.addEventListener('businessLogic', procedure(e: variant) begin var msg := JSON.parse(e.detail); If msg.type = 'addPerson' then begin todolist.addPerson(msg.payload01, msg.payload02); end; end, false); 4) use an inter-process event ('postMessage' event) // 04 add person by sending postMessage; browserapi.window.postMessage(['addPerson','name04','W3Button1'],'*'); and in the constructor of the todo-list set up an event listener : browserapi.window.onmessage := procedure(evt:variant) begin if evt.data[0] = 'addPerson' then begin todolist.AddPerson(evt.data[1],evt.data[2]); //'name01','W3Button1' end; end; There are subtle differences between event-methods 2-4, but essentially they perform very similar. I personally prefer using postMessage (4) as it involves the least amount of coding. All of these methods work for single-device type applications (all 3 layers on client) and on fat-client client/server type applications (data on server, rest on client). 5) use web-sockets as communication method between server and client // 05 add person by sending a 'message' message using websocket; var msg : variant := TVariant.CreateObject; msg.type := 'addPerson'; msg.payload01 := 'name05'; msg.payload02 := 'W3Button1'; ws.send(JSON.stringify(msg)); and in the constructor of the todo-list set up an event listener : asm @ws = new WebSocket('ws://localhost:1234', 'echo-protocol'); end; ws.addEventListener('message', procedure(e: variant) begin var msg := JSON.parse(e.data); If msg.type = 'addPerson' then begin todolist.AddPerson(msg.payload01, msg.payload02); end; end, false); This method works for both fat-client and thin-client client/server applications. The server used here is a simple node echo server ( see featured demos ) or, alternatively, in plain js : //Create server and listen var http = require('http'); var server = http.createServer(function(request, response) {}); server.listen(1234, function() { console.log((new Date()) + ' Server is listening on port 1234'); }); //Create Web Socket Server var WebSocketServer = require('websocket').server; wsServer = new WebSocketServer({ httpServer: server }); var count = 0; var clients = {}; //Listening for Connections wsServer.on('request', function(r){ // Code here to run on connection //Accept the connection var connection = r.accept('echo-protocol', r.origin); // Specific id for this client & increment count var id = count++; // Store the connection method so we can loop through & contact all clients clients[id] = connection; console.log((new Date()) + ' Connection accepted [' + id + ']'); //Listen for incoming messages and broadcast // Create event listener connection.on('message', function(message) { // The string message that was sent to us var msgString = message.utf8Data; console.log('received ' + msgString); // Loop through all clients for(var i in clients){ // Send a message to the client with the message clients[i].sendUTF(msgString); var j = i + 1; console.log('sent ' + msgString + ' to client ' + j); } }); //Listen for client disconnecting connection.on('close', function(reasonCode, description) { delete clients[id]; console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); }); }); The funny thing is that this server propagates these events to all connected clients. So adding a team-member on any client auto-updates the team array on all other clients, without any user interaction. Cool. Communication between Presentation layer and data layer. Data-binding is a mechanism which synchronises data-elements with UI-components. The above event-mechanisms could conceivably also be used to implement a simple data-binding mechanism. The addPerson method f.i., once invoked, can send a message to the presentation layer ('personAdded') person := JToDoPerson.Create; person.name := PersonName; inc(guid); person.id := guid; persons.add(person); asm @event = new CustomEvent('personAdded', { detail: PersonName }); end; browserapi.document.dispatchEvent(event); which in it's turn can sync UI-components (TMemo) with the new data : // a Form's 'InitializeObject' section browserapi.document.addEventListener('personAdded', procedure(e: variant) begin W3Memo1.Text := W3Memo1.Text + #10 + e.detail; end, false); Project-source : <SMART> <Project version="2" subversion="9"> <Name>temp</Name> <Created>T00:00:00.000</Created> <Modified>2018-01-17T14:26:47.488</Modified> <Version> <Major>0</Major> <Minor>0</Minor> <Revision>0</Revision> </Version> <VendorSpecific> <Apple> <FormatDetection>1</FormatDetection> <StatusBarStyle>black</StatusBarStyle> <WebAppCapable>1</WebAppCapable> </Apple> <ChromeApp> <Kiosk>0</Kiosk> <KioskOnly>1</KioskOnly> <OfflineEnabled>1</OfflineEnabled> </ChromeApp> <Cordova> <WidgetID></WidgetID> <AllowIntent>http://*/* https://*/* tel:* sms:* mailto:* geo:*</AllowIntent> </Cordova> </VendorSpecific> <Options> <Compiler> <Assertions>1</Assertions> <Optimize>1</Optimize> <HintsLevel>1</HintsLevel> <ProjectSearchPath></ProjectSearchPath> </Compiler> <Codegen> <Obfuscation>0</Obfuscation> <RangeChecking>0</RangeChecking> <InstanceChecking>0</InstanceChecking> <ConditionChecking>0</ConditionChecking> <LoopChecking>0</LoopChecking> <InlineMagics>1</InlineMagics> <IgnorePublishedInImplementation>0</IgnorePublishedInImplementation> <EmitSourceLocation>0</EmitSourceLocation> <EmitRTTI>0</EmitRTTI> <Devirtualize>1</Devirtualize> <MainBody>1</MainBody> <CodePacking>0</CodePacking> <SmartLinking>1</SmartLinking> <Verbosity>1</Verbosity> </Codegen> <ConditionalDefines> <HandleExceptions>1</HandleExceptions> <AutoRefresh>0</AutoRefresh> <LegacySupportForIE>0</LegacySupportForIE> </ConditionalDefines> <Linker> <SourceMap>0</SourceMap> <CompressCSS>0</CompressCSS> <GenerateAppCacheManifest>1</GenerateAppCacheManifest> <GenerateChromeAppManifest>0</GenerateChromeAppManifest> <GenerateFireFoxManifest>0</GenerateFireFoxManifest> <GenerateWebAppManifest>1</GenerateWebAppManifest> <GenerateWidgetPackageConfigXML>0</GenerateWidgetPackageConfigXML> <GenerateCordovaConfigXML>0</GenerateCordovaConfigXML> <ExternalCSS>1</ExternalCSS> <Theme>default.css</Theme> <CustomTheme>0</CustomTheme> <EmbedJavaScript>1</EmbedJavaScript> </Linker> <Output> <JavaScriptFileName>main.js</JavaScriptFileName> <HtmlFileName>index.html</HtmlFileName> <OutputFilePath>www\</OutputFilePath> </Output> <Import /> <Execute> <ServeManifest>0</ServeManifest> <Server>1</Server> <CustomFile></CustomFile> <LoadCustomFile>0</LoadCustomFile> <PauseAfterExecution>0</PauseAfterExecution> <ExecuteType>0</ExecuteType> </Execute> <WebFonts> <usewebfonts>1</usewebfonts> <webfontitem> <fontname>Ubuntu</fontname> <fonturl>https://fonts.googleapis.com/css?family=Ubuntu</fonturl> </webfontitem> <webfontitem> <fontname>Ubuntu</fontname> <fonturl>https://fonts.googleapis.com/css?family=Ubuntu</fonturl> </webfontitem> </WebFonts> </Options> <Files> <File type="main"> <Name>temp</Name> <Created>2018-01-16T18:32:05.128Z</Created> <Modified>2018-01-17T11:03:42.612</Modified> <Source> <![CDATA[uses Globals, ecma.json, SmartCL.System, Unit1; {$IFDEF SMART_INTERNAL_HANDLE_EXCEPTIONS} try {$ENDIF} var Application := TApplication.Create; Application.RunApp; /* //check if local storage exists. If yes then writeln('getting data'); var todolistjson := JSON.parse(browserapi.window.localStorage.getItem("todolist")); writeln(todolistjson.persons.length); //and then fill up todolist from todolistjson; browserapi.document.addEventListener('savedata', procedure(e: variant) begin writeln('saving data'); browserapi.window.localStorage.setItem("todolist", JSON.stringify(e.detail)); //todolist end, false); */ {$IFDEF SMART_INTERNAL_HANDLE_EXCEPTIONS} except on e: Exception do ShowMessage(e.Message); end; {$ENDIF} ]]> </Source> </File> <File type="unit"> <Name>Unit1</Name> <Created>2018-01-16T18:32:05.128Z</Created> <Modified>2018-01-16T20:46:22.946</Modified> <Source> <![CDATA[unit Unit1; interface uses Pseudo.CreateForms, // auto-generated unit that creates forms during startup System.Types, SmartCL.System, SmartCL.Components, SmartCL.Forms, SmartCL.Application, Form1; type TApplication = class(TW3CustomApplication) end; implementation end.]]> </Source> </File> <File type="form"> <Name>Form1</Name> <Created>2018-01-16T18:32:05.128Z</Created> <Modified>2018-01-17T14:26:47.486</Modified> <Source> <![CDATA[unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, busLogic, Ecma.json, SmartCL.Controls.Button, SmartCL.Controls.Memo, SmartCL.Controls.RadioGroup; type TForm1 = class(TW3Form) procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end; implementation uses Globals; { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin If W3RadioGroup1.Items[0].Checked then begin // 01 add person by method call todolist.addPerson('name01','W3Button1'); end; If W3RadioGroup1.Items[1].Checked then begin // 02 add person by sending custom event; asm @event = new CustomEvent('addPerson', { detail: ['name02','W3Button1'] }); end; browserapi.document.dispatchEvent(event); end; If W3RadioGroup1.Items[2].Checked then begin // 03 add person by sending generic event; var msg : variant := TVariant.CreateObject; msg.type := 'addPerson'; msg.payload01 := 'name03'; msg.payload02 := 'W3Button1'; var payload : variant := new JObject; payload.detail := JSON.stringify(msg); asm @event = new CustomEvent('businessLogic', @payload); end; browserapi.document.dispatchEvent(event); end; If W3RadioGroup1.Items[3].Checked then begin // 04 add person by sending postMessage; browserapi.window.postMessage(['addPerson','name04','W3Button1'],'*'); end; If W3RadioGroup1.Items[4].Checked then begin // 05 add person by sending sendMessage using websocket; var msg : variant := TVariant.CreateObject; msg.type := 'addPerson'; msg.payload01 := 'name05'; msg.payload02 := 'W3Button1'; ws.send(JSON.stringify(msg)); end; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} W3RadioGroup1.Add('01 - method call'); W3RadioGroup1.Add('02 - custom event'); W3RadioGroup1.Add('03 - generic event'); W3RadioGroup1.Add('04 - postMessage'); W3RadioGroup1.Add('05 - websocket'); W3Memo1.Text := 'List of persons'; browserapi.document.addEventListener('personAdded', procedure(e: variant) begin writeln('personAdded'); W3Memo1.Text := W3Memo1.Text + #10 + e.detail; end, false); end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.]]> </Source> <Design> <![CDATA[<?xml version="1.0" encoding="utf-16"?> <Form version="2" subversion="9"> <Created>2018-01-16T18:32:05.128</Created> <Modified>2018-01-17T14:25:27.765</Modified> <object type="TW3Form"> <Caption>W3Form</Caption> <Name>Form1</Name> <object type="TW3Button"> <Caption>add person</Caption> <Width>134</Width> <Top>240</Top> <Left>104</Left> <Height>32</Height> <Name>W3Button1</Name> <OnClick>W3Button1Click</OnClick> </object> <object type="TW3Memo"> <Text>W3Memo</Text> <Width>184</Width> <Top>288</Top> <Left>80</Left> <Height>192</Height> <Name>W3Memo1</Name> </object> <object type="TW3RadioGroup"> <Width>285</Width> <Top>32</Top> <Left>32</Left> <Height>196</Height> <Name>W3RadioGroup1</Name> </object> </object> </Form>]]> </Design> <AutoCreate> <IsAutoCreate>1</IsAutoCreate> <IsMainForm>1</IsMainForm> <Order>1</Order> </AutoCreate> </File> <File type="unit"> <Name>busLogic</Name> <Created>2018-01-16T18:33:01.624Z</Created> <Modified>2018-01-17T11:29:14.034</Modified> <Source> <![CDATA[unit busLogic; interface uses System.Types, System.Types.Convert, SmartCL.System; type JToDoPerson = class id : integer; name : string; end; type JToDoTask = class id : integer; name : string; owner : string; statusidx : integer; end; type JToDoList = class tasks : Array of JToDoTask; persons : Array of JToDoPerson; statuses : array[0..2] of string := ['todo','in progress','done']; task : JToDoTask; person : JToDoPerson; constructor create; virtual; procedure addTask(TaskId: integer; TaskName, Source: string); procedure delTask(TaskId: integer; TaskName, Source: string); procedure updTask(TaskId: integer; owner: string; status: integer; Source: string); function getTask(TaskId: integer) : JToDoTask; procedure addPerson(PersonName, Source: string); procedure delPerson(PersonName, Source: string); function LoadList: JToDoList; function GetList: JToDoList; end; implementation uses Globals, Ecma.json; Constructor JToDoList.Create; begin inherited Create; // reacts to custom event browserapi.document.addEventListener('addPerson', procedure(e: variant) begin todolist.AddPerson(e.detail[0],e.detail[1]); end, false); // reacts to generic event browserapi.document.addEventListener('businessLogic', procedure(e: variant) begin var msg := JSON.parse(e.detail); If msg.type = 'addPerson' then begin todolist.addPerson(msg.payload01, msg.payload02); end; end, false); // reacts to postMessage browserapi.window.onmessage := procedure(evt:variant) begin if evt.data[0] = 'addPerson' then begin todolist.AddPerson(evt.data[1],evt.data[2]); //'name01','W3Button1' writeln(todolist.persons[todolist.persons.count-1].id); writeln(todolist.persons[todolist.persons.count-1].name); asm @event = new CustomEvent('savedata', { detail: todolist }); end; browserapi.document.dispatchEvent(event); end; end; // reacts to sendMessage using websockets asm @ws = new WebSocket('ws://localhost:1234', 'echo-protocol'); end; ws.addEventListener('message', procedure(e: variant) begin var msg := JSON.parse(e.data); If msg.type = 'addPerson' then begin todolist.AddPerson(msg.payload01, msg.payload02); end; end, false); end; Procedure JToDoList.addTask(TaskId: integer; TaskName, Source: string); begin task := JToDoTask.Create; task.id := TaskId; task.name := TaskName; task.owner := ''; task.statusidx := 0; tasks.add(task); end; Procedure JToDoList.delTask(TaskId: integer; TaskName, Source: string); begin for var i := 0 to tasks.length -1 do begin if tasks[i].id = TaskId then begin tasks.Delete(i); end; end; end; Procedure JToDoList.updTask(TaskId: integer; owner: string; status: integer; Source: string); begin for var i := 0 to tasks.length -1 do begin if tasks[i].id = TaskId then begin tasks[i].owner := owner; tasks[i].statusidx := status; end; end; end; Function JToDoList.getTask(TaskId: integer) : JToDoTask; begin for var i := 0 to tasks.length -1 do begin if tasks[i].id = TaskId then begin result := tasks[i]; end; end; end; Function JToDoList.LoadList: JToDoList; begin // end; Function JToDoList.GetList: JToDoList; begin // end; Procedure JToDoList.addPerson(PersonName, Source: string); begin person := JToDoPerson.Create; person.name := PersonName; inc(guid); person.id := guid; persons.add(person); asm @event = new CustomEvent('personAdded', { detail: PersonName }); end; browserapi.document.dispatchEvent(event); end; Procedure JToDoList.delPerson(PersonName, Source: string); begin for var i := 0 to persons.length -1 do begin if persons[i].name = PersonName then begin person := persons[i]; for var j := 0 to tasks.length -1 do begin if tasks[j].owner = PersonName then begin If tasks[j].statusidx = 1 then begin //if 'in progress' tasks[j].statusidx := 0; //reset to 'todo' tasks[j].owner := ''; //unassign task end; end; end; end; end; end; end. ]]> </Source> </File> <File type="unit"> <Name>globals</Name> <Created>2018-01-16T18:41:47.853Z</Created> <Modified>2018-01-17T11:12:16.808</Modified> <Source> <![CDATA[unit globals; interface uses System.Types, System.Types.Convert, SmartCL.System, busLogic; var todolist : JToDoList; event: variant; guid : integer; ws : variant; implementation initialization // ToDoList := JToDoList.Create; guid := 0; end. ]]> </Source> </File> </Files> <Target>Browser</Target> <Generator>Visual Components Project</Generator> </Project> </SMART>
  2. Smart Test Framework

    Thank you, great (System.diagnostics ==> smartcl.diagnostics)
  3. shoestring framework

    The documentation for the native framework can be found here
  4. Vibration API

    try this browserapi.window.navigator.vibrate(200); // vibrate for 200ms browserapi.window.navigator.vibrate([100,30,100,30,100,200,200,30,200,30,200,200,100,30,100,30,100]); // Vibrate 'SOS' in Morse. The vibration api does not work on ios safari, opera and edge
  5. SQLite Loadfromstream

    This code snippet saves a SQLite database to local storage and reloads it again. All goes fine up till the end (LoadFromStream) but leaves the db in an inactive state Any suggestions ? var MyStream : TStringStream; MyStream := TStringStream.Create; db.SaveToStream(MyStream,true); var textdata3 := MyStream.DataString; //save textdata3 to local storage var LocalStorage2 := TW3LocalStorage.Create; LocalStorage2.Open('sqlite'); LocalStorage2.SetKeyStr('stream',textdata3); LocalStorage2.Close; //read textdata3 from local storage LocalStorage2.Open('sqlite'); textdata3 := LocalStorage2.GetKeyStr('stream','unknown'); LocalStorage2.Close; MyStream.DataString := textdata3; db.LoadFromStream(MyStream); //all good up to here writeln(db.active); //false
  6. Database Examples

    Here is a dogs breakfast of internal data structures and conversions between them step-1 set up and populate data structure (business unit with multiple departments with staff per department) step-2 manipulation in code step-3 convert record structure to json text step-4 define and populate TW3Dataset from json step-5 save json to local storage (persistent per device) step-6 reload json from local storage step-7 define and populate SQLite db from json step-8 save json to server step-9 reload json from server At the end of the day all these structures can be converted to some kind of string (text, json, streamstring) and then saved to local storage. When running in the browser that is the only way to save data, which gives you device-persistence. Persistence of data over multiple devices is only possible by storing data on a server. unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Button, ECMA.JSON, System.DataSet, SmartCL.Storage.Local, System.Sqlite, W3C.XMLHttpRequest, W3C.HTML5, W3C.DOM; type TForm1 = class(TW3Form) procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; Procedure DoStuff; end; // record types type JStrC = record firstname : string; //external 'firstname'; surname : string; //external 'surname'; end; JStrB = record deptname : string; //external 'deptname'; staff : Array of JStrC; //external 'staff'; end; JStrA = record business : string; //external 'business' departments : Array of JStrB; //external 'departments'; end; implementation { TForm1 } Procedure TForm1.DoStuff; begin // // step1 : set up a leveled structure, (either records or classes) // i.e. business unit with departments with employees per department writeln('Step1'); // //business unit var StrA01 : JStrA; StrA01.business := 'lynkfs'; //department marketing var StrB01 : JStrB; StrB01.deptname := 'marketing'; //staff Keith Ansell var StrC01 : JStrC; StrC01.firstname := 'Claire'; StrC01.surname := 'Adams'; StrB01.staff.Add(StrC01); //staff Charles Bailey var StrC02 : JStrC; StrC02.firstname := 'Vicky'; StrC02.surname := 'Anderson'; StrB01.staff.Add(StrC02); StrA01.departments.Add(StrB01); //department frontdesk var StrB02 : JStrB; StrB02.deptname := 'front desk'; //staff Keith Ansell var StrC03 : JStrC; StrC03.firstname := 'Keith'; StrC03.surname := 'Ansell'; StrB02.staff.Add(StrC03); //staff Charles Bailey var StrC04 : JStrC; StrC04.firstname := 'Charles'; StrC04.surname := 'Bailey'; StrB02.staff.Add(StrC04); StrA01.departments.Add(StrB02); // check writeln(inttostr(StrA01.departments.length) + ' departments'); for var i := 0 to StrA01.departments.length -1 do begin writeln(StrA01.departments[i].deptname + ' ' + inttostr(StrA01.departments[i].staff.length) + ' staff'); for var j := 0 to StrA01.departments[i].staff.length -1 do begin writeln(StrA01.departments[i].staff[j].firstname + ' ' + StrA01.departments[i].staff[j].surname); end; end; // // step2 : depending on business logic, add/delete/change data in these arrays at will writeln('Step2'); // // employee Claire Adams leaves StrA01.departments[0].staff.Delete(0); //or coding to that effect // a new empty department is created var StrB : JStrB; StrB.deptname := 'new department'; StrA01.departments.add(StrB); // check writeln(inttostr(StrA01.departments.length) + ' departments'); for var i := 0 to StrA01.departments.length -1 do begin writeln(StrA01.departments[i].deptname + ' ' + inttostr(StrA01.departments[i].staff.length) + ' staff'); for var j := 0 to StrA01.departments[i].staff.length -1 do begin writeln(StrA01.departments[i].staff[j].firstname + ' ' + StrA01.departments[i].staff[j].surname); end; end; // // step3 : Turn object to json writeln('Step3'); // var textdata: string := ''; asm @textdata = JSON.stringify(@StrA01); console.log(@textdata); // {"business":"lynkfs","departments":[{"deptname":"marketing","staff":[{"firstname":"Vicky","surname":"Anderson"}]},{"deptname":"front desk","staff":[{"firstname":"Keith","surname":"Ansell"},{"firstname":"Charles","surname":"Bailey"}]},{"deptname":"new department","staff":[]}]} end; // // step4 : Define and populate TW3DataSet from json writeln('Step4'); // var DataSet := TW3DataSet.Create; // note TW3Dataset is not leveled Dataset.FieldDefs.Add('business',ftString); Dataset.FieldDefs.Add('deptname',ftString); Dataset.FieldDefs.Add('firstname',ftString); Dataset.FieldDefs.Add('surname',ftString); DataSet.CreateDataset; var v := json.parse(textdata); For var i := 0 to v.departments.length -1 do begin For var j := 0 to v.departments[i].staff.length -1 do begin DataSet.Append; Dataset.Fields.FieldByName('business').AsString := v.business; Dataset.Fields.FieldByName('deptname').AsString := v.departments[i].deptname; Dataset.Fields.FieldByName('firstname').AsString := v.departments[i].staff[j].firstname; Dataset.Fields.FieldByName('surname').AsString := v.departments[i].staff[j].surname; DataSet.Post; end; If v.departments[i].staff.length = 0 then begin //empty department DataSet.Append; Dataset.Fields.FieldByName('business').AsString := v.business; Dataset.Fields.FieldByName('deptname').AsString := v.departments[i].deptname; DataSet.Post; end; end; // check DataSet.First; while not DataSet.Eof do begin writeln(inttostr(DataSet.recno+1) + ' ' + Dataset.Fields.FieldByName('business').AsString + ' ' + Dataset.Fields.FieldByName('deptname').AsString + ' ' + Dataset.Fields.FieldByName('firstname').AsString + ' ' + Dataset.Fields.FieldByName('surname').AsString); DataSet.Next; end; // // step5 : save json to local storage (persistent on specific device) writeln('Step5'); // step 5 only needs to be run once // var LocalStorage := TW3LocalStorage.Create; LocalStorage.Open('mytreasure'); LocalStorage.SetKeyStr('json',textdata); LocalStorage.Close; // // step6 : retrieve json from local storage (persistent on specific device) writeln('Step6'); // textdata := ''; LocalStorage.Open('mytreasure'); textdata := LocalStorage.GetKeyStr('json','unknown'); LocalStorage.Close; var v1 := json.parse(textdata); // check writeln(inttostr(v1.departments.length) + ' departments'); for var i := 0 to v1.departments.length -1 do begin writeln(v1.departments[i].deptname + ' ' + inttostr(v1.departments[i].staff.length) + ' staff'); for var j := 0 to v1.departments[i].staff.length -1 do begin writeln(v1.departments[i].staff[j].firstname + ' ' + v1.departments[i].staff[j].surname); end; end; // // step7 : save json to SQLite writeln('Step7'); // var db : TSQLiteDatabase; var res : TSQLiteResult; db := TSQLiteDatabase.Create; db.Run('CREATE TABLE `business` (id INTEGER, business TEXT)'); db.Run('CREATE TABLE `departments` (id INTEGER, busid INTEGER, deptname TEXT)'); db.Run('CREATE TABLE `staff` (id INTEGER, deptid INTEGER, busid INTEGER, firstname TEXT, surname TEXT)'); var v2 := json.parse(textdata); db.Run("INSERT INTO `business`(`id`,`business`) VALUES (0,'" + v2.business + "')"); for var i := 0 to v2.departments.length -1 do begin db.Run("INSERT INTO `departments`(`id`,`busid`,`deptname`) VALUES (" + inttostr(i) + ",0,'" + v2.departments[i].deptname + "')"); for var j := 0 to v2.departments[i].staff.length -1 do begin db.Run("INSERT INTO `staff`(`id`,`deptid`,`busid`,`firstname`,`surname`) VALUES (" + inttostr(j) + "," + inttostr(i) + ",0,'" + v2.departments[i].staff[j].firstname + "','" + v2.departments[i].staff[j].surname + "')"); end; end; // check // res := db.Exec("SELECT a.business, b.deptname, c.firstname, c.surname " + // "from business a, departments b, staff c " + // "where c.busid = a.id and c.deptid = b.id and b.busid = a.id"); // or something a bit simpler res := db.Exec("SELECT surname FROM staff"); //writeln(res.columns[0]); //title of first field : 'business' var rows := res.values; for var i := 0 to rows.length -1 do begin var columns := rows[i].values; for var j := 0 to columns.length-1 do begin writeln(columns[j]); end; end; // or alternatively, same result // // for var i := 0 to res.values.length -1 do begin //rows // for var j := 0 to res.values[i].values.length-1 do begin //columns // writeln(res.values[i].values[j]); //cell // end; // end; /* // // step8 : save json to server (persistent over all devices) writeln('Step8'); // var s := "UPDATE `test` SET `webjson`='" + textdata + "'"; var FHttp := JXMLHttpRequest.Create; FHttp.open("POST",'...../smsdmlmysql.php'); FHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); var encodedstr1 := browserapi.window.encodeURIComponent(S); var sql_statement := 'sql_statement=' + encodedstr1; FHttp.send(sql_statement); // // step9 : retrieve json from server writeln('Step9'); // s := "select * from test"; var FHttp2 := JXMLHttpRequest.Create; FHttp2.open("POST",'...../smsdbmysql.php'); FHttp2.setRequestHeader("Content-type","application/x-www-form-urlencoded"); var encodedstr2 := browserapi.window.encodeURIComponent(S); var sql_statement2 := 'sql_statement=' + encodedstr2; FHttp2.send(sql_statement2); JGlobalEventHandlers(FHttp2).onLoad := lambda(e:JEvent) var smscursor := JSON.parse(FHttp2.responseText); for var i := 0 to smscursor.rows.length -1 do begin textdata := smscursor.rows[i].webjson; end; writeln(textdata); result :=true; end; */ end; procedure TForm1.W3Button1Click(Sender: TObject); begin DoStuff; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.
  7. Save Content Dialog

    sorry, code example local storage was not quite right procedure TForm1.W3Button2Click(Sender: TObject); begin // Retrieve Memo1.Text := browserapi.window.localStorage.getItem("lastname"); end; procedure TForm1.W3Button1Click(Sender: TObject); begin // Store browserapi.window.localStorage.setItem("lastname", "Smith"); end; this works better. Or the slightly longer version using SmartCL.Storage.Local unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Storage.Local, SmartCL.Controls.Memo, SmartCL.Controls.Button; type TForm1 = class(TW3Form) procedure W3Button2Click(Sender: TObject); procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; LocalStorage : TW3LocalStorage; end; implementation { TForm1 } procedure TForm1.W3Button2Click(Sender: TObject); begin LocalStorage.Open('aaa'); W3Memo1.Text := LocalStorage.GetKeyStr('businessname','unknown'); LocalStorage.Close; end; procedure TForm1.W3Button1Click(Sender: TObject); begin LocalStorage.Open('aaa'); LocalStorage.SetKeyStr('businessname','lynkfs'); LocalStorage.Close; end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} LocalStorage := TW3LocalStorage.Create; end;
  8. Save Content Dialog

    Edited If you're running a project in the browser, it is not possible to save files to the local harddrive. The easiest work-arounds : - use local storage. This is a browser function which lets you store key-value pairs, and it is persistent. Caveats of course are that storage capacity is limited, storage is specific to the protocol of the page, data stored when in stealth mode will be deleted after exiting from stealth mode (and is maybe even browser vendor specific ?) and data gets formatted as strings. Might be ok for your purposes though ... procedure TForm1.W3Button2Click(Sender: TObject); begin // Retrieve Memo1.Text := browserapi.window.localStorage.getItem("lastname"); end; procedure TForm1.W3Button1Click(Sender: TObject); begin // Store browserapi.window.localStorage.setItem("lastname", "Smith"); end; - use session storage instead of local storage if you don't want to hang on to data longer than current session - use cookies. An old feature to basically do something like local storage. - use a server. Uploading files to a php or node server is pretty straight forward. You'll need access to a server though - exit the browser and produce a native app
  9. back(s)lash

    OK Lennart, brain-teaser here. example of unwanted suffix : {"ribbons$1":[{"ribbonname$1":"ribbonname","params$1":[{"paramtype$1":"paramtype"}]}]} code to reproduce (normal standard latest alpha) type JW3WebParam = class paramtype : string; paramcontent : string; strbefore : string; strafter : string; end; JW3WebRibbon = class ribbonname : string; ribbonid : integer; imagename : string; category : string; params : Array of JW3WebParam; end; JW3WebProject = class projectname : string; author : string; password : string; webheader : string; webtrailer : string; webjson : string; ribbons : Array of JW3WebRibbon; end; type TForm1 = class(TW3Form) procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; Procedure ShowProblem; end; // record types for stringify purposes type JStrC = record paramtype : string; //external 'paramtype'; end; JStrB = record ribbonname : string; //external 'ribbonname'; params : Array of JStrC; //external 'params'; end; JStrA = record ribbons : Array of JStrB; //external 'ribbons'; end; var StrC : JStrC; StrB : JStrB; StrA : JStrA; implementation { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin ShowProblem; end; Procedure TForm1.ShowProblem; begin StrB.ribbonname := 'ribbonname'; StrC.paramtype := 'paramtype'; StrB.params.add(StrC); StrA.ribbons.add(StrB); // Turn object to json text var textdata: string := ''; var res: string := ''; asm @textdata = JSON.stringify(@StrA); console.log(@textdata); end; end; The above code produces the faulty stringyfied string. Now notice that the first classes are actually not used : type JW3WebParam = class paramtype : string; paramcontent : string; strbefore : string; strafter : string; end; JW3WebRibbon = class ribbonname : string; ribbonid : integer; imagename : string; category : string; params : Array of JW3WebParam; end; JW3WebProject = class projectname : string; author : string; password : string; webheader : string; webtrailer : string; webjson : string; ribbons : Array of JW3WebRibbon; end; So delete these from the code, leaving just type TForm1 = class(TW3Form) procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; Procedure ShowProblem; end; // record types for stringify purposes type JStrC = record paramtype : string; //external 'paramtype'; end; JStrB = record ribbonname : string; //external 'ribbonname'; params : Array of JStrC; //external 'params'; end; JStrA = record ribbons : Array of JStrB; //external 'ribbons'; end; var StrC : JStrC; StrB : JStrB; StrA : JStrA; implementation { TForm1 } procedure TForm1.W3Button1Click(Sender: TObject); begin ShowProblem; end; Procedure TForm1.ShowProblem; begin StrB.ribbonname := 'ribbonname'; StrC.paramtype := 'paramtype'; StrB.params.add(StrC); StrA.ribbons.add(StrB); // Turn object to json text var textdata: string := ''; var res: string := ''; asm @textdata = JSON.stringify(@StrA); console.log(@textdata); end; end; Executing this gives as output : {"ribbons":[{"ribbonname":"ribbonname","params":[{"paramtype":"paramtype"}]}]} Go figure Note : may or may not be related to
  10. back(s)lash

    As to the unwanted suffix error, it doesn't reproduce using a simple code snippet like above, and I can't pinpoint why it did in my codebase. It was resolved though by EWB's suggestion of using the 'external' keyword. Mapping to existing data : Thanks
  11. shoestring framework

    The native framework (see this thread) has been finalised and can be acquired through www.lynkfs.com.
  12. back(s)lash

    This works out quite well. It even solves the unmentioned problem that I can now use code obfuscation.
  13. back(s)lash

    Thank you, amazing
  14. back(s)lash

    5 lines of code which drove me bonkers. My brilliant idea was to have a set of classes, where class A has a field which is an array of class B, and class B has a field which is an array of class C etc - and then just stringify the whole setup and store that string in a MySQL database. That would save me from setting up multilayered tables and I could just compress everything in a single base table. JB = record field1 : string; params : Array of JC; end; JA= record fields : array of JB; end; stringifying this (asm @textdata = JSON.stringify(@data); where @data refers to JA) gives what I was after. Except that all fieldnames in the result-string have a '$1' appended ({"field1$1"). No idea why that happens or how to avoid that. Pushing on, using a while-loop with 'pos' and 'delete' to get rid of this suffix doesn't work. The Pos and Delete functions don't work on strings resulting from an asm @string operation. Next best thing : string replace with regex. In the end I needed to do something like this : asm @textdata = JSON.stringify(@data); var r = new RegExp('\\$1', 'g'); //delete all name-postfixes of '$1' @res = (@textdata).replace(r, ""); var r2 = new RegExp('\\"', 'g'); //add backslashes just before double quotes @res2 = (@res).replace(r2, '\\"'); //and add mysql backslash character as well end; The first replacement operation gets rid of all the $1 suffixes The second replacement operation sets a \ around text pieces which either have a special character or space in their content, like : font-family: \"Old Standard TT\" And to make matters worse, found out that MySQL just ignores all backslashes in update statements. Unless these backslashes are preceded with a special character. (In this case another backslash). Haven't mentioned some other caveats like handling EOL characters Anyway, got it working, but it might have been easier to set up multi-layer tables after all. If anyone has a solution for the automatic suffix addition ($1) in stringify operations please let me know.
  15. Black Screen of Death

    what is the source / domain of this conversion ?
×