SVG: compact way to create slightly different elements? - javascript

I'm creating several elements which are almost identical paths, with a long list of co-ordinates. Is there a compact way to create one element, and to to make slightly different copies of it?
The elements are created by 'createElementNS'. The obvious (I think) answer is to clone the first element into a new element, and to set only the attributes in the second element which have changed. This works in Chrome and IE9, but not in FF4 or Opera.
Another obvious solution is just to copy the first element into a var, and to set the changed attributes in the var. This doesn't work in Chrome or FF.
I could possibly create a new element via createElementNS, and copy all the attributes in from the old element, but I don't know of a way to cycle through all attributes, which would help.
This is an example of the almost-working clone code:
obj = svgDocument.createElementNS(svgns, "path");
obj.setAttributeNS(null, "id", "pbox1");
...lots more attributes set
svgDocument.documentElement.appendChild(obj);
// now try to clone and copy:
var obj2 = obj.cloneNode(true);
obj2.setAttributeNS(null, "id", "pbox2");
...change a few obj2 attributes
svgDocument.documentElement.appendChild(obj2);
Any ideas?
Thanks -
Al

var templateElement = document.createElement(// create template element);
var firstElement = templateElement.cloneNode(true) // the true make sure it clones all child nodes
var firstElement.setAttribute()// change what you need
and so on for as many elements as you need.

aaah.. stupid typo on my part; sorry. The code I posted above was correct, but I didn't show the other clones underneath. On the final one, I put in var obj10 = obj.cloneNode() , leaving out the 'true'. It looks like FF4 and Opera got the right answer, and Chrome and IE9 copied all the attributes anyway.

Related

Javascript .replace() method failing in IE8

Why does this not work in IE8?
var accordion = $(this),
header = accordion.find(':header')[0],
titleHTML = header.outerHTML,
innerHTML = accordion.html().replace(titleHTML, '');
The header variable is populated, but .replace() does not find the string of HTML. It works in Chrome, FF etc but IE8 does not want to play.
I tried removing the element before building the innerHTML variable but that introduced more problems further down the execution path.
Can anyone shed any light on this?
EDIT
The rewrite worked with a bit of massage.
var accordion = $(this),
header = accordion.find(':header').first(),
titleHTML = header.prop('tagName'),
titleLabel = header.text();
header.remove();
var innerHTML = accordion.html();
Essentially the two main changes were to getting the first element with [0] was a bit flaky and indeed removing the element first was the way to go.
Try this
$(this).find(':header').first().remove();
It seems that's basically what you're doing, find any header elements, get the native DOM node of the first one, then getting the outerHTML, and replacing it in this, trying to remove it, and it seems like a strange way to do it.

Adding Javascript variables to HTML elements

So, I have some code that should do four things:
remove the ".mp4" extension from every title
change my video category
put the same description in all of the videos
put the same keywords in all of the videos
Note: All of this would be done on the YouTube upload page. I'm using Greasemonkey in Mozilla Firefox.
I wrote this, but my question is: how do I change the HTML title in the actual HTML page to the new title (which is a Javascript variable)?
This is my code:
function remove_mp4()
{
var title = document.getElementsByName("title").value;
var new_title = title.replace(title.match(".mp4"), "");
}
function add_description()
{
var description = document.getElementsByName("description").value;
var new_description = "Subscribe."
}
function add_keywords()
{
var keywords = document.getElementsByName("keywords").value;
var new_keywords = prompt("Enter keywords.", "");
}
function change_category()
{
var category = document.getElementsByName("category").value;
var new_category = "<option value="27">Education</option>"
}
remove_mp4();
add_description();
add_keywords();
change_category();
Note: If you see any mistakes in the JavaScript code, please let me know.
Note 2: If you wonder why I stored the current HTML values in variables, that's because I think I will have to use them in order to replace HTML values (I may be wrong).
A lot of things have been covered already, but still i would like to remind you that if you are looking for cross browser compatibility innerHTML won't be enough, as you may need innerText too or textContent to tackle some old versions of IE or even using some other way to modify the content of an element.
As a side note innerHTML is considered from a great majority of people as deprecated though some others still use it. (i'm not here to debate about is it good or not to use it but this is just a little remark for you to checkabout)
Regarding remarks, i would suggest minimizing the number of functions you create by creating some more generic versions for editing or adding purposes, eg you could do the following :
/*
* #param $affectedElements the collection of elements to be changed
* #param $attribute here means the attribute to be added to each of those elements
* #param $attributeValue the value of that attribute
*/
function add($affectedElements, $attribute, $attributeValue){
for(int i=0; i<$affectedElements.length; i++){
($affectedElements[i]).setAttribute($attribute, $attributeValue);
}
}
If you use a global function to do the work for you, not only your coce is gonna be easier to maintain but also you'll avoid fetching for elements in the DOM many many times, which will considerably make your script run faster. For example, in your previous code you fetch the DOM for a set of specific elements before you can add a value to them, in other words everytime your function is executed you'll have to go through the whole DOM to retrieve your elements, while if you just fetch your elements once then store in a var and just pass them to a function that's focusing on adding or changing only, you're clearly avoiding some repetitive tasks to be done.
Concerning the last function i think code is still incomplete, but i would suggest you use the built in methods for manipulating HTMLOption stuff, if i remember well, using plain JavaScript you'll find yourself typing this :
var category = document.getElem.... . options[put-index-here];
//JavaScript also lets you create <option> elements with the Option() constructor
Anyway, my point is that you would better use JavaScript's available methods to do the work instead of relying on innerHTML fpr anything you may need, i know innerHTML is the simplest and fastest way to get your work done, but if i can say it's like if you built a whole HTML page using and tags only instead of using various semantic tags that would help make everything clearer.
As a last point for future use, if you're interested by jQuery, this will give you a different way to manipulate your DOM through CSS selectors in a much more advanced way than plain JavaScript can do.
you can check out this link too :
replacement for innerHTML
I assume that your question is only about the title changing, and not about the rest; also, I assume you mean changing all elements in the document that have "title" as name attribute, and not the document title.
In that case, you could indeed use document.getElementsByName("title").
To handle the name="title" elements, you could do:
titleElems=document.getElementsByName("title");
for(i=0;i<titleElems.length;i++){
titleInner=titleElems[i].innerHTML;
titleElems[i].innerHTML=titleInner.replace(titleInner.match(".mp4"), "");
}
For the name="description" element, use this: (assuming there's only one name="description" element on the page, or you want the first one)
document.getElementsByName("description")[0].value="Subscribe.";
I wasn't really sure about the keywords (I haven't got a YouTube page in front of me right now), so this assumes it's a text field/area just like the description:
document.getElementsByName("keywords")[0].value=prompt("Please enter keywords:","");
Again, based on your question which just sets the .value of the category thingy:
document.getElementsByName("description")[0].value="<option value='27'>Education</option>";
At the last one, though, note that I changed the "27" into '27': you can't put double quotes inside a double-quoted string assuming they're handled just like any other character :)
Did this help a little more? :)
Sry, but your question is not quite clear. What exactly is your HTML title that you are referring to?
If it's an element that you wish to modify, use this :
element.setAttribute('title', 'new-title-here');
If you want to modify the window title (shown in the browser tab), you can do the following :
document.title = "the new title";
You've reading elements from .value property, so you should write back it too:
document.getElementsByName("title").value = new_title
If you are refering to changing text content in an element called title try using innerHTML
var title = document.getElementsByName("title").value;
document.getElementsByName("title").innerHTML = title.replace(title.match(".mp4"), "");
source: https://developer.mozilla.org/en-US/docs/DOM/element.innerHTML
The <title> element is an invisible one, it is only displayed indirectly - in the window or tab title. This means that you want to change whatever is displayed in the window/tab title and not the HTML code itself. You can do this by changing the document.title property:
function remove_mp4()
{
document.title = document.title.replace(title.match(".mp4"), "");
}

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

Convert string into jQuery object and select inner element

I wonder if there is a way to convert string into jQuery object and select inner elements without injecting the whole string into DOM and manipulate it in there. Thanks.
If possible, please give me example of converting
<div id=a1></div>
<div id=a3></div>
And select a1 from the object variable.
This will create elements from the html and find the a1 element:
var element = $('<div id="a1"></div><div id="a3"></div>').filter('#a1').get(0);
The correct way to do this is:
var a1 = $('<div id="a1"></div><div id="a3"></div>').filter('#a1')[0];
Getting the DOM element out with [0] is equivalent to .get(0).
Update: interesting, I've never come across this corner case before but this:
var a1 = $("#a1", "<div id=a1><//div><div id=a3><//div>")[0];
doesn't work when the element is at the top level, which I consider to be a bug. I've never come across that before so I thought I'd leave it up here as a cautionary tale. Thanks to Crescent Fresh for pointing that out.

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