Jquery: Count node separation in xml - javascript

I'm loading an xml document using JavaScript (Jquery $.ajax).
I need to be able to count the number of branches (b) separating 2 text nodes (s).
E.g.
<b n="Archaea">
<s>Archaea</s>
<b n="Eubacteria">
<s>Cyanobacteria</s>
<s>Spirochaete</s>
<b n="Seaweeds">
<s>Red rags</s>
<s>Calliblepharis</s>
</b>
</b>
<b n="Land plants">
<s>Liverwort</s>
<s>Moss</s>
<s>Bracken fern</s>
<b n="Seed plants">
<s>Scots pine</s>
<s>Ginko</s>
<s>Welwitschia</s>
</b>
</b>
</b>
So, how many branches is 'Scots pine' away from 'Calliblepharis', for example. In this case, the answer would be 4 (Seed plants > Land plants > Archaea > Eubacteria > Seaweeds).
I also need to calculate the 'closest common ancestor' between 2 elements. For example, between 'Scots pine' and 'Ginko' it would be 'Bracken fern' (because Bracken fern is the closest species to the branch that contains 'Scots pine' and 'Ginko'). I'm really not sure how this would work when the 2 elements are very far from each other in different branches.
Sorry if I'm using the wrong language here. Hope it makes sense.

Sorry about the late reply.
I've set up a a demo at jsbin
Hopefully it's fairly self explanatory but if not ask me.
For the xhr bit you need to have an file.xml in the same directory as the page.
This is the main function that gets the distance between the branches
function elementDistance(elem1, elem2) {
var steps = 0;
//the parent elements are the branches
var first = elem1.parentElement;
var second = elem2.parentElement;
//if the elements are at different depths you need to even them up
//and count each time as a step
if (elem1.depth() > elem2.depth()) {
while (first.depth() > second.depth()) {
first = first.parentElement;
steps++;
}
}
else if (elem1.depth() < elem2.depth()) {
while (first.depth() < second.depth()) {
second = second.parentElement;
steps++;
}
}
while (first !== second) {
steps += 2;
first = first.parentElement;
second = second.parentElement;
}
return steps;
}
p.s. the demo doesn't work in firefox or IE

Related

jQuery: How do I check a number from string?

there is a class named:
<div class="level_11 price_level" style="display: block;">
I have a script that runs a browser function.
But I want to run this only when my number in the script is lower than the number from the "level_" div.
I have no idea how to do this.
Well, there are everytime another number. Sometimes level_4, sometimes level_18, etc.
I need to check the number and say if my number is lower then the number from the level_, then run the script.
let setLevel = 3; // Change this to set the building level. Example: let Level = 20 //
let Level = setLevel -1; // Don't touch this //
let logLevel = Level +1; // Don't touch this //
console.log(`Success ✓ - ${IBuilding.length} buildings left`);
$.each(IBuilding, function(Index, Entity) {
let BuildingMissing = IBuilding.length - (Index + 1);
window.setTimeout(function() {
$.get(`/buildings/${Entity.id}/expand_do/credits?level=${Level}`)
console.log(`${BuildingMissing > 0 ? BuildingMissing : 'Success ✓ - last building successfully expanded to level: ' + logLevel }`);
}, Index * 250);
});
});
Basically the script request all sites, and every site have another "level_".
On the sites where the "level_" number is higher than the number in my variable, then dont run the script at the site. but run the script at the sites where my number is higher then the "level_"
Can anyone help me out? :/
You can get the level number like this:
let classname = $("div[class^='level']").attr("class").split(" ").filter(getClass);
function getClass(value) {
return value.startsWith("level_");
}
let level = classname.toString().substr(6);
console.log(level);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="level_11 price_level" style="display: block;">
I found this on link /buildings/ID/:
current level of building
Maybe I can check the Level from there?
The HTML section for that is:
<dd>
13
Ausbauen
</dd>

Create for loop with changing image

