Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


lynkfs last won the day on July 19

lynkfs had the most liked content!

About lynkfs

  • Rank

Profile Information

  • Gender
  • Location

Recent Profile Visitors

2,633 profile views
  1. lynkfs

    file system

    A quick test on persistence : indexedDB (and hence the client file system) is persistent per device. Which means that once a filesystem is created, it will be there the next time the app is activated. Some caveats though it is pretty safe, but not 100% so. Browsers may delete a database if there is not enough diskspace. Why and when differs between browsers somewhat too. storage access is regulated by 'same origin'. In the demo above the file system is accessible in the lynkfs.com domain, but not in ibm.com Which is ok. firing up multiple instances of chrome does not disrupt access, the db is accessible in all instances there is no cross-access between browsers (firefox doesn't see indexDB instances created in Chrome) users may clear browser cache/memory/storage All in all not too bad
  2. lynkfs

    Learning Smart Mobile Studio

    Look at Primož Gabrijelčič's book on SMS, available on https://leanpub.com/asmartbook Very valuable and I still consult it on a regular basis There is some info on http://www.pp4s.co.uk/main/smart-gettingstarted.html as well
  3. lynkfs

    file system

    This code works (File system in the browser) create and mount a FileSystem create a directory write a file with some contents read the stats of this file read the contents of this file write another file list the directory contents in a memo Demo procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var Script := browserapi.document.createElement('script'); Script.src := 'res/filer.js'; //https://raw.githubusercontent.com/filerjs/filer/develop/dist/filer.js browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('filer loaded'); var Filer := browserapi.window.Filer; writeln('instance created'); var fs : variant := new JObject; asm @fs = new (@Filer).FileSystem({ name: "myfilesystem01", flags: [ 'FORMAT' ] }); end; writeln('filesystem created'); //mkdir fs.mkdir('/docs', procedure(err:boolean) begin if (err) then writeln('Unable to create /docs dir'); end); //write file fs.writeFile('/docs/firstDoc.txt', 'Hello World', procedure(err:boolean) begin if (err) then writeln('Unable to write /docs/firstDoc.txt'); end); //stats fs.stat('/docs/firstDoc.txt', procedure(err, stats:variant) begin if (err) then writeln ('Unable to stat /docs/first.txt') else writeln(stats); end); // Read file fs.readFile('/docs/firstDoc.txt', 'utf8', procedure (err:boolean; data:string ) begin if (err) then writeln ('Unable to read /docs/firstDoc.txt') else writeln(data); end); //write second file fs.writeFile('/docs/secondDoc.txt', 'Brave new world', procedure(err:boolean) begin if (err) then writeln('Unable to write /docs/secondDoc.txt'); end); //get reference to shell var sh : variant := new JObject; asm @sh = new (@fs).Shell(); end; // list directory, shallow listing sh.ls('/docs', procedure(err:boolean; entries: array of variant) begin if (err) then writeln ('Unable to read /docs directory') else begin W3Memo1.Text := '/docs' + #10; for var i := 0 to entries.length -1 do begin writeln(entries[i].name); W3Memo1.Text := W3Memo1.Text + ' ' + entries[i].name + #10; end; end; end); end; end; I like this a lot note : works from file or server, not in the ide
  4. lynkfs

    file system

    The original html5 spec had a file api which allowed to read and write files clientside (the File Directories and System Api). This was only fully implemented by Chrome at one point, but even then Chrome needed to be started with a switch (-allow-file-access-from-files). Today clientside storage is limited to cookies, local/session storage, indexedDB and the Cache API. While I understand the reasons for limiting file handling on the client, it is sometimes annoying. Fortunately some pretty creative solutions have emerged. One of them is Filer. This is a nodejs fs filesystem look-a-like which uses indexedDB for storage, and runs in the browser. Basically it allows for a complete filesystem clientside. Not bad at all. To continue stacking : Filer is stacked on top of indexedDB. Stack nohost on top of Filer and you have a web fileserver (based on service workers) in the browser. Amazing. Will certainly try this out.
  5. lynkfs


    The standard generated index file in smart doesn't contain a link tag for a favicon (something like <link id="favicon" rel="icon" href="res/favicon.png" type="image/png" sizes="16x16"> However this can be added in code (or add it manually to the default.html file in the template directory, or add it to a Custom Template in the ide) In code : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var link := w3_createHtmlElement('link'); link.id := 'favicon'; link.href := 'res/favicon.png'; link.rel := 'icon'; link.&type := 'image/png'; link.sizes := '16x16'; browserapi.document.getElementsByTagName('head')[0].appendChild(link); end; If you want to change the icon later on, you can do that too procedure TForm1.W3Button1Click(Sender: TObject); begin var favicon := browserapi.document.getElementById('favicon'); favicon.href := 'res/favicon2.png'; end;
  6. lynkfs

    Responsive Design

    If this shortcut approach (providing html code directly in smart code) works for you, then adding event handlers has to be done in html too. Just add an ID to the img element so you can find the image element in the dom, and add an eventlistener to it : <!-- Photo by Drew Farwell on Unsplash --> <img id="mypic01" src="https://images.unsplash.com/photo-1513309914637-65c20a5962e1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3450&q=80" alt="Several friends doing a toast"> <figcaption><h3>Drake Equation</h3></figcaption> etc... browserapi.document.getElementById("mypic01").addEventListener("click", procedure(event:variant) begin writeln('clicked'); end); Note that once you start mixing direct dom manipulation with standard rtl processing, you're mixing different worlds. Things might look the same but not necessarily have the same effect. The onclick event handler above is not the same as the standard smart OnClick handling etc. See what works for you.
  7. lynkfs


    Edited. The final piece of the puzzle is to find out if it is possible to use IPP printing directly from the browser, bypassing the need for a node server. The answer, surprisingly, is that it is possible. There are some caveats, but essentially it can be done. First thing is to get the node.js project and transform it into something a browser understands. The way to do this is to use Browserify, which will bundle the project file, the ipp library and all of its dependencies : install browserify (npm install browserify) Save the first node.js code example of the previous post (the code which lists printer capabilities) to file, say 'ipp.js' Execute 'browserify ipp.js -o bundle.js'. This produces a 'bundle.js' file which incorporates everything the browser needs. The minimum html file with a reference to this file included : <!doctype html> <html> <head> <title>Browserify</title> </head> <body> <script src="bundle.js"></script> </body> </html> Executing this should produce the list of printer capabilities. However it doesn't and a CORS error occurs. In short : essentially scripts calling other scripts (which happens here) is forbidden if these scripts are not on the same origin (domain). CORS is a method to relax that, but it requires the non-origin server to send a specific header in its response (Access-Control-Allow-Origin). If not, browsers will throw an error. Apparently the ipp library doesn't handle this CORS requirement. The only way to circumvent this is to either use a proxy server (see article link) or to instruct the browser to disregard same origin checking alltogether. The latter option involves firing up a separate Chrome instance with some switches : start chrome --args --disable-web-security --user-data-dir="C:\chrome_temp" file:///C:/Users/...../print/test.html Works for me. Executing the html file above generates the expected output. Edited : If the app is produced using Phonegap/Cordova apparently it is possible to handle CORS by whitelisting the various origin domainnames. Have not tried that. Is IPP worth the trouble ? I think so if the use-case is what started this post : having to print from a webpage (intranet) without having to go through a printer selection dialog every time a print is initiated. In my case dealing with a retail ePos application where every transaction needs a printed docket. Afaik the only other option is to use specialised printers like the ones in the epson family.
  8. lynkfs


    Edited. And then there is IPP : Internet Printing Protocol This protocol allows direct access to networked printers, ask for their capabilities, prepare print jobs, manage print queues, execute print jobs and get results. Exactly what I was after. I read through the documentation on the website of "The Printer Working Group", which looks after the standardisation of this protocol. (http://www.pwg.org/ipp/ippguide.html). To actually use this protocol, there are various IPP client libraries available (C, Java, Python). Fortunately there is a node.js variant as well (https://github.com/williamkapke/ipp). Both these links give some examples on how to use IPP in real world situations However the code examples on both sites contain many errors. Amazing. On the plus side, apparently some 98% of all networkable printers have an IPP driver installed by default, and should be adressable this way. To test this all out, I selected a printer on my network. The first thing to find out is the identification of the printer. IPP has its own protocol : ipp(s)://<printer uri> where the printer uri is the printers ip address followed by a path. Example : ipp:// Behind the scenes all traffic is actually routed over http(s), and printer uri's can also be specified as http over port 631. Example : The ip address can usually be found in the OS printer settings somewhere and the path is mostly standardised to be just '/ipp/print' Not too bad. After installing the node.js ipp library (npm install ipp) the following node.js code queries the printers capabilities var ipp = require("ipp"); var printer = ipp.Printer(""); var msg = { "operation-attributes-tag": { "requesting-user-name": "John Doe", "document-format": "image/pwg-raster", "requested-attributes": ["printer-description", "job-template", "media-col-database"] } }; printer.execute("Get-Printer-Attributes", msg, function(err, res) { console.log(err); console.log(res); }); This produces quite a list. To print a textfile to this printer, I got this code working var ipp = require("ipp"); var printer = ipp.Printer(""); var fs = require("fs"); fs.readFile("example.txt", function(err, content) { if (err) throw err; var msg = { "operation-attributes-tag": { "requesting-user-name": "John Doe", "document-format": "application/octet-stream" }, data: content }; printer.execute("Print-Job", msg, function(err, res) { console.log(err); console.log(res); }); }); Edited : The javascript sources above can be produced by a regular smart project : create a new node project and replace unit1 with unit Unit1; interface uses nodeBasics; //https://forums.smartmobilestudio.com/topic/4652-node-ground-zero/?tab=comments#comment-23115 type TNodeProgram = class(TObject) public constructor Create; virtual; ipp, msg, printer : variant; procedure callback(err, res: variant); procedure Execute; end; implementation constructor TNodeProgram.Create; begin inherited Create; ipp := RequireModule('ipp'); printer := ipp.Printer(""); msg := new JObject; asm @msg = { "operation-attributes-tag": { "requesting-user-name": "John Doe", "document-format": "image/pwg-raster", "requested-attributes": ["printer-description", "job-template", "media-col-database"] } }; end; end; procedure TNodeProgram.callback(err, res: variant); begin console.log(err); console.log(res); end; procedure TNodeProgram.Execute; begin printer.execute("Get-Printer-Attributes", msg, @callback); end; end. note : unfortunately we can't make up anonymous classes with hyphens in the classname, otherwise the msg variable could have been constructed as var msg = class operation-attributes-tag = class requesting-user-name = "John Doe", document-format = "image/pwg-raster", requested-attributes : array[0..2] of string = ["printer-description", "job-template", "media-col-database"]; end; end; Next step is to see if this can be made to work in the browser as well
  9. lynkfs


    Printers used to be dumb peripherals. Not so with the latest receipt printers (epson). They have actually an apache webserver built in, and the printer can act as the hub for displays, scanners and other (dumb) peripherals. amazing. Connecting them to Smart looks like not too difficult.
  10. lynkfs


    I need to print receipts from a webpage (intranet). The difficulty is not so much to get a print happening on some printer, but to do it in such a way that the usual printer selection dialog gets bypassed. So basically a form with a print button which, when pressed, prints a receipt to a designated printer straightaway, without every time asking the user to select a printer. The normal print methods (window.print(), google cloud print, external print.js libraries) do not solve that problem. The only reference I found that might work is the ePOS_SDK from Epson, which can be used with some of their receipt printers. See https://www.epson.com.au/pos/products/software/index.asp which lists sdk versions for iOs/Android, but also for webapps / javascript. If someone has solved a similar problem then feedback is appreciated.
  11. lynkfs

    reserved project name

    I accidentally gave a project a reserved name ('implementation'). Not a good idea: compiles then crashes
  12. lynkfs


    or use the built-in TreeWalker
  13. lynkfs


    There is a treeview component in the 'native rtl' (demo, docum) See 'JTreeView' under 'Components' in the demo usage: var TreeView1 := JW3TreeView.Create(self); TreeView1.SetBounds(0, 0, 250, 200); TreeView1.Subject := 'Job roles'; TreeView1.Add('ceo','','chief executive officer'); //root TreeView1.Add('cto', 'ceo','chief technology officer'); TreeView1.Add('dev1', 'cto','developer 1'); TreeView1.Add('dev2', 'cto','developer 2'); TreeView1.Add('dev3', 'cto','developer 3'); TreeView1.Add('assistent', 'dev2','assistant developer 2'); TreeView1.Add('cfo', 'ceo','chief financial officer'); TreeView1.Add('accountant', 'cfo','bean counter'); TreeView1.Add('cmo', 'ceo','chief marketing officer'); component code unit JTreeView; interface uses JElement, JListBox, JPanel; type JW3TreeNode = class public Node, ParentNode, NodeDescription : String; Level : Integer; Children : Array of JW3TreeNode; Expanded: Boolean; Showing: Boolean; end; JW3TreeView = class(TElement) private ListBox: JW3ListBox; Title : JW3Panel; Function FindNode(ThisNode: string):JW3TreeNode; Procedure HideAllChildren(node: JW3TreeNode); Procedure Order(node: JW3TreeNode); Node : JW3TreeNode; Root : JW3TreeNode; public constructor Create(parent: TElement); virtual; Procedure Add(NewNode, ParentNode, NodeDescription: String); Procedure ShowTree; Subject: String := 'TreeView...'; end; implementation uses Globals; { JW3TreeView } constructor JW3TreeView.Create(parent: TElement); begin inherited Create('div', parent); setProperty('background-color', 'white'); Title := JW3Panel.Create(self); Title.SetProperty('background-color','#699BCE'); Title.SetProperty('color','white'); Title.SetProperty('border','1px solid white'); ListBox := JW3ListBox.Create(self); ListBox.Top := 25; //self.Observe; self.OnReadyExecute := procedure(sender: TObject) begin Title.SetBounds(0,0,width-2,22); Title.handle.value := Subject; Title.SetProperty('font-size', '0.95em'); Title.Text := Subject; ListBox.Width := self.Width; ListBox.Height := self.Height - 25; ShowTree; end; end; Procedure JW3TreeView.Add(NewNode, ParentNode, NodeDescription: string); begin // Node := JW3TreeNode.Create; Node.Node := NewNode; Node.ParentNode := ParentNode; Node.NodeDescription := NodeDescription; Node.Expanded := false; Node.Showing := false; If ParentNode = '' then begin Root := Node; Node.Level := 1; //Node.Expanded := true; //only when initially displaying all levels 2 Node.Showing := True; end; var Parent := JW3TreeNode.Create; Parent := FindNode(ParentNode); If assigned(Parent) then begin var temp := JW3TreeNode.Create; // eliminate unwanted double ups Temp := FindNode(NewNode); If (not assigned(temp)) or (temp.ParentNode <> parent.node) then begin Parent.Children.push(Node); Node.Level := Parent.Level + 1; //If node.level = 2 then node.Showing := true; //see line 82 end; end; end; Function JW3TreeView.FindNode(ThisNode:string):JW3TreeNode; begin var queue: Array of JW3TreeNode = [Root]; while (queue.length > 0) do begin var node := queue[0]; queue.delete(0); if node.Node = ThisNode then result := node; for var i := 0 to node.Children.length - 1 do begin queue.push(node.Children[i]); end; end; end; Procedure JW3TreeView.HideAllChildren(node: JW3TreeNode); begin Node.Showing := false; Node.Expanded := false; for var i := 0 to node.Children.length -1 do begin HideAllChildren(node.Children[i]); end; end; Procedure JW3TreeView.ShowTree; begin ListBox.Clear; Order(Root); end; Procedure JW3TreeView.Order(node: JW3TreeNode); begin if Node.Showing then begin var Item := JW3Panel.Create(Self); Item.SetAttribute('type','text'); Item.setProperty('background-color', 'whitesmoke'); Item.SetProperty('font-size', '0.85em'); Item.Height := 21; var prefix : string := ''; If node.children.count > 0 then prefix := '▸ ' //triangle right else prefix := ' ▫ '; //white square If node.children.count > 0 then If node.Children[0].Showing then prefix := '▾ '; //triangle down var s: string := ''; For var i := 1 to node.Level do begin S := S + ' '; end; S := S + prefix; Item.SetinnerHTML(S + node.NodeDescription); Item.tag := node.NodeDescription; ListBox.Add(Item); Item.OnClick := procedure(Sender:TObject) begin Subject := (Sender as TElement).tag; Title.Text := Subject; node.expanded := not node.expanded; For var j := 0 to Node.Children.Count -1 do Node.Children[j].Showing := Node.Expanded; If Node.Expanded = false then begin HideAllChildren(Node); Node.Showing := true; end; ShowTree; end; Item.handle.ondblclick := procedure begin window.postMessage([self.handle.id,'dblclick',subject],'*'); //= title.handle.value end; end; for var i := 0 to node.Children.length -1 do begin Order(node.Children[i]); end; end; end. // The same guts, in regular RTL code format, is used here (https://www.lynkfs.com/Experiments/Menu/Copperhead.MenuNodes.pas) and walking the tree Procedure Traverse(node: TCHMenuNode); begin writeln(node.NodeDescription) for var i := 0 to node.Children.length -1 do begin Traverse(node.Children[i]); end; end; Traverse(MenuNodes.Root);
  14. lynkfs

    Node.js API in a Visual Components Project

    You're pushing the boundaries here and probably too far if you want to write directly from a client. If you only need to read a file you can do that in a couple of ways, see this post Otherwise also have a look at SmartCL.Require.pas, this article, this post and this post and even https://nwjs.io/ to see if that helps in any way. Keep us posted
  15. lynkfs

    web design system usa

    Reading up a bit on web design systems Basically web design systems are a collection of rules, components, tools etc helping developers to create a consistent look and feel for the websites or apps they are developing. This link links to the design system of the us gov. Whether or not one likes this system, it is certainly comprehensive. As an example I quite like f.i this site which conforms to these rules.