I have the following dojo codes to create a surface graphics element under a div:
....
<script type=text/javascript>
....
function drawRec(){
var node = dojo.byId("surface");
// remove all the children graphics
var surface = dojox.gfx.createSurface(node, 600, 600);
surface.createLine({
x1 : 0,
y1 : 0,
x2 : 600,
y2 : 600
}).setStroke("black");
}
....
</script>
....
<body>
<div id="surface"></div>
....
drawRec() will draw a rectangle graphics first time. If I call this function again in an anchor href like this:
...
it will draw another graphics again. What I need to clean all the graphics under the div and then create again. How can I add some dojo codes to do that?
while (node.hasChildNodes()) {
node.removeChild(node.lastChild);
}
node.innerHTML = "";
Non-standard, but fast and well supported.
First of all you need to create a surface once and keep it somewhere handy. Example:
var surface = dojox.gfx.createSurface(domNode, widthInPx, heightInPx);
domNode is usually an unadorned <div>, which is used as a placeholder for a surface.
You can clear everything on the surface in one go (all existing shape objects will be invalidated, don't use them after that):
surface.clear();
All surface-related functions and methods can be found in the official documentation on dojox.gfx.Surface. Examples of use can be found in dojox/gfx/tests/.
while(node.firstChild) {
node.removeChild(node.firstChild);
}
In Dojo 1.7 or newer, use domConstruct.empty(String|DomNode):
require(["dojo/dom-construct"], function(domConstruct){
// Empty node's children byId:
domConstruct.empty("someId");
});
In older Dojo, use dojo.empty(String|DomNode) (deprecated at Dojo 1.8):
dojo.empty( id or DOM node );
Each of these empty methods safely removes all children of the node.
From the dojo API documentation:
dojo.html._emptyNode(node);
If you are looking for a modern >1.7 Dojo way of destroying all node's children this is the way:
// Destroys all domNode's children nodes
// domNode can be a node or its id:
domConstruct.empty(domNode);
Safely empty the contents of a DOM element. empty() deletes all children but keeps the node there.
Check "dom-construct" documentation for more details.
// Destroys domNode and all it's children
domConstruct.destroy(domNode);
Destroys a DOM element. destroy() deletes all children and the node itself.
const wipeOut = elm => [...elm.childNodes].forEach(child => child.remove());
wipeOut(elm);
Related
I am trying to parse with a chrome extension I am making, and replace ever instance of one word with another. This is what I have that is not working for me
function jamify() {
$("body").html().replace(/James/g,"Jamie");
}
The quick and rather dirty replacement of .html() has a couple of downsides.
it will actually replace the entire DOM structure, removing any event bindings if these are not bound 'live' on an element higher up the hierarchy
it will replace a lot more than you may expect. You should be safe with 'James' to 'Jamie', but it may get funky when 'em' wants to be named 'emmy' and suddenly certain italic texts get straightened out.
A better way is to replace only strings in actual text nodes, as jQuery is not (currently) a tag on the question, I assume vanilla javascript is a proper option.
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
while (walker.nextNode()) {
walker.currentNode.data = walker.currentNode.data.replace(/James/g, 'Jamie');
}
<!-- James -->
<div data-name="James" class="James">
James
</div>
This example will only touch the actual text element(s), the comment and both the attributes (data-name and class) will not get replaced, so it remains safe to have javascript and/or css referring to these.
If the words are in the textContent you can try:
var all = document.querySelectorAll('.test')
//using .test as a wrapper section, try using body in production as selector (in the snippets it breaks)
all.forEach(x => x.textContent = x.textContent.replace(/James/gi, "Jamie"))
// keep in mind forEach for nodes has limited support, tested in chrome
<div class="test">
<p>James is here</p>
<div >this div is James</div>
</div>
I am showing it this way to show that you have to call some function to reset the html to the newly replaced string
NOTE: This will destroy any DOM event you had attached before the replace
you can shorten this by nesting the call all into one if you wanted
function jamify() {
var str = $(".test").html();
console.log('jamify', str);
str2 = str.replace(/James/g,"Jamie");
$(".test").html(str2);
//to simplify it could be done this way too
//$(".test").html($(".test").html().replace(/James/g,"Jamie"))
}
$(document).ready(function(){
//alert('ready');
$('.inner').click(function(){console.log('inner click')})
//Yea!, my click event is all good.
jamify();
//Now all your inner click EVENT is broken so this is not good
//solution if there are any events attached in your DOM
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">
<p>James is here</p>
<div class="inner">this div is James</div>
</div>
Context:
I'm working on an app where I add elements dynamically to a container via myModule.getElement() . That will return me a template:
function getElement(){
var source = $("#entry-template").html();
var template = Handlebars.compile(source);
return template;
}
and then I'll just append it to a container.
The template contains an SVG object.
I'd like though to 'initialize' the element, before appending it to the DOM (or right after doing so), for example:
$(template).find("path, polygon, circle").attr("fill", "#ddd");
But that doesn't work, and my guess is because at that point the element hasn't been added to the DOM (please correct me if I'm wrong).
I can do this:
for (var i = numElements; i > 0; i--) {
elArray.push(myModule.getElement());
container.append(elArray[i]);
};
myModule.initializeElements();
and then in MyModule::
function initElements(){
$('.elClass').find("path, polygon, circle").attr("fill", "#ddd");
}
But it doesn't feel good. I wonder if I could change the properties of the SVG object before it's been appended to the DOM, is that possible?
I read that I could extend the append function and create a trigger for an event handler to it, but that seems overly complex to this situation.
Any ideas? I'm also not tied to this pattern/architecture, this is a very simple case and I could easily re-arrange it.
Trying to load external svgs in to a dynamically created svg but still access the properties of the loaded svgs. That is why I'm using d3.xml but haven't figured out how to integrate it in to the dynamic svg created with d3.
Code and js console here
The code produces this svg
<svg id = "svgObj">
<g class = "grp"></g>
<g class = "grp"></g>
</svg>
I'm trying to load circle.svg within each element so it looks like this
<svg id = "svgObj">
<g class = "grp"><svg id=minus>...</svg></g>
<g class = "grp"><svg id=minus>...</svg></g>
</svg>
I tried the code below but console errors that there is no appendChild method
var grps = d3.selectAll( "g" );
img = grps.appendChild( svgNode.cloneNode( true ) );
Thanks ahead
As #helderdarocha explained, you're mixing up your d3 methods with your plain Javascript methods. That answer gave you how to do it with plain Javascript methods, I'll balance that out by explaining how to do it with d3 methods.
To append a new element within each element of a d3 selection, the method name is simply append, not appendChild. The parameter to append is either a tag name (for which d3 creates a new element of that type for each element in the selection) or a function that returns an actual DOM element (the function will get called for each element in the selection with the data value and index as parameters). Since you're cloning an existing node, that's the version you want to use:
var grps = d3.selectAll( "g" );
img = grps.append( function(){return svgNode.cloneNode( true );} );
I haven't used D3 before but it seems easy to understand. The error message says that Array has no appendChild() method. I read the documentation and discovered that the selections return as double nodes (see Operating on selections) so you would have to add [0][0] (which would select the first node) to be able to use appendChild().
This selects the first node using plain DOM and produces no error (and draws a partial shape on the output):
var grps = d3.selectAll( "g" )[0][0];
Since you need to insert code in each node, you can use each() (see Control) like this:
d3.selectAll( "g" ).each(function() {
img = this.appendChild( svgNode.cloneNode( true ) );
});
I tested it on your code and it produces a black circle with a white dash in the middle. Is that what you expected?
I'm using jQuery to add an element to an embedded SVG like this:
var rect = SVG('rect');
$(rect).attr( { x: left,
y: top,
width: right - left,
height: bottom - top,
style: style } );
$(parentElement).append(rect);
parentElement could be for example $('g:first', svgRoot), where svgRoot refers to the embedded SVG element.
function SVG(elementName) {
return document.createElementNS('http://www.w3.org/2000/svg', elementName);
}
This works well, the new rectangle is shown in the browser and added to the DOM:
However, removing this rectangle fails. It is still shown in the browser and present in the DOM:
$(rect).remove();
I also tried
rect.parentNode.removeChild(rect);
which results in the error message "Uncaught TypeError: Cannot call method 'removeChild' of null".
Do you have any idea how I can fix that?
Using jQuery SVG or another plugin/framework is not possible in my project.
I ended up solving this problem using groups.
I ended up with this code :
var group = getGroupByName(name);
group.parentNode.removeChild(group);
...
function getGroupByName(name) {
var container = document.getElementById("container");
var groups = container.getElementsByTagName("g");
for(var i=0; i<groups.length; i++) {
if(groups[i].getAttributeNS(null, "name") === name) {
return groups[i];
}
}
return null;
}
Where container is my main SVG element.
This is tried and true. Works properly.
EDIT
As pointed out in the comments. You can find this fiddle that works. Similar to your example. It creates 4 rectangles and removes the 2 first ones.
If you want to remove the first element you have to specify this :
$("rect").first().remove();
Or if you want to do something with ALL of your rectangles you could approach this with something of the sort :
$("rect").each(function() {
... //could remove them here
}
Edit 2
According to last comment, as long as you have the reference to the object, you can use it's variable to remove it.
This updated fiddle will show you that using lastRect you can remove this last rectangle that was added.
I found that doing a .find("*") helped a lot, I'm guessing it flattens the DOM out and thus ignores any nesting complexities that jQuery can't handle (perhaps... this is my theory at least).
So for example this removes anything other than rect, g, svg elements.
$("svg").find("*").not("rect, g").remove();
A jSFiddle showing find() and removing svg elements
Is it possible to compute resulting css style on the element manually (without need to render it)?
Lets say I'm supposed to have an HTML structure:
<p style="some_style1">
<span style="some_style2">
<span style="some_style3">
TEXT
</span>
</span>
</p>
I know what are some_style1, some_style2, some_style3 in terms of JS object (for example i have data for each element like: {font: 'Times New Roman' 12px bold; text-align: center;})
I want to MANUALLY (without need to render in browser the whole structure) compute resulting style that will effect "TEXT".
What algorithm (or solution) should I use?
There exist browsers that don't need rendering in a window (headless browser). You can load a page and query what you want. It won't be easier than in a normal browser to obtain what you ask though.
JSCSSP is a CSS parser written in cross-browser JavaScript that could be a first step to achieve what you want from scratch or quite. Give it a stylesheet and it'll tell you what a browser would've parsed. You still must manage:
the DOM,
inheritance of styles,
determine which rules apply to a given element with or without class, id, attributes, siblings, etc
priorities of selectors
etc
Its author is D. Glazman, co-chairman of the W3C CSS group and developer of Kompozer, NVu and BlueGriffon so it should parse CSS as expected :)
The simplest thing I can think of is to wrap the whole thing in a a container that you set display: none on, and append it to the DOM. The browser won't render it, but you'll then be able to query the computed style.
Here's an example showing how jQuery can't find the style information when the structure isn't connected to the DOM, but when it is, it can:
jQuery(function($) {
// Disconnected structure
var x = $("<p style='color: red'><span style='padding: 2em'><span style='background-color: white'>TEXT</span></span></p>");
// Get the span
var y = x.find("span span");
// Show its computed color; will be blank
display("y.css('color'): " + y.css('color'));
// Create a hidden div and append the structure
var d = $("<div>");
d.hide();
d.append(x);
d.appendTo(document.body);
// Show the computed color now; show red
display("y.css('color'): " + y.css('color'));
// Detach it again
d.detach();
function display(msg) {
$("<p>").html(String(msg)).appendTo(document.body);
}
});
Live copy | source
I can't guarantee all values will be exactly right, you'll have to try it and see; browsers may defer calculating some things until/unless the container is visible. If you find that some properties you want aren't calculated yet, you may have to make the div visible, but off-page (position: absolute; left: -10000px);
I found some articles about this: Can jQuery get all styles applied to an element on Stackoverflow.
Also this one on quirksmode: Get Styles that shows the following function:
function getStyle(el,styleProp)
{
var x = document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
return y;
}
This allows you to query for style properties
Styles override each other in the order in which they're defined: So anything in some_style3 that overrides the same selector in some_style2, say, will do. Otherwise, it will just be a union of the sets of selectors.
EDIT Some selectors won't override, but instead act relatively on a previous definition, so you've got to be careful about that.