I haven't been able to find my specific case on here yet so I thought I'd ask. I'm trying to make a very simple Tamagotchi in Javascript for a school project. Musts are that I apply DOM manipulation, use a loop, use an array(or an object), and use a function.
My idea was to make an array with all the 'emotions' as images and then a for loop to slowly count them down. Giving the impression that the mood of the Tamagotchi gets worse as time passes.
This is the code I have so far, it's not a lot:
var imgArray = ["super.png", "blij.png", "neutraal.png", "meh.png", "verdrietig.png", "dood.png"] //Array with the images
for (var i = 0; i < imgArray.length; i++)
{
//for loop that counts down array
//Here I want a function that changes the image according to the array number
}
Sorry for the bad formatting, this is my first time on here :)
This is what I have in the body:
<h1>Tamagotchi</h1>
<button id="feed">Give food</button>
<button id="play">Entertain</button>
<button id="walk">Walk</button>
<div id="tamagotchi"></div>
I'd also then like the buttons that you see above to add points to make the Tamagotchi feel better (so in the for loop the array automatically keeps ++i but I'd like the button to --i, so subtract one point) imgArray[0] is the happiest and imageArray[5] is the saddest.
I hope this wasn't too vague, please let me know if I need to better explain anything!
Here is some draft so you can start from something. I've created a function allowing you to improve the state of the tamagoshi.
For you now :
Making a function to decrease it
Make them to be displayed as image and not strings
Make it prettier using css rules
If you get trouble with the code, Stack Overflow will help. SO is not made to write code from scratch, but to fix bug and coding issues.
// Array with the images
const imgArray = [
'super.png',
'blij.png',
'neutraal.png',
'meh.png',
'verdrietig.png',
'dood.png',
];
let tamagoshiState;
// Pointer to the div where you are going to insert the picture
const tamagoshiFeel = document.getElementById('tamagotchi');
// Function that can change the state of the tamagoshi
function setTamagoshiState(i) {
tamagoshiState = i;
tamagoshiFeel.innerHTML = imgArray[i];
}
// Change the tamagoshi state in a positive way
function improveTamagoshiState() {
// Tamagoshi can't be happier
if (tamagoshiState === 0) {
return;
}
setTamagoshiState(tamagoshiState - 1);
}
// Initialize the tamagoshi state at very sad
setTamagoshiState(imgArray.length - 1);
#tamagotchi {
margin-top: 2em;
}
<h1>Tamagotchi</h1>
<button id="feed" onclick="improveTamagoshiState()">Give food</button>
<button id="play" onclick="improveTamagoshiState()">Entertain</button>
<button id="walk" onclick="improveTamagoshiState()">Walk</button>
<!-- How the tamagochi feels -->
<div id="tamagotchi">
</div>

Performance with jQuery .nextUntil on large DOM

I'm looking to address a performance issue I'm having with a very large DOM. In essence, this a word-processing style app inside the browser using contenteditable divs.
Suppose I have a structure like this:
<div class="header">...</div>
<div class="actor">...</div>
<div class="director">...</div>
<div class="producer">...</div>
<div class="writer">...</div>
<div class="executive">...</div>
<div class="studio">...</div>
<div class="footer">...</div>
I then have some code which ends up returning (for example):
<div class="writer">...</div>
as a jQuery object. I then need to retrieve all of this object's surrounding divs between header and footer as a selection and then further filter this list using a class e.g. 'actor'.
Currently, I have the following code, which works correctly:
// Find header
var header_object = object.prevUntil(".header").last().prev();
// Select all objects between header and footer, and then filter
var object_list = header_object.nextUntil(".footer", ".actor");
// Iterate through object_list
object_list.each(function()
{
// Run additional code on the objects
});
The only problem is that due to the app being a word processor of sorts, the DOM structure is often very large (e.g. over 5000 elements) and executing this code locks up the browser for an unacceptable amount of time (over 10 - 30 seconds).
As such, I'm looking for a way to customize the code I have to make it more efficient / improve performance.
I should also point out that the HTML structure above is not (header - 5000 elements - footer), rather it is 200 x (header - elements - footer). As such, each traversal operation is only maybe 25 elements from header to footer, but it has to run many times.
Any suggestions? Many thanks!
You could enhance performance by not using jQuery, and creating your own functions that are more specific to your use case.
function getClosest(el, klass, dir) {
while (el && (!el.classList.contains(klass))) {
el = el[dir ? 'previousElementSibling' : 'nextElementSibling'];
}
return el;
}
function getbetween(from, to, filterKlass) {
var list = [];
while(from && to && from !== to) {
if ((from = from.nextElementSibling) !== to) {
filterKlass ? (from.classList.contains(filterKlass) ? list.push(from) : Infinity) : list.push(from);
}
}
return list;
}
var object = $('.writer');
var element = object.get(0);
var header_object = getClosest(element, 'header', true);
var footer_object = getClosest(element, 'footer', false);
var object_list = getbetween(header_object, footer_object, 'actor');
object_list.forEach(function(element) {
console.log(element);
});
FIDDLE
Traversing the next element sibling directly, and checking for classes, should be much faster than using nextUntil

HTML comment if without javascript

