Jump to content

lynkfs

Moderators
  • Content Count

    740
  • Joined

  • Last visited

  • Days Won

    146

Reputation Activity

  1. Like
    lynkfs reacted to jarto in TW3StringGrid is available   
    A new update is available in the development-channel of SmartUpdate. It contains a completely new Grid: TW3StringGrid. It's a completely new design, which was made to be very fast and able to handle lots of data. This is achieved through dynamic drawing: Only the visible grid lines are rendered.
    There is a new StringGrid-demo in the Featured Demos. It downloads a json file and populates the grid with data from it.
    TW3StringGrid supports six different column types:
    Text (column class TW3StringGridTextColumn) Numeric (column class  TW3StringGridNumericColumn) EditBox (column class TW3StringGridEditColumn) ComboBox (column class TW3StringGridComboColumn) Checkmark (column class TW3StringGridCheckmarkColumn) Button (column class TW3StringGridButtonColumn) Columns are created by calling:
    MyNewColumn:=Grid.AddColumn(ColumnClass);
    If no ColumnClass is given, a Text column is created.
    Column properties:
    Caption Width BorderType Default is btLightBorderRight. Set to btNone if you don't want any vertical borders. Backgroundtype Default is btNone to let the line color through. AlignText (The same way as in TW3Label) taLeft (default) taCenter taRight SelectOptions (for TW3StringGridComboColumn only) This is an array of selectable values. First value (index 0) is the value for no selection. For example: SelectOptions:=['','First','Second','Third']; RowBorderType: Default is btLightBorderBottom. Set to btNone if you don't want any horizontal borders. RowBackgroundType: Default is bsListItemBackground. Set to bsDecorativeListItemBackground (or test other backgrounds) to change the background style for even rows. RowOddBorderType: Default is btLightBorderBottom. Set to btNone if you don't want any horizontal borders. Affects odd rows. RowOddBackgroundType: Default is bsListItemBackground. Set to bsDecorativeListItemBackground (or test other backgrounds) to change the background style for odd rows. Grid properties:
    RowCount: Set number of rows to show FixedColumns: How many columns should be fixed (aka not scrollable horizontally) LineHeight MultiSelect Events:
    OnCellClick: Is triggered when the row is clicked. OnCellChanged: Is triggered when cell content changes through editing (for example: through editing) OnDrawGridLineTheme: Can be used to set custom backgrounds and borders for a row. (for example, set background to bsErrorBackground for lines that should be highlighted to the user) Methods:
    InvalidateGrid: Triggers a repaint of the grid. Sort Sorting
    TW3StringGrid supports sorting based on one of multiple columns. Clicking on the column headers sets or reverses sort order. You can also control sorting in code:
    procedure AddSortColumn(Index: Integer; SortOrder: TW3SortOrder = soNormal); overload; procedure AddSortColumn(Col: TW3StringGridColumn; SortOrder: TW3SortOrder = soNormal); overload; procedure SetSortColumn(Index: Integer; SortOrder: TW3SortOrder = soNormal); overload; procedure SetSortColumn(Col: TW3StringGridColumn; SortOrder: TW3SortOrder = soNormal); overload; procedure ToggleSortColumn(Index: Integer); For example:
    Grid.SortOptions.AddSortColumn(3,soReverse); //Set primary sort column Grid.SortOptions.AddSortColumn(1); //Add secondary sort columns Grid.Sort; Working with sorted data
    When the Grid is sorted, it only affects how data is shown. Sorting does not change Grid data itself at all. When you work with a sorted grid events and indexes (for example SelectedIndex) always refer to the index in the grid data itself and NOT the visible line number.
    TW3StringGrid uses these events:
    TW3StringGridColumnEvent = procedure(const Sender: TW3StringGridColumn; const Row: Integer); TW3StringGridEvent = procedure(const Sender: TObject; const Row, Col: Integer); If you have a Grid where "Australia" is on line 2, SelectedIndex:=2 selects that row regardless of how the grid is sorted. Clicking on that row or changing data on that row also returns Row=2 regardless of how the grid is sorted.
  2. Like
    lynkfs reacted to jarto in Beware of box shadows!   
    I have been working on a new TW3StringGrid for Smart Mobile Studio. The original plan was to include it in 3.0, but there was a problem: It was slow on iOS. So, ever since the release, I've tried to crack this problem. How can Javascript that works beautifully on every other platform and browser be absolutely horrible on iOS? It was extremely fast on desktop browsers and  Android tablets. On iOS it took seconds to draw the grid and scrolling it was close to unusable.   After countless of hours of testing and trying, I finally found the reason: Box shadows in CSS. For example: .TW3ContainerBorder { border-radius: <?pas=EdgeRounding?>; border-top: 1px solid rgba(250, 250, 250, 0.7); border-left: 1px solid rgba(250, 250, 250, 0.7); border-right: 1px solid rgba(240, 240, 240, 0.5); border-bottom: 1px solid rgba(240, 240, 240, 0.5); -webkit-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -moz-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -ms-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; -o-box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; box-shadow: 0px 0px 1px 1px <?pas=GetRGBA(clDlgShadowBaseColor, 0.3)?>; } .TW3FlatBorder { border-radius: <?pas=EdgeRounding?>; border-top: 1px solid rgba(44, 44, 44, 0.8); border-left: 1px solid rgba(44, 44, 44, 0.8); border-right: 1px solid rgba(44, 44, 44, 0.8); border-bottom: 1px solid rgba(44, 44, 44, 0.8); } I had used a TW3ContainerBorder for every cell in the grid. Replacing them with a flat border improved performance dramatically on iOS. Something like an order of magnitude faster.
      So if you've been having performance problems with iOS, that's most likely the reason.
  3. Like
    lynkfs got a reaction from IElite in 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
     
     
  4. Like
    lynkfs got a reaction from IElite in Wrapping javascript library or ASM blocks ?   
    i'll have a look over the weekend
  5. Like
    lynkfs got a reaction from IElite in 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
  6. Like
    lynkfs got a reaction from jarto in 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
  7. Thanks
    lynkfs reacted to DavidRM in Chat Scroller - Need Some Tips   
    Here's the overhauled version, that only uses as many actual on-screen elements as required to show the visible items. It's been working pretty well.
    unit UPBTInfoScroll; interface uses System.Types, System.Lists, System.Time, System.Colors, SmartCL.System, SmartCL.Theme, SmartCL.Components, SmartCL.Controls.Label, SmartCL.Scroll, SmartCL.Controls.ScrollBar, SmartCL.Css.Classes; type TPBTScrollItemData = class(TObject) private FItemText: string; FItemTop: integer; FItemHeight: integer; public function ItemStyleName: string; virtual; function InnerHtml: string; virtual; property ItemText: string read FItemText write FItemText; property ItemTop: integer read FItemTop write FItemTop; property ItemHeight: integer read FItemHeight write FItemHeight; end; TPBTScrollInfoItemData = class(TPBTScrollItemData) public function ItemStyleName: string; override; end; TPBTScrollErrorItemData = class(TPBTScrollItemData) public function ItemStyleName: string; override; end; TPBTChatType = (ctAnnounce, ctChat, ctSay, ctShout, ctWhisper, ctTell, ctPlan, ctTeamPlan, ctWizChat); TPBTScrollChatItemData = class(TPBTScrollItemData) private FChatType: TPBTChatType; FChatHeader: string; FChatColor: TColor; public function ItemStyleName: string; override; function InnerHtml: string; override; property ChatType: TPBTChatType read FChatType write FChatType; property ChatHeader: string read FChatHeader write FChatHeader; property ChatColor: TColor read FChatColor write FChatColor; property ChatText: string read ItemText write ItemText; end; TPBTScrollItem = class(TW3CustomControl) protected FItemData: TPBTScrollItemData; procedure SetItemData(aValue: TPBTScrollItemData); public function CreationFlags: TW3CreationFlags; override; procedure InitializeObject; override; procedure UpdateDisplay; virtual; property ItemData: TPBTScrollItemData read FItemData write SetItemData; end; TPBTScrollItemClass = class of TPBTScrollItem; TPBTScrollInfoItem = class(TPBTScrollItem) end; TPBTScrollErrorItem = class(TPBTScrollInfoItem) end; TPBTScrollChatItem = class(TPBTScrollItem) end; TPBTScrollItems = class(TW3ScrollContent) private function GetItem(Index: Integer): TPBTScrollItem; function GetCount: integer; public function Add: TPBTScrollItem; overload; function Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; overload; procedure Clear; virtual; procedure FinalizeObject; override; procedure Delete(aIndex: integer); published property Items[index: Integer]: TPBTScrollItem read GetItem; default; property Count: integer read GetCount; end; TPBTInfoScrollAddItemDataCallback = function: TPBTScrollItemData; TPBTInfoScrollAddItemCallback = function: TPBTScrollItem; TPBTInfoScroll = class(TW3ScrollControl) private FDataItems: array of TPBTScrollItemData; FNewItems: array of TPBTScrollItemData; function GetItems: TPBTScrollItems; function GetScrollIsAtBottom: boolean; procedure ScrollControllerScrolling(Sender: TObject); protected procedure InitializeObject; override; procedure Resize; override; function GetScrollContentClass: TW3ScrollContentClass; override; public function CreationFlags: TW3CreationFlags; override; procedure Clear; procedure Refresh; procedure ScrollToBottom; procedure RealignItems; procedure UpdateVisibleItems; procedure AddItem(aCallback: TPBTInfoScrollAddItemDataCallback); procedure AddText(const aText: string); procedure AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); procedure AddInfo(const aInfoText: string); procedure AddError(const aErrorText: string); function MaxInfoWidth: integer; function MinInfoWidth: integer; published property Items: TPBTScrollItems read GetItems; property ScrollIsAtBottom: boolean read GetScrollIsAtBottom; end; implementation function TPBTScrollItemData.ItemStyleName: string; begin Result := 'TPBTScrollItem'; end; function TPBTScrollItemData.InnerHtml: string; begin Result := Format('<span style="user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text">%s</span>', [TString.EncodeTags(FItemText)]); end; function TPBTScrollInfoItemData.ItemStyleName: string; begin Result := 'TPBTScrollInfoItem'; end; function TPBTScrollErrorItemData.ItemStyleName: string; begin Result := 'TPBTScrollErrorItem'; end; function TPBTScrollChatItemData.ItemStyleName: string; begin Result := 'TPBTScrollChatItem'; end; function TPBTScrollChatItemData.InnerHtml: string; begin Result := Format('<span style="color:%s;user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text"><b>%s</b>: %s</span>', [ColorToWebStr(FChatColor), TString.EncodeTags(FChatHeader), TString.EncodeTags(ChatText)]); end; // TPBTScrollItem procedure TPBTScrollItem.SetItemData(aValue: TPBTScrollItemData); begin if FItemData <> nil then TagStyle.RemoveClassFromControl(Handle, FItemData.ItemStyleName); FItemData := aValue; if FItemData <> nil then begin Top := FItemData.ItemTop; TagStyle.AddClassToControl(Handle, FItemData.ItemStyleName); end; UpdateDisplay; end; function TPBTScrollItem.CreationFlags: TW3CreationFlags; begin inherited; Include(Result, cfAllowSelection); end; procedure TPBTScrollItem.InitializeObject; begin inherited; SetContentSelectionMode(tsmText); w3_setStyle(Handle, 'min-width', '100%'); end; procedure TPBTScrollItem.UpdateDisplay; begin if FItemData <> nil then InnerHTML := FItemData.InnerHtml else InnerHTML := ''; end; // TPBTScrollInfoItem // TPBTScrollErrorItem // TPBTScrollChatItem // TPBTScrollItems function TPBTScrollItems.GetItem(Index: Integer): TPBTScrollItem; begin Result := TPBTScrollItem(GetChildren[Index]); end; function TPBTScrollItems.GetCount: integer; begin Result := GetChildCount; end; function TPBTScrollItems.Add: TPBTScrollItem; begin Result := Add(TPBTScrollItem); end; function TPBTScrollItems.Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; begin Result := aScrollItemClass.Create(Self); end; procedure TPBTScrollItems.Clear; begin // can't get rid of from [0]. Not sure why. while Count > 0 do GetChildren[Count - 1].Free; Height := 0; end; procedure TPBTScrollItems.FinalizeObject; begin Clear; inherited; end; procedure TPBTScrollItems.Delete(aIndex: integer); begin if aIndex < GetChildCount then GetChildren[aIndex].Free; end; // TPBTInfoScroll function TPBTInfoScroll.GetItems: TPBTScrollItems; begin Result := TPBTScrollItems(Content); end; function TPBTInfoScroll.GetScrollIsAtBottom: boolean; begin Result := (Content.Height <= ClientHeight) or (ScrollController.ContentTop <= -(Content.Height - (ClientHeight + 10))) end; procedure TPBTInfoScroll.ScrollControllerScrolling(Sender: TObject); begin UpdateVisibleItems; end; procedure TPBTInfoScroll.InitializeObject; begin inherited; SetBarSize(CNT_SCROLLBAR_SIZE); ScrollBars := sbScrollBar; ScrollController.OnScrolling := ScrollControllerScrolling; end; procedure TPBTInfoScroll.Resize; begin inherited; Content.Width := ClientWidth - CNT_SCROLLBAR_SIZE; Refresh; end; function TPBTInfoScroll.GetScrollContentClass: TW3ScrollContentClass; begin Result := TPBTScrollItems; end; function TPBTInfoScroll.CreationFlags: TW3CreationFlags; begin inherited; // Allow key-capture and selection include(result, cfKeyCapture); include(result, cfAllowSelection); end; procedure TPBTInfoScroll.Clear; begin FDataItems.Clear; Refresh; end; procedure TPBTInfoScroll.Refresh; begin if Width > 0 then begin var wasAtBottom := GetScrollIsAtBottom; RealignItems; ScrollController.Refresh; if wasAtBottom then ScrollToBottom; UpdateVisibleItems; end; end; procedure TPBTInfoScroll.ScrollToBottom; begin ScrollController.ScrollTo(0, -(Content.Height - ClientHeight)); end; procedure TPBTInfoScroll.RealignItems; begin // recalculate the size of each itemData if (FDataItems.Length > 0) or (FNewItems.Length > 0) then Items.BeginUpdate; try var aItem := Items.Add; try var aTop: integer := 0; for var aItemData in FDataItems do begin aItem.ItemData := aItemData; aItemData.ItemHeight := aItem.Height; aItemData.ItemTop := aTop; Inc(aTop, aItemData.ItemHeight); end; for var aItemData in FNewItems do begin FDataItems.Add(aItemData); aItem.ItemData := aItemData; aItemData.ItemHeight := aItem.Height; aItemData.ItemTop := aTop; Inc(aTop, aItemData.ItemHeight); end; FNewItems.Clear; Content.Height := aTop; if Content.Height < ClientHeight then Content.Height := ClientHeight; finally aItem.Free; end; finally Items.EndUpdate; end; end; procedure TPBTInfoScroll.UpdateVisibleItems; var aStartIdx, aEndIdx, aDataIdx, viewTop: integer; begin viewTop := ScrollController.ContentTop; if viewTop < 0 then viewTop := -viewTop; // find first visible itemData // start from the bottom of the list aStartIdx := FDataItems.Length; while (aStartIdx > 0) do begin Dec(aStartIdx); if FDataItems[aStartIdx].ItemTop <= viewTop then break; end; // find last visible itemData aEndIdx := aStartIdx; while (aEndIdx < FDataItems.Length) do begin if (aEndIdx = FDataItems.Length - 1) or (FDataItems[aEndIdx].ItemTop > viewTop + ClientHeight) then break; Inc(aEndIdx); end; Items.BeginUpdate; try if (aStartIdx >= FDataItems.Length) then Items.Clear else begin // adjust item count while Items.Count < (aEndIdx - aStartIdx) + 1 do Items.Add; while Items.Count > (aEndIdx - aStartIdx) + 1 do Items.Delete(Items.Count - 1); aDataIdx := aStartIdx; for var ii := 0 to Items.Count -1 do begin var aItem := Items[ii]; aItem.ItemData := FDataItems[aDataIdx]; Inc(aDataIdx); end; end; finally Items.EndUpdate; end; end; procedure TPBTInfoScroll.AddItem(aCallback: TPBTInfoScrollAddItemDataCallback); const margin = 5; begin var aItemData: TPBTScrollItemData := aCallback; if Width = 0 then begin FNewItems.Add(aItemData); exit; end; var wasAtBottom := (ScrollController.ContentTop <= -(Content.Height - (ClientHeight + 2 * margin))); Items.BeginUpdate; try if FDataItems.Length > 0 then aItemData.ItemTop := FDataItems[FDataItems.Length - 1].ItemTop + FDataItems[FDataItems.Length - 1].ItemHeight; FDataItems.Add(aItemData); var aItem := Items.Add; aItem.ItemData := aItemData; aItemData.ItemHeight := aItem.Height; Content.BeginUpdate; try Content.Height := aItem.Top + aItem.Height; if Content.Height <= ClientHeight then begin Content.Height := ClientHeight; wasAtBottom := True; end; finally Content.EndUpdate; end; finally Items.EndUpdate; end; ScrollController.Refresh; if wasAtBottom then ScrollToBottom; UpdateVisibleItems; end; procedure TPBTInfoScroll.AddText(const aText: string); begin AddItem( lambda Result := new TPBTScrollItemData; Result.ItemText := aText; end ); end; procedure TPBTInfoScroll.AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); begin AddItem( lambda Result := new TPBTScrollChatItemData; TPBTScrollChatItemData(Result).ChatType := aChatType; TPBTScrollChatItemData(Result).ChatHeader := aChatHeader; TPBTScrollChatItemData(Result).ChatText := aChatText; TPBTScrollChatItemData(Result).ChatColor := aChatColor; end ); end; procedure TPBTInfoScroll.AddInfo(const aInfoText: string); begin AddItem( lambda Result := new TPBTScrollInfoItemData; Result.ItemText := aInfoText; end ); end; procedure TPBTInfoScroll.AddError(const aErrorText: string); begin AddItem( lambda Result := new TPBTScrollErrorItemData; Result.ItemText := aErrorText; end ); end; function TPBTInfoScroll.MaxInfoWidth: integer; var aMax: string; begin while aMax.Length < 70 do aMax += 'XXXXXXXXXX'; Result := MeasureText(aMax).tmWidth + GetBarSize; end; function TPBTInfoScroll.MinInfoWidth: integer; begin Result := MaxInfoWidth div 2; end; end. There are some bits that specific to my Paintball Net project, like the style names, which I have keyed to CSS.
    -David
  8. Like
    lynkfs got a reaction from jarto in 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)


     
  9. Like
    lynkfs got a reaction from Czar in 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)


     
  10. Like
    lynkfs got a reaction from IgorSavkic in 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.
     
     
     
     
  11. Like
    lynkfs got a reaction from jarto in 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;
     
     
  12. Like
    lynkfs got a reaction from jarto in 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.
  13. Like
    lynkfs reacted to jarto in Chat Scroller - Need Some Tips   
    I'm going to take a break from my Chromium work and have a look if I can modify ListBox to work with variable row heights.
    Edit: That's not too difficult to do. It'll just slow down a bit the calculation of rows to be shown. With variable heights you need to loop through all items when with fixed heights you can calculate which ones should be shown.
    However, having dynamic row heights is a pain and I believe that a chat control would need those. In that case the height of all items change during a device flip or window resize. And I believe that a proper chat control actually needs dynamic size.
    So, how would I do this? I'd set the min-width of the items to 100% and use PositionMode pmRelative. Then you should be able to just add and remove controls while the browser takes care of resizing and stacking them.
    Something like this:
    procedure TForm1.W3Button1Click(Sender: TObject); // Add to the end begin var Item:=TW3DivHtmlElement.Create(Content); //Or anything else Item.PositionMode:=pmRelative; w3_setStyle(Item.Handle, 'min-width', '100%'); Item.InnerText:='Hello world! I am control number '+IntToStr(Content.ControlCount)+'. How are you doing there?'; end; procedure TForm1.W3Button2Click(Sender: TObject); // Remove 1st. begin Content.ForEach( function(const Child: TW3TagContainer): TEnumResult begin Child.Free; result:=erBreak; end); end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components Content.NativeScrolling:=True; end;  
  14. Like
    lynkfs got a reaction from jarto in 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.
     
     
     
     
  15. Like
    lynkfs got a reaction from jarto in 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
     
  16. Thanks
    lynkfs reacted to jarto in Update embedded Chromium   
    Hi guys,
    I've been a busy for a while working on updating the Embedded Chrome we use in the IDE. The one we've used so far has been from Henri Gourvest. Unfortunately it's not been updated for 3 years, but there's a new fork by Salvador Diaz Fau, which is actively updated: https://github.com/salvadordf/CEF4Delphi
  17. Like
    lynkfs got a reaction from IElite in 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;  
     
     
     
     
  18. Like
    lynkfs got a reaction from jarto in 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;  
     
     
     
     
  19. Like
    lynkfs got a reaction from Czar in 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;  
     
     
     
     
  20. Like
    lynkfs got a reaction from IgorSavkic in 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;  
     
     
     
     
  21. Thanks
    lynkfs got a reaction from IgorSavkic in WebSocket Srv in Node   
    The other native library referenced in this post was NW.js, previously Node-WebKit
    From your description that may be what you're after ?
    There are some other links on this forum
    node-webkit
    https://forums.smartmobilestudio.com/topic/4577-drag-and-drop-a-local-file/?tab=comments#comment-22735
    nw.js @Czar
    https://forums.smartmobilestudio.com/topic/4048-nwjs-previously-known-as-“node-webkit/?tab=comments#comment-17309
     
  22. Like
    lynkfs reacted to warleyalex in async / await keywords   
    look this following procedure: 
    procedure main; begin Sleep(5000); console.log('A'); Sleep(3000); console.log('B'); end; when we run this code, it will immediately output 'A' and 'B' - JS the asyncronous nature. ...but I want our program to log 'A' after 5 seconds then log 'B' after 3 seconds...  that why we, we need the async / await native keywords in SMS. In a "synchronous" manner.  Await and Async are just syntactic sugar for working with promises. It would be cool if we can declare a method like:
    procedure main; async;  The compiler can check whether the encompassing function has async() in then and emit expectd JS, for instance:
    (async function(){ })(); Yeah, we need to to wrap it inside an async function and add "await" before the method "Sleep" to work in synchrounous manner.
    ther await keyword native in SMS would be nice.
    await Sleep(5000);  
    I found out an ugly workaround for the "Immediately Invoked Function Expression (IIFE) -  async function" to work at SMS.
      { global functions }   procedure async(fn: Variant = nil); external '(async function(){' ;   procedure &end; external '})';   function await(promise: Variant): JPromise; external 'await '; function Sleep(ms: Integer): JPromise; begin Result := JPromise.Create( procedure(resolve,reject : TCallBack) begin window.setTimeout( procedure() begin console.log('Done waiting'); resolve(ms); end, ms); end); procedure main; begin async; await( Sleep(5000) ); console.log('A after 5s'); await (Sleep(3000)); console.log('B after 3s'); &end; end; end; the compiler will emit expected JS at least:
    function main() { (async function(){(null); await (Sleep$1(5000)); console.log("A after 5s"); await (Sleep$1(3000)); console.log("B after 3s"); })(); }; but it would super awesome if we could
    // define procedure as async function procedure main: async; procedure main; begin await Sleep(5000); console.log('A after 5s'); await Sleep(3000); console.log('B after 3s'); end;  
  23. Thanks
    lynkfs got a reaction from IgorSavkic in 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.  
     
     
     
  24. Like
    lynkfs got a reaction from IgorSavkic in WebWorkers and Flow Based Programming   
    WebWorkers and Flow Based Programming
    It has been a while since I got as excited as I was when I read J.Morrison's book "Flow-Based Programming: A New Approach to Application Development - 2nd edition".
    This post is not meant to give an overview of FBP, but if someone is interested, the first edition of his book is available online for free.
    FBP utilises independantly running processes which are wired together in an assembly line type of arrangement. The main advantages are that each process runs on demand rather than on availability, components can easily be re-wired to implement changes in requirements and there are possibilities for extensive component re-use.
    I was in the process of porting this framework to Smart, when I realised that javascript and concurrently running processes is not an easy match. Basically all js code in the browser executes in the same thread, and time-slicing mechanisms like setTimeout (used in the framework I was porting) doesn't enable real concurrent processing.
    However there are 2 exceptions to escape the single-threadedness : IFrames and webworkers. Both implement runtimes separate from the main thread and have their own heap, stack and message handling mechanism.
    The similarities between the FBP and shared webworker specs are (more than) striking, so this post is about having a go at implementing FBP using webworkers. 
    Technically
    - processes (fbp) can be implemented through shared webworkers
    - connections between processes (fbp) can be implemented using channels (webworker to webworker, buffered)
    - ports (fbp) can be implemented using ports (webworker)
    - reading/writing of information packets's (fbp) can be implemented using messaging (webworker)
    - information packet handling (fbp) can be implemented using the transferable interface (webworker)
    The demo chosen is a variation of the 'Galaxy Clock' project in the featured-demo's section. The variation being that the 'single motor' driving the 3 hands of the clock (hr, min, sec) is replaced by 3 separate 'motors', each driving one of the hands and each one operating in its own thread.
    The architecture of this demo : a scheduler webworker receives the current local time when a button is clicked in the main program. It transfers the time-elements to each of the other web-workers through the channels created. These other webworkers in their turn update the main project at appropriate intervals.

     
    The result of this rather peculiar architecture.
    The hr/min/sec displays are purposely not synchronised and are all running at 10x normal clock-speed.
  25. Like
    lynkfs got a reaction from IgorSavkic in 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
     
×
×
  • Create New...