Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tagfield scroll is not working

I am using 6.0 Sometime I am getting weird problem in ExtJS 6 tagfield. I am using a tagfield with growMax : 3. Now When value selected for tagfield is more than three tyhen I am getting a pointer up and down option in tagfield.

This is fine Now the weired part is when I am click on down arrow this taking me exactly the bottom of field. I can not see what else value is selected which is placed in between. Is there any way I can slow the speed of moving scroll of those value.

My Fiddle : Fiddle

Step to reproduce.

  1. Select few values(more than 2 or 3)
  2. Click on down pointer. (Red box in image) Image

It will may skip 2nd third value and leads you to end.

Note : Sometime I need to perform this in for 100 data in tagfield. Can't even see what and all I selected. Also I can't change height.

Is there any event which fier on click of scroll buttons.

like image 846
David Avatar asked Mar 20 '17 07:03

David


2 Answers

Update (actually implement inheritance)

OK, it looks like you really need an inheritance-based solution. This code is obviously not an idiomatic ExtJS but it seems to work for me. First define a custom subclass SingleLineTag and assign 'singleline-tagfield' as its xtype (there is some description of the main ideas behind this code in the "older answer" section)

Ext.define('Ext.form.field.SingleLineTag', {
    extend: 'Ext.form.field.Tag',
    xtype: 'singleline-tagfield',

    initEvents: function () {
        var me = this;
        me.callParent(arguments);

        me.itemList.el.dom.parentElement.addEventListener('scroll', Ext.bind(me.zzzOnTagScroll, me));
    },

    zzzGetTagLastScroll: function () {
        var me = this;
        return me.zzzLastScroll = me.zzzLastScroll || {
                lastIndex: 0,
                lastTop: 0,
                lastTimeStamp: 0
            };
    },

    zzzScrollToTagIndex: function (index) {
        var tagField = this;
        var lastScroll = tagField.zzzLastScroll;
        if (lastScroll) {
            var lstDom = tagField.itemList.el.dom;
            var childrenDom = lstDom.children;
            var containerDom = tagField.itemList.el.dom.parentElement;

            if ((index >= 0) && (index < childrenDom.length)) {
                lastScroll.lastIndex = index;
                containerDom.scrollTop = lastScroll.lastTop = childrenDom[index].offsetTop - lstDom.offsetTop;
            }
        }
    },

    zzzOnTagScroll: function (ev) {
        var me = this;
        var lastScroll = me.zzzGetTagLastScroll();

        // throttle scroll events as thy occur to often and we might scroll to much
        if (Math.abs(lastScroll.lastTimeStamp - ev.timeStamp) < 200) {
            ev.preventDefault();
            return;
        }

        lastScroll.lastTimeStamp = ev.timeStamp;

        var lstDom = me.itemList.el.dom;
        var childrenDom = lstDom.children;
        var containerDom = me.itemList.el.dom.parentElement;
        var scrollTop = containerDom.scrollTop;

        var index = lastScroll.lastIndex;
        if (index >= childrenDom.length)
            index = childrenDom.length - 1;
        if (index < 0)
            index = 0;
        var lstTop = lstDom.offsetTop;
        if (scrollTop > lastScroll.lastTop) {
            // scrolling down, find next element
            for (; index < childrenDom.length; index++) {
                if (childrenDom[index].offsetTop - lstTop > scrollTop) {
                    break;
                }
            }
            if (index < childrenDom.length) {
                // we've found the next element so change scroll position to it's top
                me.zzzScrollToTagIndex(index);
            }
            else {
                lastScroll.lastIndex = childrenDom.length;
                lastScroll.lastTop = containerDom.scrollTop;
            }
        }
        else {
            // scrolling up, find prev element
            for (; index >= 0; index--) {
                if (childrenDom[index].offsetTop - lstTop < scrollTop) {
                    break;
                }
            }
            if (index >= 0) {
                // we've found the prev element so change scroll position to it's top
                me.zzzScrollToTagIndex(index);
            }
            else {
                lastScroll.lastIndex = 0;
                lastScroll.lastTop = 0;
            }
        }
    },


    onBeforeDeselect: function (list, record) {
        var me = this;
        me.callParent(arguments);
        var value = record.get(me.valueField);
        var index = me.getValue().indexOf(value);
        var lastScroll = me.zzzGetTagLastScroll();
        if (lastScroll.lastIndex > index)
            lastScroll.lastIndex -= 1;
        var nextIndex = (lastScroll.lastIndex > index) ? lastScroll.lastIndex - 1 : lastScroll.lastIndex;
        setTimeout(function () {
            me.zzzScrollToTagIndex(nextIndex);
        }, 0);
    },

    onItemListClick: function(ev) {
        var me = this;

        // HACK for IE: throttle click events after scroll
        // click on the scrollbar seem to generate click on the "itemList" as well
        // which lead to showing of the dropdown
        var lastScroll = me.zzzGetTagLastScroll();
        if (Math.abs(lastScroll.lastTimeStamp - ev.timeStamp) > 200) {
            me.callParent(arguments);
        }
    }
});