I know that I can display/hide content whether the browser is IE or not or even the version of IE. I was wondering if I can use other expressions too such as
<!--[if 1 == 0]-->
This should be hidden
<!--[endif]-->
The reason behind this is that I'm sending auto generated E-Mails and for me it would be easier to insert such comments in the template E-Mail instead of creating multiple templates.
if you have a template system, then make this in your template. Anyway when you render the template you calculate the condition, but instead of printing "0 == 1" or "0 == 0", use the template's ability to print or not to print the following paragraph
I know this would look like a long answer but I just wanted to divide the code into small functions each does its own job -kind of-, first select each element with a class name of hasComment in an array using querySelectorAll then pass this array to updateHTML() function, loop through its element and call returnComment() function for each item in the array.
The returnComment() function first call hasComment() function on the element passed to it, and using .replace() to get the exact string. Function hasComment() loop through the child nodes of the element and if the nodeType of the child node is 8 it then it's a comment, we return the text between the comment <!-- and -->.
This .replace(/\[|\]/ig, ''); omits the brackets to get value of either show or hide which according to it we "hide" or "show" the child .contentDiv div.
JS Fiddle
var commentDivs = document.querySelectorAll('.hasComment');
updateHTML(commentDivs);
function updateHTML(arr) {
for (var i = 0; i < arr.length; i++) {
var childDiv = arr[i].querySelector('.contentDiv'),
showIt = returnComment(arr[i]);
if (showIt == 'show') {
childDiv.style.display = 'block';
console.log('Div-' + (i + 1) + ': shown');
} else if (showIt == 'hide') {
childDiv.style.display = 'none';
console.log('Div-' + (i + 1) + ': hidden');
}
}
}
function returnComment(element) {
var comment = hasComment(element);
comment = comment.replace(/\[|\]/ig, '');
return comment;
}
function hasComment(element) {
for (var i = 0; i < element.childNodes.length; i++) {
if (element.childNodes[i].nodeType == 8) {
return element.childNodes[i].data;
}
}
}
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -1: This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -2: Again, This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[show]-->
<div class="contentDiv">Div -3: But this should be shown</div>
</div>
----------
Notes:
Wrapping the all contents of each .hasComment elements making controlling the content easier.
The above solution only work on the very top level of .hasComment element children, so if you have other comments inside .contentDiv these comments won't be affected.Demo Fiddle
You could probably use [if 1==0] for "templating" like in your code then use eval() or more complex regex to check upon it, but IMHO I think using show and hide look easier and mostly less bugs as you this over and over through your document.
More details about nodeType:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
https://developer.mozilla.org/fr/docs/Web/API/Node/nodeType
http://www.w3schools.com/xml/dom_nodetype.asp
Since you are developing for email clients, no this isn't possible. You need to figure out how different clients can be targeted. Then set the display property via CSS to whatever is affected.
Ideally, your emails shouldn't need any kind of crazy logic like this. It is a smell that your email is bad. Not to mention, anything you put in the email itself is viewable, all someone needs to do is turn off HTML rendering or view the source.

Trying to add two values from different javascript arrays

I am deriving two different values from these scripts.
Script #1...
//JS for Potential Gen Ed TOC
$(function($) {
$('#CourseMenu select').change(function() {
var sum = 0;
$('#CourseMenu select').each(function(idx, elm) {
sum += parseFloat(elm.value, 10);
});
$('#total_potential').html(Math.min(sum,72).toFixed(2));
});
});
Script #2...
//JS for Potential Gen Ed TOC from Electives only
$(function($) {
$('#CourseMenu_Electives select').change(function() {
var sum = 0;
$('#CourseMenu_Electives select').each(function(idx, elm) {
sum += parseFloat(elm.value, 10);
});
$('#total_potential').html(Math.min(sum,33).toFixed(2));
});
});
However, I'd like to pull the data from both of these and have the result display in the following HTML...
<p><fieldset id="PotentialTOC">
<legend style="font-weight: bold; font-size: 140%;">Potential TOC Evaluation Results</legend>
<div id="Results" style="text-align:left; font-family: 'Century Gothic', Gadget, sans-serif; font-size:14px;"><br />
<div>
<h2><span id="span"></span>
Potential Gen Ed TOC: <span id="total_potential"></span>
<br />
Potential Money Saved: $<span id="total_money"></span>
<br />
Potential Class Time Saved: <span id="total_time"></span> weeks
</fieldset></p>
Here's a jsfiddle to show what I've done so far... I can't transfer more than 33 elective credits and no more than 72 credits overall. I have the scripts laid out well, but again, need them combined to spit out one value.
The first thought that came to mind was to store the result of each function in a hidden div (or even displayed, so a user can see the weight of each choice). Then update the total value by just adding the two columns that contribute to it after the new credit total has been calculated for the new choice.
It will be a minor change, just alter where the current values are being inserted and add an additional line or two to each change callback that pulls in those two values, parses them, adds them, and then updates the total div.
I thought about doing it altogether, just using the previous value from the div, but the problem I ran into was that you weren't sure what the previous contribution was so it made it hard to "zero" the div before adding in the new choice since the choices are not cumulative. One select box should only make one contribution to the final value.
Edit:
So I fiddled with the fiddle and went with the state object approach: http://jsfiddle.net/MCg2d/1
COURSE['Non-electives'] = Math.min(sum, 72).toFixed(2);
var total = (parseFloat(COURSE['Non-electives'], 10) || 0) + (parseFloat(COURSE['Electives'], 10) || 0)
$('#total_potential').html(total);
});
It's very rough but it probably makes more sense than my ramblings above.

Categories

Resources