I'm trying to create a resizeable box with Flapjax. Four draggable boxes
are created, one for each edge -- top, left, right, bottom (we'll call them
"handles"). The handles define dimensions of the box.
The problem is this: every handle depends on the dimensions of the box
being resized, which, in turn, depend on the positions of other handles.
I wonder how to express this in Flapjax.
The equations I use are:
top.width = area.width
bottom.width = area.width
left.height = area.height
right.height = area.height
area.width = right.x - left.x
area.height = bottom.y - top.y
The code is on Patch-Tag:
<http://patch-tag.com/r/fjwt/snapshot/current/content/pretty/dnd>
(dragdrop.js, lines 358-394)
Cheers,
Artyom Shalkhakov.
2009/8/11 Arjun Guha <arjun...@gmail.com>:
> I think there are two things that make this hard. First, you need to
> break the cycle in those constraints using delayB or delayE.
I don't understand this. Can you show where the cycle is, specifically?
> Once you're past that, the obvious approach is to define a behavior
> (e.g. top.width) that is dependent on the time-varying value of the
> DOM attribute dom.width. You can do this with the DOMAttrModified
> event, which isn't well supported. You could try using the following
> function I wrote. It uses Prototype and Flapjax. It does not work on
> Safari 4, but it does work on Firefox 3.5:
Well, this suggests selector functions for DOM objects managed by
Flapjax. Is this possible with current implementation?
> // posB :: Element -> Behavior { left, top, width, height: String }
> function posB(t_) {
> var t = $(t_); // adds Prototype methods
> var f = function() {
> var pos = t.cumulativeOffset();
> var size = t.getDimensions();
> return { left : pos.left, top: pos.top,
> height: size.height, width: size.width }};
>
> return $E(t, "DOMAttrModified")
> .filterE(function(x) { return x.attrName == "style"; })
> .mapE(f)
> .startsWith(f());
> }
I don't quite like this approach, because it doesn't work on all
browsers (and it feels like a hack, I must admit).
> The alternative approach is to build a data structure with al the
> variables you're interested in, setup the equations and project back
> and forth from the DOM (a.k.a. "lenses"). I'll give that a shot this
> evening. Let me know if DOMAttrModified works for you.
This is interesting. I think it would be necessary for GUI layout as well
(element layout has mixed top-down bottom-up dependencies -- you
can't express that using current Flapjax without resorting to hacks AFAIK).
Cheers,
Artyom Shalkhakov.
I think I misunderstood the system you want to setup. Let me try to
restate it:
You have a central area (that cannot be dragged) and four edges that
can be dragged, called top, right, bottom, left. As you drag and top
and left edges, shouldn't the position of the area move? I'd expect to
see area.left and area.right somewhere in those equations.
So, focusing just on the top edge and area, I'd have imagined you'd
want:
area.top = top.top + top.height
top.top = ... determined by dragging ...
top.height = ... constant ...
Let me know what you think.
As for the cycles... I may be wrong about that. I believe I was
imaging dragging corners.
>
> Well, this suggests selector functions for DOM objects managed by
> Flapjax. Is this possible with current implementation?
The only two functions used from Prototype in that code are
cumulativeOffset and getDimensions. cumulativeOffset is in Flapjax; I
lifted it from Prototype. We could add getDimensions too, but at some
point it's just easier to use jQuery/Prototype instead of replicating
their functionality in Flapjax.
Arjun
Right.
> As you drag and top and left edges, shouldn't the position of the area
> move? I'd expect to see area.left and area.right somewhere in those
> equations.
Yes it should! I haven't included those because I thought it would be
"trivial" to get that detail right after edges' width and height are
dealt with. Sorry about that.
> So, focusing just on the top edge and area, I'd have imagined you'd
> want:
>
> area.top = top.top + top.height
> top.top = ... determined by dragging ...
> top.height = ... constant ...
Precisely! top.height might be a behavior but it shouldn't really
matter.
> As for the cycles... I may be wrong about that. I believe I was
> imaging dragging corners.
Imagine constraining central area's width and height so that it is
within certain bounds, or even bounding draggable edges to an
outer element's dimensions. Would they introduce cycles?
Cheers,
Artyom Shalkhakov.
A good one, much much better than sources of the image croppers I've
checked out. And very easy, just convert your constraints to code. :)
Thanks a lot!
However, there are other questions remaining open:
- how to make this functionality reusable?
- what about dynamically created markup? (via JS)
- is there a way to assign constraints once?
but I guess these are less important.
Cheers,
Artyom Shalkhakov.
So, that's what this is about! Let us in on the final result, if you
can.
> - what about dynamically created markup? (via JS)
> - how to make this functionality reusable?
I think these two are related and fairly easy, depending on your
definition of reusable. You can generate markup.
> - is there a way to assign constraints once?
Unfortunately, that code does have to setup 20 constraints. If you
worked with dynamically created markup, and used Flapjax's HTML
constructors, you could simultaneously specify the dimensions of each
edge:
DIV({ style: { left: ... as before ...,
top: ... as before ...,
width: ... as before ..,
height: ... as before ...,
... inline attributes from horzEdge ... });
You'd still write 20 constraints, but it would be slightly cleaner.
I don't think there is any way to avoid writing each constraint
explicitly, which is why an abstraction would help.
Arjun
Will do, of course! I'm working on a widget toolkit, actually, and
intend to make it
freely available. The motivation is to make it easier to program and extend
in-browser applications.
Yes I know about Dojo/Dijit, YUI, Qooxdoo, jQuery UI -- but they are
IMHO hard to
work with, as they lack compositionality almost by design (and they have lots of
policies (but no mechanisms), -- that's where most of them differ!). I tried to
integrate Qooxdoo with Flapjax but then I didn't like how it was turning out, so
I decided to go another way.
>> - what about dynamically created markup? (via JS)
>> - how to make this functionality reusable?
>
> I think these two are related and fairly easy, depending on your
> definition of reusable. You can generate markup.
What I meant was that
- resizeable elements are quite widespread:
image cropping, dialog/window resizing, selection (not quite
cropping!), panels, etc.
- sometimes you don't want to resize a particular edge (or two or three),
e.g. Windows doesn't allow you to resize left/top window edges while NeXT
allowed dragging only the bottom edge and corners
- sometimes resizing affects other elements:
imagine resizing two panels laid out from left to right
"stitched" by a vertical element (the one you'd move to resize them)
- sometimes you'd like to limit minimum and maximum size,
or constrain width to height ratio or even snap to grid
There are lots of use-cases to consider. We can surely go the easy route,
making the API complex trying to cover all our use-cases, but I'd like to
define widgets via combinators. Mechanism not policy, like Flapjax.
>> - is there a way to assign constraints once?
>
> Unfortunately, that code does have to setup 20 constraints. If you
> worked with dynamically created markup, and used Flapjax's HTML
> constructors, you could simultaneously specify the dimensions of each
> edge:
>
> DIV({ style: { left: ... as before ...,
> top: ... as before ...,
> width: ... as before ..,
> height: ... as before ...,
> ... inline attributes from horzEdge ... });
>
> You'd still write 20 constraints, but it would be slightly cleaner.
Oops, I must have been vague. I meant that the code you posted
destructively updates left/width/top/height constraints for each edge.
Is there a way to do single assignment here?
> I don't think there is any way to avoid writing each constraint
> explicitly, which is why an abstraction would help.
This is great and such, but I don't have an idea how would it look like.
I'm experimenting with directed interval algebra at the moment, it
seems useful:
- a rectangle is modeled by two closed intervals (one for each axis)
but "improper" intervals are allowed
- width and height of resizeable area should be just an operation away
- wrapping is expressible by direction
Cheers,
Artyom Shalkhakov.
So I did it using the directed interval algebra. Seems easier this way.
Here's how the code looks like:
function loader() {
// commutative version of betweenD
function between(a,b) {
return lleD(a,b)? betweenD(a,b) : betweenD(b,a);
}
// set constraints up
var leftBD = dragBD('leftEdge','left',directed(20,30));
var rightBD = dragBD('rightEdge','left',directed(210,220));
var topBD = dragBD('topEdge','top',directed(20,30));
var bottomBD = dragBD('bottomEdge','top',directed(200,210));
var widthBD = liftB(between,leftBD,rightBD);
var heightBD = liftB(between,topBD,bottomBD);
// assign them to elements
// insertBR :: (behavior rect) (string | dom) -> unit
function insertBR(x,dom) {
insertValueB({left:liftB(leftR,x),top:liftB(topR,x)
,width:liftB(widthR,x),height:liftB(heightR,x)},
dom, 'style');
}
insertBR(liftB(directedR,widthBD,heightBD),'area');
insertBR(liftB(directedR,leftBD,heightBD),'leftEdge');
insertBR(liftB(directedR,rightBD,heightBD),'rightEdge');
insertBR(liftB(directedR,widthBD,topBD),'topEdge');
insertBR(liftB(directedR,widthBD,bottomBD),'bottomEdge');
}
(the rest is here:
<http://patch-tag.com/r/fjwt/snapshot/current/content/pretty/dnd/resize.html>)
A rectangle is represented by a pair of intervals, ranging over X or Y
axis, respectively. "Improper" intervals are allowed (an interval is
improper if its left bound is greater than its right bound), this accounts
for wrapping.
We have a special "between" operation, which, given two intervals,
yields the shortest distance between them, represented by an interval.
This is how we obtain width and height.
Now, how to make this dynamic? Arjun, do you have any idea?
Cheers,
Artyom Shalkhakov.