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.
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.
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With