I have a List with dragEnabled = true, and selectionColor = "#ff0000", when the user selects one item, it behaves as what I expect. But when I complete drag and release item, it still remains selected state. What I want is when drag complete, the item back to normal state, NOT selected state. What I do is that I check if the data dropComplete property is true, then change the state, but it can NOT work.
my code is following:
override protected function getCurrentRendererState():String {
currentState = super.getCurrentRendererState();
if(data.dropComplete) {
currentState = null;
data.dromComplete = false;
}
}
select state:
normal state:
I made a wrong direction. The solution is really simple, just listen for dragComplete event on List, code below:
private function myDragCompleteHandler(event:DragEvent):void{
this.selectedIndices = new Vector.<int>;
}
Related
I have a ListView displaying custom objects from my domain model, and if I use a custom cell factory to display the objects' properties in each row of the list, I get strange behaviour when I delete items. If the item is not the last in the list, the deleted item remains visible and the last item disappears. However, the item has been removed from the backing list as expected, and attempting to delete the phantom object has no further effect.
The display seems not to be refreshing properly, because after some arbitrary resizing of the window, the list eventually refreshes to its expected values. I've tried calling refresh() on the ListView manually but it has no noticeable effect.
Removing my custom cell factory fixes the problem, and I've seen other posts that have had a similar problem using standard JavaFX (ListView using custom cell factory doesn't update after items deleted) where the problem is fixed by changing the implementation of updateItem(Object item, boolean empty), but I can't work out how to do that in TornadoFX.
Here's an example that demonstrates the update issue (but not the phantom item, that only happens if the delete button is part of the custom cell):
package example
import javafx.scene.control.ListView
import tornadofx.*
data class DomainClass(val name: String, val flag1: Boolean, val flag2: Boolean, val info: String)
class UpdateIssue : App(UpdateIssueView::class)
class UpdateIssueView : View() {
val listSource = mutableListOf(
DomainClass("object1", true, false, "more info"),
DomainClass("object2", false, true, "even more info"),
DomainClass("object3", false, false, "all the info")
).observable()
var lst: ListView<DomainClass> by singleAssign()
override val root = vbox {
lst = listview(listSource) {
cellFormat {
graphic = cache {
hbox {
textfield(it.name)
combobox<Boolean> {
selectionModel.select(it.flag1)
}
combobox<Boolean> {
selectionModel.select(it.flag2)
}
textfield(it.info)
}
}
}
}
button("delete") {
action {
listSource.remove(lst.selectedItem)
}
}
}
}
Any help greatly appreciated!
The suggestion from #Edvin Syse to remove the cache block fixed this for me (although note that he also said a more performant fix would be to implement a ListCellFragment, which I haven't done here):
....
lst = listview(listSource) {
cellFormat {
graphic = hbox {
textfield(it.name)
combobox<Boolean> {
selectionModel.select(it.flag1)
}
combobox<Boolean> {
selectionModel.select(it.flag2)
}
textfield(it.info)
}
}
}
I noticed that the ComboBoxes don't show any other selectable values besides it.flag1 and flag2. You'll want to set the values property to true/false or true/false/null. You can then set the value item directly.
lst = listview(listSource) {
cellFormat {
graphic = hbox {
textfield(it.name)
combobox(values=listOf(true, false)) {
value = it.flag1
}
combobox(values=listOf(true, false)) {
value = it.flag2
}
textfield(it.info)
}
}
}
See fiddle here: https://fiddle.sencha.com/#fiddle/2iig&view/editor
The docs (https://docs.sencha.com/extjs/6.6.0/classic/Ext.ux.TreePicker.html#event-change) list 'change' in the events section but when I set the value or reset the field this event never fires. The 'select' event fires as expected but that only fires when the user selects a field.
EDIT:
Based on Snehal's suggestion below, I was able to accomplish this using the following override. Not sure if there is a simpler way to do it but this was the best I could manage:
Ext.define('MyApp.overrides.TreePicker', {
override: 'Ext.ux.TreePicker',
setValue: function (value) {
var me = this,
record;
me.value = value;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getNodeById(value) : me.store.getRoot();
if (value === undefined) {
record = me.store.getRoot();
me.value = record.getId();
} else {
record = me.store.getNodeById(value);
}
// zeke - this is the only line I added to the original source
// without this the 'change' event is not fired
me.callSuper([value]);
// set the raw value to the record's display field if a record was found
me.setRawValue(record ? record.get(me.displayField) : '');
return me;
}
});
Because setValue function does not call this.callParent(). You can do something like this in setValue function.
setValue: function(value) {
var me = this,
record;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getById(value) : me.store.getRoot();
me.callParent([record.get('valueField')]);
return me;
},
I red a lot about sorting a CellTable. I also went trough the ColumnSorting with AsyncDataProvider. But my CellTable does not sort.
Here is my code:
public class EventTable extends CellTable<Event> {
public EventTable() {
EventsDataProvider dataProvider = new EventsDataProvider(this);
dataProvider.addDataDisplay(this);
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
SimplePager pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 5, true);
pager.setDisplay(this);
[...]
TextColumn<Event> nameCol = new TextColumn<Event>() {
#Override
public String getValue(Event event) {
return event.getName();
}
};
nameCol.setSortable(true);
AsyncHandler columnSortHandler = new AsyncHandler(this);
addColumnSortHandler(columnSortHandler);
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
}
public class EventsDataProvider extends AsyncDataProvider<Event> {
private final EventTable eventTable;
public EventsDataProvider(EventTable eventTable) {
this.eventTable = eventTable;
}
#Override
protected void onRangeChanged(HasData<Event> display) {
int start = display.getVisibleRange().getStart();
int length = display.getVisibleRange().getLength();
// check false values
if (start < 0 || length < 0) return;
// check Cache before making a rpc
if (pageCached(start, length)) return;
// get Events async
getEvents(start, length);
}
}
I do now know, if all the methods are need here. If so, I will add them. But in short:
pageCached calls a method in my PageCache Class which holds a map and a list. Before making a rpc call, the cache is checked if the events where already taken and then displayed.
getEvents just makes an rpc call via asynccallback which updates the rowdata via updateRowData() on success.
My Table is displayed fast with currently around 500 entries (could be more, depends on the customer). No missing data and the paging works fine.
I just cannot get the sorting work. As far as I know, AsyncHandler will fire a setVisibleRangeAndClearData() and then an onRangeChanged(). onRangeChanged is never fired. As for the setVisibleRangeAndClearData() I do not know. But the sortindicator (arrow next to the columnname) does change on every click.
I do not want to let the server sort the list. I have my own Comparators. It is enough, if the current visible page of the table is sorted. I do now want to sort the whole list.
Edit:
I changed following code in the EventTable constructor:
public EventTable() {
[...]
addColumnSortHandler(new ColumnSortEvent.AsyncHandler(this) {
public void onColumnSort(ColumnSortEvent event) {
super.onColumnSort(event);
MyTextColumn<Event> myTextColumn;
if (event.getColumn() instanceof MyTextColumn) {
// Compiler Warning here: Safetytype unchecked cast
myTextColumn = (MyTextColumn<Event>) event.getColumn();
MyLogger.log(this.getClass().getName(), "asc " + event.isSortAscending() + " " + myTextColumn.getName(), Level.INFO);
}
List<Event> list = dataProvider.getCurrentEventList();
if (list == null) return;
if (event.isSortAscending()) Collections.sort(list, EventsComparator.getComparator(EventsComparator.NAME_SORT));
else Collections.sort(list, EventsComparator.descending(EventsComparator.getComparator(EventsComparator.NAME_SORT)));
}
});
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
I had to write my own TextColumn to determine the Name of the column. Otherwise how should I know, which column was clicked? The page gets sorted now but I have to click twice on the column. After then, the sorting is done with every click but in the wrong order.
This solution does need polishing and it seems kinda hacky to me. Any better ideas?
The tutorial, that you linked to, states:
This sorting code is here so the example works. In practice, you would
sort on the server.
Async provider is used to display data that is too big to be loaded in a single call. When a user clicks on any column to sort it, there is simply not enough objects on the client side to display "first 20 evens by name" or whatever sorting was applied. You have to go back to your server and request these first 20 events sorted by name in ascending order. And when a user reverses sorting, you have to go to the server again to get first 20 events sorted by name in a descending order, etc.
If you can load all data in a single call, then you can use regular DataProvider, and all sorting can happen on the client side.
EDIT:
The problem in the posted code was in the constructor of EventsDataProvider. Now it calls onRangeChanged, and the app can load a new sorted list of events from the server.
I use the multiselect from the Kendo UI.
I want to know if there is any way to trigger a function, when the user deletes an item from the multiselect.
So far I know that the 'change' event is triggered, but it is too generic and I can't find any info on what the user removed. Or is there?
What about define change as:
change : function (e) {
var previous = this._savedOld;
var current = this.value();
var diff = [];
if (previous) {
diff = $(previous).not(current).get();
}
this._savedOld = current.slice(0);
// diff has the elements removed do whatever you want...
}
What I do is save previous values on _savedOld and then compute the difference with current using jQuery.not. It's important to note the use of slice for cloning previous list of values, if we save current then we are actually saving a reference to current list and since it is a reference next time we try to use we get again current value.
EDIT: In order to save the values set during the initialization, you can do:
dataBound : function (e) {
saveCurrent(this);
},
change : function (e) {
var previous = this._savedOld;
var current = this.value();
var diff = $(previous).not(current).get();
saveCurrent(this);
// diff has the elements removed do whatever you want...
}
where saveCurrent is a function defined as:
function saveCurrent(multi) {
multi._savedOld = multi.value().slice(0);
}
I have a web page where the user will enter their address. They will select their country and region in cascading drop down lists. I would like to provide an auto completing textbox for their city, but I want to be context sensitive to the country and region selections. I would have just used another cascading drop down list, however the number of cities exceeds the maximum number of list items.
Any suggestions or cool code spinets out there that may help me out?
I just found the following blog post that looks at least close to what you want.
They manage it using the following javascript functions:
function initCascadingAutoComplete() {
var moviesAutoComplete = $find('autoCompleteBehavior1');
var actorsAutoComplete = $find('autoCompleteBehavior2');
actorsAutoComplete.set_contextKey(moviesAutoComplete.get_element().value);
moviesAutoComplete.add_itemSelected(cascade);
// setup initial state of second flyout
if (moviesAutoComplete.get_element().value) {
actorsAutoComplete.get_element().disabled = false;
} else {
actorsAutoComplete.get_element().disabled = true;
actorsAutoComplete.get_element().value = "";
}
}
function cascade(sender, ev) {
var actorsAutoComplete = $find('autoCompleteBehavior2');
actorsAutoComplete.set_contextKey(ev.get_text());
actorsAutoComplete.get_element().value = '';
if (actorsAutoComplete.get_element().disabled) {
actorsAutoComplete.get_element().disabled = false;
}
}
Sys.Application.add_load(initCascadingAutoComplete);
Calling the cascade function on the add_itemSelected method of the parent control for the cascading behaviour.
They cascade the contents of one auto complete extender into another, rather than taking a cascading drop down list, but hopefully you can reuse some of the ideas.