Loop through an SVG file in Javascript - javascript

I'm trying to go through a rather basic SVG document with four <rects>, each with a unique ID. I want to add those elements to an array.
This is what I have...
// event listner to make sure the page has loaded before running the scrupt
window.addEventListener("load", function(){
// gets an SVG object from the inline HTML
var svgObject = document.getElementById('rectTestSvg').contentDocument;
var elementList = [];
for(var i = 0; i < svgObject.numElements; i++){
if(svgObject[i].id('*_rect') === true)
{
elementList.push(svgObject.getElementAt(i));
}
}
console.log(elementList);
});
It doesn't work past getting the svgObject, but hopefully it at least helps illustrate the idea.
Any help anyone could throw my way would be really appreciated

In HTML5, SVG elements and their content are "just DOM elements", so queryselect the elements directly, and then form an array directly off of the result:
var myRects = Array.from(svgObject.querySelectorAll("rect"));
Done.
And the reason you want Array.from is because query selections are NodeList objects - while they're static (in this particular case, but most definitely not always so read the function documentation for functions you use!) they do not have any of the array functions that makes working with lists actually easy (map, filter, etc) so we turn it into an Array for writing normal code.
Do note that if your SVG object lives inside an iframe, you're almost guaranteed to not "just have access" to it, in which case you'll need to make sure the SVG document loads its own script that can talk to the parent page through a window.postMessage() channel (or websocket, but that's overkill).

Related

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.

How to replace a graphic with given file in InDesign via script?

I need to replace some images in an InDesign document with a given file. This happens using the InDesign server, but scripting is almost the same as with regular InDesign, except no user interaction is possible.
What I have is a InDesign Document, the ID of an Rectangle containing some image and the Path to a new image that should replace the image.
The image should be replaced, but the settings like FitOptions etc. should stay the same. Also, the new file shall be embedded in the InDesign Document. There is already some code that sort of works:
function changeImages(doc) {
var arrayLength = changeImage.length;
for (var i = 0; i < arrayLength; i++) {
var fr = doc.textFrames.itemByID(1 * changeImage[i].id);
if (!fr)
continue;
var file = File(imagePath + changeImage[i].file);
fr.place(file);
fr.fit (FitOptions.CONTENT_TO_FRAME);
fr.fit (FitOptions.PROPORTIONALLY);
fr.fit (FitOptions.CENTER_CONTENT);
}
}
This doesn't seem right. Why is it using doc.textFrames when the object is a rectangle? I am actually confused this even works.
Also it just sets some FitOptions, but I want to keep the existing.
I am very new to InDesign scripting, so I am lost here. I am reading the docs and other resources, but I am confused. e.g why is there doc.textFrames.itemByID but nothing like that for other Frames? Do I have to iterate doc.allPageItems and compare ids?
itemByID is a method available for all pageItems and both textFrames and rectangles are subclass of pageItem. So you have access to this method from both, and it'll give the same result. You should be able to use doc.rectangles.itemByID as well. See: http://www.indesignjs.de/extendscriptAPI/indesign11/#Rectangles.html#d1e201999__d1e202138
But you are right that the description is a bit confusing, it says:
Returns the TextFrame with the specified ID.
which is obviously not the case. If you already have the IDs you want to target, you could use doc.pageItems.itemByID, which is maybe less confusing, since basically you're looking for pageItems when using itemByID.
As for fitting options, they are a property of your rectangle object, so placing a new image shouldn't change the fitting options. If you want to keep the same, simply remove the calls to fit(). See in property list of Rectangle, frameFittingOptions: http://www.indesignjs.de/extendscriptAPI/indesign11/#Rectangle.html
Josef,
I've had the same problem with InDesign CS4 with keeping the original FitOptions. I was never able to figure out how to get the settings currently being used in InDesign CS4.
To get around the problem what I did was to set the value in the Fitting on Empty Frame in the Frame Fitting Options in the InDesign document.
Then in code I used that setting, something like this:
changeImages (app.activeDocument);
function changeImages(doc)
{
with(doc)
{
var rec = doc.rectangles.itemByID(207);
var file = new File("c:\\new_image.png");
rec.place(file);
rec.fit(rec.frameFittingOptions.fittingOnEmptyFrame);
}
}

Jquery to pure javascript and how the intepreter looks up with dom for elements

I have a couple of questions about the inner workings of JavaScript and how the interpreter handles certain queries
The following JQuery will correctly get all the images that contain the word "flowers" in the src
$("img[src*='flowers']");
Jquery makes this very simple but what the pure javascript version?
We have a very large DOM. I take it if I do $("*[src*='flowers']") this will greatly affect performance (wildcard element). I'm interested in what the Javascript interpreter does differently between $("img[src*='flowers']") and $("*[src*='flowers']")
Well, the clearest way to explain the difference is to show you how you'd write both DOM queries in plain JS:
jQuery's $("img[src*='flowers']"):
var images = document.getElementsByTagName('img');//gets all img tags
var result = [];
for (var i = 0; i < images.length;i++)
{
if (images[i].getAttribute('src').indexOf('flowers') !== -1)
{//if img src attribute contains flowers:
result.push(images[i]);
}
}
So as you can see, you're only searching through all img elements, and checking their src attribute. If the src attribute contains the substring "flowers", the add it to the result array.
Whereas $("[src*='flowers']") equates to:
var all = document.getElementsByTagName('*');//gets complete DOM
var result = [];
for (var i =0; i <all.length; i++)
{
if (all[i].hasAttribute('src') && all[i].getAttribute('src').indexOf('flowers') !== -1)
{//calls 2 methods, for each element in DOM ~= twice the overhead
result.push(all[i]);
}
}
So the total number of nodes will be a lot higher than just the number of img nodes. Add to that the fact that you're calling two methods (hasAttribute and getAttibute) for all img elements (thanks to short-circuit evaluation, all elements that don't have an src attribute, the getAttribute method won't be called) there's just a lot more going on behind the scenes in order for you to get the same result.
note:
I'm not saying that this is exactly how jQuery translates the DOM queries for you, it's a simplified version, but the basic principle stands. The second version (slower version) just deals with a lot more elements than the first. That's why it's a lot slower, too.
When you use *[src..] you will try to find all elements from the page, but when you use $("img[src..]") the search is restricted to img elements, like this: imgs = document.getElementsByTagName("img")
Heres a JSFiddle getting those images using pure javascript.
Edit:
turn console on so you can see the return from console.log
The direct JavaScript methods are document.querySelector or document.querySelectorAll. The problem with those is that they are not supported in all browsers, jQuery (through SizzleJS) provides a browser compatible way of doing these things. SizzleJS delegates to document.querySelectorAll if it is available, and it falls back on other mechanisms when it is not available. So unless you want to write the fall back code yourself, it's probably best to stick with something like SizzleJS, which provides the selector functionality without the overhead of jQuery.

How do I get my widget not to crash if there is no value in a xml node?

I'm getting an xml file and want to get data from it.
The source of the xml doesn't really matter but what I;ve got to get a certain field is:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
Now this works like a charm, EXCEPT when there is no value in the node. So if the structure is like this:
<xml>
<one>
<two>nodeValue</two>
</one>
<one>
<two></two>
</one>
</xml>
the widget will crash on the second 'one' node, because there is no value in the 'two' node. The console says:
TypeError: tracks.item(i).childNodes.item(4).childNodes.item(0) has no properties
Any ideas on how to get the widget to just see empty as an empty string (null, empty, or ""), instead of crashing? I'm guessing something along the lines of data, getValue(), text, or something else.
using
var track= xmlDoc.getElementsByTagName('track')[0];
var info= track.getElementsByTagName('artist')[0];
var value= info.firstChild? info.firstChild.data : '';
doesn't work and returns "TypeError: track has no properties". That's from the second line where artist is called.
Test that the ‘two’ node has a child node before accessing its data.
childNodes.item(i) (or the JavaScript simple form childNodes[i]) should generally be avoided, it's a bit fragile relying on whitespace text nodes being in the exact expected place.
I'd do something like:
var tracks= xmlDoc.getElementsByTagName('track')[0];
var track= tracks.getElementsByTagName('one')[0];
var info= track.getElementsByTagName('two')[0];
var value= info.firstChild? info.firstChild.data : '';
(If you don't know the tagnames of ‘one’ and ‘two’ in advance, you could always use ‘getElementsByTagName('*')’ to get all elements, as long as you don't need to support IE5, where this doesn't work.)
An alternative to the last line is to use a method to read all the text inside the node, including any of its child nodes. This doesn't matter if the node only ever contains at most one Text node, but can be useful if the tree can get denormalised or contain EntityReferences or nested elements. Historically one had to write a recurse method to get this information, but these days most browsers support the DOM Level 3 textContent property and/or IE's innerText extension:
var value= info.textContent!==undefined? info.textContent : info.innerText;
without a dtd that allows a one element to contain an empty two element, you will have to parse and fiddle the text of your xml to get a document out of it.
Empty elements are like null values in databases- put in something, a "Nothing" or "0" value, a non breaking space, anything at all- or don't include the two element.
Maybe it could be an attribute of one, instead of an element in its own right.
Attributes can have empty strings for values. Better than phantom elements .
Yahoo! Widgets does not implement all basic javascript functions needed to be able to use browser-code in a widget.
instead of using:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
to get values it's better to use Xpath with a direct conversion to string. When a string is empty in Yahoo! Widgets it doesn't give any faults, but returns the 'empty'. innerText and textContent (the basic javascript way in browsers, used alongside things like getElementsByTagName) are not fully (or not at all) implemented in the Yahoo! Widgets Engine and make it run slower and quite awfully react to xmlNodes and childNodes. an easy way however to traverse an xml Document structure is using 'evaluate' to get everything you need (including lists of nodes) from the xml.
After finding this out, my solution was to make everything a lot easier and less sensitive to faults and errors. Also I chose to put the objects in an array to make working with them easier.
var entries = xmlDoc.evaluate("lfm/recenttracks/track");
var length = entries.length;
for(var i = 0; i < length; i++) {
var entry = entries.item(i);
var obj = {
artist: entry.evaluate("string(artist)"),
name: entry.evaluate("string(name)"),
url: entry.evaluate("string(url)"),
image: entry.evaluate("string(image[#size='medium'])")
};
posts[i] = obj;
}

jquery: fastest DOM insertion?

I got this bad feeling about how I insert larger amounts of HTML.
Lets assume we got:
var html="<table>..<a-lot-of-other-tags />..</table>"
and I want to put this into
$("#mydiv")
previously I did something like
var html_obj = $(html);
$("#mydiv").append(html_obj);
Is it correct that jQuery is parsing html to create DOM-Objects ? Well this is what I read somewhere (UPDATE: I meant that I have read, jQuery parses the html to create the whole DOM tree by hand - its nonsense right?!), so I changed my code:
$("#mydiv").attr("innerHTML", $("#mydiv").attr("innerHTML") + html);
Feels faster, is it ? And is it correct that this is equivalent to:
document.getElementById("mydiv").innerHTML += html ? or is jquery doing some additional expensive stuff in the background ?
Would love to learn alternatives as well.
Try the following:
$("#mydiv").append(html);
The other answers, including the accepted answer, are slower by 2-10x: jsperf.
The accepted answer does not work in IE 6, 7, and 8 because you can't set innerHTML of a <table> element, due to a bug in IE: jsbin.
innerHTML is remarkably fast, and in many cases you will get the best results just setting that (I would just use append).
However, if there is much already in "mydiv" then you are forcing the browser to parse and render all of that content again (everything that was there before, plus all of your new content). You can avoid this by appending a document fragment onto "mydiv" instead:
var frag = document.createDocumentFragment();
frag.innerHTML = html;
$("#mydiv").append(frag);
In this way, only your new content gets parsed (unavoidable) and the existing content does not.
EDIT: My bad... I've discovered that innerHTML isn't well supported on document fragments. You can use the same technique with any node type. For your example, you could create the root table node and insert the innerHTML into that:
var frag = document.createElement('table');
frag.innerHTML = tableInnerHtml;
$("#mydiv").append(frag);
What are you attempting to avoid? "A bad feeling" is incredibly vague. If you have heard "the DOM is slow" and decided to "avoid the DOM", then this is impossible. Every method of inserting code into a page, including innerHTML, will result in DOM objects being created. The DOM is the representation of the document in your browser's memory. You want DOM objects to be created.
The reason why people say "the DOM is slow" is because creating elements with document.createElement(), which is the official DOM interface for creating elements, is slower than using the non-standard innerHTML property in some browsers. This doesn't mean that creating DOM objects is bad, it is necessary to create DOM objects, otherwise your code wouldn't do anything at all.
The answer about using a DOM fragment is on the right track. If you have a bunch of html objects that you are constant inserting into the DOM then you will see some speed improvements using the fragment. This post by John Resig explains it pretty well:
http://ejohn.org/blog/dom-documentfragments/
The fastest way to append items
The fastest way to append to the DOM tree is to buffer all of your append in to a single DOM fragment, then append the dom fragment to the dom.
This is the method I use in my game engine.
//Returns a new Buffer object
function Buffer() {
//the framgment
var domFragment = document.createDocumentFragment();
//Adds a node to the dom fragment
function add(node) {
domFragment.appendChild(node);
}
//Flushes the buffer to a node
function flush(targetNode) {
//if the target node is not given then use the body
var targetNode = targetNode || document.body;
//append the domFragment to the target
targetNode.appendChild(domFragment);
}
//return the buffer
return {
"add": add,
"flush": flush
}
}
//to make a buffer do this
var buffer = Buffer();
//to add elements to the buffer do the following
buffer.add(someNode1);
//continue to add elements to the buffer
buffer.add(someNode2);
buffer.add(someNode3);
buffer.add(someNode4);
buffer.add(someN...);
//when you are done adding nodes flush the nodes to the containing div in the dom
buffer.flush(myContainerNode);
Using this object i am able to render ~1000 items to the screen ~40 times a second in firefox 4.
Here's a use case.
For starters, write a script that times how long it takes to do it 100 or 1,000 times with each method.
To make sure the repeats aren't somehow optimized away--I'm no expert on JavaScript engines--vary the html you're inserting every time, say by putting '0001' then '0002' then '0003' in a certain cell of the table.
I create a giant string with and then append this string with jquery.
Works good and fast, for me.
You mention being interested in alternatives. If you look at the listing of DOM-related jQuery plugins you'll find several that are dedicated to programatically generating DOM trees. See for instance SuperFlyDom or DOM Elements Creator; but there are others.

Categories