PDA

View Full Version : [CLOSED]Suggestion: Patch to Ext.ux.Portal



mschwartz
18 Feb 2011, 10:31 AM
In notifyOver:




if (px.getProxy) {
// make sure proxy width is fluid
px.getProxy().setWidth('auto');

if (p) {
px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
}
else {
px.moveProxy(c.el.dom, null);
}
}


Note the if statement.

In notifyDrop:




if (dd.proxy.getProxy) {
dd.proxy.getProxy().remove();
dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);

if (pos !== false) {
if (c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)) {
pos++;
}
c.insert(pos, dd.panel);
}
else {
c.add(dd.panel);
}

c.doLayout();
}


Notice the if statement.

The benefit of this change allows a tree node to be dropped onto the portal.

Tree is configured with:


enableDrag: true,
ddGroup: 'somegroup'


Portal is configured with:


dropConfig: {
ddGroup: 'somegroup'
},
listeners: {
drop: function(e) {
alert('dropped ' + e.data.node.attributes.text + ' column ' + e.columnIndex);
}
}


Portal (maybe) must also have listeners for validatedrop and beforedragover events - in my current testing, they return true and I'm able to drop a tree node onto the portal. I'll post again with an update after I get the whole thing working.

mschwartz
18 Feb 2011, 11:41 AM
Additionally, the notifyDrop function needs to return true, otherwise Ext calls some invalidDrop() method internally.

The portlets have to configure:


draggable: {
ddGroup: 'somegroup'
}


or else you can't drag the portlets around.

In your portal's drop listener, you test for tree node vs. panel and return if it's a panel.