Now change the xtype in the items collections

var shows = Ext.create('Ext.data.Store', {
    fields: ['id', 'show'],
    data: [
        {id: 0, show: 'Battlestar Galactica'},
        {id: 11, show: 'Doctor Who'},
        {id: 2, show: 'Farscape'},
        {id: 3, show: 'Firefly'},
        {id: 4, show: 'Star Trek'},
        {id: 5, show: 'Star Wars: Christmas Special'}
    ]
});

Ext.create('Ext.form.Panel', {
    renderTo: Ext.getBody(),
    title: 'Sci-Fi Television',
    height: 200,
    width: 300,
    items: [{
        //xtype: 'tagfield',          // old
        xtype: 'singleline-tagfield', // new 
        growMax: 18,
        fieldLabel: 'Select a Show',
        store: shows,
        displayField: 'show',
        valueField: 'id',
        queryMode: 'local',
        filterPickList: true,
    }]
}); 

Note that if you don't configure this element to actually take only single line of height, it will behave weirdly in terms of scrolling.

See combined code at this Sencha fiddle


Older answer

I'm not good with ExtJS but I think some not so good answer is better than no answer at all. First of all I agree that growMax is in pixels and thus 3 is too little. Still considering your issue, it seems that there is just not enough space for a full-blown scrollbar and thus the only way is to add custom scrolling logic. Probably it is better to create some new class that inherits from Tag but I'm not sure how exactly to do it properly in the ExtJS so here is some custom and probably non-idiomatic code.

function findComponentByElement(node) {
    var topmost = document.body,
        target = node,
        cmp;

    while (target && target.nodeType === 1 && target !== topmost) {
        cmp = Ext.getCmp(target.id);

        if (cmp) {
            return cmp;
        }

        target = target.parentNode;
    }

    return null;
}


var getTagLastScroll = function (tagField) {
    return tagField.zzzLastScroll = tagField.zzzLastScroll || {
            lastIndex: 0,
            lastTop: 0,
            lastTimeStamp: 0
        };
};

var scrollToTagIndex = function (tagField, index) {
    var lastScroll = tagField.zzzLastScroll;
    if (lastScroll) {
        var lstDom = tagField.itemList.el.dom;
        var childrenDom = lstDom.children;
        var containerDom = tagField.itemList.el.dom.parentElement;

        if ((index >= 0) && (index < childrenDom.length)) {
            lastScroll.lastIndex = index;
            containerDom.scrollTop = lastScroll.lastTop = childrenDom[index].offsetTop - lstDom.offsetTop;

            //console.log("Scroll to " + containerDom.scrollTop);
            //console.log(lastScroll);

        }
    }
};

