Javascript: Don't sum elements that are not visible - javascript

I'm trying to sum a list of values from HTML elements, but I want to EXCLUDE values are that hidden using pure JS.
HTML:
<div class="grams">1</div>
<div style="display: none;">
<div class="grams">2</div>
</div>
<div class="milligrams">100</div>
<div class="milligrams">2</div>
<br>
<div>Total:</div>
<div class="servings"></div>
JS:
window.addEventListener('load', function() {
let gramdivs = document.getElementsByClassName("grams");
let milligramdivs = document.getElementsByClassName("milligrams");
var total = 0;
for (let item of gramdivs) {
let itemPrice=parseFloat(item.textContent);
total += itemPrice;
}
for (let item of milligramdivs) {
let itemPrice=parseFloat(item.textContent);
total = total + itemPrice / 1000;
}
document.getElementsByClassName("servings")[0].innerText = total.toFixed(3);
})
https://jsfiddle.net/smhok7yd/2/
In the JS Fiddle, you can see that all the numbers are being added, including the hidden one.
The correct output should be 1.102.
Please note that I cannot change the hierarchy of the HTML.
I am relatively new to JS and have been trying to find a solution all day.

When iterating over elements, check to see if their offsetParent is null - if so, they're not visible:
const getClassValues = (className, multiplier = 1) => [...document.getElementsByClassName(className)]
.filter(elm => elm.offsetParent !== null)
.reduce((a, b) => a + (b.textContent * multiplier), 0);
document.querySelector('.servings').textContent = (
getClassValues('grams') + getClassValues('milligrams', 0.001)
);
<div class="grams">1</div>
<div style="display: none;">
<div class="grams">2</div>
</div>
<div class="milligrams">100</div>
<div class="milligrams">2</div>
<br>
<div>Total:</div>
<div class="servings"></div>

If you set display: none; on the specific grams div you can check for the property before adding it to the total:
https://jsfiddle.net/et6wzph2/28/

function isVisible(e) {
return !!( e.offsetWidth || e.offsetHeight || e.getClientRects().length );
}
window.addEventListener('load', function() {
let gramdivs = document.getElementsByClassName("grams");
let milligramdivs = document.getElementsByClassName("milligrams");
let total = 0;
for (let item of gramdivs) {
if(!isVisible(item)) continue;
let itemPrice = parseFloat(item.textContent);
total += itemPrice;
}
for (let item of milligramdivs) {
if(!isVisible(item)) continue;
let itemPrice = parseFloat(item.textContent);
total = total + itemPrice / 1000;
}
document.getElementsByClassName("servings")[0].innerText = total.toFixed(3);
})
<div class="grams">1</div>
<div style="display: none;">
<div class="grams">2</div>
</div>
<div class="milligrams">100</div>
<div class="milligrams">2</div>
<br>
<div>Total:</div>
<div class="servings"></div>

Related

data attribute grouped by value calculate sum of other data attribute