new Portal({
...
listeners: {
drop: function(e) {
if (e.data.panel) return true;
// call portal's add() method to add your portlet
// or insert() portlet at e.position
}
...

mschwartz
18 Feb 2011, 11:42 AM
In case I'm not being clear...

The code changes I presented allows me to create a tree whose nodes can be dragged onto the portal to add portlets to it. I suspect the changes would also allow dragging a grid row or other draggable type things to it as well.

As the portal code is implemented, we developers have to Ext.override a ux or hack on a copy of them.

evant
20 Feb 2011, 8:42 PM
I'm not really clear on what you're getting at here. Perhaps you can post the whole code with your changes highlighted in red?

mschwartz
21 Feb 2011, 10:14 AM
The code is part of a very large application.

Picture a borderlayout with a tree in the west and portal in the center.

The tree has nodes that represent portlets. Drag a node from the tree to a place in the portal and the portlet appears.

The drag/drop logic I presented is changes to Ext.ux.Portal.js to support drag and drop from another component to the portal. It transparently works with the portal example/demo, so it's not breaking anything.

As Ext.ux.Portal is implemented, you drag a tree node over it and an exception is thrown because the treeNode's proxy is a different (enough) structure from the portal's proxy for dragging portlets. My first post gets rid of this exception (px.getProxy is undefined for treeNode's proxy!).

If the portal is configured with a dropConfig, it will no longer allow you to drag/drop portlets. The portlets have to have the same ddGroup configuration as the portal!

To test this all out, I created a tree with 5 nodes and one portlet. The portlet simply has a textarea as the body. When I drag any of the 5 nodes and drop on the portal, a portlet is inserted at e.columnIndex and e.position.

I drag out two portlets in the left column (3 column portal configured). I type "1" in the upper's textrea, and "2" in the lower one's. I drag a third portlet and drop between these two and I see portlet with empty textarea between the "1" and the "2" ones. Works in column 2 and column 3, too.

The use case is you want a portal of RSS feed portlets and other kinds of portlets. It starts out empty. User drags a tree node for "RSS Feed" to the portal and the portlet appears where he drops it. He clicks on portlet's settings tool and enters the RSS feed address in the Ext.MessageBox.prompt() dialog that pops up. He drags out the tree node a second time and gets a second RSS feed portlet - he configures it with a different URL. And so on.

Now that you guys are coming out with ExtJS 4, maybe it is a good time to make this kind of change?

mschwartz
21 Feb 2011, 10:19 AM
To be more clear, since you ask for changes in red...

In Ext.ux.Portal.js, I had to add if (px.getProxy) {} around the "make sure proxy width is fluid" logic in the notifyOver function.

In the notifyDrop function, I had to add if (dd.proxy.getProxy) {} around the inner logic that gets rid of the dragged portlet's proxy and moves the dragged portlet to its new position.

The rest of my code examples are config options to Portal and Portlet - no other code changes need to be made.

My request is you make the changes so I can continue to include examples/ux/portal/Ext.ux.Portal.js in my application and not have to copy the file and hack on it. If you do it, I can upgrade to ExtJS 3.4.1 (or whatever) and get any other neat features you add to Portal without having to merge with my hacked copy.

mschwartz
22 Feb 2011, 6:21 AM
Modified Portal.js. It's from an OLD version of ExtJS, but since I've been hacking on it some, I copied it from the ExtJS distribution to a folder in my project so it wouldn't get overwritten when updating. Currently using ExtJS 3.2.1.

Changes marked in RED.




/*
* Ext JS Library 3.0 Pre-alpha Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/

Ext.ux.Portal = Ext.extend(Ext.Panel, {
layout: 'column',
autoScroll: true,
cls: 'x-portal',
defaultType: 'portalcolumn',

initComponent: function() {
Ext.ux.Portal.superclass.initComponent.call(this);
this.addEvents({
validatedrop: true,
beforedragover: true,
dragover: true,
beforedrop: true,
drop: true
});
},

initEvents: function() {
Ext.ux.Portal.superclass.initEvents.call(this);
this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);
},

beforeDestroy: function() {
if (this.dd) {
this.dd.unreg();
}
Ext.ux.Portal.superclass.beforeDestroy.call(this);
}
});
Ext.reg('portal', Ext.ux.Portal);

Ext.ux.Portal.DropZone = function(portal, cfg) {
this.portal = portal;
Ext.dd.ScrollManager.register(portal.body);
Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
portal.body.ddScrollConfig = this.ddScrollConfig;
};

Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {
ddScrollConfig: {
vthresh: 50,
hthresh: -1,
animate: true,
increment: 200
},

createEvent: function(dd, e, data, col, c, pos) {
return {
portal: this.portal,
panel: data.panel,
columnIndex: col,
column: c,
position: pos,
data: data,
source: dd,
rawEvent: e,
status: this.dropAllowed
};
},

notifyOver: function(dd, e, data) {
console.log('over');
var xy = e.getXY(),
portal = this.portal,
px = dd.proxy;

// case column widths
if (!this.grid) {
this.grid = this.getGrid();
}

// handle case scroll where scrollbars appear during drag
var cw = portal.body.dom.clientWidth;
if (!this.lastCW) {
this.lastCW = cw;
}
else if (this.lastCW != cw) {
this.lastCW = cw;
portal.doLayout();
this.grid = this.getGrid();
}

// determine column
var col = 0,
xs = this.grid.columnX,
cmatch = false;
for (var len = xs.length; col < len; col++) {
if (xy[0] < (xs[col].x + xs[col].w)) {
cmatch = true;
break;
}
}
// no match, fix last index
if (!cmatch) {
col--;
}

// find insert position
var p,
match = false,
pos = 0,
c = portal.items.itemAt(col),
items = c.items.items,
overSelf = false;

for (var len = items.length; pos < len; pos++) {
p = items[pos];
var h = p.el.getHeight();
if (h === 0) {
overSelf = true;
}
else if ((p.el.getY() + (h / 2)) > xy[1]) {
match = true;
break;
}
}

pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);
var overEvent = this.createEvent(dd, e, data, col, c, pos);

if (portal.fireEvent('validatedrop', overEvent) !== false && portal.fireEvent('beforedragover', overEvent) !== false) {

if (px.getProxy) {
// make sure proxy width is fluid
px.getProxy().setWidth('auto');

if (p) {
px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
}
else {
px.moveProxy(c.el.dom, null);
}
}
this.lastPos = {
c: c,
col: col,
p: overSelf || (match && p) ? pos : false
};
this.scrollPos = portal.body.getScroll();

portal.fireEvent('dragover', overEvent);

return overEvent.status;
}
else {
return overEvent.status;
}

},

notifyOut: function() {
delete this.grid;
},

notifyDrop: function(dd, e, data) {
delete this.grid;
if (!this.lastPos) {
return false;
}
var c = this.lastPos.c,
col = this.lastPos.col,
pos = this.lastPos.p;

var dropEvent = this.createEvent(dd, e, data, col, c, pos !== false ? pos : c.items.getCount());

if (this.portal.fireEvent('validatedrop', dropEvent) !== false && this.portal.fireEvent('beforedrop', dropEvent) !== false) {
if (dd.proxy.getProxy) {
dd.proxy.getProxy().remove();
dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);

if (pos !== false) {
if (c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)) {
pos++;
}
c.insert(pos, dd.panel);
}
else {
c.add(dd.panel);
}

c.doLayout();
}
this.portal.fireEvent('drop', dropEvent);

// scroll position is lost on drop, fix it
var st = this.scrollPos.top;
if (st) {
var d = this.portal.body.dom;
setTimeout(function() {
d.scrollTop = st;
}, 10);
}

}
delete this.lastPos;
return true;
},

// internal cache of body and column coords
getGrid: function() {
var box = this.portal.bwrap.getBox();
box.columnX = [];
this.portal.items.each(function(c) {
box.columnX.push({
x: c.el.getX(),
w: c.el.getWidth()
});
});
return box;
},

// unregister the dropzone from ScrollManager
unreg: function() {
// Ext.dd.ScrollManager.unregister(this.portal.body);
Ext.ux.Portal.DropZone.superclass.unreg.call(this);
}
});

mschwartz
22 Feb 2011, 6:29 AM
Example usage.

Note code in RED.

The portal



var tab = new Ext.ux.Portal({
hideMode: 'offsets',
dropConfig: {
ddGroup: 'dashboard'
},
title: 'Dashboard',
iconCls: 'home_icon',
id: 'home-tab',
closable: false,
margins: '35 5 5 0',
items: [
{
id: 'pwp-dashboard-column1',
columnWidth: .40,
style: 'padding: 10px 0 10px 10px'
},
{
id: 'pwp-dashboard-column2',
columnWidth: .30,
style: 'padding: 10px 0 10px 10px'
},
{
id: 'pwp-dashboard-column3',
columnWidth: .30,
style: 'padding: 10px 0 10px 10px'
}
],
listeners: {
render: function() {
pwp.dashboard.Configure(function() {
pwp.cms.showDashboardControls();
(function() {
tab.fireEvent('resize', tab.getWidth(), tab.getHeight());
pwp.HideLoadingMask();
}).defer(100);

});
},
//validatedrop: function(e) {
// return true;
//},
//beforedragover: function(e) {
// return true;
//},
drop: function(e) {
if (e.data.panel) {
// hackish - user has dragged and dropped an already rendered portlet
return true;
}
var cmp;
switch (e.columnIndex) {
default:
case 0:
cmp = Ext.getCmp('pwp-dashboard-column1');
break;
case 1:
cmp = Ext.getCmp('pwp-dashboard-column2');
break;
case 2:
cmp = Ext.getCmp('pwp-dashboard-column3');
break;
}
cmp.insert(e.position, new pwp.dashboard.NotesPortlet());
Ext.getCmp('pwp-dashboard-controls-panel').Refresh();
Ext.getCmp('home-tab').doLayout();
return true;
//alert('dropped ' + e.data.node.attributes.text + ' column ' + e.columnIndex);
},
activate: function() {
}
}
});


The tree (this is controls panel mentioned in the code):



var tree = new Ext.tree.TreePanel({
title: 'dashboard controls',
id: 'pwp-dashboard-controls-panel',
border: false,
bodyBorder: false,
animate: true,
containerScroll: true,
autoScroll: true,
rootVisible: false,
expanded: true,
enableDrag: true,
ddGroup: 'dashboard',
root: new Ext.tree.TreeNode({
id: 'dashboard-controls-root',
expanded: true,
text: 'Dashboard Root'
})
});
var busy = false;
tree.Refresh = function() {
if (busy) {
return;
}
busy = true;
var root = tree.root;
while (root.firstChild) {
var c = root.firstChild;
root.removeChild(c);
c.destroy();
}
for (var i=1; i<6; i++) {
var node = new Ext.tree.TreeNode({
leaf: true,
text: 'Portlet ' + i
});
root.appendChild(node);
}
busy = false;
};


Example portlet:



var id = Ext.id();
var resized = false;
var portlet = new Ext.ux.Portlet({
title: 'Notes',
draggable: {
ddGroup: 'dashboard'
},
height: pwp.dashboard.config.notesHeight || 150,
resizable: true,
layout: 'fit',
items: [
{
xtype: 'textarea',
id: 'pwp-dashboard-notes-textarea-'+id,
value: pwp.dashboard.config.notesValue || ''
}
],
tools: [
{
id: 'save',
qtip: 'Save Notes',
handler: function() {
pwp.dashboard.config.notesValue = Ext.getCmp('pwp-dashboard-notes-textarea-'+id).getValue();
pwp.dashboard.SaveDashboard('Saved Notes');
}
},
{
id: 'close',
handler: function() {
pwp.dashboard.RemovePortlet(portlet.itemid);
portlet.ownerCt.remove(portlet, true);
Ext.getCmp('pwp-dashboard-controls-panel').Refresh();
pwp.dashboard.SaveDashboard();
}
}
],
listeners: {
resize: function() {
var resize = (pwp.dashboard.config.notesHeight != this.getHeight());
pwp.dashboard.config.notesHeight = this.getHeight();
if (resize) {
pwp.dashboard.SaveDashboard();
}
}
}
});

tobiu
22 Feb 2011, 2:05 PM
Without having it tested, it seems to make sense.

So +1 :)


best regards
tobiu