jQuery - Build object from html - javascript

My ultimate goal is to get out the information my website. I am trying to get something like this returned:
{
Goals: {
1: 'ET6',
2: 'ET10'
},
Sub-Off: 80,
Sub-On: 'ET1'
}
so I have the following markup (the huge line breaks are necessary):
<span class="stats jamie">
<img src="/client/images/icon-ball.gif" alt="Goals" width="13" height="13">
ET:6,ET:10
<img src="/client/images/suboff.gif" alt="Sub-Off" width="13" height="13">
80
<img src="/client/images/subon.gif" alt="Sub-On" width="13" height="13">
ET:1
</span>
What I have so far
$('.jamie').find('img').each(function(index){
console.info($(this).attr('alt'));
});

var stats = {};
$('.jamie img').each(function(){
var name = $(this).attr('alt');
var data = $(this)[0].nextSibling // Get the next node
.nodeValue // Get its text value
.trim() // Remove the extra spaces
.toLowerCase() // to lower case
.replace(/:/g,'') // remove colons
.split(','); // split on commas
stats[name] = data; // add to object
});
console.log(stats);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span class="stats jamie">
<img src="/client/images/icon-ball.gif" alt="Goals" width="13" height="13">
ET:6,ET:10
<img src="/client/images/suboff.gif" alt="Sub-Off" width="13" height="13">
80
<img src="/client/images/subon.gif" alt="Sub-On" width="13" height="13">
ET:1
</span>

The accepted answer actually doesn't give you what you want. Here's an example using vanilla JS, jquery isn't needed to do a simple loop. This could be used if additional data is added you would just need to change the parent element and split the data with commas.
var parent = document.querySelectorAll('.stats img');
var obj = {};
for(var i in parent){
var img = parent[i];
var key = img.alt;
if(!img.nextSibling) break;
var values = img.nextSibling.data.trim().split(',');
if(values.length > 1){
obj[key] = {};
for(var c in values){
obj[key][c] = values[c];
}
} else{
obj[key] = values[0].toString();
}
}
console.log(obj)