I am trying to calculate all the huishoudens that are in each provincie. For this question I created a fiddle which can be found here: http://jsfiddle.net/Lyf1sak3/1/
With this sample data:
<div data-provincie="Noord-Holland" data-huishoudens="102"></div>
<div data-provincie="Noord-Holland" data-huishoudens="1250"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="956"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="235"></div>
<div data-provincie="Groningen" data-huishoudens="495"></div>
<div data-provincie="Groningen" data-huishoudens="55"></div>
<div data-provincie="Groningen" data-huishoudens="247"></div>
<div data-provincie="Utrecht" data-huishoudens="123"></div>
<div data-provincie="Utrecht" data-huishoudens="675"></div>
And this code:
var provincies = {},
provincie;
sum = 0;
$('*[data-provincie]').each(function(i, el){
provincie = $(el).data('provincie');
if (provincies.hasOwnProperty(provincie)) {
provincies[provincie] += 1;
sum += $(this).data('huishoudens');
}
else {
provincies[provincie] = 1;
}
});
// print results
$('#result').append('<hr>');
for(var key in provincies){
$('#result').append(key + ' (' + provincies[key] + '|' + sum + ')<br>');
}
I am grouping each provincie by its own property and now I just need to calculate the other data attribute, but I am completely stuck here. I am getting either the result 675 which is the last div in the sample data or I get 2462 and I have no clue how it gets that number.
What do I need to modify to get this result:
Noord-Holland (2|1352)
Zuid-Holland (2|1191)
Groningen (3|797)
Utrecht (2|798)
Whatever answer you give it is really appreciated but please don't post answers where it requires to hard code the names of provincie like $('*[data-provincie="Noord-Holland"]');
If you know provincie before only you can create an array with all provincie and then you can use this as a key to compare it with all the div if matches you can add same to sum variable and finally append final result to your result div.
Demo Code :
//all data provinces
//var json_ = ["Noord-Holland", "Zuid-Holland", "Groningen", "Utrecht"]
var json_ = [];
$('*[data-provincie]').each(function(i, el) {
//check if in array or not
if ($.inArray($(this).data('provincie'), json_) < 0) {
json_.push($(this).data('provincie'));//push same
}
});
console.log(json_)
sum = 0;
count = 0;
//loop through keys
for (var key in json_) {
$('*[data-provincie]').each(function(i, el) {
var provincie = $(el).data('provincie');
//if key matches
if (json_[key] == provincie) {
sum += $(el).data('huishoudens');
count++;
}
});
//append result
$('#result').append(count + ' (' + json_[key] + '|' + sum + ')<br/>')
count = 0;
sum = 0 //change sum to 0 again
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div data-provincie="Noord-Holland" data-huishoudens="102"></div>
<div data-provincie="Noord-Holland" data-huishoudens="1250"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="956"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="235"></div>
<div data-provincie="Groningen" data-huishoudens="495"></div>
<div data-provincie="Groningen" data-huishoudens="55"></div>
<div data-provincie="Groningen" data-huishoudens="247"></div>
<div data-provincie="Utrecht" data-huishoudens="123"></div>
<div data-provincie="Utrecht" data-huishoudens="675"></div>
<div id="result"></div>
You could modify the function like,
Get the count attributes like,
var count = parseInt($(this).data('huishoudens'));
Then inside the condition assign it like,
if (provincies.hasOwnProperty(provincie)) {
provincies[provincie]["sum"] += count;
}
else {
provincies[provincie] = {"sum": count};
}
Working Snippet:
var provincies = {},
provincie;
sum = 0;
$('*[data-provincie]').each(function(i, el){
provincie = $(el).data('provincie');
var count = parseInt($(this).data('huishoudens'));
if (provincies.hasOwnProperty(provincie)) {
provincies[provincie]["sum"] += count;
provincies[provincie]["provinceCount"] += 1;
}
else {
provincies[provincie] = {"sum": count, "provinceCount": 1};
}
});
// print results
$('#result').append('<hr>');
for(var key in provincies){
$('#result').append(key + ' (' + provincies[key].provinceCount + '|' + provincies[key].sum + ')<br>');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<head>
<title>Course example</title>
<link rel="stylesheet" href="css/styles.css" />
</head>
<body>
<div data-provincie="Noord-Holland" data-huishoudens="102"></div>
<div data-provincie="Noord-Holland" data-huishoudens="1250"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="956"></div>
<div data-provincie="Zuid-Holland" data-huishoudens="235"></div>
<div data-provincie="Groningen" data-huishoudens="495"></div>
<div data-provincie="Groningen" data-huishoudens="55"></div>
<div data-provincie="Groningen" data-huishoudens="247"></div>
<div data-provincie="Utrecht" data-huishoudens="123"></div>
<div data-provincie="Utrecht" data-huishoudens="675"></div>
<div id="result"></div>
</body>
</html>

How can I write an array values to different paragraphs in a html document using javascript/jquery?

Please, I want to print out the contents of an array to different blocks of paragraphs in HTML using javascript or jquery. I can console.log the problem but can write all individually to different paragraphs of the HTML document I need that to appear at.my source code screenshot in js
// Latest
$(document).each(function() {
var price = [];
var oldprice = [];
var discount;
var i;
$('.price').children('p').each(function() {
price.push(this.innerHTML);
});
$('.old-price').children('p').each(function() {
oldprice.push(this.innerHTML);
});
$(function(){
for(i=0;i <= ((oldprice.length)&&(price.length));i++) {
var mainprice = price[i].replace("₦",""); //new price
mainprice = parseFloat(mainprice.replace(",",""));
var oldmainprice = oldprice[i].replace("₦",""); //oldprice
oldmainprice = parseFloat(oldmainprice.replace(",",""));
var disc = oldmainprice - mainprice;
var pectDisc = (disc / oldmainprice) * 100;
pectDisc = parseInt(pectDisc);
// console.log("-" + pectDisc + "%");
var prices = [];
var offs = [];
prices.push(pectDisc);
for(var x in prices) {
if($(".off")) {
$(".off").text("-" + prices[x] + "%");
// console.log(prices[x]);
}
}
};//end of for loop
});
});
<div class="asses-product">
<div class="pd">
<div class="img"><img src="img/Products/laptop.png" alt="product-image"></div>
<div class="product-description">
<div class="product-name"><h4>Hp Laptop Envy 14</h4></div>
<div class="price"><p>₦ 256,000</p></div>
<div class="old-price"><p>₦ 300,000</p></div>
<div class="off"></div>
</div>
</div>
<div class="pd">
<div class="img"><img src="img/Products/printer.png" alt="product-image"></div>
<div class="product-description">
<div class="product-name"><h4>Hp printer series 10</h4></div>
<div class="price"><p>₦ 12,500</p></div>
<div class="old-price"><p>₦ 18,000</p></div>
<div class="off"></div>
</div>
</div>
</div>
You can do all that with this code:
$(".product-description").each(function () {
// Get price within this container, and remove all non-digits
let price = $(".price", this).text().replace(/\D/g, "");
let oldprice = $(".old-price", this).text().replace(/\D/g, "");
let disc = oldprice - price;
let pectDisc = Math.floor((disc / oldprice) * 100);
$(".off", this).text("-" + pectDisc + "%");
});
This will treat each block with class "product-description" one by one (since they don't effect each other's result). Within those blocks you can retrieve the elements you need by limiting the scope to this.
Digits can be extracted easily by removing anything that is not a digit. The regular expression \D matches any non-digit.
Don't use parseInt to remove decimals from a number. Use Math.floor instead, which avoids the unnecessary string-conversion that parseInt applies.
Some of the errors in your code:
The end-condition of the outer for loop is wrong:
i < ((oldprice.length)&&(price.length))
This should be:
i < oldprice.length && i < price.length
... and really, both lengths should be the same, so you could have simplified to:
i < oldprice.length
Another error is the inner for loop. It does not get to the right .off element. It always selects them all, and sets them all to the same text. Instead, your code should have retrieved the right instance among the many .off elements, and only set the text of that one.
You could have fixed that by replacing that inner loop with this code:
$(".off").eq(i).text("-" + prices[x] + "%");
But all in all, I think the approach I have taken at the start of my answer is better: instead of collecting the prices in an array, just deal with each section one by one.
The error is on your for loop condition.
Your array has 2 element but your condition has 3 loop. so change <= condition to <.
for(i=0;i < ((oldprice.length)&&(price.length));i++) {
see snippet:
// Latest
$(document).each(function() {
var price = [];
var oldprice = [];
var discount;
var i;
$('.price').children('p').each(function() {
price.push(this.innerHTML);
});
$('.old-price').children('p').each(function() {
oldprice.push(this.innerHTML);
});
$(function(){
for(i=0;i < ((oldprice.length)&&(price.length));i++) {
var mainprice = price[i].replace("₦",""); //new price
mainprice = parseFloat(mainprice.replace(",",""));
var oldmainprice = oldprice[i].replace("₦",""); //oldprice
oldmainprice = parseFloat(oldmainprice.replace(",",""));
var disc = oldmainprice - mainprice;
var pectDisc = (disc / oldmainprice) * 100;
pectDisc = parseInt(pectDisc);
// console.log("-" + pectDisc + "%");
var prices = [];
var offs = [];
prices.push(pectDisc);
for(var x in prices) {
if($(".off")) {
$(".off").text("-" + prices[x] + "%");
// console.log(prices[x]);
}
}
};//end of for loop
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="asses-product">
<div class="pd">
<div class="img"><img src="img/Products/laptop.png" alt="product-image"></div>
<div class="product-description">
<div class="product-name"><h4>Hp Laptop Envy 14</h4></div>
<div class="price"><p>₦ 256,000</p></div>
<div class="old-price"><p>₦ 300,000</p></div>
<div class="off"></div>
</div>
</div>
<div class="pd">
<div class="img"><img src="img/Products/printer.png" alt="product-image"></div>
<div class="product-description">
<div class="product-name"><h4>Hp printer series 10</h4></div>
<div class="price"><p>₦ 12,500</p></div>
<div class="old-price"><p>₦ 18,000</p></div>
<div class="off"></div>
</div>
</div>
</div>

How can I compare numbers in an object against text in the dom?

I have a function that picks a random number from 1 - 6 and then appends it to the DOM. I am trying to compare that number to numbers that are currently in a div in the DOM. So I stored those numbers in a var called cards. When I console.log() the var it returns an array. I converted that array to a string and then I retrieved the text. What is the best way to go about comparing the numbers? Should I do a for loop and if any of those numbers match one of the random numbers drawn take some action? Can some one show me an example of doing this? My code is below.
$(function(){
function dice1(){
var randomNumber = Math.floor(Math.random() * 6) + 1;
$('#instructions').text(randomNumber);
return randomNumber;
}
function dice2(){
var randomNumber = Math.floor(Math.random() * 6) + 1;
$('#instructions2').text(randomNumber);
return randomNumber;
}
$('#dice1').click(function() {
dice1();
var cards = $('#cards div');
var single = cards.toString();
console.log(cards.text());
if ($('#instructions').text() == cards.text()) {
console.log('match');
}
});
$('#dice2').click(function(){
dice2();
});
});
<section id="container">
<div id="cards">
<div id="1">1</div>
<div id="2">2</div>
<div id="3">3</div>
<div id="4">4</div>
<div id="5">5</div>
<div id="6">6</div>
<div id="7">7</div>
<div id="8">8</div>
<div id="9">9</div>
</div>
<section>
<button id="dice1">Roll dice1</button>
<button id="dice2">Roll dice2</button>
<div id="instructions"></div>
<div id="instructions2"></div>
</section>
</section>
Why not store the cards directly in an array like this:
var cards = [1,2,3,4,5,6,7,8,9];
Then do the comparison like this:
var number = parseInt($("#instructions").text());
if(cards.indexOf(number) >= 0)
console.log("Number found");
else
console.log("Number not found");
If you really want to do it your way, you can do this:
$(".cards div").each(function(){
var number = parseInt($("#instructions").text());
var card = parseInt($(this).text());
if(card == number)
console.log("Number found");
});
Well you could indeed do it with a for loop:
$('#dice1').click(function() {
var random_number = dice1();
var cards = $('#cards div');
for(var i = 0; i < cards.length; i++ ) {
if($(this).html() == random_number) {
alert('Do something!');
}
}
console.log(cards.text());
if ($('#instructions').text() == cards.text()) {
console.log('match');
}
});
But you could also directly match to the ID you've set:
$('#dice1').click(function() {
var random_number = dice1();
var card = $('#cards div[id='+random_number+']');
if(card.length > 0) {
alert('Do something!');
}
});
Remember though, that the ID attribute can not start with a number, so I would create my own attribute here:
<div id="cards">
<div data-number="1">1</div>
<div data-number="2">2</div>
<div data-number="3">3</div>
<div data-number="4">4</div>
<div data-number="5">5</div>
<div data-number="6">6</div>
<div data-number="7">7</div>
<div data-number="8">8</div>
<div data-number="9">9</div>
</div>
And then use jQuery like so:
$('#dice1').click(function() {
var random_number = dice1();
var card = $('#cards div[data-number='+random_number+']');
if(card.length > 0) {
alert('Do something!');
}
});
You can do something like this:
New JS:
$(function () {
function dice1() {
var randomNumber = Math.floor(Math.random() * 6) + 1;
$('#instructions').text(randomNumber);
return randomNumber;
}
function dice2() {
var randomNumber = Math.floor(Math.random() * 6) + 1;
$('#instructions2').text(randomNumber);
return randomNumber;
}
$('#dice1').click(function () {
var num1 = dice1();
$('#cards div').each(function (index,item) {
if ($(item).text() == num1) {
$(item).css("color", "red");
}
});
$('#dice2').click(function () {
dice2();
});
});
});
Fiddle example
I just learned a way of doing it that solves it dynamically and personally I think it's easier to follow.
<div data-hook="cards" id="cards">
<div data-value="1" id="1">1</div>
<div data-value="2" id="2">2</div>
<div data-value="3" id="3">3</div>
<div data-value="4" id="4">4</div>
<div data-value="5" id="5">5</div>
<div data-value="6" id="6">6</div>
<div data-value="7" id="7">7</div>
<div data-value="8" id="8">8</div>
<div data-value="9" id="9">9</div>
</div>
$(function(){
function dice1(){
var randomNumber = Math.floor(Math.random() * 6) + 1;
$('#instructions').text(randomNumber);
return randomNumber;
}
$('#dice1').click(function() {
dice1();
$('[data-hook=cards] [data-value]').find('#instructions'); //'data-hook=cards] [data-value]' selects the element by attr'
var rolledNum = $('#instructions').text(),
selector = '[data-value=' + rolledNum + ']',
$card = $('[data-hook=cards]').find(selector);
//Instead of using an $.each or for loop to compare two numbers I am taking the random number and finding it on the div and then modifying the div.
console.log($card);
$card.css({'opacity':.2});
});
});

change a inner html id with js

how do you change a inner id with js and keep it the same id num (e.g hey1, bob2)
my js code
var obj = document.getElementById("chat").cloneNode(true);
var obj1 = document.getElementById("ch");
var obj2 = document.getElementById("chatbox");
var p = $(".chat");
var offset = p.offset();
num = num + 1;
if (num <=15) {
obj.id = obj.id + num; <--- **changing the id (this one works fine but the other two dont**
obj1.id = obj1.id + num; <--- changing the id
obj2.id = obj2.id + num; <--- changing the id
document.body.appendChild(obj);
document.body.appendChild(obj1);
document.body.appendChild(obj2);
var left = offset.left + 275;
document.getElementById("chat").style.left = left + "px";
tell me if i am doing it wrong but this was the easiest way i thought off
(ps i am a beginner at javascript)
thanks to all that try to help...
Edit
ok i clone this
<div class="chat" id="chat">
<div id="ch" class="ch">
<h2>Chat</h2></div>
<div class="chatbox" id="chatbox">
<div class="messages"></div>
<textarea id="message" class="chatinp"
rows="3" cols="27"></textarea>
<button class="send">Send</button></div>
</div>
and everytime it clones it changes the id of chat,ch and chatbox but keeping the original the same
like so...
clone1
<div class="chat" id="chat1">
<div id="ch1" class="ch">
<h2>Chat</h2></div>
<div class="chatbox" id="chatbox1">
<div class="messages"></div>
<textarea id="message" class="chatinp"
rows="3" cols="27"></textarea>
<button class="send">Send</button></div>
</div>
Clone2
<div class="chat" id="chat2">
<div id="ch2" class="ch">
<h2>Chat</h2></div>
<div class="chatbox" id="chatbox2">
<div class="messages"></div>
<textarea id="message" class="chatinp"
rows="3" cols="27"></textarea>
<button class="send">Send</button></div>
</div>
Not sure, but if I'm right, you're trying to create a new 'chatnode'. You'll have to traverse the childNodes array of the node you cloned to change id's. Try something like:
function cloneChat(){
var obj = document.getElementById("chat").cloneNode(true),
children = obj.childNodes
;
num += 1;
obj.id = obj.id+num;
if (num<16){
changeId(children,num);
}
//now appending obj to the document.body should be sufficient
document.body.appendChild(obj);
//change id recursively
function changeId(nodes, n){
for (var i=0;i<nodes.length;i=i+1){
if (nodes[i].childNodes){
changeId(nodes[i].childNodes,n);
}
if(nodes[i].id && /^ch$|^chatbox$/i.test(nodes[i].id)) {
nodes[i].id += String(n);
}
}
}
}
See this jsfiddle for a working example
Furthermore, this code won't work:
var p = $(".chat");
var offset = p.offset();
Because $(".chat") returns a list of nodes, where every node has it's own offset.
You seem to be using jQuery, so I suggest adding a 'jQuery' tag to your question. Maybe some jQuery whizzkid has a solution to offer.
In jQuery try to use
element.attr("id","newId");
See: http://api.jquery.com/attr/
How about this function?
function appendMe() {
var elementsToClone = ["chat"]; //Parent Elements to clone. This is the class name as well as the id
var childrenToHandle = new Array();
childrenToHandle["chat"] = ["ch"]; //Child elements mapping to keep sync. This is the class name as well as the id. Here we say that for the parent element chat, the inner elements to keep in sync is ch
var itemCount = 0;
for(i = 0; i < elementsToClone.length; i++) {
var refObj = document.getElementById(elementsToClone[i]);
if(refObj) {
$("." + elementsToClone[i]).each(
function() {
if(this.id.match(/\d+$/g)) {
itemCount = this.id.match(/\d+$/g);
}
}
);
var newObj = refObj.cloneNode(true);
newObj.id = elementsToClone[i] + ++itemCount;
var childrenArray = childrenToHandle[elementsToClone[i]];
if(childrenArray) {
$(childrenArray).each(
function() {
$(newObj).find("." + this).attr("id", this + itemCount);
}
);
}
document.body.appendChild(newObj);
}
}
}
Since you're already using jQuery in your code, how about:
var $obj = $("#chat").clone(),
$obj1 = obj.find("#ch"),
$obj2 = obj.find("#chatbox");
var p = $(".chat"),
offset = p.offset();
num = num + 1;
if (num <= 15) {
$obj.attr('id', $obj.attr('id') + num);
$obj1.attr('id', $obj1.attr('id') + num);
$obj2.attr('id', $obj2.attr('id') + num);
$('body').append(obj);
var newLeft = offset.left + 275;
$('#chat').css({
left: newLeft
});
}

jquery: Paste a set of elements over another set of elements / merging elements

I have 2 sets of elements:
<div class='container container1'>
<div class='colors'>
<div class='blue'></div>
<div class='red'></div>
</div>
<div class='drinks'>
<div class='soda'>coke</div>
<div class='juice'></div>
</div>
</div>
<div class='container container2'>
<div class='cars'>
<div class='sedans'></div>
<div class='vans'></div>
</div>
<div class='drinks'>
<div class='soda'>mountain dew</div>
<div class='coffee'></div>
</div>
</div>
I want to paste container1 over container2 such that any replacements are over written and any uniques to each container are put left alone and put together.
The result should be:
<div class='container container-result'>
<div class='colors'>
<div class='blue'></div>
<div class='red'></div>
</div>
<div class='cars'>
<div class='sedans'></div>
<div class='vans'></div>
</div>
<div class='drinks'>
<div class='soda'>coke</div>
<div class='juice'></div>
<div class='coffee'></div>
</div>
</div>
The elements can have any arbitrary hierarchy / depth. What's the best way to do this?
Thanks in advance.
Since your question is tagged jQuery here's a slightly shorter answer using that library:
function copy(from, to) {
from.children().each(function() {
var match = to.children("." + this.className.split(' ').join('.'));
if(match.length) {
if(match.children().length == 0) {
match.replaceWith(this);
} else {
copy($(this), match);
}
} else {
to.append(this);
}
}).end().remove();
from.remove();
}
Then you'd just call it like this:
copy($(".container1"), $(".container2"));
You can give it a try here, the result is:
<div class="container container2">
<div class="cars">
<div class="sedans"></div>
<div class="vans"></div>
</div>
<div class="drinks">
<div class="soda">coke</div>
<div class="coffee"></div>
<div class="juice"></div></div>
<div class="colors">
<div class="blue"></div>
<div class="red"></div>
</div>
</div>
Note that the class name is still container2 if you want to replace that just add this to switch the class after the copy() call:
$(".container2").toggleClass("container2 container-result");
The match is based on all classes the element contains, so if an element has class="car blue" and there's a corresponding class="blue car" it'll choose that one to overwrite.
This isn't the most efficient route since you're firing up the selector engine on the children each iteration, but unless you're doing lots of elements, it should be pretty quick.
With regard to unique merging I can't help you there, but if your app by any chance happens to be in PHP then you can use php's array_merge function to merge them before outputting the HTML.
ReplaceWith is a nice jquery function to replace aka "paste" over, it may will help you with half of your solution.
This appears to do what you wanted:
<div class='container container1'>
<div class='colors'>
<div class='blue'></div>
<div class='red'></div>
</div>
<div class='drinks'>
<div class='soda'>coke</div>
<div class='juice'></div>
</div>
</div>
<div class='container container2'>
<div class='cars'>
<div class='sedans'></div>
<div class='vans'></div>
</div>
<div class='drinks'>
<div class='soda'>mountain dew</div>
<div class='coffee'></div>
</div>
</div>
<div class='container container-result'>
</div>
<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
function getContainerArray(containers, level) {
level = level || 0;
var result = [];
for (var i=0; i<containers.length; ++i) {
var el = containers.eq(i);
var obj = { "class": el.attr("class") };
if (level == 0) {
obj.items = getContainerArray(el.children("div"), 1);
} else {
obj.text = el.text();
}
result.push(obj);
}
return result;
}
function mergeContainers(containerArray) {
var result = [];
function indexOfClass(name) {
for (var i = 0; i < result.length; ++i) {
if (result[i]["class"] == name) {
return i;
}
}
return -1;
}
for (var i = 0; i < containerArray.length; ++i) {
var obj = containerArray[i];
var name = obj["class"];
var index = indexOfClass(name);
if (index < 0) {
result.push(obj);
} else if (obj.items != null) {
result[index].items = mergeContainers(new Array().concat(result[index].items, obj.items));
}
}
return result;
}
function getHtml(objArray) {
var result = [];
for (var i = 0; i < objArray.length; ++i) {
var obj = objArray[i];
result.push("<div class=\"", obj["class"], "\">");
if (obj.text != null && obj.text != "") {
result.push(obj.text);
}
if (obj.items != null) {
result.push(getHtml(obj.items));
}
result.push("</div>");
}
return result.join("");
}
var html = getHtml(mergeContainers(getContainerArray($("div.container1>div,div.container2>div"))));
$("div.container-result").append(html);
</script>
This answer:
Does exactly what you asked for.
Handles repeated mergings, if div class container-result already exists.
Merges any number of container divs.
Uses jQuery and is more efficient than some other solutions.
See it in action at jsfiddle.net.
/*--- Get all "container" divs but exclude any "container-result" divs.
*/
var zContainers = $("div.container").not ("div.container-result");
if (zContainers && zContainers.length)
{
//--- Get or create the results div.
var zResultDiv = $("div.container-result");
if (!zResultDiv || !zResultDiv.length)
{
zResultDiv = zContainers.parent ().append ("<div class='container container-result'></div>");
zResultDiv = $("div.container-result");
}
//--- Move the container's contents to the master container, preserving order.
zContainers.each (function () {$(this).children ().appendTo (zResultDiv);} )
//--- Kill the old container(s).
zContainers.remove ();
RecursivelyMergeDivsByClass (zResultDiv);
}
function RecursivelyMergeDivsByClass (jNode)
{
/*--- Get a list of the direct-child div's class names.
Sort and winny out a list of duplicates.
*/
var zDirectChildDivs = jNode.find ("> div");
var aClassList = zDirectChildDivs.map (function () {return this.className;} ).get ();
aClassList.sort ().unshift (0);
for (var J = aClassList.length-1; J > 0; J--)
if (aClassList[J] != aClassList[J-1]) aClassList.splice (J, 1); // Delete items without duplicates.
aClassList.splice (0, 1);
/*--- For any duplicates, copy the contents into the first instance, preserving order.
For exact duplicate nodes, the first (oldest) version is kept and the remaining are discarded.
*/
for (var J = aClassList.length-1; J >= 0; J--)
{
var zDupClasses = zDirectChildDivs.filter ("." + aClassList[J]);
var zFirstDiv = zDupClasses.first ();
zDupClasses = zDupClasses.not (zFirstDiv);
zDupClasses.each (function () {$(this).children ().appendTo (zFirstDiv);} )
zDupClasses.remove ();
RecursivelyMergeDivsByClass (zFirstDiv)
}
}

Categories