Jump to content
lynkfs

Scrolling

Recommended Posts

Scrolling has been discussed extensively before in this forum and in other posts

 

However I wanted to have a simple solution which would make standard forms scrollable without any special handling or consideration.

 

First idea was to produce a specialised TW3Form :

type
  TW3ScrollForm = class (TW3Form)
    ....
  end;

Problem of course is that the invocation of such a form is not standard :

type
  TForm1 = class(TW3ScrollForm)  instead of the normal TForm1=class(TW3Form)

The other problem is that this doesn't work when using the ide-designer.

Apparently Smart expects a TW3Form exclusively, so adding elements to a TW3ScrollForm and using the designer results in errors at compile time

 

 

Second idea is to use partial classes to extend the functionality of TW3Forms :

type
  TForm1 = partial class (TW3Form)
  private
    procedure SetContentHeight(Height: Integer);
  protected
    Scroll        : TW3ScrollWindow;
  published
    property ScrollHeight: Integer write setContentHeight;
  end;

This can be made to work well enough, however the invocation is still not standard :

type
  TForm1 = partial class(TW3Form)  instead of the normal TForm1=class(TW3Form)

There is a way around this by going up the component tree : TW3Form inherits from TW3CustomControl which inherits from TW3MovableControl which inherits from TW3Component which inherits from TW3TagObj

 

In this lineage TW3CustomControl (and TW3Component) are specified as partial classes. If we move the above methods and properties up 1 step we extend TW3CustomControl

  TW3CustomControl = partial class(TW3MovableControl)
  private
    procedure SetContentHeight(Height: Integer);
  protected
    Scroll        : TW3ScrollWindow;
    procedure ChangeParent (newparent:TW3ScrollWindowContent);
  published
    property ScrollHeight: Integer write setContentHeight;
  end;

and since TW3Form is derived from TW3CustomForm we're able to use the standard invocation of

type
  TForm1 = class(TW3Form) 

So far so good.

 

The next problem is that elements created on forms have their parent set to that Form.

This needs to be altered, <element>.Parent needs to be set to the scroller object

 

"Parent" is however a read-only attribute and can't be simply altered.

This code works around that :

procedure TW3CustomControl.ChangeParent(newparent:TW3ScrollWindowContent) ;
var
  x:  Integer;
  mChild: TW3Component;
begin
  BeginUpdate;
  for x := 0 to GetChildCount - 1 do
  begin
    mChild := GetChildObject(x);
    if (mChild.parent is TW3Form) then begin
      if (not (mChild is TW3ScrollWindow)) then
        newparent.Handle.appendChild(mChild.Handle);
    end;
  end;
  EndUpdate;
end;

.

So basically this means that all standard forms in any visual project automatically become scrollable by simply adding the ScrollForm unit to the project, nothing else needed.

 

Demo and code (based on the iScroll wrapped library)

 

.

Share this post


Link to post
Share on other sites

Scrolling is a bugger of a subject

 

The scroll method in the previous thread, based on iScroll, works perfectly in all major browsers : desktop (chrome, safari, firefox) and mobile (android chrome, ios safari)

 

but surprisingly not in android chrome-beta (very jittery scrolling) and in android chrome-dev (scrolls on finger hoover but not on finger touch) - bugger

 

Maybe there is a discrepency between iScroll and Chrome development versions which will be resolved in future releases,

but then again maybe not.

Relying on external libraries and getting the versions right is always tricky

 

The other disadvantage of using iScroll of course is that it requires to load yet another external library, which adds to overall latency

 

 

So I keep coming back to native scrolling as a preferable alternative

after all scrolling is built-in natively in all browsers and is highly optimised, so why not use that rather than trying to impose external non-native functions

 

Enabling native scrolling in SMS visual projects involves 3 steps :

 

- change the default template to a scrolling enabled template

- set the style.overflow attribute of forms to 'scroll'  (see the FormActivated procedure in the demo)

- add a   "-webkit-overflow-scrolling: touch;" attribute to the html,body element in the css file

 

This demo is just a blank page with a label below the screen height of the device, so scrolling up will get this label into view ('end label')

