Thursday, April 7, 2011

Sencha Touch Scrolling a Non Touch Blackberry OS 6 Device

This is a follow up to my first post about getting clicking to work on Non-Touch Blackberry OS 6 devices with the Sencha Touch framework.

I have come up with a way to get the components to scroll when you move your mouse to the edges. It is a bit rough around the edges and can use some polish but it's a working example and can be used.

This does not require any hacking of the Sencha Touch library which is both good and bad. The good is that you don't have to mess with the library risking something to get broken the bad is that you have to add this for every panel.

Without further ado here's an example Panel implementing a List:

var scrollList;

app.views.scrollList = Ext.extend(Ext.Panel, {
    listeners: {
        mousemove: {
            element: 'el',
            fn: function (evt, div, el) {
                if (scrollList.scroller.offsetBoundary.top == 0) {
                    scrollList.scroller.updateBoundary();
                }
                var maxOffset = scrollList.scroller.offsetBoundary[1] - 400;
                if (evt.xy[1] > 300 && scrollList.scroller.offset.y >= maxOffset) {
                    var offset = -1 * scrollList.scroller.offset.y + 10;
                    scrollList.scroller.scrollTo({
                        x: 0,
                        y: offset
                    });
                } else if (evt.xy[1] < 60 && scrollList.scroller.offset.y <= 0) {
                    scrollList.scroller.scrollTo({
                        x: 0,
                        y: (-1 * scrollList.scroller.offset.y) - 10
                    });
                }
            }
        }
    },
    dockedItems: [{
        xtype: 'toolbar',
        title: 'Scroll List'
    }],
    items: [{
        xtype: 'list',
        id: 'scrollList',
        height: 275,
        scroll: 'vertical',
        store: app.stores.scrollList,
        itemTpl: '{a}, {b}',
        onItemDisclosure: function (record) {

        }

    }],
    initComponent: function () {
        app.stores.scrollList.load();
        app.views.scrollList.superclass.initComponent.apply(this, arguments);
        scrollList = Ext.getCmp('scrollList');
    }

});
What we do here is we attach a mousemove listener to the underlying panel and call the scrollTo() function on the list's Ext.util.Scroller instance. If anyone has any improvements/suggestions/their own implementation I would love to hear it.
Here's a demo:

Hope this helps those who have hit this problem and hopefully Sencha will implement this the "right" way.