a = {};
$('.jamie').find('img').each(function(){
var textVal = $.trim(this.nextSibling.nodeValue).replace(/:/,'').toLowerCase();
var textValArray = textVal.split(',');
var textValObj = {};
if( textValArray.length > 1 ){
$.map(textValArray , function( val, i ) {
textValObj[ i + 1 ] = val;
});
a[$(this).attr('alt')] = textValObj;
} else {
a[$(this).attr('alt')] = textVal;
}
});
console.log(a)
.spacer{height: 3000px; display: block; width: 100%;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<span class="stats jamie">
<img src="/client/images/icon-ball.gif" alt="Goals" width="13" height="13">
ET:6,ET:10
<img src="/client/images/suboff.gif" alt="Sub-Off" width="13" height="13">
80
<img src="/client/images/subon.gif" alt="Sub-On" width="13" height="13">
ET:1
</span>

We can only guess at the full parsing rules from the short example provided.
I expect you want something like this :
var obj = {};
$("span.stats img").each(function() {
var nextNode, key, val, text, arr;
nextNode = this.nextSibling;
if(nextNode.nodeType !== 3) { // test for text node
return true; // continue
}
key = $(this).attr('alt');
text = $.trim(nextNode);
arr = text.split(',');
if (arr.length < 2) {
val = text;
} else {
val = {};
for(var i=0; i<arr.length; i++ ) {
val[i] = arr[i].replace(':', '');
}
}
obj[key] = val;
});

Related

How to find/remove last text portion of innerHTML

I have a <div> element that contains both html elements and text. I want to find/remove the last or the last nth or the nth text only portion of it.
So for example
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I had a method to delete the last text character, the first call would delete z and the second call would delete g. Or if I had a method to find the 4th character, it would return d.
It sounds like you only care about the text nodes, so probably something like this so you can just delete the nth character:
var div = document.getElementById("foo");
const getTextNodes = (el, nodes) => {
nodes = nodes || [];
for (var i = 0; i < el.childNodes.length; i++) {
var curNode = el.childNodes[i];
if (curNode.nodeName === "#text") {
if (curNode.textContent.trim().length) {
nodes.push(curNode);
}
} else {
getTextNodes(curNode, nodes);
}
}
return nodes;
}
console.log(getTextNodes(div).map((el) => el.textContent));
const deleteNthCharacter = (el, n) => {
n--; // since we want to be "1 indexed"
const nodes = getTextNodes(el);
let len = 0;
for (let i = 0; i < nodes.length; i++) {
const curNode = nodes[i];
if (len + curNode.textContent.length > n) {
curNode.textContent = curNode.textContent.substring(0, n - len) + curNode.textContent.substring(n + 1 - len);
break;
} else {
len += curNode.textContent.length;
}
}
}
deleteNthCharacter(div, 2);
deleteNthCharacter(div, 7);
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I understood your question correctly this should do the trick:
function deleteLastChar(targetId){
const target = document.getElementById(targetId);
let lastWithText = -1;
//find last child that has text set
target.childNodes.forEach((child, iter) => {
if(child.innerText != undefined && child.innerText.length > 0){
lastWithText = iter;
}
});
// exit if no valid text node was found
if(lastWithText === -1)
return;
const lastNode = target.childNodes[lastWithText];
lastNode.innerText = lastNode.innerText.slice(0, -1);
}
deleteLastChar("foo")
deleteLastChar("foo")
deleteLastChar("foo")
deleteLastChar("foo")
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I understand the question this is probably what you're looking for
let x = document.getElementById('foo').children;
function erase() {
for (let i = x.length - 1; i >=0; i--) {
if(x[i].textContent.length > 0) {
const textC = x[i].textContent;
x[i].textContent = textC.substring(0, textC.length - 1);
return;
}
}
}
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
<button onclick="erase()">Erase</button>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="foo">
<span id="bar">abcdefg</span><br>
<span id="baz">z</span><br><br>
<div id="result"></div>
<div id="result2"></div>
</div>
<script type="text/javascript">
var s = function(x){
return document.querySelector(x)
}
log = console.log;
var span1 = s("#bar")
var span2 = s("#baz")
var result = s("#result")
var result2 = s("#result2")
var res = span1.innerText.charAt(4)
// with the charAt method
result.innerText = " Result is : " +res+"\n\n"
// with regular Expression
var reg = /e/
result2.innerText = " Result2 is : " +span1.innerText.match(reg)
</script>
</body>
</html>

Inserting text content using same class multiple times

I'm currently trying to insert text content that change depending of image validation using a single class for multiple divs. Any help is appreciate!
HTML
...
<div id="trophies"><img id="trophyimage" src="//user/trophies/A.png" height="100" width="100">
<span id="text-content" class="spaner"></span></div>
<div id="trophies"><img id="trophyimage" src="//user/trophies/B.png" height="100" width="100">
<span id="text-content" class="spaner"></span></div>
<div id="trophies"><img id="trophyimage" src="//user/trophies/C.png" height="100" width="100">
<span id="text-content" class="spaner"></span></div>
Right now using the next Javascript it's inserting the text content but it only does it once per ".spanner" class, not in the rest.
JavaScript
var trophy = document.getElementById("trophyimage");
if(trophy.src == "...//user/trophies/A.png"){
var x = document.getElementsByClassName("spaner")[0];
x.textContent = "Trophy A";
}
else if (trophy.src == "...//user/trophies/B.png"){
var x = document.getElementsByClassName("spaner")[0];
x.textContent = "Trophy B";
}
else{ var x = document.getElementsByClassName("spaner");
x.textContent = "Null";
}
I'm trying to figure out how to make it work using something like this:
JavaScript
var trophiestext = Array.from(document.querySelectorAll("spaner"));
trophiestext.forEach(function(troph) {
var trophy = document.getElementById("trophyimage");
if(trophy.src == "...//user/trophies/A.png"){
var x = document.getElementsByClassName("spaner");
x.textContent = "Trophy A";
}
else if (trophy.src == "...//user/trophies/B.png"){
var x = document.getElementsByClassName("spaner");
x.textContent = "Trophy B";
}
else{ var x = document.getElementsByClassName("spaner");
x.textContent = "Null";
}
}
Thanks in advance!
First off, there is a problem, multiple HTML elements cannot share the same id attribute, you must switch them for classes, also "//user/trophies/A.png" is probably not a valid directory
HTML:
<div class="trophies">
<img class="trophyimage" src="../user/trophies/A.png" height="100" width="100">
<span class="text-content spanner"></span>
</div>
<div class="trophies">
<img class="trophyimage" src="../user/trophies/B.png" height="100" width="100">
<span class="text-content spanner"></span>
</div>
<div class="trophies">
<img class="trophyimage" src="../user/trophies/C.png" height="100" width="100">
<span class="text-content spanner"></span>
</div>
Now, JavaScript can handle your HTML much better
Javascript:
// Don't forget the dot before the word trophies
const trophies = document.querySelectorAll('.trophies')
trophies.forEach(element => {
const img = element.querySelector('.trophyimage')
const src = img.getAttribute('src')
const span = element.querySelector('.spanner')
// change for the src to fit your files
if (src === '../user/trophies/A.png') span.innerText = 'Trophy A'
else if (src === '../user/trophies/B.png') span.innerText = 'Trophy B'
else span.innerText = 'Null' // Actually writes the word null, for no text use empty quotes
})
If you need more help, just reply to this answer :)
Use classes, add descriptions based on that and then add a generic description to those not found.
let trophyDesc = [{
name: "type-a",
description: "Trophy A"
}, {
name: "type-b",
description: "Trophy B"
}, {
name: "type-c",
description: "Trophy C"
}, {
name: "type-unknown",
description: "Trophy Grand"
}];
const result = trophyDesc.find(({
name
}) => name === 'type-unknown');
//console.log(result.description);
let trophies = document.getElementById("trophies");
let trophylist = trophies.getElementsByClassName("trophy");
//console.log(trophylist);
for (var i = 0, len = trophylist.length | 0; i < len; i = i + 1 | 0) {
let t = trophylist[i];
for (let d of trophyDesc) {
let hasClass = t.classList.contains(d.name);
if (hasClass) {
let e = t.querySelector(".trophy-text-content");
e.textContent = d.description;
t.classList.add('found');
break;
}
}
}
// now do not found ones
const f = trophies.querySelectorAll(`.trophy:not(.found)`);
for (let n of f) {
n.querySelector(`.trophy-text-content`).textContent = result.description;
}
console.log(f.length);
.trophyimage {
height: 100px;
width: 100px;
}
<div id="trophies">
<div class="trophy type-a"><img class="trophyimage" src="//user/trophies/A.png" alt="a">
<span class="trophy-text-content">A</span></div>
<div class="trophy type-b"><img id="trophyimage" src="//user/trophies/B.png" alt="b">
<span class="trophy-text-content">B</span></div>
<div class="trophy type-c"><img id="trophyimage" src="//user/trophies/C.png" alt="c">
<span class="trophy-text-content">C</span></div>
<div class="trophy type-u"><img id="trophyimage" src="//user/trophies/u.png" alt="u">
<span class="trophy-text-content">U</span></div>
</div>

Get DOM path from a node to another

I need a method that, taken as parameters two nodes (node1 and node2), returns the minimum path that leads to node2 from node1.
Ideally, it returns an array of nodes, but for the moment it's OK to a string. So for example:
P
/ \
#text U
/ \
B I
| |
#text #text
function foo(node1, node2) {
...
}
when I run it in this way, for example on the nodes P (root) and B:
var res = foo(P, B);
console.log(res);
I obtain:
res = Array[3] {
0: P (class=..., id=...)
1: U (class=..., id=...)
2: B (class=..., id=...)
}
or, in the form of string:
res = "P(class=..., id=...) > U(class=..., id=...) > B(class=..., id=...)";
If the nodes have attributes (such as id or class), then returns even those (as in the example).
I searched the internet methods that did similar things but I found only methods that return the full path of the entire document and not between two nodes.
For example, I tried this method doesn't work for me because it returns the full path of a single node.
function getDomPath(el) {
var stack = [];
while ( el.parentNode != null ) {
console.log(el.nodeName);
var sibCount = 0;
var sibIndex = 0;
for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
var sib = el.parentNode.childNodes[i];
if ( sib.nodeName == el.nodeName ) {
if ( sib === el ) {
sibIndex = sibCount;
}
sibCount++;
}
}
if ( el.hasAttribute('id') && el.id != '' ) {
stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
} else if ( sibCount > 1 ) {
stack.unshift(el.nodeName.toLowerCase() + ':eq(' + sibIndex + ')');
} else {
stack.unshift(el.nodeName.toLowerCase());
}
el = el.parentNode;
}
return stack.slice(1); // removes the html element
}
Another thing, I would use pure JavaScript, no jQuery.
I have no idea how to do what I need, a your help would be greatly appreciated.
Thank you
<!DOCTYPE html>
<html>
<script>
window.onload = function() {
console.log(min_path(
document.getElementById("4"),
document.getElementById("9")
));
};
function min_path(node1, node2) {
if(node1 === node2) {
return node1;
}
var node_1_ancestors = get_ancestors(node1);
var node_2_ancestors = get_ancestors(node2);
var divergent_index = 0;
while(node_1_ancestors[divergent_index] === node_2_ancestors[divergent_index]) {
divergent_index++;
}
var path = [];
for(var i = node_1_ancestors.length - 1; i >= divergent_index - 1; i--) {
path.push(node_1_ancestors[i]);
}
for(var i = divergent_index; i < node_2_ancestors.length; i++) {
path.push(node_2_ancestors[i]);
}
return path;
}
function get_ancestors(node) {
var ancestors = [node];
while(ancestors[0] !== null) {
ancestors.unshift(ancestors[0].parentElement);
}
return ancestors;
}
</script>
</head>
<body>
<div id="0">
<div id="1">
<div id="2">
<span id="3"></span>
<span id="4">node1</span>
</div>
<div id="5">
<p id="6"></p>
<span id="7">
<div id="8">
<div id="9">node2</div>
<div id="10"></div>
</div>
</span>
</div>
</div>
<div id="11"></div>
</div>
</body>
</html>
Edit: It was going in to an infinite loop when the nodes were equal, so I added a check for that.