This works in all desktop and mobile browsers (that I've tried) and provides fast native momentum scrolling out of the box

 

Code here

 

.

Share this post


Link to post
Share on other sites

Hi, I'm trying to understand how to scroll long-forms and came across your post.  There's this part in the 'scrolling enabled template' which I don't understand:

<script type="text/javascript">
(function(){
    function isTouchDevice(){
        try{
            document.createEvent("TouchEvent");
            return true;
        }catch(e){
            return false;
        }
    }

    function touchScroll(id){
        if(isTouchDevice()){ //if touch events exist...
            var el=document.getElementById(id);
            var scrollStartPos=0;

            document.getElementById(id).addEventListener("touchstart", function(event) {
                scrollStartPos=this.scrollTop+event.touches[0].pageY;
                event.preventDefault();
            },false);

            document.getElementById(id).addEventListener("touchmove", function(event) {
                this.scrollTop=scrollStartPos-event.touches[0].pageY;
                event.preventDefault();
            },false);
        }
    }

})();
  </script">
 

Why is there a function inside a function, and why does the closing script tag have a " in it?  It seems all wrong, but it works.  If I change the above to what I think is right i.e.

  <script type="text/javascript">
    function isTouchDevice(){
        try{
            document.createEvent("TouchEvent");
            return true;
        }catch(e){
            return false;
        }
    }

    function touchScroll(id){
        if(isTouchDevice()){ //if touch events exist...
            var el=document.getElementById(id);
            var scrollStartPos=0;

            document.getElementById(id).addEventListener("touchstart", function(event) {
                scrollStartPos=this.scrollTop+event.touches[0].pageY;
                event.preventDefault();
            },false);

            document.getElementById(id).addEventListener("touchmove", function(event) {
                this.scrollTop=scrollStartPos-event.touches[0].pageY;
                event.preventDefault();
            },false);
        }
  }
</script">
 

then I can't scroll the form any more.  I could just use the code as is, but I'll like to understand what's happening.  Admittedly, my javascript isn't too good.

Thanks in advance.

B

Share this post


Link to post
Share on other sites

Thanks for the hint.

I just started evaluating SMS, wanting to use it for a data collection app on mobile phones.  So my forms basically contain a long list of labels and edit boxes.  Was happily using the TLayout component to arrange them nicely, then discovered that the form won't scroll on mobile browsers.  Came across a post that suggested using this setting in the InitializeObject event:

  Self.handle.style.overflow := 'auto';

That worked, at least on Chrome browsers on Android phones.  Then discovered that the form is still not scrollable on the default browser on Android phones, nor on iPad (Chrome nor Safari).  Came across this post, which suggested using a different template, css, and some initialization code.  That worked, but I don't understand why (would like to, hence my original post).

Took a look at TW3ScrollBox - dropped it on a form, dropped 2 labels in the scroll box area (1 top, 1 bottom).  The scroll bars do show up, but the labels remain where they were at design time.  I'm guessing I need to set up the controls in code?  Also, don't really want the scroll bars to appear by default, only when the user wants to scroll the form.

Is this really the best optionto get scrollable forms?

Thanks.

 

Share this post


Link to post
Share on other sites
  Self.handle.style.overflow := 'auto';

The reason this doesn't work for iOS is because the platform uses touch movements. This is what this method above rectifies. 

I too was using this method up until recently when i moved to using the RTL's scrolling method. I can confirm that these methods support all browsers and works on both iOS and Android. 

Quote

Took a look at TW3ScrollBox - dropped it on a form, dropped 2 labels in the scroll box area (1 top, 1 bottom).  The scroll bars do show up, but the labels remain where they were at design time.  I'm guessing I need to set up the controls in code?  Also, don't really want the scroll bars to appear by default, only when the user wants to scroll the form.

I generally find that it is easier to initialise the controls under initializeform from within the code rather than use the IDE to drag and drop components currently just fyi. 
As for why it isn't working, you need to make sure that the components are bound to tw3scrollbox.contents instead of the form itself.
 

procedure tForm1.InitializeForm
begin
inherited;

var scrbox := tw3scrollbox.create(self);

var mylabel1 := tw3label.create(scrbox.contents);
var mylabel2 := tw3label.create(scrbox.contents);

// apply component code, dimensions etc
end;

(this code might have syntax errors im away from an SMS ide right now.)

Take a look at Jon's facebook post on Scrolling: https://www.facebook.com/SmartMobileStudio/posts/1597157790347571
 

 

Share this post


Link to post
Share on other sites

Thanks for the explanantion.  2 quick questions:

- is it possible to style the scrollbar in the scroll box so that it only appears when the user scrolls the form, and have it look like the browser scroll bar (i.e. a thin line, instead of the default large bar)

- if I lay out my controls using TLayout in this fashion:

  FLayout :=
    Layout.Client(Layout.Margins(8).Spacing(4),
    [
      Layout.Top(W3Label1),
      Layout.Top(W3Label2),
      Layout.Top(W3Label3),
      Layout.Top(W3Label4),
      Layout.Top(W3Label5),
...

so that everything is aligned below each other, how I can size the scroll box accordingly so that its height is just enough to display the last control?

Thanks in advance.

 

 

Share this post


Link to post
Share on other sites
Quote

is it possible to style the scrollbar in the scroll box so that it only appears when the user scrolls the form, and have it look like the browser scroll bar (i.e. a thin line, instead of the default large bar

Again, hard to answer these questions without the IDE directly in front of me, but there is a value (sbIndicator i believe) that you can set to ScrBox.ScrollController.Scrollbars i think? Have a look into the RTL by ctrl clicking into the TW3ScrollBox component when hovering over the line of code. It shouldn't be too hard to find what  you are looking for from there. You can also type Scrbox and a fullstop and it should come up with a list of potential methods for you to explore. I wish i could be more helpful but i'm not at work right now haha.

The ScrBox has two sizing aspects. The ScrBox itself (the area on screen where scrolling occurs when mouse wheel is used) and The ScrBox.Contents dimensions which dictate how far you will scroll both in width and height.

Scrbox.UpdateContents should calculate this automatically however based on the inner components bound to the Scrbox.

Also remember when resizing the layout to resize it according to Scrbox.content rather than the form! 

I apologise in advance if this is either partially wrong or misleading, will update with more clearer info when i can. (Or someone can confirm).

Share this post


Link to post
Share on other sites

Thanks, what you described works.  There is indeed a ScrollBars property for the TW3ScrollBox that can be used to change the appearance of the scroll bars.  Also, UpdateContents works as you described.

Just for my reference, do you know why the code that adds support for touch interfaces work the way it does, even though errors are raised?  The code that was provided looks like this:

 

<script type="text/javascript">
(function(){
    function isTouchDevice(){
        try{
            document.createEvent("TouchEvent");
            return true;
        }catch(e){
            return false;
        }
    }

    function touchScroll(id){
        if(isTouchDevice()){ //if touch events exist...
            var el=document.getElementById(id);
            var scrollStartPos=0;

            document.getElementById(id).addEventListener("touchstart", function(event) {
                scrollStartPos=this.scrollTop+event.touches[0].pageY;
                event.preventDefault();
            },false);

            document.getElementById(id).addEventListener("touchmove", function(event) {
                this.scrollTop=scrollStartPos-event.touches[0].pageY;
                event.preventDefault();
            },false);
        }
    }

})();
  </script">

I don't understand the items highlighted in red, and both the SMS browser and Chrome raises errors re. the compiled index.html:

sms.png.999258caab08a20306af0260ef8a5b71.png

Funny thing is, scrolling actually works on mobile browsers.  If I change the code to what I think looks right i.e.:

<script type="text/javascript">
  function isTouchDevice(){
        try{
            document.createEvent("TouchEvent");
            return true;
        }catch(e){
            return false;
        }
    }

    function touchScroll(id){
        if(isTouchDevice()){ //if touch events exist...
            var el=document.getElementById(id);
            var scrollStartPos=0;

            document.getElementById(id).addEventListener("touchstart", function(event) {
                scrollStartPos=this.scrollTop+event.touches[0].pageY;
                event.preventDefault();
            },false);

            document.getElementById(id).addEventListener("touchmove", function(event) {
                this.scrollTop=scrollStartPos-event.touches[0].pageY;
                event.preventDefault();
            },false);
        }
    }

</script>

no errors are raised, but then I can no longer scroll the form on mobile browsers.  Do you know why?

Thanks in advance.

 

 

Share this post


Link to post
Share on other sites

This is an old hack back from the early days of SMS when everything had to be figured out yourself, very limited documentation etc

I've never liked this hack much from a coding point of view. It did my head in big time - a case where obviously problematic code works fine and corrected code doesn't :(

Anyway, at the time I needed a solution for page scrolling and, faulty code or not, this worked beautifully.

I've used it for years and it still works.

Personally I've gone back to native scrolling, but the new alpha scrollbox / iScroll solution works fine too

Share this post


Link to post
Share on other sites

Thanks for your replies.  It's that I've already created my forms with a lot of controls, and if I were to use the scrollbox option, I'll need to recreate the controls by code, hence why I would like to use the 'TouchDevice' option.  If I was starting on a new project, then creating the controls by code would be feasible and using TScrollBox would surely be the way to go.

Could you please elaborate on what do you mean by 'native scrolling'?  There is another option with regards to forms scrolling?

Thanks in advance.

Share this post


Link to post
Share on other sites

Here's how you can use the Alpha's ScrollController to scroll form contents:

Add SmartCL.Scroll.pas to the uses clause

  FScroller:=TW3ScrollController.Create(Self);
  FScroller.Direction := mdVertical;
  FScroller.ScrollSpeed := ssFast;
  FScroller.OnScrolling:=lambda ScrollInfo.ScrollTo(0,-FScroller.ContentTop); end;
  if ScrollInfo.ScrollHeight>Height then FScroller.MinValueY := -ScrollInfo.ScrollHeight+Height else FScroller.MinValueY:=0;

Do update that MinValueY the same way in resize so the scroller knows how much it can scroll.

Share this post


Link to post
Share on other sites

@jarto while on the subject: I wanted to report a bug that might be related to the ScrollController.
It's recreateable by clicking on an edit box whose position is near the bottom of the screen where the popup keyboard would cover it when it shows, for either Android or iOS. 

When editbox clicked the keyboard naturally shows on both platforms;

  • On iOS this auto scrolls the form immediately to show the edit box in the remaining space.
  • On Android the keyboard shows but auto scrolls the form only when you begin typing.

When either of those auto scrolls occur, The starting position of the TW3ScrollBox becomes screwed up /lower down than it's initial position, presumably because of the scrolling being handled by the OS? 

  • On both platforms when the keyboard is shown you cannot scroll above the current scrollTop (you can scroll up and down but a new limit has been set almost). 
  • On Android this is then stuck as the new maximum top position until you scroll all  the way down to the bottom of the scrollbox's content and then back up again.

Look forward to hearing from you, Cheers!

 

Share this post


Link to post
Share on other sites

The ScrollController option is an interesting alternative to using the scroll box when the controls are created using the designer.  Looking at the source, it would take some work to show the scroll bars, yes? 

Share this post


Link to post
Share on other sites
10 hours ago, recursiveElk said:

On Android this is then stuck as the new maximum top position until you scroll all  the way down to the bottom of the scrollbox's content and then back up again.

Look forward to hearing from you, Cheers!

This happens as Android does scroll the ScrollControl.Container to show the EditBox, but does not scroll it back when the keyboard is closed. You can yourself fix this by adding this to the Form.Resize: YourScrollBox.Container.ScrollInfo.ScrollTo(0,0);

I'll add this to the ScrollController's Refresh -procedure, so the ScrollBox will take care of this automatically in the future.

Share this post


Link to post
Share on other sites
5 hours ago, Bruno said:

The ScrollController option is an interesting alternative to using the scroll box when the controls are created using the designer.  Looking at the source, it would take some work to show the scroll bars, yes? 

Yes, it takes a lot more than a few lines.

Share this post


Link to post
Share on other sites

A reply to some of the previous comments / questions in this thread

1) Bruno said : " 

Quote

It's that I've already created my forms with a lot of controls, and if I were to use the scrollbox option, I'll need to recreate the controls by code, hence why I would like to use the 'TouchDevice' option. "

If you really really don't want to change an existing form but still want to use the scrollbox option, then you could use the method described in the first item of this thread. The partial class extension of TW3Form auto-magically places all elements of a form onto a scrollbox, just by adding the ScrollForm unit to a project.

Note : developed for a previous version of SMS. I don't have the time to make it compatible with the latest alpha although it does compile with some minor changes. I wouldn't recommend this as a preferred developer option, but hey, if it helps it helps. PM me if you want the code.

2) Native scrolling

Quote

Could you please elaborate on what do you mean by 'native scrolling'?  There is another option with regards to forms scrolling?

To cut a long story short : I've become more and more frugal in using external libraries and tools. External libraries like iScroll may make life easier by hiding implementation details and smoothing out differences over multiple types of browsers, and may have other plusses as well. However cons are that these external files need to be loaded, there may be version control issues between library and browser versions and basically they are a black hole unless you read and understand minified js. 

To explore the idea of 'native' a bit more, I've spend some time in developing a super minimalistic framework. The idea is to use the least amount of code and still end up with a framework usable for regular project development. See shoestring demo and code.

Native scrolling in this framework is based on relying on the capacities of the browser. Browsers are awesome and they are built with fast scrolling in mind.

Setting the 'overflow' property of scrollable elements to 'auto' enables scrolling on most platforms. Since I'm using the word 'most', there are obviously a couple of caveats to address and in this case these are a) scrolling speed and b ) iOS

A) Scrolling speed

Desktop projects generally don't have a problem with scrolling longer lists or long forms. This is different on mobile where even scrolling lists of less than 100 items may become sluggish. To help the browser in this respect a simple but effective measure is to hook into the scroll event and set all display properties of items which are positioned either before or after the viewport to 'none', and only the elements which are visible in the scrolling viewport to 'inline-block'. Always making the last item in the list visible as well, even when outside the viewport, enables the browser to correctly proportion the size of the scroll-indicator.

The JGrid in the component list (see demo) features a couple of hundred items, and scrolls beautifully on mobile. This holds up even to a couple of thousand.

Code implementing this is available in the constructor of the JListBox unit. I'm sure more optimisations are possible but this works fine for me.

B ) iOS / iPhone / iPad

Native scrolling works on all desktop browsers and most mobile environments. The notable exception is iOS (safari, chrome) on iPhone and iPad.

Apple requires the use of a "-webkit-overflow-scrolling: touch" entry in the css-body section. This is not supported in any w3c standard, just something Apple dreamed up. The other requirement is to handle touch-events on iOS which is a bit different than on other platforms. I've whittled it down to a couple of lines of code (7) so that magnetic scrolling on iOS is enabled by default. (still think it should be less than 7 lines, working on it).

 

 

 

 

 

 

Share this post


Link to post
Share on other sites

This is great info @lynkfs@gmail.com and thank you for all the work you've done. I found when i was using native scrolling that disabling chrome's pull-to-refresh mechanism was proving problematic, it was being triggered on every scroll because of the form being treated as a giant fixed div. 

If you end up packaging/outlining a succinct step by step method to enabling native scrolling for both iOS ( the 7 lines etc ) and Android that would be hugely helpful for us!  Cheers.

Share this post


Link to post
Share on other sites

Finally found out what's wrong with the scripts in the <body> section (see previous comments in this thread).

I'm not a javascript expert but what happened is that there were 2 scripts stacked on top of each other. The typo in the closing </script"> tag in the first script also completely invalidates the following second script.

This second invalidated script happens to be the standard script generated from the "default.html" template :

Quote

<body>  <script type="text/javascript">    /* This prevents the window being moved by touches,
      to give the impression of a native app */
    document.ontouchmove = function(e) { e.preventDefault(); }
      </script>

which, when looking at it a bit closer, is also the main culprit in inhibiting native scrolling on iOS.

So, in the end simply deleting all <body> scripts is one of the keys to enable native scrolling everywhere.

Quote

 

<body>

    <script type="text/javascript" src="index.js"></script>
</body>

 

 

 

Share this post


Link to post
Share on other sites

UPDATE: The post below was for versions of Smart Mobile Studio where native scrolling did not work at all. Since then proper support has been added and overriding document.ontouchmove should NOT be done any more! Also, you can enable scrolling by simply setting: YouControl.NativeScrolling := True;

 

I've done some testing and come up with a beautiful solution, which takes 3 lines:

Add this somewhere in your code where it is run once:

asm document.ontouchmove = function(e) { } end;

Then add this for any control that you want to make scrollable. It can be TW3Form or TW3CustomControl or whatever.

Quote

Handle.style.overflow := 'auto';
Handle.style['-webkit-overflow-scrolling'] := 'touch';

 

We're going to add an option so, that you can decide yourself, should that blocking document.ontouchmove be there or not. It does make sense in games, but IMHO, not in normal apps.

Share this post


Link to post
Share on other sites
43 minutes ago, recursiveElk said:

@jarto  very exciting! How does this work in relation to ScrollController / ScrollBox in that case? Is it essentially true now that one can either go with this method or ScrollBox instead?

If you do not mind letting the user drag down the window in iOS, you can use this method. Then you do not need to use the ScrollController or ScrollBox.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×