It suddenly occurred
to me this evening, well after I had given up and
erased my code, that there is a simple and elegant solution to my
problems with using a single qx.ui.list.List and swapping the model to
avoid all the headaches with incorrect selection:
qx.ui.container.Stack
So, I whipped up a new proof-of-concept widget that creates 3
qx.ui.list.List objects, 1 for composers, 1 for pieces, and 1 for
movements. As of right now, it literally worked on the first try after I
fixed the expected typo bugs and minor omissions.
So for anyone struggling with using a qx.ui.list.List with changing
model and selection issues, use more than one of the things with a stack
and your problem disappears. You do lose some memory efficiency, but
it's a pretty small footprint since the data will be re-used. Here is
the actual code (minus the guess box stuff, which is irrelevant to this
discussion)
/**
* programinfo widget for complex program handling
*/
qx.Class.define("qooxdoo.Widgets.SimpleProgram", {
extend: qx.ui.core.Widget,
include: [qx.ui.form.MModelProperty],
properties :
{
appearance :
{
refine : true,
init : "simpleprogram"
}
},
events :
{
removeProgram : "qx.event.type.Data",
},
construct: function()
{
this.base(arguments);
this._setLayout(new qx.ui.layout.VBox(5));
this._add(this._createChildControl("erasebutton"));
this._add(this._createChildControl("guessbox"));
this._add(this._createChildControl("composerpiece"));
},
members: {
_stack : null,
_composerList: null,
_pieceList: null,
_movementsList: null,
_makeEraseButton: function(id)
{
var control;
control = new qx.ui.form.Button;
control.addListener("execute", function() {
this.fireDataEvent("removeProgram", this);
}, this);
control.setAppearance("programinfo/erasebutton");
return control;
},
_makeComposerPiece: function(id)
{
var control;
control = new qx.ui.container.Stack();
this._stack = control;
control.add(this.getChildControl("composer"));
control.add(this.getChildControl("piece"));
control.add(this.getChildControl("movements"));
return control;
},
_makeComposer: function(id)
{
var control = new qx.ui.list.List();
control.setLabelPath("title");
control.setModel(qx.core.Init.getApplication().getRepModel().getKids());
this._composerList = control;
return control;
},
_makePiece: function(id)
{
var control = new qx.ui.list.List();
control.setLabelPath("title");
this._pieceList = control;
return control;
},
_makeMovements: function(id)
{
var control = new qx.ui.list.List().set({
labelPath: "title",
selectionMode: "additive"
});
control.setLabelPath("title");
this._movementList = control;
return control;
},
_loadPieces: function() {
this._pieceList.setModel(this._composerList.getSelection().getItem(0).getKids());
this._stack.setSelection([this._pieceList]);
for (var i = 0; i <
this._composerList.getSelection().getItem(0).getKids().getLength(); i++)
{
var piece =
this._composerList.getSelection().getItem(0).getKids().getItem(i);
// try to head off some of the bullshit at the pass
piece.lazyLoadKids();
}
},
_switchMovements: function() {
this._movementList.setModel(this._pieceList.getSelection().getItem(0).getKids());
for (var i = 0; i <
this._pieceList.getSelection().getItem(0).getKids().getLength(); i++) {
var movement =
this._pieceList.getSelection().getItem(0).getKids().getItem(i);
this.getModel().getMovements().push(movement.getId());
this._movementList.getSelection().push(movement);
}
this._stack.setSelection([this._movementList]);
},
_createChildControlImpl: function(id)
{
var control;
if (id == "erasebutton") {
control = this._makeEraseButton(id);
this._add(control);
} else if (id == "guessbox") {
control = new qx.ui.form.TextField();
} else if (id == "composerpiece") {
control = this._makeComposerPiece(id);
this._add(control);
} else if (id == "composer") {
control = this._makeComposer(id);
control.getSelection().addListener("change", function(e) {
this.getModel().setMovements(new qx.data.Array());
this.getModel().setPiece(0);
this.getModel().getComposer().setId(e.getData());
if (this._composerList.getSelection().getItem(0).getTitle() ==
"Loading...") {
var composer = this._composerList.getSelection().getItem(0);
composer.lazyLoadKids();
composer.addListenerOnce("changeKids", this._loadPieces,
this);
} else {
this._loadPieces();
}
}, this);
} else if (id == "piece") {
control = this._makePiece(id);
control.getSelection().addListener("change", function(e) {
this.getModel().setMovements(new qx.data.Array());
this.getModel().setPiece(e.getData());
this._switchMovements();
}, this);
} else if (id == "movements") {
control = this._makeMovements(id);
}
return control || this.base(arguments, id);
}
}
});
Just a thought for when
you get back to it then – is there a chance that two bits of code were
trying to change the model(s) at the same time? I have had something
similar where recursive binding can cause conflicts and the wrong things
get added/removed - e.g. An event changes the model which causes an
event which indirectly causes a change to the model etc. Have you tried
setting a long timeout (e.g. a second or two) before adjusting the
model?
John
Ack! Accidentally clicked send.
I
have a database of composers, the pieces they wrote and the movements
of those pieces. It is sent to qooxdoo as Json, and using a Json
marshal made into 3 classes. The kids property in composers stores the
array of pieces, and the same is true for the kids property of pieces,
which stores an array of movements.
I use this
to render a tree in one tab of the backend, and I can add composers,
pieces and movements as needed. However, because the data set is so
large, I do some lazy loading. At first, each composer's only child
node is a piece called "Loading..." When the code sees it, it lazy
loads the children and updates the display. All this works perfectly
for years and still works.
In the tab where I
edit concert info, I am re-using the data from the
composers/pieces/movements tab to provide the source to choose program
info. I created a widget years ago that allows clicking a composer from
a selectbox qx.ui.form.List, which populates another selectbox with the
composer's pieces, lazy loading as necessary, and then chooses
movements, lazy loading as necessary.
This
process is too slow, it takes me hours because there is a subtle bug
where selecting the movements does not actually work unless I de-select
them and re-select them. Also, scrolling through the list of composers
and pieces is too slow.
Thus I had the idea of
instead using qx.ui.list.List. Coupling this with a text field and
using that to filter the list. So if we are playing Beethoven String
Quartet Op. 59 No. 2, I could type "bee" and it will filter the list of
115 composers to just Beethoven. I click Beethoven and the
qx.ui.list.List model changes to the pieces he wrote and the text field
changes to "Beethoven, Ludwig can ". I type "59" after that and it
filters to the 3 Op. 59 quartets he wrote. I click 59 No. 2 and the
text field changes to "Beethoven, Ludwig van String Quartet in E minor
Op. 59 No. 2" and disables itself. Meanwhile, the list updates to the 4
movements and I can deselect any we are not playing on that concert.
All
of this worked except when I clicked Beethoven, the widget remained
selected after the model change and often the movements would not
display properly sometimes, with no clear cause-effect, implying an
asynchronous issue of some sort.
I pulled out
the specific code, and the bug disappeared. So, there is something
about the interaction of the larger code and the smaller that triggers
the bug. This is why I erased the whole thing. It needs a smarter
re-imagining before I can restart it, and as I said, my available
development time was this week. I won't have another window for a month
or so.
If you have read this far, thank you!
Thank
you also for the quick and calmly measured responses. Much appreciated
Greg
------------------------------------------------------------------------------
Learn
the latest--Visual Studio 2012, SharePoint 2013, SQL 2012, more!
Discover the easy way to master current and previous Microsoft
technologies
and advance your career. Get an incredible 1,500+ hours of step-by-step
tutorial videos with LearnDevNow. Subscribe today and save!
http://pubads.g.doubleclick.net/gampad/clk?id=58040911&iu=/4140/ostg.clktrk_______________________________________________
qooxdoo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
------------------------------------------------------------------------------ Learn
the latest--Visual Studio 2012, SharePoint 2013, SQL 2012, more! Discover
the easy way to master current and previous Microsoft technologies and
advance your career. Get an incredible 1,500+ hours of step-by-step tutorial
videos with LearnDevNow. Subscribe today and save! http://pubads.g.doubleclick.net/gampad/clk?id=58040911&iu=/4140/ostg.clktrk
Ack! Accidentally
clicked send.
I have a database of composers,
the pieces they wrote and the movements of those pieces. It is sent to
qooxdoo as Json, and using a Json marshal made into 3 classes. The kids
property in composers stores the array of pieces, and the same is true
for the kids property of pieces, which stores an array of movements.
I use this to render a tree in one tab of the
backend, and I can add composers, pieces and movements as needed.
However, because the data set is so large, I do some lazy loading. At
first, each composer's only child node is a piece called "Loading..."
When the code sees it, it lazy loads the children and updates the
display. All this works perfectly for years and still works.
In the tab where I edit concert info, I am re-using
the data from the composers/pieces/movements tab to provide the source
to choose program info. I created a widget years ago that allows
clicking a composer from a selectbox qx.ui.form.List, which populates
another selectbox with the composer's pieces, lazy loading as necessary,
and then chooses movements, lazy loading as necessary.
This process is too slow, it takes me hours because
there is a subtle bug where selecting the movements does not actually
work unless I de-select them and re-select them. Also, scrolling
through the list of composers and pieces is too slow.
Thus I had the idea of instead using
qx.ui.list.List. Coupling this with a text field and using that to
filter the list. So if we are playing Beethoven String Quartet Op. 59
No. 2, I could type "bee" and it will filter the list of 115 composers
to just Beethoven. I click Beethoven and the qx.ui.list.List model
changes to the pieces he wrote and the text field changes to "Beethoven,
Ludwig can ". I type "59" after that and it filters to the 3 Op. 59
quartets he wrote. I click 59 No. 2 and the text field changes to
"Beethoven, Ludwig van String Quartet in E minor Op. 59 No. 2" and
disables itself. Meanwhile, the list updates to the 4 movements and I
can deselect any we are not playing on that concert.
All of this worked except when I clicked Beethoven,
the widget remained selected after the model change and often the
movements would not display properly sometimes, with no clear
cause-effect, implying an asynchronous issue of some sort.
I pulled out the specific code, and the bug
disappeared. So, there is something about the interaction of the larger
code and the smaller that triggers the bug. This is why I erased the
whole thing. It needs a smarter re-imagining before I can restart it,
and as I said, my available development time was this week. I won't
have another window for a month or so.
If you have read this far, thank you!
Thank
you also for the quick and calmly measured responses. Much appreciated
Greg
|