+ In the quest to abolish reloadBounds(), I've rewritten draggable, droppable and resizable to operate on an item's bounds rather than directly on the div. I've also removed those routines from iQ, and integrated them into Item. Additional related cleanup and documentation.

+ Removed a little bit of aza debug code
+ Added size and position routines to Rect
This commit is contained in:
Ian Gilman 2010-06-22 16:42:06 -07:00
parent f894f8646f
commit 688afd22c9
6 changed files with 348 additions and 340 deletions

View File

@ -52,30 +52,41 @@ var drag = {
//
// ----------
// Constructor: Drag
// Called to create a Drag in response to a jQuery-UI draggable "start" event.
var Drag = function(element, event) {
this.el = element;
this.$el = iQ(this.el);
this.item = Items.item(this.el);
this.parent = this.item.parent;
this.startPosition = new Point(event.clientX, event.clientY);
this.startTime = Utils.getMilliseconds();
this.item.isDragging = true;
this.item.setZ(999999);
this.safeWindowBounds = Items.getSafeWindowBounds();
Trenches.activateOthersTrenches(this.el);
// When a tab drag starts, make it the focused tab.
if(this.item.isAGroup) {
var tab = Page.getActiveTab();
if(!tab || tab.parent != this.item) {
if(this.item._children.length)
Page.setActiveTab(this.item._children[0]);
// Called to create a Drag in response to an <Item> draggable "start" event.
// Note that it is also used partially during <Item>'s resizable method as well.
//
// Parameters:
// item - The <Item> being dragged
// event - The DOM event that kicks off the drag
var Drag = function(item, event) {
try {
Utils.assert('item', item && item.isAnItem);
this.item = item;
this.el = item.container;
this.$el = iQ(this.el);
this.parent = this.item.parent;
this.startPosition = new Point(event.clientX, event.clientY);
this.startTime = Utils.getMilliseconds();
this.item.isDragging = true;
this.item.setZ(999999);
this.safeWindowBounds = Items.getSafeWindowBounds();
Trenches.activateOthersTrenches(this.el);
// When a tab drag starts, make it the focused tab.
if(this.item.isAGroup) {
var tab = Page.getActiveTab();
if(!tab || tab.parent != this.item) {
if(this.item._children.length)
Page.setActiveTab(this.item._children[0]);
}
} else {
Page.setActiveTab(this.item);
}
} else {
Page.setActiveTab(this.item);
} catch(e) {
Utils.log(e);
}
};
@ -106,8 +117,6 @@ Drag.prototype = {
if (update)
this.item.setBounds(bounds,true);
return ui;
},
// --------
@ -172,16 +181,9 @@ Drag.prototype = {
// ----------
// Function: drag
// Called in response to a jQuery-UI draggable "drag" event.
// Called in response to an <Item> draggable "drag" event.
drag: function(event, ui) {
// if(this.item.isAGroup) {
var bb = this.item.getBounds();
bb.left = ui.position.left;
bb.top = ui.position.top;
this.item.setBounds(bb, true);
ui = this.snap(event,ui,true);
// } else
// this.item.reloadBounds();
this.snap(event,ui,true);
if(this.parent && this.parent.expanded) {
var now = Utils.getMilliseconds();
@ -195,19 +197,10 @@ Drag.prototype = {
// ----------
// Function: stop
// Called in response to a jQuery-UI draggable "stop" event.
// Called in response to an <Item> draggable "stop" event.
stop: function() {
this.item.isDragging = false;
// I'm commenting this out for a while as I believe it feels uncomfortable
// that groups go away when there is still a tab in them. I do this at
// the cost of symmetry. -- Aza
/*
if(this.parent && !this.parent.locked.close && this.parent != this.item.parent
&& this.parent._children.length == 1 && !this.parent.getTitle()) {
this.parent.remove(this.parent._children[0]);
}*/
if(this.parent && !this.parent.locked.close && this.parent != this.item.parent
&& this.parent._children.length == 0 && !this.parent.getTitle()) {
this.parent.close();
@ -220,11 +213,9 @@ Drag.prototype = {
this.item.setZ(drag.zIndex);
drag.zIndex++;
this.item.reloadBounds();
this.item.pushAway();
}
Trenches.disactivate();
}
};

View File

@ -203,10 +203,7 @@ window.Group = function(listOfEls, options) {
"padding-left": "1px"
}, {
duration: 200,
easing: 'tabcandyBounce',
complete: function(){
window.aza = self.$title;
}
easing: 'tabcandyBounce'
});
}
};
@ -694,7 +691,7 @@ window.Group.prototype = iQ.extend(new Item(), new Subscribable(), {
if( this.isNewTabsGroup() ) $el.addClass("inNewTabGroup")
if(!wasAlreadyInThisGroup) {
iQ($el.get(0)).droppable("disable");
item.droppable(false);
item.groupData = {};
item.addOnClose(this, function() {
@ -758,7 +755,7 @@ window.Group.prototype = iQ.extend(new Item(), new Subscribable(), {
item.setRotation(0);
item.setSize(item.defaultSize.x, item.defaultSize.y);
iQ($el.get(0)).droppable("enable");
item.droppable(true);
item.removeOnClose(this);
if(typeof(item.setResizable) == 'function')
@ -1172,16 +1169,16 @@ window.Group.prototype = iQ.extend(new Item(), new Subscribable(), {
var self = this;
this.dropOptions.over = function(){
if( !self.isNewTabsGroup() )
iQ(this).addClass("acceptsDrop");
if( !this.isNewTabsGroup() )
iQ(this.container).addClass("acceptsDrop");
};
this.dropOptions.drop = function(event){
iQ(this).removeClass("acceptsDrop");
self.add( drag.info.$el, {left:event.pageX, top:event.pageY} );
iQ(this.container).removeClass("acceptsDrop");
this.add( drag.info.$el, {left:event.pageX, top:event.pageY} );
};
if(!this.locked.bounds)
iQ(container).draggable(this.dragOptions);
this.draggable();
iQ(container)
.mousedown(function(e){
@ -1224,7 +1221,7 @@ window.Group.prototype = iQ.extend(new Item(), new Subscribable(), {
self._mouseDown = null;
});
iQ(container).droppable(this.dropOptions);
this.droppable(true);
this.$expander.click(function(){
self.expand();
@ -1241,10 +1238,10 @@ window.Group.prototype = iQ.extend(new Item(), new Subscribable(), {
if(value) {
this.$resizer.fadeIn();
iQ(this.container).resizable(this.resizeOptions);
this.resizable(true);
} else {
this.$resizer.fadeOut();
iQ(this.container).resizable('destroy');
this.resizable(false);
}
},

View File

@ -98,15 +98,35 @@ window.Item = function() {
this.userSize = null;
// Variable: dragOptions
// Used to pass into iQ.fn.draggable
// Used by <draggable>
//
// Possible properties:
// cancelClass - A space-delimited list of classes that should cancel a drag
// start - A function to be called when a drag starts
// drag - A function to be called each time the mouse moves during drag
// stop - A function to be called when the drag is done
this.dragOptions = null;
// Variable: dropOptions
// Used to pass into iQ.fn.droppable
// Used by <draggable> if the item is set to droppable.
//
// Possible properties:
// accept - A function to determine if a particular item should be accepted for dropping
// over - A function to be called when an item is over this item
// out - A function to be called when an item leaves this item
// drop - A function to be called when an item is dropped in this item
this.dropOptions = null;
// Variable: resizeOptions
// Used to pass into iQ.fn.resizable
// Used by <resizable>
//
// Possible properties:
// minWidth - Minimum width allowable during resize
// minHeight - Minimum height allowable during resize
// aspectRatio - true if we should respect aspect ratio; default false
// start - A function to be called when resizing starts
// resize - A function to be called each time the mouse moves during resize
// stop - A function to be called when the resize is done
this.resizeOptions = null;
// Variable: isDragging
@ -174,23 +194,16 @@ window.Item.prototype = {
group.remove(drag.info.$el, {dontClose: true});
}
iQ(this).removeClass("acceptsDrop");
iQ(this.container).removeClass("acceptsDrop");
},
drop: function(event){
iQ(this).removeClass("acceptsDrop");
iQ(this.container).removeClass("acceptsDrop");
},
// Function: dropAcceptFunction
// Given a DOM element, returns true if it should accept tabs being dropped on it.
// Private to this file.
accept: function dropAcceptFunction(el) {
var $el = iQ(el);
if($el.hasClass('tab')) {
var item = Items.item($el);
if(item && (!item.parent || !item.parent.expanded)) {
return true;
}
}
return false;
accept: function dropAcceptFunction(item) {
return (item && item.isATabItem && (!item.parent || !item.parent.expanded));
}
};
@ -205,11 +218,9 @@ window.Item.prototype = {
resizeInfo = new Drag(this, e);
},
resize: function(e,ui){
self.reloadBounds();
resizeInfo.snap(e,ui, false, self.keepProportional);
},
stop: function(){
self.reloadBounds();
self.setUserSize();
self.pushAway();
resizeInfo.stop();
@ -528,6 +539,254 @@ window.Item.prototype = {
Trenches.unregister(this.guideTrenches[edge]); // unregister can take an array
}
this.guideTrenches = null;
},
// ----------
// Function: draggable
// Enables dragging on this item. Note: not to be called multiple times on the same item!
draggable: function() {
try {
Utils.assert('dragOptions', this.dragOptions);
var cancelClasses = [];
if(typeof(this.dragOptions.cancelClass) == 'string')
cancelClasses = this.dragOptions.cancelClass.split(' ');
var self = this;
var $container = iQ(this.container);
var startMouse;
var startPos;
var startSent;
var startEvent;
var droppables;
var dropTarget;
// ___ mousemove
var handleMouseMove = function(e) {
// positioning
var mouse = new Point(e.pageX, e.pageY);
var box = self.getBounds();
box.left = startPos.x + (mouse.x - startMouse.x);
box.top = startPos.y + (mouse.y - startMouse.y);
self.setBounds(box, true);
// drag events
if(!startSent) {
if(iQ.isFunction(self.dragOptions.start)) {
self.dragOptions.start.apply(self,
[startEvent, {position: {left: startPos.x, top: startPos.y}}]);
}
startSent = true;
}
if(iQ.isFunction(self.dragOptions.drag))
self.dragOptions.drag.apply(self, [e, {position: box.position()}]);
// drop events
var newDropTarget = null;
iQ.each(droppables, function(index, droppable) {
if(box.intersects(droppable.bounds)) {
var possibleDropTarget = droppable.item;
var accept = true;
if(possibleDropTarget != dropTarget) {
var dropOptions = possibleDropTarget.dropOptions;
if(dropOptions && iQ.isFunction(dropOptions.accept))
accept = dropOptions.accept.apply(possibleDropTarget, [self]);
}
if(accept) {
newDropTarget = possibleDropTarget;
return false;
}
}
});
if(newDropTarget != dropTarget) {
var dropOptions;
if(dropTarget) {
dropOptions = dropTarget.dropOptions;
if(dropOptions && iQ.isFunction(dropOptions.out))
dropOptions.out.apply(dropTarget, [e]);
}
dropTarget = newDropTarget;
if(dropTarget) {
dropOptions = dropTarget.dropOptions;
if(dropOptions && iQ.isFunction(dropOptions.over))
dropOptions.over.apply(dropTarget, [e]);
}
}
e.preventDefault();
};
// ___ mouseup
var handleMouseUp = function(e) {
iQ(window)
.unbind('mousemove', handleMouseMove)
.unbind('mouseup', handleMouseUp);
if(dropTarget) {
var dropOptions = dropTarget.dropOptions;
if(dropOptions && iQ.isFunction(dropOptions.drop))
dropOptions.drop.apply(dropTarget, [e]);
}
if(startSent && iQ.isFunction(self.dragOptions.stop))
self.dragOptions.stop.apply(self, [e]);
e.preventDefault();
};
// ___ mousedown
$container.mousedown(function(e) {
if(Utils.isRightClick(e))
return;
var cancel = false;
var $target = iQ(e.target);
iQ.each(cancelClasses, function(index, class) {
if($target.hasClass(class)) {
cancel = true;
return false;
}
});
if(cancel) {
e.preventDefault();
return;
}
startMouse = new Point(e.pageX, e.pageY);
startPos = self.getBounds().position();
startEvent = e;
startSent = false;
dropTarget = null;
droppables = [];
iQ('.iq-droppable').each(function() {
if(this != self.container) {
var item = Items.item(this);
droppables.push({
item: item,
bounds: item.getBounds()
});
}
});
iQ(window)
.mousemove(handleMouseMove)
.mouseup(handleMouseUp);
e.preventDefault();
});
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: droppable
// Enables or disables dropping on this item.
droppable: function(value) {
try {
var $container = iQ(this.container);
if(value)
$container.addClass('iq-droppable');
else {
Utils.assert('dropOptions', this.dropOptions);
$container.removeClass('iq-droppable');
}
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: resizable
// Enables or disables resizing of this item.
resizable: function(value) {
try {
var $container = iQ(this.container);
iQ('.iq-resizable-handle', $container).remove();
if(!value) {
$container.removeClass('iq-resizable');
} else {
Utils.assert('resizeOptions', this.resizeOptions);
$container.addClass('iq-resizable');
var self = this;
var startMouse;
var startSize;
// ___ mousemove
var handleMouseMove = function(e) {
var mouse = new Point(e.pageX, e.pageY);
var box = self.getBounds();
box.width = Math.max(self.resizeOptions.minWidth || 0, startSize.x + (mouse.x - startMouse.x));
box.height = Math.max(self.resizeOptions.minHeight || 0, startSize.y + (mouse.y - startMouse.y));
if(self.resizeOptions.aspectRatio) {
if(startAspect < 1)
box.height = box.width * startAspect;
else
box.width = box.height / startAspect;
}
self.setBounds(box, true);
if(iQ.isFunction(self.resizeOptions.resize))
self.resizeOptions.resize.apply(self, [e]);
e.preventDefault();
e.stopPropagation();
};
// ___ mouseup
var handleMouseUp = function(e) {
iQ(window)
.unbind('mousemove', handleMouseMove)
.unbind('mouseup', handleMouseUp);
if(iQ.isFunction(self.resizeOptions.stop))
self.resizeOptions.stop.apply(self, [e]);
e.preventDefault();
e.stopPropagation();
};
// ___ handle + mousedown
iQ('<div>')
.addClass('iq-resizable-handle iq-resizable-se')
.appendTo($container)
.mousedown(function(e) {
if(Utils.isRightClick(e))
return;
startMouse = new Point(e.pageX, e.pageY);
startSize = self.getBounds().size();
startAspect = startSize.y / startSize.x;
if(iQ.isFunction(self.resizeOptions.start))
self.resizeOptions.start.apply(self, [e]);
iQ(window)
.mousemove(handleMouseMove)
.mouseup(handleMouseUp);
e.preventDefault();
e.stopPropagation();
});
}
} catch(e) {
Utils.log(e);
}
}
};

View File

@ -69,23 +69,24 @@ window.TabItem = function(container, tab) {
// override dropOptions with custom tabitem methods
// This is mostly to support the phantom groups.
this.dropOptions.drop = function(e){
$target = iQ(this);
iQ(this).removeClass("acceptsDrop");
$target = iQ(this.container);
$target.removeClass("acceptsDrop");
var phantom = $target.data("phantomGroup")
var group = drag.info.item.parent;
if( group == null ){
phantom.removeClass("phantom");
phantom.removeClass("group-content");
var group = new Group([$target, drag.info.$el], {container:phantom});
group = new Group([$target, drag.info.$el], {container:phantom});
} else
group.add( drag.info.$el );
};
this.dropOptions.over = function(e){
var $target = iQ(this);
var $target = iQ(this.container);
function elToRect($el){
return new Rect( $el.position().left, $el.position().top, $el.width(), $el.height() );
return new Rect( $el.position().left, $el.position().top, $el.width(), $el.height() );
}
var height = elToRect($target).height * 1.5 + 20;
@ -112,16 +113,18 @@ window.TabItem = function(container, tab) {
$target.data("phantomGroup", phantom);
};
this.dropOptions.out = function(e){
var phantom = iQ(this).data("phantomGroup");
this.dropOptions.out = function(e){
var phantom = iQ(this.container).data("phantomGroup");
if(phantom) {
phantom.fadeOut(function(){
iQ(this).remove();
});
}
}
$div.draggable(this.dragOptions);
$div.droppable(this.dropOptions);
};
this.draggable();
this.droppable(true);
// ___ more div setup
$div.mousedown(function(e) {
@ -373,10 +376,10 @@ window.TabItem.prototype = iQ.extend(new Item(), {
if(value) {
$resizer.fadeIn();
iQ(this.container).resizable(this.resizeOptions);
this.resizable(true);
} else {
$resizer.fadeOut();
iQ(this.container).resizable('destroy');
this.resizable(false);
}
},

View File

@ -792,258 +792,6 @@ iQ.fn = iQ.prototype = {
}
return this;
},
// ----------
// Function: draggable
draggable: function(options) {
try {
if(!options)
options = {};
var cancelClasses = [];
if(typeof(options.cancelClass) == 'string')
cancelClasses = options.cancelClass.split(' ');
var startMouse;
var startPos;
var elem;
var $elem;
var startSent;
var startEvent;
var droppables;
var dropTarget;
// ___ mousemove
var handleMouseMove = function(e) {
// positioning
var mouse = new Point(e.pageX, e.pageY);
var newPos = {
left: startPos.x + (mouse.x - startMouse.x),
top: startPos.y + (mouse.y - startMouse.y)
};
$elem.css(newPos);
// drag events
if(!startSent) {
if(iQ.isFunction(options.start))
options.start.apply(elem, [startEvent, {position: {left: startPos.x, top: startPos.y}}]);
startSent = true;
}
if(iQ.isFunction(options.drag))
options.drag.apply(elem, [e, {position: newPos}]);
// drop events
var bounds = $elem.bounds();
var newDropTarget = null;
iQ.each(droppables, function(index, droppable) {
if(bounds.intersects(droppable.bounds)) {
var possibleDropTarget = droppable.element;
var accept = true;
if(possibleDropTarget != dropTarget) {
var dropOptions = iQ(possibleDropTarget).data('iq-droppable');
if(dropOptions && iQ.isFunction(dropOptions.accept))
accept = dropOptions.accept.apply(possibleDropTarget, [elem]);
}
if(accept) {
newDropTarget = possibleDropTarget;
return false;
}
}
});
if(newDropTarget != dropTarget) {
var dropOptions;
if(dropTarget) {
dropOptions = iQ(dropTarget).data('iq-droppable');
if(dropOptions && iQ.isFunction(dropOptions.out))
dropOptions.out.apply(dropTarget, [e]);
}
dropTarget = newDropTarget;
if(dropTarget) {
dropOptions = iQ(dropTarget).data('iq-droppable');
if(dropOptions && iQ.isFunction(dropOptions.over))
dropOptions.over.apply(dropTarget, [e]);
}
}
e.preventDefault();
};
// ___ mouseup
var handleMouseUp = function(e) {
iQ(window)
.unbind('mousemove', handleMouseMove)
.unbind('mouseup', handleMouseUp);
if(dropTarget) {
var dropOptions = iQ(dropTarget).data('iq-droppable');
if(dropOptions && iQ.isFunction(dropOptions.drop))
dropOptions.drop.apply(dropTarget, [e]);
}
if(startSent && iQ.isFunction(options.stop))
options.stop.apply(elem, [e]);
e.preventDefault();
};
// ___ mousedown
this.mousedown(function(e) {
if(Utils.isRightClick(e))
return;
var cancel = false;
var $target = iQ(e.target);
iQ.each(cancelClasses, function(index, class) {
if($target.hasClass(class)) {
cancel = true;
return false;
}
});
if(cancel) {
e.preventDefault();
return;
}
elem = this;
$elem = iQ(this);
var pos = $elem.position();
startMouse = new Point(e.pageX, e.pageY);
startPos = new Point(pos.left, pos.top);
startEvent = e;
startSent = false;
dropTarget = null;
droppables = [];
iQ('.iq-droppable').each(function() {
if(this != elem) {
droppables.push({
element: this,
bounds: iQ(this).bounds()
});
}
});
iQ(window)
.mousemove(handleMouseMove)
.mouseup(handleMouseUp);
e.preventDefault();
});
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: droppable
droppable: function(options) {
try {
if(options == 'enable')
this.addClass('iq-droppable');
else if(options == 'disable')
this.removeClass('iq-droppable');
else {
this.addClass('iq-droppable');
this.data('iq-droppable', options || {});
}
} catch(e) {
Utils.log(e);
}
},
// ----------
// Function: resizable
resizable: function(options) {
try {
iQ('.iq-resizable-handle', this).remove();
if(options == 'destroy') {
this.removeClass('iq-resizable');
} else {
if(!options)
options = {};
this.addClass('iq-resizable');
var startMouse;
var startSize;
var elem;
var $elem;
// ___ mousemove
var handleMouseMove = function(e) {
var mouse = new Point(e.pageX, e.pageY);
var newSize = {
width: Math.max(options.minWidth || 0, startSize.x + (mouse.x - startMouse.x)),
height: Math.max(options.minHeight || 0, startSize.y + (mouse.y - startMouse.y))
};
if(options.aspectRatio) {
if(startAspect < 1)
newSize.height = newSize.width * startAspect;
else
newSize.width = newSize.height / startAspect;
}
$elem.css(newSize);
if(iQ.isFunction(options.resize))
options.resize.apply(elem, [e]);
e.preventDefault();
e.stopPropagation();
};
// ___ mouseup
var handleMouseUp = function(e) {
iQ(window)
.unbind('mousemove', handleMouseMove)
.unbind('mouseup', handleMouseUp);
if(iQ.isFunction(options.stop))
options.stop.apply(elem, [e]);
e.preventDefault();
e.stopPropagation();
};
// ___ handle + mousedown
iQ('<div>')
.addClass('iq-resizable-handle iq-resizable-se')
.appendTo(this)
.mousedown(function(e) {
if(Utils.isRightClick(e))
return;
elem = this.parentNode;
$elem = iQ(elem);
startMouse = new Point(e.pageX, e.pageY);
startSize = new Point($elem.width(), $elem.height());
startAspect = startSize.y / startSize.x;
if(iQ.isFunction(options.start))
options.start.apply(elem, [e]);
iQ(window)
.mousemove(handleMouseMove)
.mouseup(handleMouseUp);
e.preventDefault();
e.stopPropagation();
});
}
} catch(e) {
Utils.log(e);
}
}
};

View File

@ -193,6 +193,16 @@ window.Rect.prototype = {
return new Point(this.left + (this.width / 2), this.top + (this.height / 2));
},
// ----------
size: function() {
return new Point(this.width, this.height);
},
// ----------
position: function() {
return new Point(this.left, this.top);
},
// ----------
inset: function(a, b) {
if(typeof(a.x) != 'undefined' && typeof(a.y) != 'undefined') {