Looking for a javascript solution to reorder divs

I have some divs in the page that show different things of the same kind, for example offers, now offers have ending time, and also posted time, if the user wants to order by ending time, or posted time, they should be re ordered.
I'm looking for a javascript solution that could do that, any particular libraries under Ext JS , or JQuery would work
Here is how these divs look like
<div data-sortunit="1" data-sort1="40" data-sort2="156" data-sort3="1"
data-sort4="1317620220" class="item">
</div>
<div data-sortunit="2" data-sort1="30" data-sort2="116" data-sort3="5"
data-sort4="1317620220" class="item">
</div>
<div data-sortunit="3" data-sort1="10" data-sort2="157" data-sort3="2"
data-sort4="1317620220" class="item">
</div>
So I wanna be able to sort these divs based on data-sortN, N being an integer
Edit: OK, now that you've supplied some HTML, here's javascript code that will sort that specific HTML by the desired column number:
function sortByDataItem(containerID, dataNum) {
var values = [];
$("#" + containerID + " .item").each(function(index) {
var item = {};
item.index = index;
item.obj = this;
item.value = $(this).data("sort" + dataNum);
values.push(item);
});
values.sort(function(a, b) {return(b.value - a.value);});
var container = $("#" + containerID);
for (var i = 0; i < values.length; i++) {
var self = $(values[i].obj);
self.detach();
container.prepend(self);
}
return;
}
$("#sort").click(function() {
var sortValue = $("#sortColumn").val();
if (sortValue) {
sortValue = parseInt(sortValue, 10);
if (sortValue && sortValue > 0 && sortValue <= 3) {
sortByDataItem("container", sortValue);
return;
}
}
$("#msg").show(1).delay(5000).fadeOut('slow');
});
You can see it work here in a jsFiddle: http://jsfiddle.net/jfriend00/JG32X/
Since you've given us no HTML to go on, I've made my own HTML and shown you how you can use jQuery to sort:
HTML:
<button id="sort">Sort</button><br>
<div id="productList">
<div class="row"><div class="productName">Popcorn</div><div class="price">$5.00</div></div>
<div class="row"><div class="productName">Peanuts</div><div class="price">$4.00</div></div>
<div class="row"><div class="productName">Cookie</div><div class="price">$3.00</div></div>
<div class="row"><div class="productName">Beer</div><div class="price">$5.50</div></div>
<div class="row"><div class="productName">Soda</div><div class="price">$4.50</div></div>
</div>
Javascript (run after page is loaded):
$("#sort").click(function() {
var prices = [];
// find all prices
$("#productList .price").each(function(index) {
var str = $(this).text();
var item = {};
var matches = str.match(/\d+\.\d+/);
if (matches && matches.length > 0) {
// parse price and add it to the prices array
item.price = parseFloat(matches[0]);
item.row = $(this).closest(".row").get(0);
item.index = index;
prices.push(item);
}
});
// now the prices array has all the prices in it
// sort it using a custom sort function
prices.sort(function(a, b) {
return(a.price - b.price);
});
// now pull each row out and put it at the beginning
// starting from the end of the prices list
var productList = $("#productList");
for (var i = prices.length - 1; i >= 0; i--) {
var self = $(prices[i].row);
self.detach();
productList.prepend(self);
}
});
And, a jsFiddle that shows it in action: http://jsfiddle.net/jfriend00/vRdrA/.
I made a tiny jqueryPlugin out of jfriend00's answer:
(function($){
$.fn.sortChildrenByDataKey = function(key, desc){
    var i, els = this.children().sort(function(a, b) {return (desc?1:-1)*($(a).data(key) - $(b).data(key));});
    for (i = 0; i < els.length; i++) {
        this.prepend($(els[i]).detach());
    }
return this;
};
})(jQuery);
Your HTML:
<div id="myContainer">
<div data-myKey="4"> ... </div>
<div data-myKey="2"> ... </div>
...
</div>
Usage:
$('div#myContainer').sortChildrenByDataKey('myKey', true_or_false);
The children of the container can be any Elements. Its only important, that they are immediate children and have data-X key.
Thank you, jfriend00!!

