Building a Raphael path object in piecemeal fashion - javascript

I'm trying to create a little free-hand drawing app, and to figure out a way to add path segments (e.g. "L10,10") to a Raphael path Element. This answer suggests that isn't possible.
I've tried doing something like:
var e = paper.path("M0,0L100,100")
e.attr("path").push(["L",50,100])
...which does alter the array returned by e.attr("path") but doesn't change the graphic, so I guess this isn't supported behavior.

It looks like you have to call the setter version of .attr() to update the display. The following seems to work:
var e = paper.path("M0,0L100,100");
e.attr("path").push(["L",50,100]);
e.attr("path", e.attr("path"));
although this does look pretty clumsy. I don't really see a better way to do it using push(), though.

After looking through the Raphael 2 source I figured out a method to create an incremental path efficiently, by:
initializing the path using the Raphael API w/ elem = paper.path()
attaching the mousemove handler to alter the SVG DOM path directly, via elem.node.setAttribute("d", elem.node.getAttribute("d")+newLineSegment); Raphael uses the 'd' attribute to set path string internally so this should be cross-browser compatible AFAICT (Update: actually I'm mistaken; this only works for the SVG-compatible browsers, not VML), while bypassing a whole mess of code we don't need to have run on an inner loop
when done drawing, set the path attribute for the path element explicitly through Raphael's API, so it can do all the proper housekeeping on the Element e.g.: elem.attr( {path: elem.node.getAttribute("d") })
This performs reasonably well on Chrome, and other modern browsers I tested on.
I've finished a jQuery UI widget for a sketchpad that uses this. Please leave a comment if you would find such a thing useful as open source. If there's interest I'll see if I can make that happen.

I can conform that this works:
var arr = somePath.attrs.path;
arr.push(["L", x, y]);
somePath.attr({path: arr});

Related

Casting for element access in Haxe for HTML-elements

If we want to draw something on canvas we need to get 2D context of it.
I have a canvas element in index.html of project:
<body>
<canvas id="canv" width="200" height="200"></canvas>
</body>
So now I need to get access for that element, ok, let's write code:
var cans:CanvasElement = Browser.document.getElementById("canv");
and in compile phase I get error:
src/Main.hx:32: characters 2-78 : js.html.Element should be js.html.CanvasElement
But if we use unsafe casting, already will be fine:
var cans:CanvasElement = cast Browser.document.getElementById("canv");
And everything works fine, I get access and can get 2D context or make some settings like:
cans.width = cans.height = 800;
cans.style.backgroundColor = 'rgba(158, 167, 184, 0.4)';
Yes, I know, "if it works - don't fix", and I roughly understand that everything is normal, in principle I get what I need, when get cast, but can anybody explain this process for me?
What does that mean - js.html.Element should be js.html.CanvasElement? I'm only started Haxe learning (and programming in particular), I'm glad, that I can do workable things, but I want to know, why it works when it works and why not when it doesn't.
js.html.Element should be js.html.CanvasElement
This just means that the compiler expected the type CanvasElement (because that's what the type hint for the cans variable tells it), but encountered something else (Element in this case). You're trying assign the value returned by getElementById() to cans, but getElementById() returns an Element.
Since Element is less specific than CanvasElement (CanvasElement extends Element), you can't just assign an Element to a CanvasElement - who's to say it's not a DivElement, or any of the other options? This can only be decided when the code is executed / the compiler can't know this, hence the error (runtime vs compile time).
This works fine in this case because you know that the element with the ID "canv" is in fact a CanvasElement, so you can silence the compiler by telling it that you know what you're doing with a cast. It will go wrong when the returned value actually has another type.
Gama11 answer is really good. Sometimes easier just to create the canvas in code, like this simple snake.
https://try.haxe.org/#D0324
Secondly sometimes you can use another variable and let the abstractions stuff auto cast it for you, but I think you might end up still using a cast in your code.
var canvas = document.createCanvasElement();
var dom = canvas;
var style = dom.style;
// HTMLDocument is needed here so dom is infered to
// HTMLDocument type and the canvas is auto cast,
// via the use of the secondary variable.
The problem here is not really Haxe, the problem is the way HTML5 has evolved or retrospecively re-designed, Haxe is just trying to wrap something that is sort of Dynamic and inconsistant with some attempt at providing proper compiler checking and consistancy, often when types come into Haxe like loading data, you have to cast them for Haxe, but you only have one risky point, from then on the compiler can help you. You can in some cases be more explicit with your cast:
var style = cast( canvas, HTMLDocument ).style;
telling Haxe how you are expecting it to be cast. For some targets that are less dynamic Haxe can do a better job with the type systems, but the fact that js is so slack means despite the odd complication the use of Haxe is even more powerful for js coding. Stick with Haxe it's good for your general code understanding of many languages.

Error using built version of Dojo (but not the uncompressed source)

I noticed something weird when using the uncompressed source of Dojo our code runs normally without error. I tried these two from the archives so far
dojo-release-1.10.6-src and dojo-release-1.10.8-src
However when I switch to the built versions, either
dojo-release-1.10.6 or dojo-release-1.10.8
There is an error that occurs when using dojo.query
TypeError: root.getElementsByTagName is not a function
My function call looks like this
var dom_frag = domConstruct.toDom(response);
var title = dojo.query(".accordion_title", dom_frag)[0];
where response contains HTML string. (too long to post here)
EDIT: Image of debugger showing contents of 'dom_frag'
Ok, have you checked to see if the dom_frag variable is a single dom node? If the dom fragment is multiple nodes, then the dojo.query won't work, because it needs to search the children of a single dom node.
To solve this, try wrapping the toDom contents with a single node... like so:
var dom_frag = domConstruct.toDom("<div>"+response+"</div>");
var title = dojo.query(".accordion_title", dom_frag)[0];
This is, of course, a bit of a hack... but if you can't guarantee that the response will end up a single node, then you need to do it.
Make sure your root is actually a DOM element as:
the Element.getElementsByTagName() method returns a live
HTMLCollection of elements with the given tag name. The subtree
underneath the specified element is searched, excluding the element
itself. Ref.

Why can't I position the browsers focus on the svg/div element that I need?

Okay, so this is annoying the hell out of me, I have a webpage with a whole bunch of svgs on it (the svg JQuery plugin), I needed the browser to focus on certain ones(horizontally), so I made a simple function to achieve my end:
function adjustWindowPos(svg){
var left = svg.scrollLeft();
$(document).scrollLeft(left);
}
Where the svg object is just an object we get via (we draw and shit to is before we do this, so it is rendered):
$("#someDivID").svg('get');
I've tested this last night and everything worked fine, this morning I come into work and it throws an error in firebug saying:
TypeError: svg.scrollLeft is not a function
I realize that it can't find the function because the object is an SVG wrapper not a JQuery object per say...but it worked yesterday. That's what I don't get (a lot of things, no coffee today).
You need to create jQuery-object that will contain svg DOM-element, that is jQuery wrapper which you are looking for. You can do it several ways using the $ jQuery function:
pass a jQuery selector string to it: $('jquery selector');
pass a DOM object to it;
get it from already existing jQuery object, if you have one;
Let me suppose that you have an id-selector:
var svg = '#id_of_the_svg_element'
function adjustWindowPos(svg){
var $svg = $(svg); // now this is the jQuery object
var left = $svg.offset().left; // this is its horizontal position relative to the document
$(document).scrollLeft(left);
}

jQuery - Raphael - SVG - selector based on custom data

I have assigned a custom data attribute to some circles added to the Raphael canvas as follows in a each() loop:
marker.data('transaction', transaction);
How do I find elements on the canvas that have the same transaction data value?
Currently I have the code:
var found = document.querySelectorAll("[transaction='" + current_transaction +"']");
Which should return a NodeList with the elements, but it doesn't work. To retrieve the data into a variable, it is as simple as var foo = marker.data('transaction'), but obviously, this doesn't work if I want to retrieve a NodeList of the elements.
Therefore, I want my selector to be look as follows, but I cannot work out the correct syntax:
var found = document.querySelectorAll("data('transaction' = 1)");
Any help would be much appreciated
Being that Raphael must support VML, it doesn't keep data in the DOM as is normal with html5 applications. If you want to store data in the dom you must access the html node and set the attribute there...
marker.node.setAttribute('data-transaction', transaction);
Then you can then query the elements with querySelectorAll. Keep in mind this will fail on < IE8.
If you want to keep older IE support I'd recommend writing a function that iterates over your markers and returns the Raphael object when mark.data("transaction") == transaction
I think the problem is that jQuery has no access to the SVG nodes. You have to try normal Javascript. The problem could be the compatibility with older browsers if you use querySelectorAll.
Look here: http://dean.edwards.name/jsb/behavior/querySelectorAll.html
and here: http://www.w3.org/TR/selectors-api/#queryselectorall
Possible solution:
Have a look in Raphael-Doc : http://raphaeljs.com/reference.html#Element.data

node selection and manipulation out of the dom (What is jQuery's trick ?)

Hi I would like to do dom selection and manipulation out of the dom.
The goal is to build my widget out of the dom and to insert it in the dom only once it is ready.
My issue is that getElementById is not supported on a document fragment. I also tried createElement and cloneNode, but it does not work either.
I am trying to do that in plain js. I am used to do this with jQuery which handles it nicely. I tried to find the trick in jQuery source, but no success so far...
Olivier
I have done something similar, but not sure if it will meet your needs.
Create a "holding area" such as a plain <span id="spanReserve"></span> or <td id="cellReserve"></td>. Then you can do something like this in JS function:
var holdingArea = document.getElementById('spanReserve');
holdingArea.innerHTML = widgetHTMLValue;
jQuery will try to use getElementById first, and if that doesn't work, it'll then search all the DOM elements using getAttribute("id") until it finds the one you need.
For instance, if you built the following DOM structure that isn't attached to the document and it was assigned to the javascript var widget:
<div id="widget">
<p><strong id="target">Hello</strong>, world!</p>
</div>
You could then do the following:
var target;
// Flatten all child elements in the div
all_elements = widget.getElementsByTagName("*");
for(i=0; i < all_elements.length; i++){
if(all_widget_elements[i].getAttribute("id") === "target"){
target = all_widget_elements[i];
break;
}
}
target.innerHTML = "Goodbye";
If you need more than just searching by ID, I'd suggest installing Sizzle rather than duplicating the Sizzle functionality. Assuming you have the ability to install another library.
Hope this helps!
EDIT:
what about something simple along these lines:
DocumentFragment.prototype.getElementById = function(id) {
for(n in this.childNodes){
if(id == n.id){
return n;
}
}
return null;
}
Why not just use jQuery or the selection API in whatever other lib youre using? AFAIK all the major libs support selection on fragments.
If you wan tto skip a larger lib like jQ/Prototype/Dojo/etc.. then you could jsut use Sizzle - its the selector engine that powers jQ and Dojo and its offered as a standalone. If thats out of the question as well then i suppose you could dive in to the Sizzle source and see whats going on. All in all though it seems like alot of effort to avoid a few 100k with the added probaility that the code you come up with is going to be slower runtime wise than all the work pulled into Sizzle or another open source library.
http://sizzlejs.com/
Oh also... i think (guessing) jQ's trick is that elements are not out of the DOM. I could be wrong but i think when you do something like:
$('<div></div>');
Its actually in the DOM document its just not part of the body/head nodes. Could be totally wrong about that though, its just a guess.
So you got me curious haha. I took a look at sizzle.. than answer is - its not using DOM methods. It seems using an algorithm that compares the various DOMNode properties mapped to types of selectors - unless im missing something... which is entirely possible :-)
However as noted below in comments it seems Sizzle DOES NOT work on DocumentFragments... So back to square one :-)
Modern browsers ( read: not IE ) have the querySelector method in Element API. You can use that to get and element by id within a DocumentFragment.
jQuery uses sizzle.js
What it does on DocumentFragments is: deeply loop through all the elements in the fragment checking if an element's attribute( in your case 'id' ) is the one you're looking for. To my knowledge, sizzle.js uses querySelector too, if available, to speed things up.
If you're looking for cross browser compatibility, which you probably are, you will need to write your own method, or check for the querySelector method.
It sounds like you are doing to right things. Not sure why it is not working out.
// if it is an existing element
var node = document.getElementById("footer").cloneNode(true);
// or if it is a new element use
// document.createElement("div");
// Here you would do manipulation of the element, setAttribute, add children, etc.
node.childNodes[1].childNodes[1].setAttribute("style", "color:#F00; font-size:128px");
document.documentElement.appendChild(node)
You really have two tools to work with, html() and using the normal jQuery manipulation operators on an XML document and then insert it in the DOM.
To create a widget, you can use html():
$('#target').html('<div><span>arbitrarily complex JS</span><input type="text" /></div>');
I assume that's not what you want. Therefore, look at the additional behaviors of the jQuery selector: when passed a second parameter, it can be its own XML fragment, and manipulation can happen on those documents. eg.
$('<div />').append('<span>').find('span').text('arbitrarily complex JS'). etc.
All the operators like append, appendTo, wrap, etc. can work on fragments like this, and then they can be inserted into the DOM.
A word of caution, though: jQuery uses the browser's native functions to manipulate this (as far as I can tell), so you do get different behaviors on different browsers. Make sure to well formed XML. I've even had it reject improperly formed HTML fragments. Worst case, though, go back and use string concatenation and the html() method.

Categories