I'm using qooxdoo to build a custom virtualTree, which works fine as expected until I open/close/reopen a node.
It's a mess then. I don't know which part I'm missing.
here's a code sample:
virtual tree
To reproduce, please open a node (for example "Dep1"). You can hover childrens and everything is fine. Now close the node and reopen it. Now icons get changed in node and node receives the hover effect, which is not the case when we initially opened it.
Am I missing something?
Regards
code below:
var data = {
"label": "ROOT",
"children" : [
{
"LDEP" : "Dep1",
"children" : [
{
"CVEH" : 1,
"LVEH" : "veh1_1"
},
{
"CVEH" : 2,
"LVEH" : "veh1_2"
}
]
},
{
"LDEP" : "Dep2",
"children" : [
{
"CVEH" : 3,
"LVEH" : "veh2_1"
},
{
"CVEH" : 4,
"LVEH" : "veh2_2"
}
]
},
]
};
var model = qx.data.marshal.Json.createModel(data, false);
var vtree = new qx.ui.tree.VirtualTree(model, "children", "children");
this.getRoot().add(vtree,
{
left : 100,
right : 100,
top : 50
});
vtree.set({
showTopLevelOpenCloseIcons : true,
hideRoot : true,
backgroundColor : "gray"
});
/* label options */
vtree.setLabelOptions({
converter : function(value, model)
{
if (value){
return "<b>" + model.get("LDEP") + "</b>";
}
else
{
return model.get("LVEH");
}
}
});
/*
* icon options, if a dep then return a generic symbol else return
* vehicle icon
*/
vtree.setIconPath("children");
vtree.setIconOptions({
converter : function(value, model)
{
if (value){
return "icon/22/mimetypes/text-html.png";
}
else
{
return "icon/22/mimetypes/media-image.png"
}
}
});
var delegate = {
bindItem : function(controller, item, index)
{
controller.bindDefaultProperties(item, index);
//set icon size to 24x24 for leaves
var icon = item.getChildControl("icon");
if(item.getModel().getChildren){
//dept
item.setBackgroundColor("gray");
} else {
//vehicle
item.setBackgroundColor("white");
icon.set({
width : 32,
height : 32,
scale : true,
marginTop : -4
});
}
//labels, accept html
var lbl = item.getChildControl("label");
lbl.set({
rich : true,
textColor : "black"
});
//change color on pointerin and pointerout of vehicles
if (!item.getModel().getChildren){
item.addListener("pointerover", function(){
item.getChildControl("label").fadeIn(100);
item.setBackgroundColor("blue");
item.getChildControl("label").setTextColor("orange");
});
item.addListener("pointerout", function(){
item.setBackgroundColor("white");
item.getChildControl("label").setTextColor("black");
});
}
},
/*
* sorting
*/
sorter : function(a, b){
var A = (a.getChildren? a.get("LDEP") : a.get("LVEH")).toUpperCase(),
B = (b.getChildren? b.get("LDEP") : b.get("LVEH")).toUpperCase();
return A > B ? 1 : A < B ? -1 : 0;
}
};
vtree.setDelegate(delegate);
To understand the effects you are observing, it needs understanding on how virtual widgets work in qooxdoo.
Virtual widget means that you are able do display huge amounts of data, only having a few widgets rendering the visible content. Imagine a tree with hundreds of nodes and child nodes, but you only have 10 nodes visible at one time. The virtual widget then instantiates as many real widgets as needed and re-uses those widgets to display the visible part of the tree.
The virtual tree widget, which diplays nodes an leaves of a tree, reuses the instantiated widgets by changing the model of the widget and it's appearance. This way it may happen that on user interaction, a virtual tree node is rendered by an item which formely displayed a leafe.
All this is done via the delegates bindItem member function which is called every time a real widget is re-used for a virtual item. So adding an event listener in bindItem adds subsequent more and more event listeners to a single widget tree/leaf instance, showing the effects you described.
To achieve what you want, you have to add your logic into the configureItem delegate member, which is only called once on instantiation of a tree item widget. There you have to differentiate between the item currently displaying a node or a leaf which you can simply accomplish by getting the current appearance of that item via item.getAppearance(). The result will be either virtual-tree-file for a leaf and virtual-tree-folder for a node.
The event listeners which are added for pointerover and pointerout should then add the styles needed depending on the appearance.
Note all this should better be handeled by a custom appearance theme, where you could add most styles you used based on the widgets states like hover, besides the animation for the label child control with the fading in label.
Please paste the following gist into the qooxdoo playground where I've created an example which demonstrates all the speech above:
https://gist.github.com/level420/ba4e25f98618064f91f5aa6cb6bb1124
Related
I want to add a 3rd Inspector which will open only for an element(not a link) of specific type, for example only for basic.Rect in Rappid.
So far, there are 2 Inspectors.For elements and for links.
Is there any way it can be done?
The following code is a part of the KitchenSkink version of Rappid.
Here is function createInspector:
createInspector: function(cellView) {
var cell = cellView.model || cellView;
// No need to re-render inspector if the cellView didn't change.
if (!this.inspector || this.inspector.options.cell !== cell) {
// Is there an inspector that has not been removed yet.
// Note that an inspector can be also removed when the underlying cell is removed.
if (this.inspector && this.inspector.el.parentNode) {
this.inspectorClosedGroups[this.inspector.options.cell.id] = _.map(app.inspector.$('.group.closed'), function(g) {
return $(g).attr('data-name');
});
// Clean up the old inspector if there was one.
this.inspector.updateCell();
this.inspector.remove();
}
var inspectorDefs = InspectorDefs[cell.get('type')];
this.inspector = new joint.ui.Inspector({
inputs: inspectorDefs ? inspectorDefs.inputs : CommonInspectorInputs,
groups: inspectorDefs ? inspectorDefs.groups : CommonInspectorGroups,
cell: cell
});
this.initializeInspectorTooltips();
this.inspector.render();
$('.inspector-container').html(this.inspector.el);
if (this.inspectorClosedGroups[cell.id]) {
_.each(this.inspectorClosedGroups[cell.id], this.inspector.closeGroup, this.inspector);
} else {
this.inspector.$('.group:not(:first-child)').addClass('closed');
}
}
}
If you use joint.ui.Inspector.create('#path', inspectorProperties) any previous instance of the Inspector in a specific DOM element is removed and new one is created and rendered into the DOM automatically (it avoids creating a new instance of joint.ui.Inspector(), rendering it, adding the rendered result manually and removing the previous instance).
It also keeps track on open/closed groups and restore them based on the last used state.
Besides this, you may always have several different inspectorProperties objects previously defined when you are about to create() the inspector. So following the code you pasted, you could perform the tests you need first and then create the appropriate inspector:
if(cell instanceof joint.basic.Rect){
var customInputs = _.clone(CommonInspectorInputs);
// extend more inputs into `customInputs` from a variable previously defined
// OR modify the default rectangle's inspector directly, example:
customInputs.attrs.text = {
type: 'textarea',
label: 'Multiline text',
text: 'Type\nhere!',
group: joint.util.getByPath(CommonInspectorInputs.attrs, 'text/group', '/');
};
joint.ui.Inspector.create('.extra-inspector-container', {
cell: cell
inputs: customInputs,
groups: CommonInspectorGroups,
});
} // if only ONE inspector needs to be loaded add an ELSE block here
// and use '.inspector-container' in the `create()` above
// If `InspectorDefs` is a global variable with all the cells inspectors properties
// create and load the default inspector
joint.ui.Inspector.create('.inspector-container', _.extend({cell: cell},
InspectorDefs[cell.get('type')])
);
I’m pretty new to backbonejs and i’m trying to create a basic application.
The application is something like this:
I have 5 sections: A, B, C, D and E
Each section has 2 radio buttons.
Section A - Radio1, Radio2
Section B - Radio3, Radio4
Section C - Radio5, Radio6
Section D - Radio7, Radio8
Section E - Radio9, Radio10
Depending on what radio button is selected, I need to display a section (previous sections must also display)
I have had a look at maybe using a model to determine which radio was selected and also what section is displayed. Is this the correct approach?
var State = Backbone.Model.extend({
defaults: {
id: null,
name: "",
isOn: false
}
});
var Section = Backbone.View.extend({
model: State,
events: {
'change [type="checkbox"]': function (event) {
var $checkbox = $(event.target);
this.model.set("isOn", $checkbox.is(":checked"));
this.model.get("dispatcher").trigger("toggle", this.model.get("id"));
}
},
initialize: function (options) {
this.listenTo(this.model, "change:isOn", function (model, isOn) {
if ( isOn ) {
this.$el.find(".section").show();
this.$el.find("input").prop("checked", true);
}
else {
this.$el.find(".section").hide();
this.$el.find("input").prop("checked", false);
}
});
this.listenTo(dispatcher, "toggle", function (id) {
if ( this.model.get("id") < id ) {
this.model.set("isOn", true);
}
if ( this.model.get("id") > id ) {
this.model.set("isOn", false);
}
});
},
render: function () {
this.$el.html('<div>' + this.model.get("name") + '</div><input type="checkbox"><div class="section" style="min-height:100px; background-color:grey; display:none;"></div>');
this.$el.appendTo("body");
}
});
var dispatcher = _.extend({}, Backbone.Events);
_.each([
{id: 1, name: "A", isOn: false, dispatcher: dispatcher},
{id: 2, name: "B", isOn: false, dispatcher: dispatcher},
{id: 3, name: "C", isOn: false, dispatcher: dispatcher},
{id: 4, name: "D", isOn: false, dispatcher: dispatcher},
{id: 5, name: "E", isOn: false, dispatcher: dispatcher}
], function (item) {
var view = new Section({model: new State(item)});
view.render();
});
I didn't understand the meaning of two radio.. so I used checkbox per section.. Hope this will uncover some basics of backbone.
Yes this approach will work. I recommend if you need to communicate between views to use models via events - this will generally result in better architecture (your views will be more decoupled).
You can react to the change event in the view (using the events hash) and update an attribute on a model for each group, e.g. this.model.set('termsAccepted', true/false) then as long as the other view(s) have access to this model you can react to the change event of that attribute, e.g. this.listenTo(this.model, 'change:termsAccepted', this.onTermsAcceptedChange).
There may be a very simple solution to your objective. If you monitor the radio-button state via a javascript function, then you can use that function to change a class statement for the parent div. In CSS, you can then define actions to hide or display a div based on it's class. This would only take a few lines of javascript and a few lines of CSS.
For example, this example in codepen shows a way to accomplish the hide/show for a variably sized div. The number of divs in this approach is arbitrary - you can have as many or few as you like, and the only action required of the user is to click on the header to expand or collapse the associated div. In this example, clicking on the header acts as a toggle to expand or collapse the associated div. The example is set up so that only one div is expanded at a time and clicking on a different header automatically collapses any open div so that only one div is open at a time. If you do not want that behavior, just remove the for loop in the accOff() function.
It's kinda like a choose-your-own-adventure, ya sipher_z?
I advise using the Backbone Router with route parameters storing the current state, that is, which section is currently showing.
Each component of the view should be a Backbone.View.extend({...}). Components might be Section and Radio.
On each Radio button, in HTML, put a data-go-to attribute, with a value of the section to go to next. Then, in your RadioView code, put a click event. When clicked, extract this data-go-to, and do something like location.hash = '/section/' + section to trigger your router.
Then, all your router does is hide all the Sections except the selected one whenever triggered. If there's no selection, it just shows the first one!
I'm not 100% sure of this strategy, but this is definitely "the Backbone way". Let me know if I can clear anything up.
I am trying to implement qx.ui.embed.Html with Qooxdoo themed scroll-bars.
I wanted to implement it inside vitrual list .
All i need is a qx.ui.embed.Html widget with Custom (qx themed) scroll-bars because it is so ugly in native scrolls.
Here is my test :
http://tinyurl.com/kcouvj2
It works in playground.
But I tried to make it a widget:
(i am not an expert in doing so):
qx.Class.define("phwabe.view.ChatView.PostItem",
{
extend : qx.ui.container.Scroll,
properties :
{
/** Any text string which can contain HTML, too */
html :
{
check : "String",
apply : "_applyHtml",
event : "changeHtml",
nullable : true
}
},
members :
{
construct : function()
{
this.base(arguments)
},
_createChildControlImpl : function(id)
{
var control;
switch(id)
{
//case "icon":
// control = new qx.ui.basic.Image(this.getIcon());
// control.setAnonymous(true);
// this._add(control, {row: 0, column: 0, rowSpan: 2});
// break;
case "html":
control = new qx.ui.embed.Html()
control.setAllowGrowX(true);
control.setOverflowY('hidden');
control.setAllowShrinkY(false)
this.add(control)
}
},
_applyHtml : function(value, old)
{
var post = this.getChildControl("html");
// Workaround for http://bugzilla.qooxdoo.org/show_bug.cgi?id=7679
// Insert HTML content
post.setHtml(value||"");
}
}
})
But that is failing hard with :
Error: Exception while creating child control 'html' of widget
phwabe.view.ChatView.PostItem[446-0]: Unsupported control: pane
I am doing it wrong obviously. Any Proper way of implementing it in qooxdoo?
You need to return the created control - and if none matched (in your case "pane"), return the base class method:
// overridden
_createChildControlImpl : function(id)
{
var control;
...
return control || this.base(arguments, id);
}
Here is a changed playground example: http://tinyurl.com/loljtz6
You need to add a mechanism to adjust the content size of the qx.ui.embed.Html widget automatically if the height is unknown since there is no automatic resizing.
Hello I am using gridviewdragdrop and I wonder if it is possible to change the drag text according to some condition?
Example: if the node that is caught is empty name field show drag text message: You are not allowed to catch this node because of the name is empty
You mean treeviewdragdrop plugin?
Yes for sure you can. There are a lot of events on drag&drop
In your controller you can add listeners to them:
'tree > treeview': {
'nodedragover': me.onNodeDragOver,
}
or
view.on('beforedrop', onBeforeDropNode);
view.on('drop', onDropNode);
Also general event on drag and drop is:
onDragEnter : function(evtObj, targetElId) { //Called when a drag element first intersects another drag/drop element within the
same drag/drop group. This is where you can code for drop invitation.
var targetEl = Ext.get(targetElId);
targetEl.addCls('dropZoneOver');
},
onDragOut : function(evtObj, targetElId) {
var targetEl = Ext.get(targetElId);
targetEl.toggleCls('dropZoneOver');
},
b4StartDrag : Ext.emptyFn,
onInvalidDrop : Ext.emptyFn,
onDragDrop : Ext.emptyFn,
endDrag : Ext.emptyFn
Here's my goal :
- open a tree
- download the root nodes
- expand automatically one specific node using AJAX (and loop n times here) until i find a leaf then select the leaf
Here's the function that works when I declare the Tree :
listeners: {
load: function(n) {
console.log('load(n)');
n.eachChild(
function(n) {
if ((n.id=='lys/2007') ||
(n.id=='lys/2007/08') ||
(n.id=='lys/2007/08/29')) {
n.expand(false,false);
}
});
}
}
But if I don't know how to make it more "generic" (almost exactly like the ExtJs documentation). But they don't jump automatically to a specific node (i.e. I want no user interaction).
Any idea / advice how to do this?
Don't hesitate to edit my post to make it proper English :)
If you already have a handle to your node, use node.getPath() to get the full "path" of it, and then use selectPath to "select" it programatically.
tree.selectPath(node.getPath());
Since you seem to know the exact path, you can probably just call selectPath on the tree.
tree.selectPath('lys/2007/08/29'); //adjust to match node.getPath() format
Thanks for the answer.
Here's the code that works : I moved it outside, into the TreeLoader object, this way :
var lysTreeLoader = new Ext.tree.TreeLoader({
dataUrl: 'json/lys.php',
listeners: {
load: function(loader,n,response) {
console.log('Données chargées');
n.eachChild(
function(n) {
if ((n.id=='lys/2007') ||
(n.id=='lys/2007/08') ||
(n.id=='lys/2007/08/29')) {
n.expand(false,false);
}
if (n.id=='lys/2007/08/29/21_14_04') {
n.select();
console.log(n.id);
console.log(n.getPath());
}
});
}
}
});
Then in the tree declaration, declare the lysTreeLoader :
...blabla...
id: 'treepanel-labys',
xtype: 'treepanel',
width: 400,
autoScroll: true,
split: true,
// use a TreeLoader :
loader: lysTreeLoader,
...blabla...
And I just had to use the function select(); (which didn't work as expected in my question)
Thanks again !