Raphaeljs: How to get the elements reference back using event.target? - javascript

Currently I have an small application for drawing shapes.
Here is an example including my problem: http://jsfiddle.net/auyaC/
I get the error: Uncaught TypeError: Object [object Object] has no method 'getBBox'
Below stripped code where the error comes from
When the user clicks on a shape, I catch the event.target
var onMouseDown = function(event) {
setBBoxes($(event.target)); // Seems OK
};
I want the BBoxes back again but my shape has lost the BBox..
var setBBoxes = function(shape) {
shape.getBBox(); // Unable.. getBBox is part of Raphael shapes, but mine is not the real reference?
};
And a stripped example: http://jsfiddle.net/auyaC/2/
Edit
Ok so my problem was mixing up jQuery and Raphaeljs, because I am unable to use the mouse events of Raphael.
It seem that none of the examples online using mouse events or touch events work.
I have read these issue reports
https://github.com/DmitryBaranovskiy/raphael/issues/720
https://github.com/DmitryBaranovskiy/raphael/pull/737
Also Windows thinks I have touch input available for 255 touch points.
But I don't have a touchscreen anymore (had one but changed screen and deleted drivers).
So for me, even http://jsfiddle.net/5BPXD doesn't work on my computer...

You generally do not want to mix jQuery and Raphael like this, as it's easy to get confused about which library's event handlers and methods you're using. You also lose Raphael's fallback capabilities for old browsers when you start directly messing with the DOM elements that Raphael creates.
In this case, I recommend adding the .mousedown() listener directly to the Raphael element.
var paper = new Raphael($(".testarea")[0], $(".testarea").width(), $(".testarea").height());
var circAttr = {
"fill": "#4ba6e8",
"stroke": "#1a81cc",
"stroke-width": "2"
};
paper.circle(200, 200, 80).attr(circAttr).mousedown(function() {
someFunction(this);
});
var someFunction = function(shape) {
console.log(shape.getBBox());
};
Updated fiddle: http://jsfiddle.net/auyaC/3/
Of course, you lose the ability to select all the shapes at once with a selector and add the event to all of them at once. You'll need to add the mousedown event to each one as it's created. Small tradeoff, I think.

I've finally found the fix for my bug...
It seem that Raphael thought that I had a touch screen.
The "Tablet PC Components" Windows feature was turned on. After disabling this feature, I was able to use the mouse and touch events again!

Related

Top level mouse event handler