var onTagScroll = function (ev) {
    var tagField = findComponentByElement(ev.target);
    var lastScroll = getTagLastScroll(tagField);

    // need to throttle scroll events or will scroll to much
    if (Math.abs(lastScroll.lastTimeStamp - ev.timeStamp) < 200) {
        ev.preventDefault();
        return;
    }

    //console.log(ev);
    lastScroll.lastTimeStamp = ev.timeStamp;

    var lstDom = tagField.itemList.el.dom;
    var childrenDom = lstDom.children;
    var containerDom = tagField.itemList.el.dom.parentElement;
    var scrollTop = containerDom.scrollTop;

    //console.log("Before " + containerDom.scrollTop);
    //console.log(lastScroll);

    var index = lastScroll.lastIndex;
    if (index >= childrenDom.length)
        index = childrenDom.length - 1;
    if (index < 0)
        index = 0;
    var lstTop = lstDom.offsetTop;
    if (scrollTop > lastScroll.lastTop) {
        // scrolling down, find next element
        for (; index < childrenDom.length; index++) {
            if (childrenDom[index].offsetTop - lstTop > scrollTop) {
                break;
            }
        }
        if (index < childrenDom.length) {
            // we've found the next element so change scroll position to it's top
            scrollToTagIndex(tagField, index);
        }
        else {
            lastScroll.lastIndex = childrenDom.length;
            lastScroll.lastTop = containerDom.scrollTop;
        }
    }
    else {
        // scrolling up, find prev element
        for (; index >= 0; index--) {
            if (childrenDom[index].offsetTop - lstTop < scrollTop) {
                break;
            }
        }
        if (index >= 0) {
            // we've found the prev element so change scroll position to it's top
            scrollToTagIndex(tagField, index);
        }
        else {
            lastScroll.lastIndex = 0;
            lastScroll.lastTop = 0;
        }
    }
    //console.log("After " + containerDom.scrollTop);
    //console.log(lastScroll);
};


var beforeDeselect = function (tagField, record) {
    var value = record.get(tagField.valueField);
    var index = tagField.getValue().indexOf(value);
    var lastScroll = getTagLastScroll(tagField);
    if (lastScroll.lastIndex > index)
        lastScroll.lastIndex -= 1;
    var nextIndex = (lastScroll.lastIndex > index) ? lastScroll.lastIndex - 1 : lastScroll.lastIndex;
    setTimeout(function () {
        scrollToTagIndex(tagField, nextIndex);
    }, 0);
};

var attachCustomScroll = function (tagField) {
    var containerDom = tagField.itemList.el.dom.parentElement;
    containerDom.addEventListener('scroll', onTagScroll);
    tagField.on('beforeDeselect', beforeDeselect);
};

You can use it by simply doing something like

var pnl = Ext.create('Ext.form.Panel', { 
    ...
});
var tagField = pnl.items.items[0];
attachCustomScroll(tagField);

The main idea behing my code is to intercept scroll event for the container element that contains ul with selected items and treat events not as real scrolling but just as a direction to scroll by one element to it. Data that is needed for that to work correctly is attached back to the widget under hopefully unique zzzLastScroll name.

Also there is additional piece of logic to make scrolling look better when some item is removed.


Full code (instead of fiddle)

Unfortunatelly I don't have ExtJS account and without it I can't create a new fiddle there so just in case here is full code of modified app.js that I used to test my code.

var shows = Ext.create('Ext.data.Store', {
    fields: ['id', 'show'],
    data: [
        {id: 0, show: 'Battlestar Galactica'},
        {id: 11, show: 'Doctor Who'},
        {id: 2, show: 'Farscape'},
        {id: 3, show: 'Firefly'},
        {id: 4, show: 'Star Trek'},
        {id: 5, show: 'Star Wars: Christmas Special'}
    ]
});


var pnl = Ext.create('Ext.form.Panel', {
    renderTo: Ext.getBody(),
    title: 'Sci-Fi Television',
    height: 200,
    width: 300,
    items: [{
        xtype: 'tagfield',
        growMax: 18,
        fieldLabel: 'Select a Show',
        store: shows,
        displayField: 'show',
        valueField: 'id',
        queryMode: 'local',
        filterPickList: true,


    }]
});

window.tagField = pnl.items.items[0];
window.lstDom = window.tagField.itemList.el.dom;
window.container = window.lstDom.parentElement;

tagField.setValue([11, 3, 4, 5]);