Bug in javascript function

I've got a bug when I'm using a javascript function : my function displays the content of a div element but when I uncomment some code it doesn't work anymore.
Someone has any idea why ?
function traverse(){
var root=document.getElementById('tree0').childNodes;
for(var i=0;i<root.length;i++) {
var lis = root[i];
var number =0;
for (var member in lis) {
output.innerHTML+=lis[member];
/*var assertion = lis[member];
var result = assertion.indexOf("Bookmarks menu");
if(result != -1) {
output.innerHTML+='Test';
}*/
}
}
}
thanks,
Bruno
You may get more that you expect when you do for ... in ...
Is THIS what you want? http://jsfiddle.net/bM9Bn/
<div id="tree0">
<div id="bla">
bla
</div>
<div id="Bookmarks menu">
Bookmarks Menu
</div>
</div>
<hr />
<div id="output"></div>
<script>
var output = document.getElementById("output")
function traverse(){
var root=document.getElementById('tree0').childNodes;
for(var i=0;i<root.length;i++) {
var lis = root[i];
var number =0;
for (var member in lis) {
// output.innerHTML+="["+member+":"+lis[member]+"]";
if (member == "id" || member == "textContent") {
output.innerHTML+="["+member+":"+lis[member]+"]";
var assertion = lis[member];
// the typeof test not needed if we only process textContent and ID
if (typeof assertion == "string") {
var result = assertion.indexOf("Bookmarks menu");
if(result != -1) {
output.innerHTML+='<span style="color:red">Test</span>';
}
}
}
}
}
}
traverse()
</script>
Just looking quickly- var and if statement should both us resultat?
var resultat = assertion.indexOf("Bookmarks menu");
if(result*at* != -1) {
You are checking the 'result' variable, but setting the 'resultat' variable. Try using resultAt in the conditional, and I think it will work for you.

Categories

Resources