I'm confused as to why this is failing.
I'm trying to directly replace a DIV completely with replaceWith from jquery.
i've tried bunch of variation, but either nothing gets updated. or if i try outerhtml [object HTMLDivElement] instead of the html. (which means I'm for sure working with a node, right? which means replaceWith() should work?).
I'm iterating through an array of currently displayed 'cardObjects', and comparing it to an identically structured incoming array (it's a sorted ordered list). the object's.html is the node element. and the object's.target is pointing to the element's id.
function cardConstructor(item)
{
var cardDiv = constructElement('div', "queCard", "machine"+item.machineID+"ID"+item.ID, "");
//more html stuff gets appended to this element, but not relevant for problem
cardObject =
{
ID: item.ID,
machineID: item.machineID,
lastID: item.lastID,
nextID: item.nextID,
jobID: item.jobID,
target: cardDiv.id, //string
html: cardDiv //node
}
return cardObject;
}
// here is where the problem is -
//it is in an update loop,
//this is failing
else if(inc[y].ID != current[y].ID)
{
console.log("ids do not match, splicing and replacing");
console.log("current y target is:");
console.log(current[y].target); //334 ---console output
var updateTarget = document.getElementById(current[y].target);
console.log("old html"); //337
console.log(updateTarget); //338
console.log("new html"); //339
console.log(inc[y].html); //340
updateTarget.replaceWith(inc[y].html);
console.log("update target updated: as"); //343
console.log(updateTarget); //344
current.splice(y,1, inc[y]);
}
the console.log output is:
current y target is:
machinewindow.php:334 machine1ID775
machinewindow.php:337 old html
machinewindow.php:338 <div class="queCard" id="machine1ID775"><div class="queCardTitle" id="Titlemachine1ID775"><div class="itter" id="itterDiv+1machine1ID775">1</div><button class="completeBtn" id="complete775" value="775">complete</button></div><div class="queCardBody" id="Bodymachine1ID775">…</div><div class="queCardFooterW" id="queCardFooterWmachine1ID775">…</div></div>
machinewindow.php:339 new html
machinewindow.php:340 <div class="queCard" id="machine1ID774"><div class="queCardTitle" id="Titlemachine1ID774">…</div><div class="queCardBody" id="Bodymachine1ID774">…</div><div class="queCardFooterW" id="queCardFooterWmachine1ID774">…</div></div>
machinewindow.php:343 update target updated: as
machinewindow.php:344 <div class="queCard" id="machine1ID775"><div class="queCardTitle" id="Titlemachine1ID775">…</div><div class="queCardBody" id="Bodymachine1ID775">…</div><div class="queCardFooterW" id="queCardFooterWmachine1ID775">…</div></div>
here as shown in the console.log we get no updated div.
if i instead try updateTarget.outerHTML = inc[y].html - then I get [object HTMLDivElement] on the browser window.
I've tried a few iterations - and I'm a bit lost as to why this isn't working.
the object.html is a node, but won't replace another node with replaceWith();
I'm probably missing something.
also below is that constructElement function by request.
function constructElement(element, styleClass, type, data)
{
var ele = document.createElement(element);
ele.setAttribute("class", styleClass);
ele.setAttribute("id", type);
ele.innerHTML = data;
return ele;
}
If you want to use updateTarget.replaceWith(inc[y].html);
replace your line 335 with:
var updateTarget = $(current[y].target);
as replaceWith is a jQuery method and should be applied to jQuery object.
If you prefer using outerHTML, make sure you have a HTML string (not a node) on the right side, e.g.:
updateTarget.outerHTML = "<div>blah blah</div>";
or in your case:
updateTarget.outerHTML = inc[y].html.outerHTML;
Hope it helps.
Related
I'm confused a bit, please help me to figure out a problem.
I am using VueJS + d3.js.
I have a code that looks like this:
<template>
<div class='chart-frame-chart-comp border-black-1 border-radius-1'>
<div class='plot'>
</div>
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
name: 'chart-frame-chart-comp',
mounted () {
this.$nextTick(this.plot)
},
methods: {
plot () {
console.log(this.getPlotSize())
},
getPlotSize () {
let a = d3.select('.chart-frame-chart-comp .plot')
console.log('a', a)
let b = a.attr('tagName')
console.log('b', b)
return b
}
}
}
</script>
In getPlotSize I am trying to return an attribute of a selected element (the one with .plot class)
So in debugger, I see output:
Obviously, d3 managed to obtain a node (since it is the actual element, I was looking for plus selection return only one element that, again, I was looking for). But when d3 tries to return value of getAttribute, former returns null and hasAttribute returns false. But Chrome debugger sees that element has those attributes:
Obviously I'm missing something! So my question is: what am I doing wrong?
This is a misconception as there is no attribute tagName on your node. What you see in your console is the tagName property of the Element you selected. At the time you are selecting the element—as shown in your second screenshot— there are two attributes, namely class and data-v-391ae376. Hence, neither .getAttribute("tagName") nor D3's .attr("tagName") will return any value.
If you are interested in the element's tagName you can simply call selection.node() to get the first element in the selection and access its tagName property afterwards. In your example this will print "div".
getPlotSize () {
let a = d3.select('.chart-frame-chart-comp .plot');
console.log('a', a);
let b = a.node().tagName; // Get the DOM node and read its tagName property
console.log('b', b);
return b;
}
I have a html 'div' and I am trying to append another div to it by the following :
var myDiv = d3.select("#container")
myDiv.append("div")
And I get the following error :
myDiv .append is not a function
Any ideas ? I am using Vuex, so I instantiated the variable in the store and trying to set it to the d3 select.
I have put together a codepen that's similar to what I have : https://codepen.io/anon/pen/oMBGmd?editors=1010
const store = new Vuex.Store({
state: {
networkVariables: {
node: "",
data: [1, 2, 3, 4, 5]
},
container: ""
},
getters: {},
mutations: {}
});
console.log("line 33");
console.log(store);
var networkVariables = store.state.networkVariables;
var container = store.state.container;
container = d3.select("#container");
console.log(networkVariables);
networkVariables.node = container
.selectAll(".node")
.data(networkVariables.data);
console.log(networkVariables.node);
networkVariables.nodeEnter = networkVariables.node.append("div");
In your latest codepen, the error you mentioned is coming from the last line, where you are trying to append to the networkVariable.node directly -- without using the enter selection.
It seems to work for me when I change the last line to be:
networkVariables.nodeEnter = networkVariables.node.enter();
networkVariables.nodeEnter.append("div");
Here networkVariables.nodeEnter = networkVariables.node.append("div");, there is no append() in networkVariables.node object. I tried replacing networkVariables.node.append() with networkVariables.node[0].parentNode.append("div"), this seems to work in the snippet. can you try with this change.
The line in the codepen with the error is like #SteveR noticed the last line.
It is not the result of a d3.select() but a d3.selectAll(..).data(...). The array contains 5 empty placeholders and two fields. These are enter and exit, both functions. To convert this to a "selection" result you have to call one of these functions first before you can append().
But in the original question there should be no problem appending if the tag with the given id exists.
The codepen is not similar to the original question/example.
I have an array that contains dates. and for some reason I can't get it to show on my screen I've been debugging for a few days now and I've tracked it down to a single line, but the line has worked before and I can't figure out what the issue might be.
The array looks like this:
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"...];
It's passed as an argument from another function, but that's how it's showing in console.log().
I might be going about this the wrong way, maybe even a lot further around then I need to but this is what I've come up with:
1. function setTHead(selectItems) {
2 var formatString;
3. for (var x = 0; x < 12; x++) {
4. formatString = selectItems[x].replace(/[^0-9/-]/g, "").toString();
5. console.log(selectItems);
6. $('#datTab').append("<div id='col" + x + "' class='column'>'" + formatString + "'</div>");
7. }
8. }
the array up top is what's showing from the console.log 5 lines down.
the sixth line is what is seeming to give me issues. Nothing is put on the page at all.
I'm getting a console error saying:
jQuery.Deferred exception: selectItems is undefined setTHead#http://localhost/mySite/script.js:136:9
startUp2#http://localhost/mySite/script.js:146:5
#http://localhost/mySite/table.php:19:9
mightThrow#http://localhost/mySite/lib/jquery.js:3586:52
resolve/</process<#http://localhost/mySite/lib/jquery.js:3654:49
setTimeout handler*resolve/<#http://localhost/mySite/lib/jquery.js:3692:37
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
fire#http://localhost/mySite/lib/jquery.js:3458:21
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
ready#http://localhost/mySite/lib/jquery.js:3923:13
completed#http://localhost/mySite/lib/jquery.js:3933:9
EventListener.handleEvent*#http://localhost/mySite/lib/jquery.js:3949:9
#http://localhost/mySite/lib/jquery.js:39:9
#http://localhost/mySite/lib/jquery.js:17:3
undefined
followed by:
TypeError: selectItems is undefined
and thats pointing to line 6.
if anyone has any advice I would be very much appreciative. Thank you in advance.
EDIT: A little more code:
function startTblView(defSel) {
if (defSel === true) {
setCookie('defSel', true, 7);
} else{
setCookie('defSel', false, 7);
}
saveSelected();
window.open('table.php', '_self');
defSel = getCookie('defSel');
if (defSel) {
selectItems = getDefDates();
}else {
selectItems = reGetSelected();
}
setTHead(selectItems);
}
defSel, is a boolean passed from my last page stating whether I'm doing a default view or a custom view, the custom view is passed from saveSelected();
saveSelected is a function for just saving the selected global value as a cookie so I can pull it out on the next page.
getDefDates pulls the default values for the array
reGetSelected, gets the selected array from the cookie.
I apologize for wonky naming conventions. I'm the only one working on this site and I'm just making sure the names don't overlap.
You can do this :
HTML code
<div id="datTab"></div>
JS code
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"];
function setTHead(selectItems) {
var formatString;
$.each( selectItems, function( index, value ){
formatString = value.replace(/[^0-9/-]/g, "").toString();
$('#datTab').append("<div id='col" + index + "' class='column'>'" + value + "'</div>");
});
};
You can use $.each, its better than 'for' with javascript.
The .each() method is designed to make DOM looping constructs concise
and less error-prone. When called it iterates over the DOM elements
that are part of the jQuery object. Each time the callback runs, it is
passed the current loop iteration, beginning from 0. More importantly,
the callback is fired in the context of the current DOM element, so
the keyword this refers to the element.
I did a JsFiddle
Here.
I have a string of text that I am storing mark-up for separately. For example:
var content = {
text: "a little white rabbit hops",
style: [
{
type: "strong",
start: 0,
length: 8
},
{
type: "em",
start: 2,
length: 14
}
]
}
I am then parsing that into html, but the em tag has to be opened and closed twice to be properly formatted:
<strong>a <em>little</em></strong><em> white</em> rabbit hops
My question is: what is the best way to parse the html retrieved from the DOM to consolidate the separated em tags (or conceivably the strong tags: in my scenario either could be nested).
If I iterate over a NodeList of children (p.getElementsByTagName('em')) I would have to do multiple for loops and check the start/length of all nested tags. There has got to be an easier way, but I haven't thought of one - is there a library out there that handles this kind of formatting (or a way to to do this directly via the DOM)?
I am not using jQuery and don't want to add it to my project just for this. Any help is much appreciated!
---EDIT---
To clarify the question: this is essentially about translating the formatting into/out of the HTML, the issue is the best way to handle the tag nesting: i.e. even though there are two em child tags, there is really only one em formatted block (the end/start of em child tag 1 and 2 are contiguous)
Here are two functions for the conversion in either direction.
First the one that converts an HTML string to the content structure you described:
function htmlToContent(html) {
// The object to fill and return:
var content = {
text: '',
style: []
};
// Keep track of recently closed tags (i.e. without text following them as of yet)
var closedStyles = [];
// Recursive function
function parseNode(elem) {
var style;
if (elem.nodeType === 3) {
// This is a text node (no children)
content.text += elem.nodeValue;
// Any styles that were closed should be added to the content
// style array, as they cannot be "extended" any more
[].push.apply(content.style, closedStyles);
closedStyles = [];
} else {
// See if we can extend a style that was closed
if (!closedStyles.some(function (closedStyle, idx) {
if (closedStyle.type === elem.nodeName) {
style = closedStyle;
// Style will be extended, so it's no longer closed
closedStyles.splice(idx, 1);
return true; // exit ".some"
}
})) {
// No style could be extended, so we create a new one
style = {
type: elem.nodeName,
start: content.text.length,
length: 0
};
}
// Recurse into the child nodes:
[].forEach.call(elem.childNodes, function(child) {
parseNode(child);
});
// set style length and store it as a closed one
style.length = content.text.length - style.start;
closedStyles.push(style);
}
}
// Create a node with this html
wrapper = document.createElement('p');
wrapper.innerHTML = html;
parseNode(wrapper);
// Flush remaining styles to the result
closedStyles.pop(); // Discard wrapper
[].push.apply(content.style, closedStyles);
return content;
}
This function first injects the HTML string into a DOM wrapper element, and then recurses into the node hierarchy to build the content structure. The main idea in this code is that it first collects closed nodes in a temporary closedStyles array. Only when it becomes certain that these cannot be used anymore for a merge with an upcoming node, they are added to the content structure. This happens when a text node is encoutered. If however tags close and open again without intermediate text, the matching style is located and extracted from this closedStyles array, and reused for extension.
The function that does the opposite could be defined as follows:
function contentToHtml(content) {
var tags = [];
// Build list of opening and closing tags with the offset of injection
content.style.forEach(function (tag) {
tags.push({
html: '<' + tag.type + '>',
offset: tag.start
}, {
html: '</' + tag.type + '>',
offset: tag.start + tag.length
});
});
// Sort this list by decreasing offset:
tags.sort(function(a, b) {
return b.offset - a.offset;
});
var html = '';
var text = content.text;
// Insert opening and closing tags from end to start in text
tags.forEach(function (tag) {
// Prefix the html with the open/close tag and the escaped text that follows it
html = tag.html + textToHtml(text.substr(tag.offset)) + html;
// Reduce the text to the part that still needs to be processed
text = text.substr(0, tag.offset);
});
// Remaining text:
html = textToHtml(text) + html;
// Create a node with this html, in order to get valid html tag sequences
p = document.createElement('p');
p.innerHTML = html;
// p.innerHTML will change here if html was not valid.
return p.innerHTML;
}
This function first translates each of the styles to two objects, one representing the opening tag, and the other the closing tag. These tags are then inserted into the text at the right position (from then of the text to the beginning). Finally the trick you described yourself is applied: the resulting html is put in a dom object and taken out of it again. This way any invalid HTML tag sequence is fixed.
The function uses the textToHtml utility function, which could be defined as follows:
function textToHtml(text) {
// See http://www.w3.org/International/questions/qa-escapes#use
return text.replace('&', '&').replace('<', '<').replace('>', '>');
}
You can see it work in this fiddle, where an example HTML string is used that also includes nested tags of the same type. These are maintained.
I'm having trouble chaining ".data" to a raphael.js element.
When I try to retrieve the data later, I get "undefined".
Here's my fiddle: http://jsfiddle.net/HkNgT/9/
//Draw:
paper.circle(circleCenterX, circleCenterY, circleCenterRadius)
.attr({'fill':'blue', 'stroke':'red', 'stroke-width':2, 'opacity': 0.8})
.data({"circleCenterX":circleCenterX,
"circleCenterY":circleCenterY,
"boxYPosition":boxYPosition,
"boxXPosition":boxXPosition})
.id = name+"-circle";
//Output:
console.log("data is = ", paper.getById(name+"-circle").data("circleCenterX"));
With this code, the ".data(...)" part just doesn't work.
When I try to retrieve it (with paper.getById), it displays "data is = undefined".
Other ways that I have tried:
//Doesn't work
paper.circle....
.data("circleCenterX",circleCenterX)
.data("circleCenterY",circleCenterY)
.data("boxYPosition",boxYPosition)
.data("boxXPosition",boxXPosition);
And another:
//Doesn't work
var c = paper.circle....;
c.data("circleCenterX",circleCenterX);
c.data("circleCenterY",circleCenterY);
c.data("boxYPosition",boxYPosition);
c.data("boxXPosition",boxXPosition);
And another:
//DOES work, but gives me huge performance losses
paper.circle....;
paper.getById(name+"-circle").data("circleCenterX",circleCenterX);
paper.getById(name+"-circle").data("circleCenterY",circleCenterY);
paper.getById(name+"-circle").data("boxYPosition",boxYPosition);
paper.getById(name+"-circle").data("boxXPosition",boxXPosition);
Any help?
So, just trying to help, I've never used raphael before. I assume you're trying to retrieve the circleCenterX value?
Inspecting the data function, it looks as though it's only used for setting the value. (It only returns a value, if you're setting a value).
function (b,c){
var d = bb[this.id] = bb[this.id] || {};
if(arguments.length==1){
if(a.is(b,"object")){
for(var e in b)b[g](e)&&this.data(e,b[e]);
return this
}
eve("raphael.data.get."+this.id,this,d[b],b);
return d[b]
}
d[b]=c,eve("raphael.data.set."+this.id,this,c,b);
return this
}
(Afaict, eve is an Event framework, bundled with Raphael. Anyone know where the data goes from here?)
So this isn't going to help.
Inspecting the circle object, I was able to see/pull out a cx value, which I assume is the circleCenterX value that you're looking for...
var circ = paper.getById(name+"-circle");
console.log("baseVal is = ", circ[0].cx.baseVal.value);
console.log("animVal is = ", circ[0].cx.animVal.value);
Gives
baseVal is = 100
animVal is = 100
Is this what you're looking for?