Hm. Maybe Is that I can't figure out the correct search keywords, since I'm new in some areas of javascript.
I am making a two.js based interactive game that comprises dragging stuff around. I need to detect when a mouse is released regardless the elements that are (or aren't) over the canvas. The elements (which are SVG canvas elements) capture the mouse events and prevent the canvas mouse event detection.
Mouse events:
$("#canvas").on("mousemove", function(e){
//do stuff
}).on("mousedown",function(){
//do stuff
}).on("mouseup",function(){
//do stuff
})
So, I can either address a event handler to an object, which will happen only within that object, or address it to the canvas, which will only happen when there is no object over. Appending to both will be unelegant, and will need a major re estructuration in the code that is huge (and I admit, messy);
Example elements that steal the mouse events in the inspector
I hope not to be re-asking. I have looked up and tried for some hours. Thanks!
Ok. So I tried an approach trying to think more javascript wise. Mainly I come from the other OOP school of thought; which is why is hard to unlock my understanding of prototypical objects.
I made a global function named pointer, and other named draggable, which goes in the prototype of each object to be dragged. On this function and over any object over which things could be dragged (the canvas), I attached the functions onRelease and release. When the mouse button is released, it runs its release function, which triggers a pointer.release(). The pointer will have an array of any object being dragged, so when it receives the release, it will trigger the onRelease of each dragged element, passing the object under the mouse. So the object being dragged, at the end receives the event handling object (the one upon which mouse was released).
I hope this method doesn't produce any face-palm. If so, I'd like to learn how it was better.
var pointer={
pos:{x:0,y:0},
dragging:false,
//who will be the object that detected the mouse released. see below
mouseUp:function(who){
for(drg in this.dragging){
//look if the -being-dragged- object does have the function
if(typeof(this.dragging.onRelease)=="function")
this.dragging.onRelease(who);
this.dragging=false;
}
}
}
This is the draggable class. The last part is the only concerning one, the rest is for context purpose:
function Draggable(){
this.main=this;
if(!this.pos)
this.pos={x:0,y:0};
this.dragging=false;
this.hover=false;
this.sprite=false;
this.move=function(pos){
this.sprite.translation.x=pos.x;
this.sprite.translation.y=pos.y;
this.pos=pos;
this.moving();
}
this.moving=function(){}
this.release=function(){
pointer.mouseUp(this);
}
this.init=function(){
//fallback sprite is a circle
if(!this.sprite)
this.sprite=two.makeCircle(0,0,this.rad);
this.move(this.pos);
two.update();
this.elem=$(domElem(this.sprite));
//attach a this to the dom element to make back-scope
this.elem[0].main=this;
//append jquery functions to this object's dom sprite
//pendiente: this may need to go in the pointer object, isn't it?
this.elem.on("mouseenter",function(){
this.main.sprite.fill="rgba(127,127,255,0.7)";
this.main.hover=true;
}).on("mouseleave",function(){
this.main.sprite.fill="rgba(127,127,255,0.3)";
this.main.hover=false;
//avoid pieces stuck to mouse. should this be?
/*this.main.dragging=false;
this.main.release();
pointer.dragging=false;*/
}).on("mousedown",function(){
this.main.dragging=this.main.hover;
pointer.dragging=this.main;
}).on("mouseup",function(){
//the important part is here: it triggers an pointer.mouseUp
this.main.dragging=false;
this.main.release();
pointer.mouseUp(this);
});
}
};
And so the node which will be prototype-blended with the draggable
function Node(ind){
//just to make more console-friendly
this.name="node"+ind;
this.pos=a;
this.ind=ind;
this.par=par;
this.broc;
this.sprite=two.makeCircle(0,0,brocSize*1.2);
this.sprite.addTo(layer[2]);
this.$elem=$(domElem(this.sprite));
main=this;
//these are triggered by their draggable bit
//who, is the subject over which mouse was released
this.onRelease=function(who){
//if(typeof(who)=="Broc")
console.log(who);
this.move(this.broc.pos);
console.log("this:");
console.log(this);
}
//pendant: make the abspos function once an unpack
this.son=false;
this.active=false;
};
This implements a bit of jQuery, which may later be replaced by plain javascript, for prototype and learn purposes I feel more comfortable with jQuery.

How is jQuery mobile interfering with my mouse/touch listening on SVG documents?

I have an app that uses my own approach to SVG buttons, which required some hackery to get to work but I've liked how it works. However when I add jQuery Mobile to the project, my buttons are no longer responding to clicks or touch.
My buttons are not <button> elements, but <object> tags that link an external SVG file. I have code to hook these up like so:
function buttonifySVG(id, clickHandler) {
var obj = document.getElementById(id);
var svgDoc = obj.getSVGDocument();
function addClickHandler() {
svgDoc.removeEventListener('touchstart', clickHandler);
svgDoc.removeEventListener('mousedown', clickHandler);
svgDoc.addEventListener('touchstart', clickHandler);
svgDoc.addEventListener('mousedown', clickHandler);
}
addClickHandler();
obj.addEventListener('load', addClickHandler, false);
}
Here's a sample "button":
<object id="stepForward"type="image/svg+xml" data="stepForward.svg"></object>
And just to be completely clear:
...
buttonifySVG('stepForward', function() { doTheThing(); })
I can confirm with logging that the buttons are still being hooked up by this code but that the passed in clickHandler is never called. Beyond that, poking around in jquery-mobile.js, looks like there's at least one place where clicks are being intercepted and stopped, but I can't tell when, and more to the point, I'd rather not start hacking jquery code to get things to work.
Can anyone tell me what's likely the problem? I may be able to hack around it if I know what's going on here.
Also, does jQuery Mobile do anything special with <object id="myButton" type="image/svg+xml" data="foo.svg"> elements? This approach has so many nice features I'd really like to get it to play well with jQuery Mobile -- the solution I'm seeking is not to replace my smart buttons with jQuery SVG icon buttons (though I plan to use those for other parts of the UI).
Thanks for any help!
I'm not sure what exactly in JQM is causing the problem described, but I was able to get my code to work with a little modification:
function buttonifySVG(id, clickHandler) {
var obj = document.getElementById(id);
function addClickHandler() {
var svgDoc = obj.getSVGDocument();
var rect = svgDoc.getElementsByTagName('rect')[0];
rect.removeEventListener('touchstart', clickHandler);
rect.removeEventListener('mousedown', clickHandler);
rect.addEventListener('touchstart', clickHandler);
rect.addEventListener('mousedown', clickHandler);
}
obj.addEventListener('load', addClickHandler, false);
}
This relies on the fact that I authored the SVG images myself to have a single rect element as the top-most object for simple mouse/touch events. Not sure if there is a more generic approach that would work, depends on how the SVG is made. Whatever JQM is doing seems to be blocking events where the target is the SVG document itself, but not blocking events within that document. I have noticed a new bug with my code on mobile devices where I'm getting 2 touch events for each button touch, which may or may not be due to the above code...

Obtain coordinates on doubleTap using Zepto-js and iPad

Anybody familiar with zepto?
I'm open to other mobile frameworks suggestions,too if they have good implantation of doubleTap and they handle the job.
I need to detect coordinates on the second tap of doubleTap event in mobile Safari.
So far I've been using jQuery for the event-obj and that syntax was fine
x: e.pageX
y: e.pageY
But it doesn't work in iPad
Appreciate any kind help, BR
I don't know if this can be made to work on Zepto (I'm having the same issue on JQMobi) but if you use HammerJS http://eightmedia.github.com/hammer.js/ instead you can get the actual position of the tap
See my SoftPaws game for an example of this: https://github.com/gavD/soft-paws
I hope this helps!
As I've mentioned I found a solution so I'm posting it if other users face the same issue:
// double tap (tapped twice within 250ms)
if (touch.isDoubleTap) {
touch.el.trigger('doubleTap')
touch = {}
This code above in zepto.js detects that the touch event was doubleTap and triggers its handler. And here is a little modification:
// double tap (tapped twice within 250ms)
...............
touch.el.trigger('doubleTap', {touch: touch})
overriding it with passing the event as parameter solves the problem. Now the event object is accessible with all its properties including coordinates.
And here is an example how you extract them from defined handler:
Zepto('selector').doubleTap(function(e){
var dblTap = e.data.touch;
var coord = {
x: dblTap.x1,
y: dblTap.y1
}
});
This is valid for v1.0.rc1 don't know what the case will be in future releases

Determine what is being dragged from dragenter & dragover events

I'm trying to use the HTML5 draggable API (though I realize it has its problems). So far, the only showstopper I've encountered is that I can't figure out a way to determine what is being dragged when a dragover or dragenter event fires:
el.addEventListener('dragenter', function(e) {
// what is the draggable element?
});
I realize I could assume that it's the last element to fire a dragstart event, but... multitouch. I've also tried using e.dataTransfer.setData from the dragstart to attach a unique identifier, but apparently that data is inaccessible from dragover/dragenter:
This data will only be available once a drop occurs during the drop event.
So, any ideas?
Update: As of this writing, HTML5 drag-and-drop does not appear to be implemented in any major mobile browser, making the point about multitouch moot in practice. However, I'd like a solution that's guaranteed to work across any implementation of the spec, which does not appear to preclude multiple elements from being dragged simultaneously.
I've posted a working solution below, but it's an ugly hack. I'm still hoping for a better answer.
I wanted to add a very clear answer here so that it was obvious to everyone who wanders past here. It's been said several times in other answers, but here it is, as clear as I can make it:
dragover DOES NOT HAVE THE RIGHTS to see the data in the drag event.
This information is only available during the DRAG_START and DRAG_END (drop).
The issue is it's not obvious at all and maddening until you happen to read deeply enough on the spec or places like here.
WORK-AROUND:
As a possible work-around I have added special keys to the DataTransfer object and tested those. For example, to improve efficiency I wanted to look up some "drop target" rules when my drag started instead of every time a "drag over" occurred. To do this I added keys identifying each rule onto the dataTransfer object and tested those with "contains".
ev.originalEvent.dataTransfer.types.includes("allow_drop_in_non_folders")
And things like that. To be clear, that "includes" is not a magic bullet and can become a performance concern itself. Take care to understand your usage and scenarios.
The short answer to my question turns out to be: No. The WHATWG spec doesn't provide a reference to the element being dragged (called the "source node" in the spec) in the dragenter, dragover, or dragleave events.
Why not? Two reasons:
First, as Jeffery points out in his comment, the WHATWG spec is based on IE5+'s implementation of drag-and-drop, which predated multi-touch devices. (As of this writing, no major multi-touch browser implements HTML drag-and-drop.) In a "single-touch" context, it's easy to store a global reference to the current dragged element on dragstart.
Second, HTML drag-and-drop allows you to drag elements across multiple documents. This is awesome, but it also means that providing a reference to the element being dragged in every dragenter, dragover, or dragleave event wouldn't make sense; you can't reference an element in a different document. It's a strength of the API that those events work the same way whether the drag originated in the same document or a different one.
But the inability to provide serialized information to all drag events, except through dataTransfer.types (as described in my working solution answer), is a glaring omission in the API. I've submitted a proposal for public data in drag events to the WHATWG, and I hope you'll express your support.
A (very inelegant) solution is to store a selector as a type of data in the dataTransfer object. Here is an example: http://jsfiddle.net/TrevorBurnham/eKHap/
The active lines here are
e.dataTransfer.setData('text/html', 'foo');
e.dataTransfer.setData('draggable', '');
Then in the dragover and dragenter events, e.dataTransfer.types contains the string 'draggable', which is the ID needed to determine which element is being dragged. (Note that browsers apparently require data to be set for a recognized MIME type like text/html as well in order for this to work. Tested in Chrome and Firefox.)
It's an ugly, ugly hack, and if someone can give me a better solution, I'll happily grant them the bounty.
Update: One caveat worth adding is that, in addition to being inelegant, the spec states that all data types will be converted to lower-case ASCII. So be warned that selectors involving capital letters or unicode will break. Jeffery's solution sidesteps this issue.
Given the current spec, I don't think there is any solution that isn't a "hack". Petitioning the WHATWG is one way to get this fixed :-)
Expanding on the "(very inelegant) solution" (demo):
Create a global hash of all elements currently being dragged:
var dragging = {};
In the dragstart handler, assign a drag ID to the element (if it doesn't have one already), add the element to the global hash, then add the drag ID as a data type:
var dragId = this.dragId;
if (!dragId) {
dragId = this.dragId = (Math.random() + '').replace(/\D/g, '');
}
dragging[dragId] = this;
e.dataTransfer.setData('text/html', dragId);
e.dataTransfer.setData('dnd/' + dragId, dragId);
In the dragenter handler, find the drag ID among the data types and retrieve the original element from the global hash:
var types = e.dataTransfer.types, l = types.length, i = 0, match, el;
for ( ; i < l; i++) {
match = /^dnd\/(\w+)$/.exec(types[i].toLowerCase());
if (match) {
el = dragging[match[1]];
// do something with el
}
}
If you keep the dragging hash private to your own code, third-party code would not be able to find the original element, even though they can access the drag ID.
This assumes that each element can only be dragged once; with multi-touch I suppose it would be possible to drag the same element multiple times using different fingers...
Update: To allow for multiple drags on the same element, we can include a drag count in the global hash: http://jsfiddle.net/jefferyto/eKHap/2/
To check if it is a file use:
e.originalEvent.dataTransfer.items[0].kind
To check the type use:
e.originalEvent.dataTransfer.items[0].type
i.e. I want to allow only one single file jpg, png, gif, bmp
var items = e.originalEvent.dataTransfer.items;
var allowedTypes = ["image/jpg", "image/png", "image/gif", "image/bmp"];
if (items.length > 1 || items["0"].kind != "file" || items["0"].type == "" || allowedTypes.indexOf(items["0"].type) == -1) {
//Type not allowed
}
Reference: https://developer.mozilla.org/it/docs/Web/API/DataTransferItem
You can determine what is being dragged when the drag starts and save this in a variable to use when the dragover/dragenter events are fired:
var draggedElement = null;
function drag(event) {
draggedElement = event.srcElement || event.target;
};
function dragEnter(event) {
// use the dragged element here...
};
In the drag event, copy event.x and event.y to an object and set it as the value of an expando property on the dragged element.
function drag(e) {
this.draggingAt = { x: e.x, y: e.y };
}
In the dragenter and dragleave events find the element whose expando property value matches the event.x and event.y of the current event.
function dragEnter(e) {
var draggedElement = dragging.filter(function(el) {
return el.draggingAt.x == e.x && el.draggingAt.y == e.y;
})[0];
}
To reduce the number of elements you need to look at, you can keep track of elements by adding them to an array or assigning a class in the dragstart event, and undoing that in the dragend event.
var dragging = [];
function dragStart(e) {
e.dataTransfer.setData('text/html', '');
dragging.push(this);
}
function dragEnd(e) {
dragging.splice(dragging.indexOf(this), 1);
}
http://jsfiddle.net/gilly3/4bVhL/
Now, in theory this should work. However, I don't know how to enable dragging for a touch device, so I wasn't able to test it. This link is mobile formatted, but touch and slide didn't cause dragging to start on my android. http://fiddle.jshell.net/gilly3/4bVhL/1/show/
Edit: From what I've read, it doesn't look like HTML5 draggable is supported on any touch devices. Are you able to get draggable working on any touch devices? If not, multi-touch wouldn't be an issue and you can resort to just storing the dragged element in a variable.
From what I have read on MDN, what you are doing is correct.
MDN lists some recommended drag types, such as text/html, but if none are suitable then just store the id as text using the 'text/html' type, or create your own type, such as 'application/node-id'.
I think you can get it by calling e.relatedTarget See: http://help.dottoro.com/ljogqtqm.php
OK, I tried e.target.previousElementSibling and it works, sorta.... http://jsfiddle.net/Gz8Qw/4/ I think it hangs up because the event is being fired twice. Once for the div and once for the text node (when it fires for text node, it is undefined). Not sure if that will get you where you want to be or not...

CKEditor with MVC: Event handling

I have the following code, which generates my default CKEditor on my form:
$('#Content').ckeditor({
filebrowserUploadUrl: '/ControlPanel/Reviews/UploadReviewImage/'
});
As you can see, my implementation is quite simple - most settings are controlled in config.js and are global to my site, except the uploader handlers.
Been looking for a few days, but can't find anything concise. I need to be able to detect when CKEditor is clicked, and make the window larger (though not full screen). I also want to display some help text when CKEditor is clicked... but I can't figure out how to handle dom events, or apply JQuery events to it? I've tried a few different things, the following being my latest attempt:
$("#Content").ckeditorGet().click(window.showDescription);
But I get a TypeError:
Uncaught TypeError: Object #<Object> has no method 'click'
Which is understandable, as CKEditor has its own event system.
The CKEditor documentation seems very unintuitive - and the examples aren't really much help.
UPDATE
I managed to get this working on my own, though because of thw way I've written my window.ShowDescription function, I needed to pass a JQuery event in to it. Here's the code for anyone interested :)
$("#Content").ckeditorGet().on("instanceReady", function () {
this.on("focus", function () {
this.resize(638);
// Mimic JQuery event for textarea
var originalEditor = $(this.container.$).parents(".input").find("textarea");
originalEditor.trigger(jQuery.Event("focus"));
});
this.on("blur", function () {
this.resize(400);
});
});
As you already suspected, what you did above won't work. The ckeditorGet method returns an instance of an editor, not the DOM element of the editor.
You can use the focus event of ckeditor to determine what to do when the editor receives focus.
Example:
$("#Content").ckeditorGet().on('focus', function(e) {
// do what you want on focus
});
May I suggest enabling the autogrow plugin to change the content area size? It's more generalized to the size of your content, and may work better than trying to resize on your own.

Categories

Resources