【问题标题】:Tagfield scroll is not workingTagfield 滚动不起作用
【发布时间】:2017-08-11 09:26:15
【问题描述】:

我正在使用 6.0 有时我在 ExtJS 6 tagfield 中遇到奇怪的问题。我正在使用带有growMax : 3 的标签字段。现在,当为标签字段选择的值超过三个时,我在标签字段中获得了一个上下指针选项。

这很好现在奇怪的部分是当我点击向下箭头时,这让我正好处于字段的底部。我看不到选择了介于两者之间的其他值。有什么办法可以减慢这些值的移动滚动速度。

我的小提琴:Fiddle

复制步骤。

  1. 选择几个值(超过 2 或 3 个)
  2. 单击向下指针。 (图中红框)

它可能会跳过第二个第三个值并导致您结束。

注意:有时我需要为标签字段中的 100 个数据执行此操作。甚至看不到我选择的所有内容。我也不能改变高度。

是否有任何事件会在单击滚动按钮时触发。

【问题讨论】:

  • 是否值得考虑删除filterPickList: true?或者也许选择具有多项选择的组合框?您提到您可能需要在该字段中查看多达 100 个标签,但通过缓慢滚动一次查看一两个标签似乎并不实用。
  • 是的,我可以删除filterPickList: true 并进行测试。带有多项选择的组合框我无法使用,因为我的应用是 extJs 6。
  • @Jaimee 没有,即使在filterPickList: true 之后,情况也一样。
  • 我可以知道投反对票的原因是什么吗?
  • 我并不是建议将其作为修复,而是作为用户查看他们选择的内容的替代选项。以这种方式滚动浏览 100 个标签需要很长时间。如果最终用户必须查找并取消选择第 70 个项目怎么办?也许需要对 UI 进行审核 - 需要在小空间中展示大量信息。

标签: javascript extjs extjs6


【解决方案1】:

更新(实际实现继承)

好的,看来您确实需要基于继承的解决方案。这段代码显然不是惯用的 ExtJS,但它似乎对我有用。首先定义一个自定义子类SingleLineTag 并将'singleline-tagfield' 分配为其xtype(在“旧答案”部分中有一些关于此代码背后的主要思想的描述)

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);
        }
    }
});

现在更改 items 集合中的 xtype

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,
    }]
}); 

请注意,如果您不将此元素配置为实际上只采用单行高度,则它在滚动方面的行为会很奇怪。

Sencha fiddle查看组合代码


旧答案

我不擅长 ExtJS,但我认为一些不太好的答案总比没有答案好。首先,我同意 growMax 以像素为单位,因此 3 太少了。仍在考虑您的问题,似乎没有足够的空间容纳完整的滚动条,因此唯一的方法是添加自定义滚动逻辑。可能最好创建一些继承自 Tag 的新类,但我不确定如何在 ExtJS 中正确执行此操作,所以这里有一些自定义且可能不习惯的代码。

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);
};

你可以通过简单的操作来使用它

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

我的代码的主要思想是拦截包含 ul 和选定项目的容器元素的滚动事件,并将事件不视为真正的滚动,而只是将其视为滚动一个元素的方向。正常工作所需的数据以希望唯一的 zzzLastScroll 名称附加回小部件。

还有一些额外的逻辑可以在删除某些项目时使滚动看起来更好。


完整代码(而不是小提琴)

不幸的是,我没有 ExtJS 帐户,没有它我无法在那里创建一个新的小提琴,所以以防万一这里是我用来测试我的代码的修改后的 app.js 的完整代码.

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);

【讨论】:

  • 感谢您的回答和宝贵的时间。这需要我很多时间在我的小应用程序中实现它。让我试试这个,过一段时间再回复你。
  • @David,我不确定您在集成此解决方案时的具体问题在哪里。如果您提供一些更接近您的真实代码的示例,我可能(或可能不会)也能提供帮助。
  • 我仍然不知道如何在我的应用程序中实现。但是您的代码在小提琴中完美运行。我将在我的应用程序中使用它。感谢您的回答。
  • @David,您能否澄清将其集成到您的应用程序中有什么问题。作为最后的手段,我想我可以将它作为Tag 类的子类来实现,它应该可以解决你所有的问题。
  • 我有一个标签字段作为项目。我只是想知道我应该在哪里写这段代码。 window.tagField = pnl.items.items[0];window.lstDom = window.tagField.itemList.el.dom;window.container = window.lstDom.parentElement;
【解决方案2】:

growMax 的设置应该是以像素为单位的高度。要允许选择其中 3 个项目的空间和合理的滚动空间,您可以尝试设置 growMax=60。查看更新的fiddle

【讨论】:

  • 感谢您的回答。但这会增加高度。如果我有一个固定的高度怎么办?比如列小部件等等。我还注意到,当我单击向下滚动箭头时,我的标签字段菜单正在打开。只有当我点击标签字段的常用箭头时,它才会打开。
  • 我想要恒定的高度。
  • 这个答案提到了非常重要的一点,即 growMax 是以像素为单位的最大高度。 Extjs Doc 必须更新。在 Extjs 6.2.3 中像魔术一样工作。我不确定为什么我们有 maxHeight 配置。
猜你喜欢
  • 2023-03-13
  • 2017-12-15
  • 2016-11-01
  • 2023-04-05
  • 2014-01-19
  • 2017-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多