Jump to content


Popular Content

Showing content with the highest reputation since 02/16/2018 in all areas

  1. 6 points

    Smart Mobile Studio 3.0 is released!

    One year of hard work, lots of changes, new features and bug fixes. But here it is! Release announcement: https://smartmobilestudio.com/2018/07/20/smart-mobile-studio-3-0-released/ Most of you have already been using beta-versions, that are really, really close to this release. The last bit was to fix the cors-problem in the Images and to add the Smart Desktop source code to the Featured Demos. We've also set up a live demo of the Desktop to showcase what Smart Mobile Studio and JavaScript can do: desktop.smartmobilestudio.com Big thanks to everybody here, who have helped by testing, suggesting improvements and reporting bugs.
  2. 5 points

    Font Demo

    in project options add a webfont (see https://jonlennartaasenden.wordpress.com/2017/10/04/webfonts-in-smart-mobile-studio/) W3Memo1.Font.Name := 'Tangerine'; W3Memo1.Font.Style := [fsItalic]; W3Memo1.Font.Size := 48; W3Memo1.Font.Weight := 'bold'; In this case I added Font 'Tangerine' (see also https://developers.google.com/fonts/docs/getting_started)
  3. 4 points
    I'd rather do it like Delphi and Lazarus: Keycode as a var parameter and setting it to #0 would cancel. However, that would break backwards compatibility of SMS code just as any other new parameters would do. If you guys don't see it as a big problem, we could do it in a future version. Let me know what your thoughts are.
  4. 4 points

    Scroll bar bug

    This was a weird bug that required changes in SynEdit code itself. May be a Delphi bug even. But I got it fixed and it will be in 3.0
  5. 4 points
    @rshotbolt Here are some resources that might help you out. I like the first one as it explains the precedence and how each overrides the others. Playing with the design 03.06.2013 https://smartmobilestudio.com/2013/06/03/playing-with-the-design/ Smart Mobile Studio and CSS: part 1 October 9, 2017 https://jonlennartaasenden.wordpress.com/2017/10/09/smart-mobile-studio-and-css-part-1/ Smart Mobile Studio and CSS: part 2 https://jonlennartaasenden.wordpress.com/2017/10/11/smart-mobile-studio-and-css-part-2/ Smart Mobile Studio and CSS: part 3 https://jonlennartaasenden.wordpress.com/2017/10/12/smart-mobile-studio-and-css-part-3/ Smart Mobile Studio and CSS: part 4 https://jonlennartaasenden.wordpress.com/2017/10/18/smart-mobile-studio-and-css-part-4/ Themes and styles https://smartmobilestudio.com/documentation/getting-started/themes-and-styles/ Working with controls, the boxing model 05.01.2012 https://smartmobilestudio.com/2012/01/05/working-with-controls-the-boxing-model/ Hidden stylesheets with Smart Pascal August 13, 2014 https://jonlennartaasenden.wordpress.com/2014/08/13/hidden-stylesheets-with-smart-pascal/ HTML5 Attributes, learn how to trigger conditional styling with Smart Mobile Studio November 8, 2017 https://jonlennartaasenden.wordpress.com/2017/11/08/html5-attributes-learn-how-to-trigger-conditional-styling-with-smart-mobile-studio/
  6. 4 points

    databases and applications

    This is taken into account with the framework we are researching / making now. The objects you use to access data act more as "front-ends" to the mechanisms behind it, and just like the filesystem classes the operations are all async. This has the benefit of decoupling the consumer-part (i.e controls + bindings) from the producer aspect. To the consumer parts the database can be resident - or on the other side of the planet, it cares not where the data originates, only that its made available in a format that it can use. A typical middleware entity would be a node.js server that is connected to X number of databases. The client will use the framework and connect to the node.js endpoint, but neither the controls or bindings will have any clue about the data originating from a server. It will simply see a driver and issue calls to it. When the driver (or class) gets the data it will validate and dispatch it via its internal mechanisms. The nice part about this is that, a datasource can be almost anything. As long as someone implements the 3 core classes, it can be anything from a hardcoded file, local database or remote data server for that matter. I will go into more detail about these classes when we are a little closer, but you are quite right that old-school 1:1 endpoints is useless in the new paradigm
  7. 4 points
    A new update is available (in both alpha- and beta-channels on SmartUpdate) aand it's a big one: IDE: - Added TStrings-support to Object Inspector, which allows editing of: - TW3ListBox.Items - TW3ComboBox.Items - TW3RadioGroup.Items - TW3TabControl.Tabs - NodeJS background executions are stopped when project is closed. - Bug fixes to OmniXML - Updated Datasnap proxy generator dll to latest - Fixed problem with Datasnap access interface importer RTL: - New control: TW3GoogleMaps - TW3TabControl can use Forms as Tab contents (Check TabForms -demo) - Updated node.js headers to version 8.10.0 LTS compatible with stable v9.8.0) - Isolated node.js EventEmitter in separate unit (NodeJS.Events) as per specification (node.js v6.9.1) - Implemented System.IOUtils with storage independent TPath class - Implemented a synchronous node.js filesystem API, TW3NodeSyncFileAPI class in SmartNJ.System - Implemented abstract directory parser (TW3DirectoryParser in System.IOUtils) - Implemented standard system file and folder functiction in SmartNJ.System - Upgraded our virtual, BTree based in-memory filesystem to use TPath - Changed TW3VirtualFileSystem to store data as TByteArray rather than variant - Implemented directory parser for Linux, Unix and Windows - Implemented abstract storage device driver (System.Device.Storage) - Implemented storage device driver for browsers (SmartCL.Device.Storage) - Implemented storage device driver for node.js (SmartNJ.Device.Storage) - Implemented TW3DirectoryWatch class for node.js (SmartNJ.Device.Storage) - Implemented TApplication object for node.js, exposing traditional properties and process info - Updated our message api (SmartCL.Messages) to support javascript messagechannel ports - Added full support for socket.io clients (SmartCL.Net.Socketio) - Updated TW3Memo control to initialize states for autocorrect, autocapitalize etc. - Added GetIsRunningInBrowser function to system.types - TStreamReader and TStreamWriter moved to System.Reader and System.Writer units. This deprecates the System.StreamReader and System.StreamWriter units. - Implemented new string parser functions for recognizing intrinsic types (TDataType in System.Types.Convert) - Updated TW3DatasetFilter to use new parser framework - Fixed string-to-intrinsic-type (TryStrToInt, TryStrToBoolean etc) functions - Full re-implementation of our parser framework (System.Text.Parser) - Partial class TBinaryData, under node has functions for consuming and emitting data as a node buffer - Fixed bug in our node.js http server (SmartNJ.Server.HTTP) where the response object would not release if an error occured, causing the server to drop the connection. - Cleaned up multiple declarations for JBuffer and JNodeBuffer, now isolated in unit nodejs.core - Changed default creation flags for node.js files to R+W as opposed to R+W+E (only affects Linux) - Fixed bug where TNJHttpRequest.GetHeaders function returned null - Removed use of JError under node where applicable, now use TJSErrorObject from system.types unit - Updated bytecode assembler and virtual machine project to latest - Removed TFileNameHelper class from SmartCl.Legacy unit, this is now handled by TPath in System.IOUtils - Updated codec manager (system.codec unit) - Implemented RC4 encryption codec and binding (System.Codec.RC4) - Updated Base64.js to latest revision (libraries/base64.js) which is added by the linker on demand - Full re-implementation of base64 codec (System.Codec.base64 unit) - Changed TString.ToBase64() function to use new codec - TW3GroupBox header fixed, now uses TW3Label rather than mapping the unreliable <legend> tag - Improved AutoCreateForm routine, this now performs better checking before construction - TW3Grid control now uses TW3ScrollControl as a container, adding momentum scrolling and better touch handling - Fixed important problem with TString helper class, function missing @ for result caused exception Demos: - Full reimplementation of our websocket server, replacing websocket.io with the standard ws package - Updated all node.js demos to use our high-level classes - Updated WebSQL demo to use correct names - Updated "Binary data" demo to use TBinaryData class (System.Memory.Buffer unit) - Fishfacts demo updated to use new functionality - Fixed problems with advanced demos (biotopia in particular) to work with latest RTL
  8. 4 points

    Google Maps API

    the google map, defaults to the roadmap view, so I added the MapTypeId to the code i posted above just add the following to the interface section const HYBRID = 'hybrid'; ROADMAP = 'roadmap'; SATELLITE = 'satellite'; TERRAIN = 'terrain'; JMapOptions = record property Zoom : Integer; property Center : JLatLngLiteral; property mapTypeId: String; end; change the following procedure to include the MapType param procedure InitMap(AAPIKey, ATitle: String; AMapType: String; ALat, ALong: Double; AControl: TW3CustomControl); and then in the implementation section, modify the require to include the mapTypeId option Require(['https://maps.googleapis.com/maps/api/js?key=' + AAPIKey],procedure() begin ... LMapOptions.mapTypeId:= AMapType; ... end); In my case, i needed my map to default to the TERRAIN view so, i used the TERRAIN const InitMap('myApiKey', Mountain, TERRAIN, Latitude, Longitude, FMap); e.g.
  9. 3 points

    OpenStreetMap component?

    you can use a simple IFrameHtmlElement on a form and set src to something like this W3IFrameHtmlElement1.Src := "//www.openstreetmap.org/export/embed.html?bbox=10.970685482025146%2C49.5968664515866%2C10.98160743713379%2C49.603138573344914&layer=mapnik&marker=49.60000261331038%2C10.976146459579468";
  10. 3 points
    New update available with SmartUpdate in both alpha- and beta-channels. The update contains lots of bug fixes to the IDE. Some are smaller, but the most important deals with debugging and breakpoints. With this update, breakpoints finally work as they should. The changes to background compilation also makes the IDE a lot smoother to use. We also went through all the demos, fixed and improved a bunch of them and added two new ones. Before updating, please delete the "Featured Demos" -folder to make sure that no old demos will remain after the update. Changelog: 15.7.2018 RTL: - ShowModal: Trigger a cancel if the user clicks on the opaque background. - Bug fixes to Scrollbars - Changed padding in all themes. - Previously all controls had default padding (usually 2px), including div. - Now only known controls have the default padding. - Bug fix to TW3Image.LoadFromStream() IDE: - Bug fixes and improvements to background compilation. - Fixes problems with setting breakpoints in the SMS IDE. - If debugging with breakpoints, code obfuscation and packing is switched off. - If execute was set to a browser, instead of embedded Chrome, closing the debugger window caused an exception. - Fix "code painting bug while scrolling" in SynEdit. - Color picker to work with styled application. Demos: - Added: - https server demo - ThemeView demo - Bug fixes and improvements to many demos.
  11. 3 points

    compiled procedures

    Every now and then (not often) it is worthwhile to check what a compiled function actually looks like. An easy way to do that is to use the toString method function test(a:string): string; begin result := 'Da'+a; end; Procedure TForm1.ListProc; begin console.log(test('da')); //"Dada" asm console.log( (@test).toString() ); end; //lists compiled external function 'test' asm console.log( (this.ListProc).toString() ); end; //lists compiled procedure TForm1.ListProc asm console.log( (this.@InitializeForm).toString() ); end; //lists compiled procedure TForm1.InitializeForm end; which results in "Dada" function test(a) { return "Da"+a; } function (Self) { console.log( test("da") ); console.log( (test).toString() ); console.log( (this.ListProc).toString() ); console.log( (this.InitializeForm).toString() ); } function (Self) { TW3Form.InitializeForm(Self); }
  12. 3 points
    New update available with SmartUpdate in both alpha- and beta-channels: 1.7.2018 Range checking caused apps not to start due to an error in Base64 lookup tables. Update TW3AceEditor. TW3TabControl: If PrepareTab was called for the visible tab, it was moved to the wrong place. TW3Label: Fix vertical alignment to work with older browsers. TW3ComboBox: Allow values to be set during ObjectReady. Bug fix to TMemoryStream.WriteBuffer. Bug fix to WaitFor.
  13. 3 points
    in the smartcl.system unit you'll find the browserapi classes. use them like var URL := browserapi.window.location.href; or alternatively, I usually set a snippet like below in an interface section var document external 'document': variant; var window external 'window': variant; var console external 'console': variant; which gives me references to these objects without having to rely on asm sections : var URL := window.location.href;
  14. 3 points

    OAUTH reading gmail account

    To get my head around OAUTH2 I bought a book with the same title by Matt Biehl. This gives a good overview on the internals and use of OAUTH2 This post is about accessing Gmail accounts using Googles OAUTH2 authorisation process. Essentially this is a conversation involving multiple parties/servers and goes something like this : register app with google app sends request to googles oauth2 server : I would like to get access to emails from a specific account oauth server sends mail to email account owner : do you agree in general y/n oauth server sends mail to email account owner : do you agree this app specifically y/n oauth server sends authorisation code to app app sends to oauth/token : authorisation code, client id and client secret code oauth server sends an access token to app app sends access token to Gmail server Gmail server checks with oauth server if all ok y/n Gmail server send email info to app 1 Register app (manual) go to https://console.developers.google.com and start a new project. Select this project and enable the Gmail API. Next steps are to create the credentials for this project. Set type of project to 'call Gmail API from webbrowser' and give it a re-direct uri (http://www.lynkfs.com/oauth2callback) which is used to receive the access token later on. Also supply the gmail account. Output of this process is a client-id and client-secret code. The json output looks something like this client_id.json: {"web":{"client_id":"**********54-g488vat18lh9letonlclj82mpisso25o.apps.googleusercontent.com","project_id":"sms-gmail-******","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"**********fbob3FiixcNkGO"}} 2-5 Request permissions (browser) This process uses the generated codes from the previous step : https://accounts.google.com/o/oauth2/auth? redirect_uri=http%3A%2F%2Fwww.lynkfs.com%2Foauth2callback&response_type=code&client_id=**********54-g488vat18lh9letonlclj82mpisso25o.apps.googleusercontent.com&scope=https%3A%2F%2Fmail.google.com%2F&approval_prompt=force Note that the redirect_uri and scope are in urlencoded format (use https://www.urlencoder.org to get this encoding) This generates a warning that the app is not verified yet by Google, which can be ignored for now. It also asks the gmail account owner if it is ok to give permission to access emails. When all good, it generates an access code directed to the specified endpoint, where 'code' is the access code : http://www.lynkfs.com/oauth2callback/?code=**********oOs4d1E5Ud-uFIUGocpqJLNGvEz07_pWXEHbJzAe-XfhRa3oqb_iSa3KfavmhS96m2dfW30RxgVG9bo# 6-10 Getting emails (app) Access code, client ID and client secret are used for a verification process by Googles OAUTH server. This is initiated by the app through a normal XMLHTTPRequest, specifying 'POST', a header ('application/x-www-form-urlencoded') and specific form-fields for access-code, client ID and client secret. The below curl command emulates this post request. (cURL is embedded in windows since Win10 and can be used from a terminal window (cmd). curl -X POST -H "content-type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=**********oOs4d1E5Ud-uFIUGocpqJLNGvEz07_pWXEHbJzAe-XfhRa3oqb_iSa3KfavmhS96m2dfW30RxgVG9bo#&redirect_uri=http%3A%2F%2Fwww.lynkfs.com%2Foauth2callback&client_id=**********54-g488vat18lh9letonlclj82mpisso25o.apps.googleusercontent.com&client_secret=**********fbob3FiixcNkGO" "https://accounts.google.com/o/oauth2/token" The response to this request is an access token { "access_token" : "**********ZUMtl3tRxBaubj7CkGohXnKyqZetVwgd-PDEBBdXwsQyS6OMlYw7EVUgsI31KpZ-d0FRGD3uF6P-A4qHLbE_zEaIS5t0ypHon8Ujr4SY2-qGZgvayHETa_u", "expires_in" : 3600, "token_type" : "Bearer" } which then is used to access the actual Gmail server : curl -H "Authorization: Bearer **********ZUMtl3tRxBaubj7CkGohXnKyqZetVwgd-PDEBBdXwsQyS6OMlYw7EVUgsI31KpZ-d0FRGD3uF6P-A4qHLbE_zEaIS5t0ypHon8Ujr4SY2-qGZgvayHETa_u" "https://www.googleapis.com/gmail/v1/users/lynkfs@gmail.com/messages" The response is a list of email-id's : "threadId": "16365b4d517c6efb" }, { "id": "1636587e232e2b8d", "threadId": "1636587e232e2b8d" }, { "id": "16364296ff92443a", "threadId": "16364296ff92443a" }, { "id": "1636420869e48b44", "threadId": "1636420869e48b44" }, { "id": "16363f9ada40efd7", "threadId": "16363f9ada40efd7" }, which can be individually accessed by appending the id to the previous request (.....@gmail.com/messages/16363ac8731244e4) and the final email details is a json structure looking like { "id": "16363ac8731244e4", "threadId": "16363ac8731244e4", "labelIds": [ "CATEGORY_PROMOTIONS", "UNREAD", "INBOX" ], "snippet": "Hey LynkFS, Since the Community Facebook Group was launched last year, we've grown, found our voice, and watched our customers find theirs. It's an amazing experience, and for those of", "historyId": "4398240", "internalDate": "1526385618000", "payload": { "partId": "", "mimeType": "multipart/alternative", "filename": "", "headers": [ { "name": "Delivered-To", "value": "lynkfs@gmail.com" }, etc etc. OAUTH2 can not only be used to access Gmail accounts, but also all 30-odd other Google services. And Facebook, LinkedIn and many more service providers. Takes a bit of doing but once set up it is quite easy to use.
  15. 3 points

    Grid, listbox or listview responsive

    I am working on a new grid, which is built on TW3ListBox. Do you need the columns to be resizable by the user (mouse and touch) or is code enough?
  16. 3 points


    It took me a while to get my head around the concept of webhooks. These have been around for 10 years or more but there is almost no to-the-point info or 'webhook-for-dummies' article on the web available. Webhooks are basically a notification system between disparate systems or servers, where the notifications are based on Http (XmlHttpRequests), so no messaging protocols or middleware software necessary. Like this : In this example there is a service which exposes changes in stock-prices. This service also has an api which allows applications to subscribe to this service (1). Subscription involves providing the url of the application-server. When a stock-price change event occurs, the service POST's a HttpRequest to the url of the server with a payload of the new price (2a) The server responds with a simple OK (2b) and initiates an action towards the client. This action can be maybe sending an email, or just sending a socket-message to the client (3) which allows the client to update its db or user-interface Setting this up in SMS takes a bit of effort as the servers need to have publicly accessible urls. Meaning nodejs servers have to be hosted somewhere (Heroku, Google Cloud). Doable I think.
  17. 3 points

    Paint box

    When it comes to saving and sending the data, I suppose something like this works: SaveButton.OnClick:=procedure(Sender: TObject) begin var FImg := TW3Image.Create(Self); FImg.SetBounds(0,0,FCanvas.Width,FCanvas.Height); FImg.LoadFromImageData(FCanvas.Canvas.ToImageData); FImg.ToStream(YourStreamWhereYouWantTheImageDataToBeSaved); end;
  18. 3 points

    Scroll form

    There are a few ways you can do this: Easiest is to just set NativeScrolling := True on the form. After that it just scrolls the whole form automatically, if the full contents are not visible. If you don't want the whole form to scroll, you can build all the scrollable contents inside a ScrollBox. Here you have two possibilities: TW3NativeScrollBox or TW3ScrollBox. If you use TW3ScrollBox, make sure to create the contents inside it's content (MyCtrl := TWhatever.Create(ScrollBox.Content) and to call ScrollBox.UpdateControl when content is built and resized. The difference between the "native" solutions and TW3ScrollBox is, that TW3ScrollBox does all the scrolling with code, which lets you control everything better.
  19. 3 points

    SVG position

    I found the answer it is one more line of code in your suggested solution W3Button1.handle.style.setProperty('background-position', '10px 12px'); Thank you very much!!!!!!
  20. 3 points

    Payment Request API

    HTML5.2 was introduced a month or 2 ago, with a couple of new features. One of them being the payment request api. So basically it provides all the forms necessary to display a checkout, capture card info, addresses and shipping costs etc. The api is implemented in Chrome and Edge, but doesn't work (yet) in Firefox. Forms in these browsers are rendered somewhat browser specific : This includes validity check of cc details etc. I suppose in time there will be a "W3C.payment" file included in the API's/W3C subdirectory to help with structuring the payment request constructor (hint) For now this works procedure TForm1.W3Button1Click(Sender: TObject); begin //set up merchant structure var supportedInstruments : array of variant; supportedInstruments[0] := class supportedMethods := 'basic-card'; data := class supportedNetworks : array of string; supportedTypes : array of string; end; end; //set up invoice structure var details := class total := class label : string; amount := class currency : string; value : string; end; end; displayItems : array of Variant; shippingOptions : array of variant; end; //set up options var options := class requestShipping := true; end; //populate merchant structure with supported cards supportedInstruments[0].data.supportedNetworks[0] := 'visa'; supportedInstruments[0].data.supportedNetworks[1] := 'mastercard'; supportedInstruments[0].data.supportedNetworks[2] := 'amex'; supportedInstruments[0].data.supportedNetworks[3] := 'jcb'; supportedInstruments[0].data.supportedNetworks[4] := 'diners'; supportedInstruments[0].data.supportedNetworks[5] := 'discover'; supportedInstruments[0].data.supportedNetworks[6] := 'mir'; supportedInstruments[0].data.supportedNetworks[7] := 'unionpay'; //populate merchant structure with supported card types supportedInstruments[0].data.supportedTypes[0] := 'credit'; supportedInstruments[0].data.supportedTypes[1] := 'debit'; //populate invoice structure with totals details.total.label := 'Payments invoice Q2018-02'; details.total.amount.currency := 'AUD'; details.total.amount.value := '230.00'; //populate invoice structure with line items var displayItems : array of variant; // displayItems[0] := new JObject; displayItems[0].label := 'labour hire'; displayItems[0].amount := new JObject; displayItems[0].amount.currency := 'AUD'; displayItems[0].amount.value := '65.00'; // displayItems[1] := new JObject; displayItems[1].label := 'material costs'; displayItems[1].amount := new JObject; displayItems[1].amount.currency := 'AUD'; displayItems[1].amount.value := '165.00'; // details.displayItems := displayItems; //populate invoice structure with shipping costs var shippingOptions : array of variant; shippingOptions[0] := new JObject; shippingOptions[0].id := 'standard'; shippingOptions[0].label := 'Standard shipping'; shippingOptions[0].amount := new JObject; shippingOptions[0].amount.currency := 'AUD'; shippingOptions[0].amount.value := '0.00'; details.shippingOptions := shippingOptions; //writeln(json.stringify(details)); //finally do a payment request var request : variant := new JObject; asm @request = new PaymentRequest(@supportedInstruments, @details, @options); end; request.show(details); // end; Does not work in the ide, starting from file system is ok
  21. 3 points
    And shorter without asm: edPhoto1.InputType := itFile; edPhoto1.Handle.ReadyExecute( procedure () begin w3_setAttrib(edPhoto1.Handle, 'accept', 'image/*'); w3_setAttrib(edPhoto1.Handle, 'capture', ''); edPhoto1.OnChanged := procedure(sender:TObject) begin var reader := JFileReader.Create; reader.onload := lambda imgPhoto1.handle.src := reader.result; end; reader.readAsDataURL(JBlob(edPhoto1.handle.files[0])); end; end);
  22. 3 points
    Hi Mate, challenge accepted! The following works for me. Sorry, too tired to see what the differences in our code are at the moment but hopefully you can jiggle it around to meet your needs. Just copy the below into a new project and create a new W3GoogleMap component in the IDE. Regards, Daniel unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.GoogleMaps; type JPolylineOptions = record property map: TGoogleMap; property path: Array of JLatLngLiteral; property geodesic : boolean; property strokeColor : string; property strokeOpacity : double; property strokeWeight : integer; property visible : Boolean; end; TGoogleMapsPolyline = partial class external 'google.maps.Polyline' public Constructor Create(options : JPolylineOptions); external 'Polyline'; path: Array of JLatLngLiteral end; TForm1 = class(TW3Form) procedure W3GoogleMaps1MapCreated(Sender: TObject); private {$I 'Form1:intf'} function Coord(lat,lng : Double) : JLatLngLiteral; protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end; implementation { TForm1 } procedure TForm1.W3GoogleMaps1MapCreated(Sender: TObject); var PO : JPolylineOptions; PL : TGoogleMapsPolyline; path : Array of JLatLngLiteral; begin PO.map := W3GoogleMaps1.map; //Set map here or with Setmap W3GoogleMaps1.AddMarker(43.974382,-74.426716,'Point 1'); W3GoogleMaps1.AddMarker(43.9734854,-74.4239453,'Point 2'); path.add(Coord(43.974382,-74.426716)); path.add(Coord(43.9734854,-74.4239453)); po.path := path; po.geodesic := true; po.strokeColor := '#FF0000'; po.strokeOpacity := 1.0; po.strokeWeight := 2; PL := new TGoogleMapsPolyline(PO); //PL.setmap(W3GoogleMaps1.map); end; function TForm1.Coord(lat: double; lng: double): JLatLngLiteral; begin Result.lat := lat; Result.lng := lng; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} W3GoogleMaps1.CenterLat := 43.974382; W3GoogleMaps1.CenterLng := -74.426716; W3GoogleMaps1.MapZoom := 16; W3GoogleMaps1.AutoCreateMap := True; end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.
  23. 3 points

    Autocomplete Address INformation

    nice @lynkfs i used your code to make a component......although, the "fillInAddress" javascript function could use some work (i.e. something different than the if else stements) unit AutoCompleteEdit; interface uses System.Types, System.Types.Convert, SmartCL.System, SmartCL.Controls.EditBox; type TAutoCompleteEdit = class(TW3EditBox) private fKey: string; fstreet_number: string; froute: string; flocality: string; fadministrative_area_level_1: string; fcountry: string; fpostal_code: string; function get_street_number: string; function get_route: string; function get_locality: string; function get_administrative_area_level_1: string; function get_country: string; function get_postal_code: string; function getKey: String; procedure setKey(Value: String); protected procedure ObjectReady; override; public property GoogleAPIKey: String read getKey write setKey; property street_number: string read get_street_number; property route: string read get_route; property locality: string read get_locality; property administrative_area_level_1: string read get_administrative_area_level_1; property country: string read get_country; property postal_code: string read get_postal_code; end; implementation function TAutoCompleteEdit.getKey: String; begin result:= fKey; end; procedure TAutoCompleteEdit.setKey(Value: String); begin fKey:= Value; end; function TAutoCompleteEdit.get_street_number: string; begin result:= fstreet_number; end; function TAutoCompleteEdit.get_route: string; begin result:= froute; end; function TAutoCompleteEdit.get_locality: string; begin result:= flocality; end; function TAutoCompleteEdit.get_administrative_area_level_1: string; begin result:= fadministrative_area_level_1; end; function TAutoCompleteEdit.get_country: string; begin result:= fcountry; end; function TAutoCompleteEdit.get_postal_code: string; begin result:= fpostal_code; end; procedure TAutoCompleteEdit.ObjectReady; begin inherited; var v : variant := new JObject; v := handle.id; var Script := browserapi.document.createElement('script'); Script.src := 'https://maps.googleapis.com/maps/api/js?key=' + fKey + '&libraries=places'; browserapi.document.head.appendChild(Script); Script.onload := procedure begin asm var componentForm = { street_number: 'short_name', route: 'long_name', locality: 'long_name', administrative_area_level_1: 'short_name', country: 'long_name', postal_code: 'short_name' }; function initAutocomplete() { autocomplete = new google.maps.places.Autocomplete( (document.getElementById(@v)), {types: ['geocode']}); autocomplete.addListener('place_changed', fillInAddress); } function fillInAddress() { var place = autocomplete.getPlace(); for (var i = 0; i < place.address_components.length; i++) { var addressType = place.address_components[i].types[0]; if (componentForm[addressType]) { var val = place.address_components[i][componentForm[addressType]]; } if (addressType == 'street_number') { @fstreet_number = val } else if (addressType == 'route') { @froute = val } else if (addressType == 'locality') { @flocality = val; } else if (addressType == 'administrative_area_level_1') { @fadministrative_area_level_1 = val; } else if (addressType == 'country') { @fcountry = val; } else if (addressType == 'postal_code') { @fpostal_code = val; } } } initAutocomplete(); document.getElementById(@v).addEventListener("focus", function( event ) { event.target.style.background = "pink"; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { var geolocation = { lat: position.coords.latitude, lng: position.coords.longitude }; var circle = new google.maps.Circle({ center: geolocation, radius: position.coords.accuracy }); autocomplete.setBounds(circle.getBounds()); }); } }, true); end; end; end; end. anyway, it fills in the components public properties street_number route locality state country postal_code i use it like such procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components EditBox1:= TAutoCompleteEdit.Create(self); EditBox1.SetBounds( 10,10, 200, 32); EditBox1.PlaceHolder := 'Enter your address'; EditBox1.GoogleAPIKey:= 'AIzaSyCU7roAAjLtFT2ItaMLcS6WN**********'; Memo1:= TW3Memo.Create(self); Memo1.SetBounds(10, 50, 190, 260); Button1:= TW3Button.Create(self); Button1.SetBounds(10, Memo1.Top + Memo1.Height + 10, 128, 32); Button1.Caption:= 'Values'; Button1.OnClick:= HandleClick; end; procedure TForm1.HandleClick(sender: TObject); begin Memo1.Clear; Memo1.Add(EditBox1.street_number + #10#13 + EditBox1.route + #10#13 + EditBox1.locality + #10#13 + EditBox1.administrative_area_level_1 + #10#13 + EditBox1.country + #10#13 + EditBox1.postal_code + #10#13); end; EXAMPLE
  24. 3 points

    Autocomplete Address INformation

    Hi Czar Here is one (of probably many) solutions. (in haste, so could be streamlined a bit) unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.EditBox, SmartCL.Controls.Memo; type TForm1 = class(TW3Form) private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; EditBox1 : TW3EditBox; Memo1 : TW3Memo; end; implementation { TForm1 } procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components EditBox1 := TW3EditBox.Create(self); EditBox1.SetBounds(40,40,350,35); EditBox1.PlaceHolder := 'Enter your address'; var v : variant := new JObject; v := EditBox1.handle.id; Memo1 := TW3Memo.Create(self); Memo1.SetBounds(40,150,350,200); var w : variant := new JObject; w := Memo1.handle.id; var Script := browserapi.document.createElement('script'); Script.src := 'https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=places'; //<======= API key browserapi.document.head.appendChild(Script); Script.onload := procedure begin writeln('loaded'); asm var placeSearch, autocomplete; var componentForm = { street_number: 'short_name', route: 'long_name', locality: 'long_name', administrative_area_level_1: 'short_name', country: 'long_name', postal_code: 'short_name' }; function initAutocomplete() { // Create the autocomplete object, restricting the search to geographical // location types. autocomplete = new google.maps.places.Autocomplete( /** @type {!HTMLInputElement} */(document.getElementById(@v)), {types: ['geocode']}); // When the user selects an address from the dropdown, populate the address // fields in the form. autocomplete.addListener('place_changed', fillInAddress); } function fillInAddress() { // Get the place details from the autocomplete object. var place = autocomplete.getPlace(); /// for (var component in componentForm) { // document.getElementById(component).value = ''; // document.getElementById(component).disabled = false; // } document.getElementById(@w).innerHTML = ''; //clear Memo // Get each component of the address from the place details // and fill the corresponding field on the form. for (var i = 0; i < place.address_components.length; i++) { var addressType = place.address_components[i].types[0]; console.log(addressType); document.getElementById(@w).innerHTML += addressType + '\n'; //add addressType to Memo if (componentForm[addressType]) { var val = place.address_components[i][componentForm[addressType]]; console.log(val); document.getElementById(@w).innerHTML += val + '\n\n'; //add address component value to Memo // document.getElementById(addressType).value = val; } } } initAutocomplete(); document.getElementById(@v).addEventListener("focus", function( event ) { console.log('onfocus'); event.target.style.background = "pink"; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { var geolocation = { lat: position.coords.latitude, lng: position.coords.longitude }; var circle = new google.maps.Circle({ center: geolocation, radius: position.coords.accuracy }); autocomplete.setBounds(circle.getBounds()); }); } }, true); end; end; end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end. To break it down a bit : The EditBox is the component where you type in (the beginning of) an address and the Google Places API produces a dropdown underneath. The initAutoComplete function searches for this field and the link is made via it's Editbox.handle.id value I like to download files in code so hence made up a script element and for simplicity added most of Googles js code in the onload handler. Then create the autocomplete object by calling initAutoComplete. There are other ways of doing this and the RTL has some script loading functions built in, but this works too. The focus handler is supposed to limit the global address domain to something in a particular radius from the user. Not sure if this works as expected here, might need some further attention. Anyway, this is the result from this project : Once the user selects an address, the individual address details are -in this code example- displayed in the Memo and on the console, but of course should be used to populate other components on the form Spending some more time on it would diminish or even eliminate the asm block, but this is a quick usage of Googles example in your post Hope this helps edit : the onFocus handler (to limit addresses to the neighbourhood) depends on the 'location' settings in your Windows settings Project tested on Chromium in the ide and on Edge, Chrome
  25. 3 points


    Yeah, what @lynkfs writes is one way of doing that. Basically you use the display for the next/prev -buttons and then just change forms normally with GotoForm. You can also do this inside a form or basically any control you want: subform := TWizardForm1.Create(Panel1); So basically, you don't need to register the forms as in @lynkfs example and you can even create many instances of the same form. I am taking advantage of that in the next update to the TTabControl where you can build tabs by using forms.