Jump to content

lynkfs

Moderators
  • Content Count

    585
  • Joined

  • Last visited

  • Days Won

    68

Everything posted by lynkfs

  1. I don't see much of an advantage to be honest. If the ide is changed to allow both versions, then that negates any code problems of course. There is however also documentation (Primoz book) and search to consider. Personally I don't use TButton etc (as that was your question).
  2. lynkfs

    css styling

    Browsers are certainly talented as multi-language processors. They have to understand html, css, javascript, svg and lately web-assembly as a fifth language. XML and Json are format specifiers, but could be borderline categorised as languages as well. Smart elevates javascript to great heights, but sort of leaves the other ones at the periphery. Especially css. Css can be used for a) component styling including colours, fonts etc, b) visual layout of pages/forms and reacting responsively to screen size and orientation and c) interactivity in various forms. Some of this overlaps with the js domain. Smart basically uses css for a) but not for b) or c). Looking at various css frameworks out there, I would categorise these as on one end of the scale there are many UI kit frameworks where the emphasis is on components - buttons, accordions, cards etc. Sometimes as css stand-alone files, sometimes in conjunction with js. Most of these also have functionality for layout purposes, usually based on flexbox or internal grid structures. on the other end of the scale there are frameworks which forego the component focus, but instead provide a large number of elementary styling functions, which can be applied to any component. and many which occupy a space in between This post is about the open source Tailwind framework, belonging in the second category above, just to see how that would work in the Smart environment. Tailwind comes in 2 flavours : a static framework which can be accessed locally or from a cdn, and a more dynamic version. The latter can be tailored dynamically for any specific project and basically needs to be part of the build toolchain. To keep it simple I used the static version. Change the res/app.css reference to the tailwind css file (either just replace it or change the reference in the index.html file). Now any component can be made to use this styling. Because this is not more (or less) than a list of individual styling functions, the use boils down to defining what you want. Tell a story as in "give me a button with a green background, which turns to blue on hover, has rounded corners and white text and a shadow effect which makes it seem floating". In code : W3Button1.handle.className := "bg-green-500 hover:bg-blue-700 text-white rounded shadow-2xl"; The second button in the demo project (see below) has a different story (className sequence), which gives it a totally different look and feel. I kind of like this approach, it works very well with any component, thus also with the built in Smart components. It is a bit of a drag specifying these className sequences by hand, so it would be nice if this would be supported in the IDE. There are after all only a small number of categories of these elementary styling bits : backgrounds, typography, borders, spacing, sizing, svg, interactivity and effects. And a relatively limited list of functions per category (bg-green-500 means a green background, 500 is the fifth shade in the green colour scheme. So there will be a bg-green-600, a bg-blue-200 etc). Doable I think. Apart from these styling functions, Tailwind also has a number of layout functions. Smart has its own layout mechanism, however the Tailwind functions can be used as well. See the 'card' component in the demo. Demo and source
  3. lynkfs

    css styling

    Thanks There are however a couple of minor / major (?) caveats doing it like this : I made up some more metro components, and it becomes apparent that it is unavoidable to use more modifier classes: TagStyle.Add('primary image-button outline icon-left rounded etc). this type of styling is not supported by the visual designer in the ide, so styling can be done in code only. Even though only one-liners (TagStayle.Add( ) (most rtl components (but not all) have a component, a border and a background theme class baked in. Not all of these can be reused, so sometimes the border and/or background classes may end up being empty) probably the second bullet (visual designer) is the most important one for future enhancements
  4. lynkfs

    css styling

    Easy to use... Suppose we want to develop a Metro theme To take buttons as an example, the official metro style has a number of predefined buttons : To replicate the above, and do it in such a way that : the component code (TW3Button) does not need any change the standard theming classes still apply (TW3Button TW3ButtonBorder TW3ButtonBackground), but with different content of course while allowing for these different predefined component versions is to do something like this procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var Button1 : TW3Button := TW3Button.Create(self); Button1.SetBounds(20,20,83,40); Button1.Caption := 'Default'; var Button2 : TW3Button := TW3Button.Create(self); Button2.SetBounds(110,20,83,40); Button2.Caption := 'Primary'; Button2.TagStyle.Add('primary'); var Button3 : TW3Button := TW3Button.Create(self); Button3.SetBounds(200,20,83,40); Button3.Caption := 'Secondary'; Button3.TagStyle.Add('secondary'); var Button4 : TW3Button := TW3Button.Create(self); Button4.SetBounds(290,20,83,40); Button4.Caption := 'Success'; Button4.TagStyle.Add('success'); end; result : with the metro css file for the above looking like this /* ////////////////////////////////////////// // TW3Button (default) ////////////////////////////////////////// */ .TW3Button { font-weight: 400; text-align: center; white-space: nowrap; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; border: 1px solid transparent; padding: 0 0.75rem; -webkit-transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out; background-color: #ebebeb; color: #1d1d1d; cursor: pointer; outline: none; -ms-touch-action: manipulation; touch-action: manipulation; -webkit-appearance: button; /* font-size: 0.875rem; line-height: 34px; height: 36px; */ } .TW3Button:focus, .TW3Button:active { text-decoration: none; -webkit-box-shadow: 0 0 0 3px rgba(228, 228, 228, 0.45); box-shadow: 0 0 0 3px rgba(228, 228, 228, 0.45); } .TW3Button:hover { background-color: rgba(29, 29, 29, 0.1); } .TW3ButtonBackground { } .TW3ButtonBorder { } /* ////////////////////////////////////////// // TW3Button Primary ////////////////////////////////////////// */ .TW3Button.primary { outline-color: #75b5fd; background-color: #0366d6; color: #ffffff; } .TW3Button.primary:hover { color: #ffffff; background-color: #024ea4; border-color: #023671; } .TW3Button.primary:focus, .TW3Button.primary:active { -webkit-box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.45); box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.45); } /* ////////////////////////////////////////// // TW3Button Secondary ////////////////////////////////////////// */ .TW3Button.secondary { outline-color: #b7c6cd; background-color: #607d8b; color: #ffffff; } .TW3Button.secondary:hover { color: #ffffff; background-color: #4b626d; border-color: #36474f; } .TW3Button.secondary:focus, .TW3Button.secondary:active { -webkit-box-shadow: 0 0 0 3px rgba(96, 125, 139, 0.45); box-shadow: 0 0 0 3px rgba(96, 125, 139, 0.45); } /* ////////////////////////////////////////// // TW3Button Success ////////////////////////////////////////// */ .TW3Button.success { outline-color: #adeb6e; background-color: #60a917; color: #ffffff; } .TW3Button.success:hover { color: #ffffff; background-color: #477c11; border-color: #2d4f0b; } .TW3Button.success:focus, .TW3Button.success:active { -webkit-box-shadow: 0 0 0 3px rgba(96, 169, 23, 0.45); box-shadow: 0 0 0 3px rgba(96, 169, 23, 0.45); } see demo (check click and hover states) What do you think ? By the way, this css file is made up by screenscraping, which is actually not too bad / timeconsuming. Alternatively it could also be composed out of relevant Tailwind functions, results would be very similar.
  5. lynkfs

    css styling

    Wow, you guys are funny. The question I had was : what is the easiest way to style Smart components. Smart ships with a couple of standard stylesheets, or themes, so styling is a matter of selecting one of these. To change the styling of a specific component in a theme, just change the css specifications in the stylesheet, either manually or through code. A different topic altogether is to develop new themes. It is not a simple task to f.i. develop a Metro theme stylesheet which covers all of the current Smart visual components. That's why I had a look at externally available css frameworks, to see if any of those would help with developing themes, or even work within the Smart development environment and if they are useful to begin with. I focused on one particular framework : Tailwindcss One of the problems with themes is when it becomes necessary to deviate from it. Say a project has chosen the default theme (default.css), but for some legitimate reason somewhere in the project a standard component has to be styled differently. Example : colour a button green to amplify a positive choice and colour it red to amplify dangerous choices ("do you really want to download these files from an unknown source ?" and then display a green "No" button and a red "Yes" button). It's not that simple to make this actually happen. Lets say we change the styling of the green button (based on the standard button), and opt to do that in code : var Button2 : TW3Button := TW3Button.Create(self); Button2.SetBounds(20,120,150,40); Button2.Caption := 'No'; // Button2.handle.style.background := '#48bb78'; //Button2.Color := clTeal; Button2.handle.style.border := '1px solid #48bb78'; //Button2.Border.Color := clTeal; Button2.handle.style.borderRadius := '0px'; //Button2.borderradius := 0; Button2.handle.style.color := 'white'; // //simulate hover Button2.handle.onmouseover := procedure(event: variant) begin event.target.style.background := 'orange'; end; Button2.handle.onmouseout := procedure(event: variant) begin event.target.style.background := '#48bb78'; end; works sort of ok, but requires a bit of effort Alternatively we can change the stylesheet, so if we opt to do that for the red button then this requires var Button3 : TW3Button := TW3Button.Create(self); Button3.SetBounds(200,120,150,40); Button3.Caption := 'Yes'; // Button3.TagStyle.Clear; Button3.TagStyle.Add('TW3Button TW3ButtonRed'); var cssProperties := 'background-color: red;'; browserapi.document.styleSheets[0].insertRule(".TW3ButtonRed { " + cssProperties + " }", 0); /* .TW3ButtonRed { background-color: red; } */ cssProperties := 'background-color: orange;'; browserapi.document.styleSheets[0].insertRule(".TW3ButtonRed:hover { " + cssProperties + " }", 0); either in code or adding the .TW3ButtonRed and the .TW3ButtonRed:hover manually to the stylesheet Again, works but requires some effort Using the Tailwind css framework, the above would be simplified to //green var Button4 : TW3Button := TW3Button.Create(self); Button4.SetBounds(20,220,150,40); Button4.Caption := 'No'; Button4.handle.className := "bg-green-500 hover:bg-blue-700 px-6 text-white rounded shadow-xl"; //red var Button5 : TW3Button := TW3Button.Create(self); Button5.SetBounds(200,220,150,40); Button5.Caption := 'Yes'; Button5.TagStyle.Clear; Button5.TagStyle.Add("bg-red-500 hover:bg-blue-700 px-6 text-white rounded shadow-xl"); Button1 : standard styling Button2 : changing standard styling in code Button3 : changing standard styling using css Button4/5 : using Tailwind I've probably confused everyone even more now Anyway, these are my thoughts : Tailwind has the advantage that it can style any of the standard Smart components. As such it could be used to make up a f.i. Metro theme. Still a significant task, but doable. Apart from that, I actually quite like Tailwind to style components on a need-to-do basis, their styling functions make the standard SMS buttons, panels, editboxes, memos etc look really good and modern. (A card btw is just a panel with a certain layout, where usually multiple of these panels appear on a form or page.)
  6. lynkfs

    css styling

    I think sometimes I'm very bad at explaining things. I'll respond over the weekend
  7. lynkfs

    css styling

    The point is that using normal Smart components, they can be styled very differently and exactly as you want with just 1 line of code. Try hovering your mouse to see visual changes
  8. lynkfs

    RoadMap 2019

    I agree. I would like to see more openness around the whole of the product. A development roadmap for sure, the success of a product depends on whether it evolves in line with the needs of its users. But I would also love to see the company's marketing and and long-term positioning, a bit of a vision of the current owners.
  9. lynkfs

    JQWidget integration

    I got asked to have a look at how to integrate jqxWidgets in SMS There are a heap of ready-made widgets under the name of jQuery UI Widgets, ranging from grids to calendars to tooltips and many more. The example here is a jqxButton with an image in the left top corner. The vanilla html file for this looks like <!DOCTYPE html> <html lang="en"> <head> <meta name="keywords" content="jQuery Button, CheckBox, Toggle Button, Repeat Button, Radio Button, Link Button, Button" /> <meta name="description" content="The jqxButton widget allows you to display a button on the Web page." /> <title id='Description'>The jqxButton widget allows you to display a button on the Web page.</title> <link rel="stylesheet" href="../../jqwidgets/styles/jqx.base.css" type="text/css" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1 maximum-scale=1 minimum-scale=1" /> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/scripts/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxcore.js"></script> <script type="text/javascript" src="https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxbuttons.js"></script> <script type="text/javascript"> $(document).ready(function () { // Create jqxButton widgets. $("#jqxButton").jqxButton({ width: 120, height: 40 }); // Subscribe to Click events. $("#jqxButton").on('click', function () { $("#events").find('span').remove(); $("#events").append('<span>Button Clicked</span'); }); }); </script> </head> <body class='default'> <div> <input type="button" value="Button" id='jqxButton' /> </div> <div style='font-size: 12px; font-family: Verdana; margin-top: 10px;'> <div>Events:</div> <div id='events'> </div> </div> </body> </html> which requires a number of external js and css files. These can be local or reside on a cdn server somewhere. Make sure they are served from either http or https, same as the platform the app is executed on. The widgets are created first, immediately after the document is dom-ready, and they will be rendered on a placeholder element with the same element.id as the created widgets. This can be simplified considerably in SMS : All functions can be recoded using this function tied to the ubiquitous jQuery "$" directive : function jqxQuery(aTagObj: string): Variant; external '$'; which results in this code : var props : variant := new JObject; props.height := 40; props.width := 120; props.imgSrc := "https://www.jqwidgets.com/jquery-widgets-demo/images/twitter.png"; props.imgPosition := "topLeft"; jqxQuery( "#jqxButton1" ).jqxButton(props); jqxQuery( "#jqxButton1" ).on('click', procedure() begin browserapi.window.alert('clicked button1'); end); and the placeholder element jqxButton1 := TW3Panel.Create(self); jqxButton1.SetBounds(30,20,126,46); jqxButton1.handle.style.border := '1px solid grey'; jqxButton1.handle.innerHTML := '<input type="button" value="Button" id="jqxButton1" />'; Sort of a rough and tumble approach, but it does work. A better way is to encapsulate these widgets in a dedicated component : unit jqxButton; { **************************************************************************** } { } { Smart Mobile Studio } { } { **************************************************************************** } interface {$I 'Smart.inc'} uses System.Types, {$IFDEF THEME_AUTOSTYLE} SmartCL.Theme, {$ENDIF} SmartCL.System, SmartCL.Components; type TJQXButton = class(TW3CustomControl) protected function MakeElementTagObj: THandle; override; procedure InitializeObject; override; published property caption: string; // read GetCaption write SetCaption; end; function jqxQuery(aTagObj: string): Variant; external '$'; implementation //############################################################################# // TJQXButton //############################################################################# procedure TJQXButton.InitializeObject; begin inherited; Caption := 'Button'; //default Handle.ReadyExecute( procedure () begin var Script := browserapi.document.createElement('script'); Script.src := 'https://www.jqwidgets.com/jquery-widgets-demo/jqwidgets/jqxcore.js'; browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('jqxcore loaded'); var props : variant := new JObject; props.height := self.height-2; props.width := self.width-2; props.imgSrc := "https://www.jqwidgets.com/jquery-widgets-demo/images/twitter.png"; props.imgPosition := "topLeft"; jqxQuery( "#jqx" + self.handle.id ).jqxButton(props); end; handle.innerHTML := '<input type="button" value="' + Caption + '" id="jqx' + self.handle.id + '" />'; end); end; function TJQXButton.MakeElementTagObj: THandle; begin result := w3_createHtmlElement('div'); end; end. which can be instantiated as simple as jqxButton2 := TJQXButton.Create(self); jqxButton2.SetBounds(230,20,130,50); jqxButton2.Caption := 'Button2'; jqxButton2.OnClick := procedure(Sender:TObject) begin browserapi.window.alert('clicked button2'); end; Project demo here and source code here
  10. lynkfs

    JQWidget integration

    and here is the Grid - JQWidgets implementation working sms demo here Notes : there are 2 different formats of defining the grid, one using jQuery explicitly and one as a web-component. The latter format has a bug somewhere and comes up consistently with a runtime error. The former format works fine. the 'export to Excel' button actually fires up Excel if installed and run locally (sms demo is grid only but take my word)
  11. lynkfs

    Font size and style in buttons

    it is a difficult job to develop themes. If you have an example of a theme you prefer, I might have a look (depending on the bounty) Cheers
  12. lynkfs

    Font size and style in buttons

    Suggestion 2
  13. lynkfs

    visual processing

    See previous post : any type of no-code / lo-code solution requires a visual ide, not only for the visual components like buttons etc but also for the non-visual process components. Such an ide allows for both visual and non-visual components to be connected according to the principles of FBP. See previous post and link here. To see how this would pan out, I made this proof of concept no-code / lo-code web-based ide. (Proof of concept not being a full fledged finished product, but at least gives some insights) To diminish the strain on the brain a bit, the layout is based on the Smart ide, including the shameless reuse of its icons. Some highlights : - drag and drop placement of components rather than point and select - the usual component move and resize facilities but now including Delphi-type align options - drag and drop fbp connections between components using jsPlumb A quick rundown : 1) drag a button on the design pane 2) select 'Database' from the top tab-control, and drag both a grid and mySQL component on the design pane. Note the property pane is updated in realtime 3) (optionally click-select any dragged component to resize them. Select both grid and button, right-click on either for alignment options) 4) switch to the No-code tab and connect the button to the mySQL component, and the mySQL component to the grid 5) right-click the mySQL component and provide details (user-id, server, passwords etc). Not shown 6) switch to the Preview tab and click the button This project compiles to 63 KB (0.06 M. Not bad Project code here
  14. lynkfs

    visual processing

    A while ago I posted some ideas and a demo on the subject of 'nocode' or 'locode' development. The idea was to do a domain modelling exercise, and extract or generate an application from that with no or minimal coding required. The demo put a couple of the proposed models through its paces. After that post I put the subject back in the incubator for a bit. Sort of recently I realised that having an rtl with visual components and an ide with a form painter is a good thing to have, but only covers part of what is necessary if we need to cover process logic, or business rules, without having to resort to coding. Various computing packages (datamining, visual analytics etc) model business logic by providing specialised components, which can be strung together and parametrised. I tried this approach out (using KNIME) with the following (nonsensical) example process : "from the NorthWind database get all companies, and from the FishFacts database extract all species, collate only those entries from both datasources where the name starts with the letter 'L' and list them. Also produce a pie-chart of all fish species showing their length in cm." This (nonsense) process translates to the node structure above. Nodes typically can be selected from a list of available nodes and right-clicking gives parametrisation forms, f.i. the MYSQL connector asks for a host, database name, credentials etc. Stringing these components together is a simple process in itself, and the output produces something like Not too bad. I've coded a couple of these type of components in Smart, which works really well. For the technical architecture I've based these on the principles of FBP (flow based programming) where every node is a webworker, and the connectors between these nodes/webworkers are defined by channel-objects. Data transfer between these nodes/channels is done by messaging. See post here. It would be nice to extend the rtl with these types of components, including a process painter
  15. lynkfs

    Wrapping javascript library or ASM blocks ?

    it works fine you have to add these lines to your index.html file <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script src="json-rpc-client.js"></script> and get this json-rpc-client.js file physically in the same directory as your index file code I used : procedure TForm1.W3Button2Click(Sender: TObject); begin var client : variant := new JObject; asm @client = new JSONRpcClient({ 'url': 'https://user-api.simplybook.me', 'headers': { 'X-Company-Login': 'lynkfs', 'X-Token': '89d85d820355.....................57eae4a72579bb' }, 'onerror': function (error) {} }); end; var services : variant := client.getEventList(); writeln(JSON.stringify(services)); //{"1":{"id":"1","name":"LynkFS","duration":"60","buffertime_before":"0","buffertime_after":"0","hide_duratio.... end; use either 'http' or 'https' for both the links to '//user-api.simplybook.me' and '//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js' , depending on what your server is set up for
  16. lynkfs

    Wrapping javascript library or ASM blocks ?

    i'll have a look over the weekend
  17. lynkfs

    Wrapping javascript library or ASM blocks ?

    Having a cursory view on this library, what you probably want to end up with is a pascal variable called 'client', which you can use outside asm blocks. This will allow you to issue calls like client.getEventList() etc. to do that you can do something like this var client : variant := new JObject; asm @client = new JSONRpcClient({ 'url': '//user-api.simplybook.me', 'headers': { 'X-Company-Login': 'ACME', 'X-Token': token }, 'onerror': function (error) { alert(error); } }); end; var EventList: variant := client.getEventList(-params-); rule of thumb: the creation of js objects via the 'new' constructor need to be kept in an asm block
  18. lynkfs

    Calendar Control

    Easiest way : procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var CalenderBox : TW3Panel := TW3Panel.Create(self); CalenderBox.SetBounds(10,10,252,266); CalenderBox.handle.id := 'CalenderBox'; var myCalendar : variant := new JObject; asm @myCalendar = new dhtmlXCalendarObject("CalenderBox"); end; myCalendar.disableDays("week", [7]); myCalendar.setDateFormat("%Y-%m-%d"); myCalendar.setHolidays("2019-03-19"); var yesterday : float := Now()-1; myCalendar.setInsensitiveRange(null, DateToStr(yesterday)); myCalendar.attachEvent("onClick", procedure(date:variant) begin writeln(myCalendar.getFormatedDate('%d-%m-%Y')); end); myCalendar.show(); end; make sure you include the necessary dhtmlx files in your index.html (Project Manager pane, right-click, add Template and select default.html from the Templates directory. This creates a new unit Custom Template. Then add the 2 lines below) . I used the cdn version (which contains the whole suite) <link rel="stylesheet" href="http://cdn.dhtmlx.com/edge/dhtmlx.css" type="text/css"> <script src="http://cdn.dhtmlx.com/edge/dhtmlx.js" type="text/javascript"></script> (alternatively you can import these files in code too)
  19. lynkfs

    Copying Text to Clipboard

    Browsers pick up selected text in <textarea> and <p> type of html elements, not necessarily in each and every other type. Also they will not mess with existing clipboard data unless initiated by a user action (like clicking a button). Bit of a roundabout way of doing this, but this works (form with W3Label1 and W3Button1) procedure TForm1.W3Button1Click(Sender: TObject); begin var xCopy : Procedure(S:String); asm @xCopy = function copyToClipboard(text) { if (window.clipboardData && window.clipboardData.setData) { // IE specific code path to prevent textarea being shown while dialog is visible. return clipboardData.setData("Text", text); } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { var textarea = document.createElement("textarea"); textarea.textContent = text; textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge. document.body.appendChild(textarea); textarea.select(); try { return document.execCommand("copy"); // Security exception may be thrown by some browsers. } catch (ex) { console.warn("Copy to clipboard failed.", ex); return false; } finally { document.body.removeChild(textarea); } } }; end; xCopy(W3Label1.Caption); end;
  20. lynkfs

    scrolling big numbers

    Alternatively the intersection observer API might be a feasible approach as well. That would have the advantage of not burdening the scroll event with expensive code and being able to scroll variable row heights.
  21. lynkfs

    scrolling big numbers

    Got challenged the other day by trying to scroll large numbers of rows in a listbox. Browsers are very good at scrolling, but when row numbers become large and the scroll context becomes complex, scroll behaviour deteriorates. A simple un-optimised scrolling div is, rule of thumb, able to scroll comfortably up to a couple of hundred rows. Depending on browser and complexity. In the native framework I tweaked that a bit by setting all rows outside the visible viewport to 'display:none'. That extends comfortable scrolling somewhat, say to numbers in the low thousands. A better way is to completely separate display from content. Meaning that the content needs to be held in some kind of memory structure, not in visual components, and that scrolling is redefined to re-using a small set of visual rows confined to the scroll-window. A structure to make that happen would be a <div> like component (TW3Panel) on a form with NativeScrolling set to true. Set up 2 child-panels on this component, where the first child-panel has dimensions equal to the maximum scrollable area, and a second childpanel dimensioned equal to this parent. The first child-panel is the scroller, which will always be completely empty. The second child-panel contains the visible rows, which will be refreshed on scroll-events. To keep this panel always in view, set the position attribute to '-webkit-sticky'. This demo shows that Chrome can readily handle 500,000 rows, even on mobile. FF a bit less. The good thing is that the number of rows doesn't really matter, scrolling behaviour stays the same regardless. Would be interesting to see if performance gets even better by pushing scrolling to the GPU, pretty sure Chrome does that by default. Infinite scrolling will be easy to implement using this structure too. Code procedure TForm1.W3Button1Click(Sender: TObject); begin //data var column1 := TVariant.CreateArray; for var i := 0 to 500000 do begin column1.push(inttostr(i)); end; var column2 := TVariant.CreateArray; for var i := 1 to 500001 do begin column2.push(inttostr(i)); end; //column2.sort(); //sorting works too //scrolling setup var rowHeight := 30; W3Panel.NativeScrolling := true; //Parent panel (in visual designer : width 408, height 266) var Panel1 : TW3Panel := TW3Panel.Create(W3Panel); //scroller (always empty!) Panel1.SetBounds (0,0,380,column1.length*rowHeight); //but with large height Panel1.BorderRadius := 0; //set up viewport var Panel2 : TW3Panel := TW3Panel.Create(W3Panel); //viewport, only the visual rows Panel2.SetBounds(0,0,380,262); //dimensions same as grid-parent Panel2.BorderRadius := 0; Panel2.handle.style.position := '-webkit-sticky'; Panel2.handle.style.position := '-moz-sticky'; Panel2.handle.style.position := '-ms-sticky'; Panel2.handle.style.position := '-o-sticky'; Panel2.handle.style.position := 'sticky'; Panel2.handle.style.top := '0px'; //set up columns Var CPanel1 : TW3Panel := TW3Panel.Create(Panel2); //column 1 CPanel1.SetBounds(-2,-2,200,264); CPanel1.BorderRadius := 0; Var CPanel2 : TW3Panel := TW3Panel.Create(Panel2); //column 2 CPanel2.SetBounds(200,-2,200,264); CPanel2.BorderRadius := 0; //initial fill viewport prior to first onscroll event for var j := 0 to 8 do begin //only first couple of rows var x : TW3Panel := TW3Panel.Create(CPanel1); //column 1 x.SetBounds(-2,j*rowHeight-2,200,rowHeight); x.BorderRadius := 0; x.innerHTML := column1[j]; x.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; // var y : TW3Panel := TW3Panel.Create(CPanel2); //column 2 y.SetBounds(-2,j*rowHeight-2,190,rowHeight); y.BorderRadius := 0; y.innerHTML := column2[j]; y.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; end; //fill viewport while scrolling var c1 := CPanel1.handle.children; var c2 := CPanel2.handle.children; W3Panel.onscroll := procedure(sender:tobject) begin var d : integer := trunc(W3Panel.handle.scrollTop/rowHeight); for var k := 0 to 8 do begin c1[k].innerHTML := column1[d+k]; c2[k].innerHTML := column2[d+k]; end; //fall back : ide chrome browser and iOS don't handle 'sticky' very well, //in that case set viewport position manually if Panel2.handle.style.position <> 'sticky' then if not w3_getIsSafari then Panel2.top := W3Panel.handle.scrollTop; end; end; infinity scroll : add to end of W3Panel.onscroll procedure //infinity scroll : if W3Panel.handle.scrollHeight - W3Panel.handle.scrollTop = W3Panel.handle.clientHeight then W3Panel.handle.scrollTop := 0;
  22. lynkfs

    Copying Text to Clipboard

    did you set designMode to 'on' ? execCommand only works if this flag is set, something like browserapi.document.designMode := 'on'; btw, there is a new Clipboard Api, which supersedes the execCommand handling. Should be supported by now in modern browsers I think.
  23. lynkfs

    node ground zero

    I had the idea firmly rooted in my mind that Node is for server-side deployment only. This happens to be not the case, it is apparently feasible to use Node packages in the browser ! Talking about packages, the largest store of Node packages is NPM, which stores some 500,000 packages or more. (350,000 as per jan 2017) Not all of those are suitable for use in the browser, but a lot of them are. The ones which don't work client-side are the ones that require internet access, graphics or files, so this is not a work-around to be able to access databases on the client without going through a server, or enabling to write files client-side. Probably a good thing. One of the ways to use an existing node package (or a self-written one) client-side is Browserify. This product basically takes a packages and iterates through all its dependencies to other packages and collates them in a single bundle. Which can then be used client-side. Browserify also has written client-side versions of some of Node's core modules, which are automatically included in a bundle when necessary. HTTP and HTTPS for instance. That still doesn't make it possible to set up a http server within the browser, but other http-functions will work : the first program in this thread (see top of this thread) was a vanilla js node program which accesses www.random.org (a site which generates random integers) and prints out the value to the console. in plain vanilla javascript : var https = require('https'); //The url is: 'https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' var options = { host: 'www.random.org', path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' }; callback = function(response) { var str = ''; //another chunk of data has been received, so append it to `str` response.on('data', function (chunk) { str += chunk; }); //the whole response has been received, so we just print it out here response.on('end', function () { console.log(str); }); } https.request(options, callback).end(); Browserifying this package and accessing it from the browser works fine, and can be an alternative to issuing xmlhttprequests Another example using Node's event mechanism // require the core node events module var EventEmitter = require('events').EventEmitter //create a new event emitter var emitter = new EventEmitter // set up a listener for the event emitter.on('test', function (message) { console.log(message) }) // emit an event emitter.emit('test', 'this is an event test') works fine as well, an alternative to add event listeners etc It wouldn't be too difficult to Smarten this up either. I'm having a look at the other 500,000 packages. Daunting but interesting.
  24. lynkfs

    node ground zero

    Node is still a new beast to tame, and at times it drives me bonkers. To get my head around node, I started with a couple of very simple node programs (js) and how these could be translated into object pascal. The exercise also meant to discover which of the RTL node units to use in which circumstances. The first one is a node program which accesses www.random.org (a site which generates random integers) and prints out the value to the console. in plain vanilla javascript : var https = require('https'); //The url is: 'https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' var options = { host: 'www.random.org', path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' }; callback = function(response) { var str = ''; //another chunk of data has been received, so append it to `str` response.on('data', function (chunk) { str += chunk; }); //the whole response has been received, so we just print it out here response.on('end', function () { console.log(str); }); } https.request(options, callback).end(); http and https are modules built into node itself, so no need to npm-install these separately. The code above makes a https request to this particular url. In the callback it catches 2 events : on-'data' and on-'end'. On-'data' is called every time a chunk of data has been read. Not sure what the standard chunk-size is, but it will be more than the size of a single integer, so this event will only occur once. After that the on-'end' event fires. and executing "node tryout.js" does indeed produce a random integer Converting this line-by-line to SMS : start a new node project (tryout.sproj) and delete unit1. Replace the code in the root unit (tryout) by this : function RequireModule(id: string): Variant; external 'require'; var https := RequireModule('https'); var options: variant := new JObject; options.host := 'www.random.org'; options.path := '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new'; var callback: variant := new JObject; callback := procedure(response: variant) begin var str: string := ''; response.on('data', procedure(chunk: variant) begin str+= chunk; end); response.on('end', lambda asm console.log(@str); end; end); end; https.request(options, callback).end(); and compile. The compiled tryout.js in the output directory reads as var https, options$1, callback; https = require("https"); options$1 = {}; options$1.host = "www.random.org"; options$1.path = "\/integers\/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new"; callback = {}; callback = function (response) { var str = ""; str = ""; response.on("data",function (chunk) { str+=String(chunk); }); response.on("end",function () { console.log(str); }); }; https.request(options$1,callback).end(); which is close enough and when executed produces the same results. btw: the compilation output is slightly different when having the 'use main body' in the 'compiler/code generation' project options selected or unselected. However results are the same for both. The first line function RequireModule(id: string): Variant; external 'require'; is copied from NodeJS.Core.pas so obviously that unit could be included in this particular use-case. Note 1 : http(s) offers a shortcut for GET calls (http.get...), so the SMS project above could be shortened to function RequireModule(id: string): Variant; external 'require'; //or alternatively : uses NodeJS.Core; var https := RequireModule('https'); var callback: variant := new JObject; callback := procedure(response: variant) begin var str: string := ''; response.on('data', procedure(chunk: variant) begin str+= chunk; end); response.on('end', lambda //procedure() //begin asm console.log(@str); end; end); end; https.get('https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new', callback).end(); Note 2: Instead of reading chunks of data and appending these into a string (see the 'data' event above), the response of the http request can also be sent to a stream. In that case the code is slightly modified and even more simplified : function RequireModule(id: string): Variant; external 'require'; var https := RequireModule('https'); var callback: variant := new JObject; callback := procedure(response: variant) begin response.setEncoding('utf8'); response.on('readable', lambda asm console.log((@response).read()); end; end); end; https.get('https://forums.smartmobilestudio.com/topic/4652-node-ground-zero/', callback).end(); Note 3 : to make it all a bit more SMS-like : start a standard new Node project and replace the code in Unit1 by : unit Unit1; interface uses nodeBasics; type TNodeProgram = class(TObject) public procedure Execute; constructor Create; virtual; https: variant; procedure callback(response: variant); end; implementation constructor TNodeProgram.Create; begin inherited Create; https := RequireModule('https'); end; procedure TNodeProgram.callback(response: variant); begin response.setEncoding('utf8'); response.on('readable', lambda console.log(response.read()); end); end; procedure TNodeProgram.Execute; begin https.get('https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new', @callback).end(); end; end. and for now the bare-bones nodeBasics : unit nodeBasics; interface type JConsole = class external 'Console' public procedure log(data: Variant); end; var Console external 'console': JConsole; function RequireModule(id: string): Variant; external 'require'; implementation end.
  25. lynkfs

    Chat Scroller - Need Some Tips

    Either one of two approaches come to mind : standardise rowheight by calculating all chat data lines in a conversation according to the width of the chatbox. And then set rowheight to the height of a single line (say 30px). For very large conversations you could use this approach, but probably a standard TW3ListBox will do. Otherwise make rowheight variable and calculate the height of all previous chats and determine which ones are in the visual viewport. That probably will take a specialised TW3ListBox.
×