10 comments:

  1. Thank you for this fix! I'm using it on a FormPanel, works good.

    I needed one change. My FormPanel is wrapped in a TabPanel, so the scroll area is not the bottom of the screen (it's about 50px up)

    - This line:
    if (evt.xy[1] > 300 && ...

    - Changed to:
    var bottom = scrollList.getHeight() - 15;
    if (evt.xy[1] > bottom && ...

    ReplyDelete
  2. Here's my example with a normal panel. This does not need the global scrollList variable.

    http://pastebin.com/Mih4Ps12


    Thanks again

    ReplyDelete
  3. I made a completely generic one. Doesn't need the size of the blackberry screen, and has no global variables either. You construct it just like you construct a normal list (with store, itemTpl, etc). Does not work for full-screen lists though (ie, fullscreen : true). In the config object, threshold sets the distance from the edges of the list for when to start scrolling (default is 30 px). interval is the 'smoothness' (i wouldnt touch this one, default is 50 milliseconds), and speed is how fast the list scrolls (default is 0.2... higher values make it scroll faster)

    Ext.ux.BBList = Ext.extend(Ext.List, {
    constructor : function(config){

    if (config.fullscreen === true)
    throw "Constructor error: BBList cannot be fullscreen";

    this.threshold = this.threshold || 30;
    this.interval = this.interval || 50;
    this.speed = this.speed || 0.2;

    Ext.ux.BBList.superclass.constructor.call(this, config);
    this.on('afterrender', this.handleAfterRender, this);
    },

    handleAfterRender : function(){
    this.on(
    'mousemove',
    this.handleMouseMove,
    this,
    {
    element: 'el'
    }
    );
    },

    handleMouseMove : function(evt){
    var y = evt.xy[1],
    yTop = this.getEl().getY();

    //scroll down
    if (y >= (yTop + this.getHeight()) - this.threshold){
    var me = this;
    if (this.timer)
    clearInterval(this.timer);
    this.timer = setInterval(
    function(){
    me.handleScrollDown(evt);
    },
    this.interval);

    //scroll up
    } else if (y <= yTop + this.threshold){
    var me = this;
    if (this.timer)
    clearInterval(this.timer);
    this.timer = setInterval(
    function(){
    me.handleScrollUp(evt);
    },
    this.interval);

    //no longer scrolling
    } else {
    if (this.timer){
    clearInterval(this.timer);
    }
    }
    },

    handleScrollUp : function(evt){
    var y = evt.xy[1],
    yTop = this.getEl().getY(),
    scrollY = this.scroller.getOffset().y,
    delta = y - ((yTop + this.getHeight()) - this.threshold);
    this.scroller.scrollTo({
    x: 0,
    y: scrollY + delta * this.speed
    }, true);
    },

    handleScrollDown : function(evt){
    var y = evt.xy[1],
    yTop = this.getEl().getY(),
    scrollY = this.scroller.getOffset().y,
    delta = (yTop + this.threshold) - y;
    this.scroller.scrollTo({
    x: 0,
    y: scrollY - delta * this.speed
    }, true);
    }
    });


    Sample program:

    Ext.regModel('record', {
    fields: ['label']
    });

    var store = new Ext.data.Store({
    model: 'record'
    });

    Ext.setup({
    onReady: function(){

    var recs = [];
    for (var i = 0; i < 200; i++){
    recs.push({label: 'label ' + i});
    }
    store.loadData(recs);

    (new Ext.Panel({
    fullscreen: true,
    dockedItems: [{
    xtype: 'toolbar',
    dock: 'top'
    }, {
    xtype: 'toolbar',
    dock: 'bottom'
    }],
    items: [new Ext.ux.BBList({
    store: store,
    itemTpl: new Ext.XTemplate('{label}'),
    flex: 1,
    listeners : {
    'itemtap' : function(){
    console.log("tapped");
    }
    }
    })],
    layout: {
    type: 'vbox',
    align: 'stretch'
    }
    })).show();
    }
    });

    ReplyDelete
  4. Yet another small change...

    var offset = -1 * scrollList.scroller.offset.y;
    if(evt.browserEvent.wheelDeltaY < 0){
    offset +=10;
    }else{
    offset-=10;
    }


    at line 13.


    This is to scroll both up and down...

    ReplyDelete
  5. Hi Guys,

    First thanks for sharing this, it helped me a lot.

    I'm unfortunatly having a problem and since I just started with BB I'm not sure where its coming form.

    I got phonegap to work and I got the example above to work on a webbrowser. But when I try to launch this on the simulator I just get a blank screen. I was told I needed to tweek a few things in Sencha touch to make it work.

    Is there any way one of you can send me a working version of the project ?

    cheers

    Jason Rogers

    ReplyDelete
  6. Great post! Thanks for sharing this.

    I take it that you're writing this in a MVC approach, as I can't get this to work, except for the one that akawry put out.

    If you don't want to use the MVC pattern, is it a case of removing 'app.views.' and 'app.stores.'?

    ReplyDelete
  7. Yeah, just put everything in your Ext.setup({})
    In your onReady initialize Ext.Panel to some variable. In your Ext.Panel config add the scroll listener.

    ReplyDelete
  8. Hmm.... Unfortunately it doesn't seem to like that approach. I can't get the list to show up unless I set the fullscreen to true and I'm assuming that if you set it to true, the scroll mechanism on a non-touch blackberry wouldn't work.

    Would appreciate it if you can point me to the right direction with this example I put up - http://pastebin.com/JmcQ71WH.

    K

    ReplyDelete
  9. I have used the generic example from the comments above and extended both a panel and a list with it. One issue that I came accross was that having my list inside my extended panel caused a problem where you had to drag the screen up first before the scroll function would work.

    I managed to fix this with help from this post - http://stackoverflow.com/questions/6455413/scroller-scrollto-in-sencha-touch-dont-work-if-not-launched-manually-just-befor

    by adding:

    handleMouseMove: function (evt) {
    //Fix to ensure it scrolls on first load
    if (this.scroller.offsetBoundary.top == 0) {
    this.scroller.updateBoundary();
    }

    Hope that helps somebody!

    ReplyDelete
  10. That was actually me who posted the answer on that SO question :)

    ReplyDelete