http://jsfiddle.net/citykid/NSD4P/
the fiddle creates 3 rect objects
component
body
port
events for the inner rects, body and port do not fire.
There is a $35 open bounty on this issue. Add to the bounty at Bountysource.
I'm not following. I see big red rectangle and a small green one attached to it. Clicking on either fires events.
the event is always fired for the group, the component here. expected result is that if the port is clicked, then the event attached to port is fired. But the port event is not fired.
var canvas = new fabric.Canvas('designer');
var h = 50;
var w = 100;
var body = new fabric.Rect({ width: w, height: h, fill: '#f55', top: 15, left: 50});
var port = new fabric.Rect({ width: 20, height: 30, fill: 'yellowgreen', top: 15, left: 50 + w - 10 }); // child
var component = new fabric.Group([body, port], { hasControls: false, hasBorders: false }); // group
component.top = 100;
component.left = 250;
canvas.add(component);
component.on("selected", function () {
console.log("component selected!"); // GROUP EVENT FIRED
});
port.on("selected", function () {
console.log("port selected!"); // CHILD EVENT NEVER FIRED, expected to fire when port is clicked
});
I think what @thulka means is that object event actual fires only on group object and not on objects inside the group.
http://jsfiddle.net/Kienz/C8udB/2/
If you click on red rectangle only group element's event is fired (not redRect event).
Maybe if target is group object (this.group exists) the events of "child" objects should also fire (behind a flag: default false).
As far as I can see that in the debugger, this could be changed in fabric.Canvas.findTarget(). I could implement that, but first I need some advise to understand that completely.
There is a for-loop that iterates over all Objects this._objects. It checks for all Objects if any of them is located at the clicked coordinates. If it finds something the Object is returned as the target. If target is a Group the same iteration could be repeated on the Group.
I hoped that this could be done by a recursion of findTarget() or parts of it. But that would require some major changes and I don't understand everything in that function.
I dont understand three parts:
-lastRenderedObjectWithControlsAboveOverlay
-activeGroup
-possibleTargets
From an architectural point of view I also have doubts that the Fabric Group is the same kind of Group that I have expected. I now think that the Fabric Group is more a temporarily selection of Objects and not a container that clusters objects like you would expect that from a composite pattern like it is used in many GUI-APIs like swing or swt. So... i am not sure about the consequences if I change that.
I am writing this because I don't want to inject a design-pattern in your architecture that doesn't fit.
@treeno
I now think that the Fabric Group is more a temporarily selection of Objects and not a container that clusters objects like you would expect that from a composite pattern like it is used in many GUI-APIs like swing or swt. So... i am not sure about the consequences if I change that.
You're right on the money with this. This is actually how things were from the start. You could select any of the objects on canvas for user interaction, or you could group them via mouse and then manipulate as one unit — just like it works in any graphical application of such sort. When an object was selected on canvas, you could reference it via canvas.activeObject. When group of objects was selected on canvas, you could reference that group via canvas.activeGroup (which answers your 2nd point). As you can imagine, there could only be 1 active group on canvas at any point, as well as only one active object.
At some point we realized an emerging need for creating such group objects programmatically. fabric.Group was born, and so you could programmatically group 2 or more objects together into one unit and add it onto canvas. That unit would then behave just like any other object, which is why fabric.Group's added onto canvas can be moved, rotated, scaled, etc. and fire events wholesomely, just like any other object.
In this sense, we satisfy composite pattern's essence of "... that a group of objects are to be treated in the same way as a single instance of an object" quite well. fabric.Group also mixes in fabric.Collection which gives it methods like item(index), getObjects(), add(), remove(), etc.
So now that there seems to be a need for group objects to fire events individually (another person asked about it just couple days ago), we can possibly introduce some kind of boolean flag on a group itself (or on canvas level), which would allow findTarget to propagate down the group elements.
I hope this clears things up a little. What do you think is the best course of actions from here on?
Not sure whether @treeno has already started implemented. If not, I am quite interested in doing this, because this is kinda essential for my project.
From my point, I would do
Any suggestions?
@kangax
Looks like we are having the same image of it now :-)
I had the same idea to use a boolean flag to activate events for the members of a group. If it is false by default we would not break the current behavior.
But after thinking about it I ask myself if this could be inconsistent to your original idea of what a Group is and how it is handled. What do you think about adding another pattern for some kind of a scene graph which is separate from the fabric.Objects? This would keep the existing Groups as they are without trying to add some behavior to it that maybe doesn't really fit.
This could be done by creating a simple tree structure that is used to accumulate transformations on Objects from nodes to subnodes to leafs in a tree... For example:
If you apply a rotation on Sun, the complete solarsystem rotates. If you apply a rotation on Earth only Earth and Moon rotate around the Sun. If you apply a rotation on Moon only the Moon rotates around Earth.
The tree could hold references to the painted Objects like fabric.Circle instead of extending them. All Objects are added to the canvas and not to Groups. In that way we could automatically use the events on every single Object without changing anything in the event-system.
We would keep the Scene Graph differentiated from the original design, maybe you could think about it as a new layer on top of it.
Without digging deeper into it I think this could be done by two Classes: Node and Leaf... where both define methods for transformations which delegate to fabric.Objects.
What do you think?
@guo7805
I haven't started with any implementation yet, I am still thinking about the best solution.
But your implementation idea seems to be in the right place, as far as I can say that...
Nevertheless I'm not sure if it is enough to do that only for the _active_ Group since there might exist non-active Groups... Don't know... I think this is the place where things start to get complicated... thats why I would prefer a solution that doesn't invade the existing event-system.
@treeno
For my specific case, the sizes of the group components are fixed, so I went for a dirty way:
Check the console for output. Basically this example gets the position of mousedown and compares against the position of two ports. Seems enough for my application. Using iteration to check each child body will definitely be more robust than hard-coded parameters.
@treeno
This could be done by creating a simple tree structure that is used to accumulate transformations on Objects from nodes to subnodes to leafs in a tree...
It's an interesting idea but I'm not sure it's worth adding something like this to Fabric. It feels very app-specific. It should be fairly straightforward to design a tree-like structure with this kind of behavior in an app (making it act exactly as needed).
I'm open to hearing more use cases though. If there are indeed more examples where this kind of behavior is useful, perhaps we can brainstorm it.
Well, maybe this is indeed app-specific... im not sure...
If you are looking for a UseCase it could be an editor for a map. Where you want to be able to scroll and zoom the whole map. And you want to move things on the map, maybe houses, or streets. So you would have a background satelite picture and a path that paints a street over the background. If you scale the whole map you need to scale it in a way that does not change the position of the path over the background.
That could easily be done with a scene graph.
Or simply a bike: You want to move the bike across the screen and rotate the tires or rotate the handlebar. This is also a tree-structure...
I personally believe that this declarative approach using a tree-structure leeds to a clearer application design, compared to the imperative approach...
When I started with 3d Graphics and OpenGL I indeed designed this scene-graph-layer for myself as a kind of app-specific-abstraction-layer. And this made the application design much clearer (compare that to manually dealing with a matrix-stack ;-)
When Java3D came out one of it's greatest advantages was the build-in SceneGraph, you don't have to care about that anymore. Something similar happens with WebGL and SceneJS (to be honest, I don't have much experience with that last two).
Somehow I don't see this development with 2D-Libraries, but I believe that this pattern can be applied here too very nicely.
I simply would appreciate it if I could use a library that has this kind of scene-graph build-in... so I would have less work ;-)
Ok... on the other hand, it's kind of fun designing such a thing ;-)
@treeno Very interesting. I do see the usefulness of such thing, but it still feels very vague and abstract to me, to be represented clearly in Fabric. If we're talking about animation (solar system or bike or map scrolling or maybe even something like parallax), I feel like there are many other aspects at play — speed of animation, easing, origin point, abort or other conditions, etc. — all of which could be different for different parts of the "system". On the other hand, if there's a way to declaratively specify each of the parameters for each of the components, and then just "start" it all, it would indeed make for a clean and pleasant-to-work-with architecture :)
In any case, I still think this is a bit too app-specific for us.
I'll check out more about ScenGraph's when I get a chance!
I think this is an important feature... the problem with groups currently is that the elements of that group don't have individual behaviors. The reason we group elements sometimes is for the sake of moving elements on the canvas. For example,
If I had a large rectangle on the canvas with some objects inside of it, it would be nice to be able to drag the rectangle itself and get the other elements to move with it... and you can do that in a group. Unfortunately, those elements are now stuck and cannot be repositioned or event can't be listened to as all the objects are essentially the same.
Unfortunately, those elements are now stuck and cannot be repositioned [...]
Of course then can. You can access any of them via getObjects() array. Or individually via item(index). The events is a different store — you can't listen to them on objects inside groups.
What I meant was you can no longer move them inside of the group... think like a popup window that contains many elements. Once these elements are grouped... they can no longer be selected individually.
but how could you get multiple elements to move with the movement of one particular object?
Events! :)
@hayatnoor note that the above example is a bit too complex because of arbitrary object syncing. If you only need to sync, for example, rect1 with the rest (in the above example) then it becomes even simpler.
Ok, as you mentioned, I am interested in only syncing rect1 with the rest, but when you move rect2 or rect3 inside of rect1 and then try to move rect1, the objects go back to their original position since it's taking rect1's top and left and adding 100. How would I go about having them stay in place after I've positioned them? Here's my jsfiddle:
... since it's taking rect1's top and left and adding 100
Not sure I understand the problem here. Make it add whichever value is correct?
Ok, in that example I posted... move the green square somewhere else in the box... now move the gray box. The green box will move back to it's original position. What are the calculations I need to do in order for the green box to stay in the position it was in originally (relative to the gray box)?
Find difference between their left/top values, then use that when moving
one or the other.
On Thu, Sep 19, 2013 at 6:11 PM, hayatnoor [email protected] wrote:
Ok, in that example I posted... move the green square somewhere else in
the box... now move the gray box. The green box will move back to it's
original position. What are the calculations I need to do in order for the
green box to stay in the position it was in originally (relative to the
gray box)?—
Reply to this email directly or view it on GitHubhttps://github.com/kangax/fabric.js/issues/485#issuecomment-24751540
.
I've tried that and couldn't get it to work... it always seems to be off because I don't think the values get updated as quickly when retrieving it from the gray object.
I don't have time to investigate this at the moment, but glancing through
it, I don't see where you're updating offset of rect2 and rect3 in relation
to rect1 when moving them
On Thu, Sep 19, 2013 at 6:16 PM, hayatnoor [email protected] wrote:
I've tried that and couldn't get it to work... it always seems to be off
because I don't think the values get updated as quickly when retrieving it
from the gray object.—
Reply to this email directly or view it on GitHubhttps://github.com/kangax/fabric.js/issues/485#issuecomment-24752035
.
Hello there.
I stumbled upon a similar problem, so.. what's the state of affairs for this feature ?
You can extends Group and you will remove the events from the Group and allow every object. ?
I was looking into this, that was i needed when i start using fabric.
I come out with something like this, not yet working good:
i added the objects to check to _searchPossibleTargets, at first call it has this._objects ( all of them ) if found target is a group or a path-group it recursively check again on the object of that group.
Drawbacks: it has to call setCoords for every object that doesn't have oCoords set, sometimes it found the target, sometimes not.
Do you think is a viable solution?
I would like to return something like taget with subtarget property that contains the object found inside the group.
Notice that 'this.relatedTarget' is unused but already present in fabric, so i used it.
Do you think it can work with some modification?
_searchPossibleTargets: function(e, objects) {
// Cache all targets where their bounding box contains point.
var target, subTarget,
pointer = this.getPointer(e, true),
i = objects.length;
while (i--) {
if (!objects[i].oCoords) {
objects[i].setCoords();
}
if (this._checkTarget(e, objects[i], pointer)){
this.relatedTarget = objects[i];
target = objects[i];
break;
}
}
if (target && this.findTargetInGroup && (target.type === 'group' || target.type === 'path-group')) {
this.relatedTarget = this._searchPossibleTargets(e, target._objects || target.paths);
}
return target;
},
What i'm looking or is a way to group an IText with a shape, so I can move them together but I could also edit the text by double clicking on it. Is there a way I could do this with groups ?.
Another interesting way of handling groups would be to have an edit mode where you only see the objects in the group and you can interact with them. once you are done you hit escape and you are back on the main canvas and your elements are still grouped together. For this a Layer object would be perfect.
Do you think it can work with some modification?
Yep, I think the general idea is going in the right direction
any workaround for this one?
I had a nearly working demo. but did not finish it yet.
can you share that please or any other quick work around to get it done? Please
is not in my repo anymore, i hope i have on some disk home.
anyway the core was this:
_searchPossibleTargets: function(e, objects) {
// Cache all targets where their bounding box contains point.
var target, subTarget,
pointer = this.getPointer(e, true),
i = objects.length;
while (i--) {
if (!objects[i].oCoords) {
objects[i].setCoords();
}
if (this._checkTarget(e, objects[i], pointer)){
this.relatedTarget = objects[i];
target = objects[i];
break;
}
}
if (target && this.findTargetInGroup && (target.type === 'group' || target.type === 'path-group')) {
this.relatedTarget = this._searchPossibleTargets(e, target._objects || target.paths);
}
return target;
},
but something was missing. You can start from this or from your own idea of course.
Thanks will look on it.
probably the problem was that i was returning target and not this.relatedTarget?
maybe is just this stupid thing.
i got it :)
The point is that in recursive checkTarget the object coords are relative to the group.
I see also that transform geometry is a little bit messed up around, so again not an easy fix
@jafferhaider i spent lot of time on this yesterday. If you need a fix quick, a way could be this:
_searchPossibleTargets: function(e, objects) {
// Cache all targets where their bounding box contains point.
var target, subTarget,
pointer = this.getPointer(e, true),
i = objects.length;
while (i--) {
if (!objects[i].oCoords) {
objects[i].setCoords();
}
if (this._checkTarget(e, objects[i], pointer)){
this.relatedTarget = objects[i];
target = objects[i];
break;
}
}
if (target && this.findTargetInGroup && (target.type === 'group' || target.type === 'path-group')) {
// here calculate transformMatrix of the group with scale, rotate, translate.
// transform cursor with this matrix inverted, be aware of group center
// check for target with this changed pointer, is easier than changing the output of setcoords.
target = this._searchPossibleTargets(e, target._objects || target.paths);
}
return target;
},
to calculate the group transformMatrix:
you can take the functions from the parser of svgs.
I will try to do in the weekend, cause i started to like the idea, but if you need it faster, this is an hint.
I have so much problem inverting rotation.
I get to the point where:

I transform the local pointer in differences between center point of target and local position , with scale inverted.
Now i should just UN-rotate the coords by - target.angle.
But this does not work.
I have tried to invert the rotation transformMatrix or create a transformMatrix of -angle.
Coords just fucks up.
if group is not rotated, the sub targeting works.
@asturur would you mind sharing updated code of the working sub targeting (minus the rotations)? just hit a use case for this on a project I am building and would love to use it and help out if I can.
https://github.com/asturur/fabric.js/pull/4
for whom may be interested before this feature is supported ( if it will ever be ) there is a partial PR with 90% work done.
almost completed on @kirkchris request.
What is missing: working with perPixelTargetFind and pathgroups, managing events for canvas.relatedTarget.
I had problem sub targeting objects inside rotated groups, the pointer pos was mirrored/fliped. Added a small modification to calcRotateScaleMatrix() that seems to work.
calcRotateScaleMatrix: function() {
var rotAngle = fabric.util.degreesToRadians(this.angle);
var n = rotAngle ? -1 : 1;
return fabric.util.multiplyTransformMatrices(
[n * Math.cos(rotAngle), Math.sin(rotAngle), -Math.sin(rotAngle), n * Math.cos(rotAngle), 0, 0],
[n * this.scaleX * (this.flipX ? -1 : 1), 0, 0, n * this.scaleY * (this.flipY ? -1 : 1), 0, 0]
);
}
On what version of fabric did you have the problem?
The problem on rotation was coming from a wrong multiply on "fabric.util.transformPoint" and not on the matrix itself.
in your code, if rotAngle = 0, n is 1, but the rotation is useless on angle 0, you can fully skip rotation.
Hi, I use version 1.4.13.
Yes you are totally right about that, might as well skip the rotation on angle 0. :) thanks
So please check if you are missing this:
https://github.com/kangax/fabric.js/commit/8747ed1bd62c6024e02b8838587328c4c4d47408
and probably the rotation matrix is correct without the added minus sign.
The above correction is important because it influences ALL the transformation that have index 2 and 3 different from 0 ( rotation, skewing )
I do hope fabric supports group item events in the near future. The features fabric does support (interactive text, interactive transform on objects) are very nicely done, and a huge time saver. The group event semantics are too limiting however.
As for the need being app specific, it is quite a common framework requirement. Most Gui/scene-graph frameworks allow items to exist in groups for positioning but delegate events to group members if a handler has been set. The dom/svg, paper.js and create.js, for instance, allows for handlers to exist on both group and item level. This is resolved through a capture/bubble mechanism described at http://www.quirksmode.org/js/events_order.html. A child can stop an event from propagating to it's parent by calling of a method like stopPropagation() on an event object.
+1; I think this will be really useful as well.
I haven't tested this, but it should work till some extend.
var $canvas = {};
$canvas.activePaths = [];
function onMouseDown = function (options) {
var mousePos = canvas.getPointer(options.e);
mousePos.x = parseInt(mousePos.x);
mousePos.y = parseInt(mousePos.y);
var width = canvas.getWidth();
var height = canvas.getHeight();
var pixels = canvas.getContext().getImageData(0, 0, width, height);
var pixel = (mousePos.x + mousePos.y * pixels.width) * 4;
var activePathsColor = new fabric['Color']('rgb(' + pixels.data[pixel] + ',' + pixels.data[pixel + 1] + ',' + pixels.data[pixel + 2] + ')');
var colorHex = '#' + activePathsColor.toHex().toLowerCase();
var activeObject = canvas.getActiveObject();
var activePath = [];
if(activeObject.type == 'group') {
for (var i = 0; i < activeObject.getObjects().length; i++) {
var path = activeObject.getObjects()[i];
if (path.getFill().toLowerCase() == colorHex) {
$canvas.activePaths.push(path);
}
}
}
}
function warpActivePaths() {
for(var i = 0; i < $canvas.activePaths.length; i++) {
canvas.add(
new fabric.Rect({
left: $canvas.getActiveObj().left + ($canvas.activePaths[i].left),
top: $canvas.getActiveObj().top + ($canvas.activePaths[i].top),
fill: '',
width: $canvas.activePaths[i].width,
height: $canvas.activePaths[i].height,
stroke: 'white',
opacity: 1
})
);
}
}
Maybe it'll help someone :).
for whom is still interested look at #2729
I need also suggestion if we want a single target approach ( check target inside group, if not found return group, otherwise return sub target ) or a multiple target approach ( return group, sub group and sub sub target )
+1; I think this will be really useful for map
Hi,
I tried to realize this functionality (selecting of a single object in a group). I wrote something like this:
`fCanvas.on('mouse:down', function(options) {
if (options.target){
var thisTarget = options.target;
var mousePos = fCanvas.getPointer(options.e);
if (thisTarget.isType('group') && thisTarget.hasOwnProperty('name')
&& thisTarget.name.indexOf('cross_') > -1) {
thisTarget.forEachObject(function(obj){
var corners = obj.get('oCoords');
// my own function (containsPoint doesn't work)
if (isPointInLocation(mousePos.x,mousePos.y,
[ {x: thisTarget.left + corners.tl.x, y: thisTarget.top + corners.tl.y},
{x: thisTarget.left + corners.tr.x, y: thisTarget.top + corners.tr.y},
{x: thisTarget.left + corners.bl.x, y: thisTarget.top + corners.bl.y},
{x: thisTarget.left + corners.br.x, y: thisTarget.top + corners.br.y}]))
{
fCanvas.setActiveObject(obj);
}
});
}
}
});`
It works, but after group rotation the .oCoords property hasn't been changed. Is there any solution?
Thanks.
Please check the changes here for a solution that works with rotation and anything else:
https://github.com/kangax/fabric.js/pull/2729/files
it will be completed soon.
I'm sorry is there any way to use your solution or test it? I'm just getting over with the same problem, for the second week, thank you
its marked for 1.6.3, i m gonna finish and merge this. help trsting is aporeciated
Any update here?...
Spent 3 hours trying to find a solution for getting events for particular object within the group :|
the pr is on the list, i will merge it as soon as i can test regressions
@asturur Any idea if 1.6.3 will be released approx. in 3 weeks, 3 months or 3 years? 😄
i would like to fix #3021 and give a check to current things merged to be sure we added as little as possible regressions ( i hope no regression at all).
I have a rect inside the group, after scaling/modify of a group i need to find the new coordinates of rect inside the group, what is the possible ways to find the rect coordinated within the group?
take oCoords of the object and multiply them by the group transformmatrix
that you can get with calcTransformMatrix
On Jul 29, 2016 14:51, "nirmal4343" [email protected] wrote:
I have a rect inside the group, after scaling/modify of a group i need to
find the new coordinates of rect inside the group, what is the possible
ways to find the rect coordinated within the group?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/kangax/fabric.js/issues/485#issuecomment-236263688,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABI4QAuQcn6GfA8DFs8VZ4dZ93qPwC5zks5qakvBgaJpZM4AgqX9
.
Is this working on 1.6.3 now? any example? like 1 rect and 1 circle inside a group, if I click the circle fills red, if I click rect fills green
Well, i find out how to do it.
Here is a sample http://jsfiddle.net/2Y587/80/
ghost I used your sample and it was pretty helpful however I noticed that, on my old iphone 4 (iOS 7) with safari, a weird issue arises.
If I click for example on polygon 1 and than I click on another different polygon, say 3, mouse:up event still returns, as target, polygon 1.
Are you aware of that? Is there any fix?
Thanks
can you make a fiddle that reproduce this?
The fiddle I used is the same ghost suggested
http://jsfiddle.net/2Y587/80/
I found out the behaviour I reported on a code I developed, then I tried the same ghost's script cos I thought the issue could depend on my code, but the behaviour was the same. But I repeat, that happened only on my iphone 4. This afternoon I tried on an IPhone 6 and there were no problems on it
Most helpful comment
Well, i find out how to do it.
Here is a sample http://jsfiddle.net/2Y587/80/