Custom dialog and loading data from JSON in Smart Pascal
Right now we are putting the finishing touches on our next update, which contains our new theme engine. As mentioned earlier (especially if you follow us on Facebook) the new system builds on the older – but we have separated border and background from the basic element styling.
When working with the new theme system, I needed an application that could demonstrate and show all the different border and background types, most of our visual controls – but also information about what Smart Mobile Studio is, what it’s features are and where you can buy it.
So it started as a personal application just to get a good overview of the CSS themes I was working on; but it has become an example in it’s own right.
Dont hardcode, just dont
If you look at the picture above, there is a MenuList with the options: “Introduction”, “Features” and “Where to buy”. When you click these I naturally want to inform the user about these things by displaying information.
I could have hardcoded the information text into the application; in many ways that would have been simpler (considering the data requirements here is practically insignificant). but all that text within the source? I hate mess like that.
Secondly, how exactly was I going to show this information? Would I use the modal framework already in place, or code something more lightweight?
As always I ended up making a new and more lightweight system. A reader style dialog appears and allows you to scroll vertically. The header contains the title of the information and a close button.

Typical “reader” style dialog with scrolling
I also used a block-box to prevent the user from reaching the UI until they click the close-button. You notice that the form, toolbar and header in the back is darkened. This is actually a control that is semi-transparent that does one thing: prevent anyone from clicking or interacting with the UI while the dialog is active.
The JSON file structure
The structure I needed was very simple: our records would have a unique ID that we use to fetch and recognize content; It would also have a Title and Text property. It really doesnt have to be more difficult than that.
To work with the JSON i used the online JSON editor JSonEditorOnline, which is actually really good! It allows you to write your JSON and then format it so that special characters (like CR+LF) is properly encoded.
Putting it all together
Having coded the dialog thing first, I now sat down and finished a sort of “Turbo Pascal” record database system for this particular file format. It’s not very big nor extremely advanced – but that’s the entire point! Throwing in SQLite or MongoDB for something as simple as a few records of data – especially when the data is so simple, is just a complete waste of time and effort.
Right, let’s have a peek at the code shall we!
unit infodialog; interface uses System.Types, System.Types.Convert, System.Types.Graphics, System.Colors, System.JSON, SmartCL.Theme, SmartCL.FileUtils, SmartCL.System, SmartCL.Effects, SmartCL.Components, SmartCL.Scroll, SmartCL.Controls.Panel, SmartCL.Controls.Scrollbox, SmartCL.Controls.Header; type TInfoDialog = class(TW3Panel) private FHead: TW3HeaderControl; FBox: TW3Scrollbox; protected procedure InitializeObject; override; procedure FinalizeObject; override; procedure Resize; override; public property Header: TW3HeaderControl read FHead; property Content: TW3Scrollbox read FBox; class function ShowDialog(Title, Content: string): TInfoDialog; end; TAppInfoRecord = record iiId: string; iiTitle: string; iiText: string; procedure Clear; class function Create(const Id, Title, Text: string): TAppInfoRecord; end; TAppInfoDB = class(TObject) private FStack: array of TStdCallback; FItems: array of TAppInfoRecord; procedure Parse(DBText: string); procedure HandleDataLoaded(const FromUrl: string; const TextData: string; const Success: boolean); public property Empty: boolean read ( (FItems.Count < 1) ); property Count: integer read (FItems.Count); property Items[index: integer]: TAppInfoRecord read (FItems[index]) write (FItems[index] := Value); function GetRecById(Id: string; var Info: TAppInfoRecord): boolean; procedure LoadFrom(Url: string; const CB: TStdCallback); procedure Clear; destructor Destroy; override; end; implementation uses SmartCL.Application; //############################################################################# // TAppInfoRecord //############################################################################# class function TAppInfoRecord.Create(const Id, Title, Text: string): TAppInfoRecord; begin result.iiId := id.trim(); result.iiTitle := Title.trim(); result.iiText := Text; end; procedure TAppInfoRecord.Clear; begin iiId := ''; iiTitle := ''; iiText := ''; end; //############################################################################# // TAppInfoDB //############################################################################# destructor TAppInfoDB.Destroy; begin if FItems.Count > 0 then Clear(); inherited; end; procedure TAppInfoDB.Clear; begin FItems.Clear(); end; function TAppInfoDB.GetRecById(Id: string; var Info: TAppInfoRecord): boolean; begin Info.Clear(); if not Empty then begin Id := Id.trim().ToLower(); if id.length > 0 then begin for var x := 0 to Count-1 do begin result := Items[x].iiId.ToLower() = Id; if result then begin Info := Items[x]; break; end; end; end; end; end; procedure TAppInfoDB.Parse(DBText: string); var vId: variant; vTitle: variant; vText: variant; begin Clear(); DbText := DbText.trim(); if DbText.length > 0 then begin var FDb := TJSONObject.Create; FDb.FromJSON(DbText); if FDb.Exists('infotext') then begin // get the infotext-> [] array of JS objects var Root: TJSInstanceArray := TJSInstanceArray( FDb.Values['infotext'] ); for var x := 0 to Root.Count-1 do begin var node := TJSONObject.Create(Root[x]); if node <> nil then begin Node .Read('id', vid) .Read('title', vtitle) .Read('text', vtext); FItems.add( TAppInfoRecord.Create(vId, vTitle, vText) ); end; end; end; end; end; procedure TAppInfoDB.LoadFrom(Url: string; const CB: TStdCallback); begin if assigned(CB) then FStack.push(CB); TW3Storage.LoadFile(Url, @HandleDataLoaded); end; procedure TAppInfoDB.HandleDataLoaded(const FromUrl: string; const TextData: string; const Success: boolean); begin try // Parse if data ready if Success then Parse(TextData); finally // Perform callbacks while FStack.Count>0 do begin var CB := FStack.pop(); if assigned(CB) then CB(Success); end; end; end; //############################################################################# // TInfoDialog //############################################################################# procedure TInfoDialog.InitializeObject; begin inherited; FHead := TW3HeaderControl.Create(self); FHead.BackButton.Visible := false; FHead.NextButton.Caption := 'Close'; // By default the header text is centered within the space allocated for it, // which by default is 2/4. This can look a bit off when we never show // the left-button. So we force text-align to the left [normal]. FHead.Title.Handle.style['text-align'] := 'left'; FBox := TW3Scrollbox.Create(self); FBox.Background.FromColor(clWhite); FBox.ScrollBars := sbIndicator; end; procedure TInfoDialog.FinalizeObject; begin FHead.free; FBox.free; inherited; end; procedure TInfoDialog.Resize; begin inherited; var LBounds := ClientRect; var dy := LBounds.top; if FHead <> nil then begin FHead.SetBounds(LBounds.left, LBounds.top, LBounds.width, 32); inc(dy, FHead.Height +1); end; if FBox <> nil then FBox.SetBounds(LBounds.left, dy, LBounds.width, LBounds.height - dy); end; class function TInfoDialog.ShowDialog(Title, Content: string): TInfoDialog; begin var Host := Application.Display; var Shade := TW3BlockBox.Create(Host); Shade.SizeToParent(); var wd := Host.Width * 90 div 100; var hd := Host.Height * 80 div 100; var dx := (Host.Width div 2) - (wd div 2); var dy := (Host.Height div 2) - (hd div 2); var Dialog := TInfoDialog.Create(Shade); Dialog.Header.Title.Caption := Title; Dialog.SetBounds(dx, dy, wd, hd); Dialog.fxZoomIn(0.3, procedure () begin Dialog.Content.Content.InnerHTML := Content; Dialog.Content.UpdateContent(); Dialog.SetFocus(); end); Dialog.Header.NextButton.OnClick := procedure (Sender: TObject) begin Dialog.fxFadeOut(0.2, procedure () begin TW3Dispatch.Execute( procedure () begin Dialog.free; Shade.free; end, 100); end); end; result := Dialog; end; end.
Using the code
The first thing you want to do is to create an instance of TAppInfoDb when your application starts. Remember to add your JSON file and that it’s formatted property, and then use the LoadFrom() method to load in the data:
// Create our info database and load in the // introduction, features etc. JSON datafile FInfoDb := TAppInfoDB.Create; FInfoDb.LoadFrom('res/JSON1', nil);
The final parameter in the LoadFrom() method is a callback. So if you want to be notified when the file has loaded, just put an anonymous procedure there if you need it.
Showing a dialog with the information is then reduced to looking up the text you need by it’s ID, and firing up the reader dialog for it:
W3Button1.OnClick := procedure (Sender: TObject) begin var LInfo: TAppInfoRecord; if FInfoDb.GetRecById('introduction', LInfo) then TInfoDialog.ShowDialog(LInfo.iiTitle, LInfo.iiText); end;
And that’s it! Simple, effective and ready to be dropped into any application. Enjoy!
Please fix RSS feed at https://jonlennartaasenden.wordpress.com/feed/
An invalid character was found in text content.
Line: 6226 Character: 10
<p></p>