function findComponentByElement(node) {
    var topmost = document.body,
        target = node,
        cmp;

    while (target && target.nodeType === 1 && target !== topmost) {
        cmp = Ext.getCmp(target.id);

        if (cmp) {
            return cmp;
        }

        target = target.parentNode;
    }

    return null;
}


var getTagLastScroll = function (tagField) {
    return tagField.zzzLastScroll = tagField.zzzLastScroll || {
            lastIndex: 0,
            lastTop: 0,
            lastTimeStamp: 0
        };
};

var scrollToTagIndex = function (tagField, index) {
    var lastScroll = tagField.zzzLastScroll;
    if (lastScroll) {
        var lstDom = tagField.itemList.el.dom;
        var childrenDom = lstDom.children;
        var containerDom = tagField.itemList.el.dom.parentElement;

        if ((index >= 0) && (index < childrenDom.length)) {
            lastScroll.lastIndex = index;
            containerDom.scrollTop = lastScroll.lastTop = childrenDom[index].offsetTop - lstDom.offsetTop;

            //console.log("Scroll to " + containerDom.scrollTop);
            //console.log(lastScroll);

        }
    }
};

var onTagScroll = function (ev) {
    var tagField = findComponentByElement(ev.target);
    var lastScroll = getTagLastScroll(tagField);

    if (Math.abs(lastScroll.lastTimeStamp - ev.timeStamp) < 200) {
        ev.preventDefault();
        return;
    }

    //console.log(ev);
    lastScroll.lastTimeStamp = ev.timeStamp;

    var lstDom = tagField.itemList.el.dom;
    var childrenDom = lstDom.children;
    var containerDom = tagField.itemList.el.dom.parentElement;
    var scrollTop = containerDom.scrollTop;

    //console.log("Before " + containerDom.scrollTop);
    //console.log(lastScroll);

    var index = lastScroll.lastIndex;
    if (index >= childrenDom.length)
        index = childrenDom.length - 1;
    if (index < 0)
        index = 0;
    var lstTop = lstDom.offsetTop;
    if (scrollTop > lastScroll.lastTop) {
        // scrolling down, find next element
        for (; index < childrenDom.length; index++) {
            if (childrenDom[index].offsetTop - lstTop > scrollTop) {
                break;
            }
        }
        if (index < childrenDom.length) {
            // we've found the next element so change scroll position to it's top
            scrollToTagIndex(tagField, index);
        }
        else {
            lastScroll.lastIndex = childrenDom.length;
            lastScroll.lastTop = containerDom.scrollTop;
        }
    }
    else {
        // scrolling up, find prev element
        for (; index >= 0; index--) {
            if (childrenDom[index].offsetTop - lstTop < scrollTop) {
                break;
            }
        }
        if (index >= 0) {
            // we've found the prev element so change scroll position to it's top
            scrollToTagIndex(tagField, index);
        }
        else {
            lastScroll.lastIndex = 0;
            lastScroll.lastTop = 0;
        }
    }
    //console.log("After " + containerDom.scrollTop);
    //console.log(lastScroll);
};


var beforeDeselect = function (tagField, record) {
    var value = record.get(tagField.valueField);
    var index = tagField.getValue().indexOf(value);
    var lastScroll = getTagLastScroll(tagField);
    if (lastScroll.lastIndex > index)
        lastScroll.lastIndex -= 1;
    var nextIndex = (lastScroll.lastIndex > index) ? lastScroll.lastIndex - 1 : lastScroll.lastIndex;
    setTimeout(function () {
        scrollToTagIndex(tagField, nextIndex);
    }, 0);
};

var attachCustomScroll = function (tagField) {
    var containerDom = tagField.itemList.el.dom.parentElement;
    containerDom.addEventListener('scroll', onTagScroll);
    tagField.on('beforeDeselect', beforeDeselect);
};

attachCustomScroll(window.tagField);
like image 90
SergGr Avatar answered Oct 19 '22 05:10

SergGr


The setting for growMax should be a height in pixels. To allow space for 3 of these items to be selected with a reasonable amount of space for scrolling, you could try setting growMax=60. See updated fiddle.

like image 29
chrisuae Avatar answered Oct 19 '22 03:10

chrisuae