Smart Mobile Studio comes with an implementation of the standard, round cornered, iPhone menu – but while handy, there are cases where you want to display more than just some options. What we really need is a scrolling menu that can be easily adapted to display more information. It’s time to dive deeper into the RTL and create a scrolling control.
Setting up a test project
First start by creating a new visual project. Give the project a name you like, in this case i just opted for an obvious name.
Next, add a new unit to the project. You do this by right-clicking on the project node in the project manager, then clicking “add unit” from the menu. You can also add a new unit directly from the menu or the toolbar. It’s the same function.
Now you have to reference the new unit from your mainform, you do this just like in Delphi or FreePascal by adding unit1 to the uses clause:
uses w3system, w3ctrls, w3components, w3forms, w3application, form1;
Now that the project is ready for some code, let’s dig into what we need to achieve our goal. What we want is a horizontal list that is scrollable. We also want this list to be isolated (in component form) so that it can be re-used in later projects. We want the list items to have all the benefits of TCustomcontrol (which includes color, font, background and style infrastructure).
Smart Mobile Studio’s RTL have a lot in common with delphi and freepascal. We are delphi developers after all, so naturally most of the principles we have worked hard to realize under javascript, are delphi centric. In delphi there is a control called TScrollingWinControl. This control is special in that it provides mechanisms for changing the position of the content, hence giving the effect of “scrolling” around a landscape.
Under Smart Mobile Studio the same mechanism is provided by TW3ScrollControl. Since the DOM (document object model, or HTML structure) is less flexible than what delphi and winapi can muster, there are ofcourse large differences, but not as much as you might think.
Items first
Right, before we dive into the details of scrolling, we start by creating the item classes. Just like in delphi we use TW3CustomControl as a base, this gives our items the full spectrum of features that we desire. To start off easy we just add support for a caption, so each item (or line) in the list can display text.
type (* Generic ancestor *) TLSTCustomMenuItem = Class(TW3CustomControl); (* Array type *) TLSTMenuItems = Array of TLSTCustomMenuItem; (* Menu item *) TLSTMenuItem = Class(TLSTCustomMenuItem) private FCaption: String; procedure setCaption(Const Value:String); protected procedure InitializeObject; override; public property Caption:String read FCaption write setCaption; End;
Next, the implementation
procedure TLSTMenuItem.InitializeObject; begin inherited; self.Color:=clWhite; self.Height:=32; end; procedure TLSTMenuItem.setCaption(Const Value:String); Begin FCaption:=Value; innerHTML:=Value; end;
As you can see from the code above, the setcaption() method simply injects the caption as raw html (the innherHTML property is direct access to the content of the control’s html tag, in this case the content of a DIV tag). Using innherHTML like this is not something you want to do with more complex controls, since altering this property erases the HTML completely. But for this quick example, it will do nicely.
The menu
Now that we have setup a rudimentary but flexible base item class, let’s dig into TW3ScrollControl. As always InitializeObject and FinalizeObject plays the role of Create and Destroy under delphi, other than that – it’s plain pascal:
(* selection event *) TMenuItemSelectedEvent = procedure (Sender:TObject;Const aItem:TLSTCustomMenuItem); (* Our menu *) TLSTMenu = Class(TW3ScrollControl) private FItems: TLSTMenuItems; FLastY: Integer; FSelected: TLSTMenuItem; FOnSelected: TMenuItemSelectedEvent; Procedure HandleTouchBegins(Sender:TObject;Info:TW3TouchData); Procedure HandleTouchEnds(Sender:TObject;Info:TW3TouchData); protected Procedure HandleItemTapped(Const aItem:TLSTMenuItem); procedure InitializeObject; override; procedure FinalizeObject; override; Procedure ReSize;override; public Property OnItemSelected:TMenuItemSelectedEvent read FOnSelected write FOnSelected; Property Items:TLSTMenuItems read FItems; function Add(Const aItem:TLSTCustomMenuItem):TLSTCustomMenuItem;overload; function Add:TLSTMenuItem;overload; End;
And finally, it’s implementation:
procedure TLSTMenu.InitializeObject; Begin inherited; FItems.SetLength(0); end; procedure TLSTMenu.FinalizeObject; Begin FItems.Clear; inherited; end; Procedure TLSTMenu.HandleTouchBegins(Sender:TObject;Info:TW3TouchData); Begin (* remember current scroll position *) if (sender<>NIL) and (sender is TLSTMenuItem) then begin FLastY:=Content.Top; FSelected:=TLSTMenuItem(sender); end else Begin FLastY:=-1; FSelected:=NIL; end; end; Procedure TLSTMenu.HandleTouchEnds(Sender:TObject;Info:TW3TouchData); Begin (* No scrolling but touched? Its a tap *) if Content.Top=FLastY then Begin if (FSelected<>NIL) then Begin if (FSelected=sender) then begin HandleItemTapped(FSelected); FLastY:=-1; FSelected:=NIL; end; end; end; end; Procedure TLSTMenu.HandleItemTapped(Const aItem:TLSTMenuItem); Begin if assigned(FOnSelected) then FOnSelected(self,aItem); end; function TLSTMenu.Add:TLSTMenuItem; Begin result:=TLSTMenuItem(Add(TLSTMenuItem.Create(Content))); result.OnTouchBegin:=HandleTouchBegins; result.OnTouchEnd:=HandleTouchEnds; end; function TLSTMenu.Add(Const aItem:TLSTCustomMenuItem):TLSTCustomMenuItem; Begin if aItem<>NIL then Begin if FItems.IndexOf(aItem)<0 then Begin BeginUpdate; FItems.Add(aItem); EndUpdate; content.LayoutChildren; end; end; result:=aItem; end; Procedure TLSTMenu.ReSize; var mItem: TLSTCustomMenuItem; mSize: Integer; x: Integer; dy: Integer; Begin inherited; for x:=0 to FItems.Length-1 do Begin mItem:=FItems[x]; if mItem.visible then inc(mSize,mItem.height); end; Content.Height:=mSize; dy:=0; for x:=0 to FItems.Length-1 do Begin mItem:=FItems[x]; if mItem.visible then Begin mItem.SetBounds(0,dy,width,mItem.Height); inc(dy,mItem.Height); end; end; end;
The result is a list of items that can be styled, scrolled and tapped. It can (and should) be optimized for more speed. You may also want to use a more lightweight base-control for the items (i’ll leave the tweaking to you), but basically – creating custom user controls that are re-usable amoung your projects is just as straight forward under Smart Mobile Studio as it is under Delphi.
Using the control
To create an instance of your new user-control on your main-form, initialize it from the constructor:
procedure TForm1.InitializeObject; var x: Integer; begin inherited; {$I 'Form1:impl'} FMenu:=TLSTMenu.Create(self); FMenu.Color:=clWhite; FMenu.StyleClass:='TPDFMenu'; //use this css style :) FMenu.Content.height:=120; FMenu.Content.color:=clGreen; FMenu.OnItemSelected:=HandleItemSelected; // Add dummy items FMenu.BeginUpdate; try for x:=1 to 20 do FMenu.Add.Caption:='List item #' + IntToStr(x); finally FMenu.EndUpdate; end; end; procedure TForm1.Resize; var dy: Integer; begin dy:=W3HeaderControl1.top + W3HeaderControl1.height + 2; FMenu.SetBounds(0,dy,width,height-dy); inherited; end;
Some things I noticed while following along with your intructions:
1.) With the new 1.1 beta, a unit1 is already automatically created
2.) w3components, w3graphics, w3Scroll, w3Touch need to be added to Unit 1
3.) <> is HTML for
4.) need to add a TW3HeaderControl to main form
5.) need to add procedure HandleItemSelected(Sender: TObject; Const aItem:TLSTCustomMenuItem); to main form and implementation
procedure TForm1.HandleItemSelected(Sender: TObject; Const aItem:TLSTCustomMenuItem);
begin
ShowMessage(‘ok’);
end;
however, having done all that, it compiles and runs, but it does not work. I can’t get an item to select
Added the project as a zip file, you can download it directly above 🙂