Trying to add two values from different javascript arrays - javascript

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.

Related

Selectively adding and removing values of same CSS property

I’m creating an Open Type features tester using jquery and want to give the user the possibility of seeing several open type features applied at the same time.
The first dropdown sets the property to font-feature-settings: ‘frac’ 1;, he second dropdown sets the property to font-feature-settings: ‘smcp’ 1;
Currently if both of the dropdowns are active one overrides the instructions of the other one. What I need is that if both of them are active at the same time the property is set to font-feature-settings: ‘frac’ 1, ‘smcp’ 1; and consequently if only one of them is deactivated only the corresponding value is removed from the property.
Note: I'm aware that the Open Type features don't work when I link the font from the Google fonts site, so I've been testing it with it installed.
Thanks in advance.
.font-tester{
color: #000;
line-height: normal;
width: 100%;
word-wrap: break-word;
padding-top: 30px;
}
<style> #import url('https://fonts.googleapis.com/css2?family=EB+Garamond&display=swap'); </style>
<div class="tester-container">
<select id="tester-figures">
<option value="none" selected>Default</option>
<option value="'frac' 1">Fractions</option>
</select>
<select id="tester-smcp">
<option value="none" selected>Default</option>
<option value="'smcp' 1">Small caps</option>
</select>
<div class="font-tester" contenteditable="true" spellcheck="false" style="font-family: 'EB Garamond', serif; font-size:65pt;">
abc 123
</div>
</div>
<script>
$("#tester-figures").change(function() {
$('.font-tester').css("font-feature-settings", $(this).val());
});
$("#tester-smcp").change(function() {
$('.font-tester').css("font-feature-settings", $(this).val());
});
</script>
There are many ways to solve a task. Additional here is an alternative solution which has less and more simplyfied and code.
It works with only one function which add the change event to both selects. On change it reads out the actual values from both selects and builds a simple css string based on the actual values of both selects.
$('#tester-figures').add('#tester-smcp').each( function(){
const $this = $(this);
$this.change(function(){
let actualVals = {};
actualVals.TesterFigures = $('#tester-figures').val();
actualVals.TesterSmcp = $('#tester-smcp').val();
let myVal = '';
if('none' == actualVals.TesterFigures){
myVal = 'none' == actualVals.TesterSmcp ? '' : actualVals.TesterSmcp;
}else {
myVal = 'none' == actualVals.TesterSmcp ? actualVals.TesterFigures : actualVals.TesterFigures + ', ' + actualVals.TesterSmcp;
}
$('.font-tester').css("font-feature-settings", myVal);
});
});
NOTE: I don't have worked with css attribute font-feature-settings yet. So I don't have any experience with this special item. What I notice is, that the strings in the selects (i.e. 'frac' 1) changes. The inner quotes and the number get lost when the css is added to the html element. But the style still works without them. Maybe you like to check if it works if you only use frac and smcp in your selects. In actual FF it works ;-)
What you need to do is basically concatenate the values into a comma-separated string as you update them, instead of just whatever the input value is. You could write your own function to do this if you wanted to. It would have to include logic for whether there is only one value or multiple values in order to make sure you don't end up with a trailing comma for no reason. However, there's an easier way: if we store the values in an array, we can use Array.join(), which will automagically handle that logic for us.
Array.join() joins all the elements in an array into one big string, and it takes an optional parameter that specifies how to join them. So we can do like so:
['one', 'two', 'three'].join(', '); // => 'one, two, three'
Note how there's not an extra comma and space at the end.
So now that we know how to get the end result, we just need to work on getting the values. You will need to either add elements to the array or remove them based on how you interact with the dropdowns. Since you only have two items in each dropdown right now, that logic can be pretty simple. If you are going to have multiple items in any dropdowns, the overall approach is the same, but you'll have to tweak the if…else part in the snippet below.
Keep in mind that, if you want to remove an item from the array, you need to know what to remove. But that's the old value. If you run $(myInput).val() inside your .change() function, you're going to get the current value. So you need to keep track of the old values separately.
Once you have the new array set up, you just have to apply the Array.join() magic and then you have your string that you can use in the jQuery .css() function.
It will be much easier to maintain this if you cut out as much repeated logic as possible and just have one single function that the inputs can each call. So I've done that for you below. You can add more inputs to the .each() function by chaining on more .add()s before it, if you need to.
let oldValues = ['none', 'none']; // whatever the default values are for each select
let currentValue = [];
const getNewValue = input => {
const val = $(input).val();
const index = input.index();
const oldValue = oldValues[index];
if (val !== 'none') {
currentValue.push(val);
} else {
const indexOfValue = currentValue.indexOf(oldValue);
currentValue.splice(indexOfValue, 1);
}
oldValues[index] = val;
const newCssValue = currentValue.join(', ');
console.clear();
console.log(`new value is "${newCssValue}"`);
return newCssValue;
};
$(function() {
const $fontTester = $('.font-tester');
$('#tester-figures').add('#tester-smcp').each(function() {
const $this = $(this);
$this.change(function() {
const newCssValue = getNewValue($this);
$fontTester.css('font-feature-settings', newCssValue);
});
});
});
.font-tester {
color: #000;
line-height: normal;
width: 100%;
word-wrap: break-word;
padding-top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="tester-container">
<select id="tester-figures">
<option value="none" selected>Default</option>
<option value="'frac' 1">Fractions</option>
</select>
<select id="tester-smcp">
<option value="none" selected>Default</option>
<option value="'smcp' 1">Small caps</option>
</select>
<div class="font-tester" contenteditable="true" spellcheck="false" style="font-family: 'EB Garamond', serif; font-size:65pt;">
abc 123
</div>
</div>

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>

jquery - add multiple timers associated to HTML divs

I have the following DIV containing multiple cards elements:
Each of those cards have the following HTML structure:
<div class="user-w">
<div class="avatar with-status status-green">
<img alt="" src="img/avatar1.jpg">
</div>
<div class="user-info">
<div class="user-date">
12 min
</div>
<div class="user-name">
John Mayers
</div>
<div class="last-message">
What is going on, are we...
</div>
</div>
</div>
Those cards are loaded dynamically using ajax. What I need is to attach to each <div class="user-w"> a stopwatch so I can change for example background color when elapsed time is 4 min or make it hidden when elapsed time reaches 6 min.
I was thinking on using SetInterval multiple times for I think this is not possible.
Each DIV card element should be totally independant in terms of timing from the others.
Any clue on how to do it correctly?
When you build the card from the ajax object, set a data element to store the timestamp on the card. Use setInterval to trigger a function that loops through all of the cards and checks their timestamps against the current time and updates the date on the ui, changes the bgcolor, or removes the element altogether.
In general, shy away from the "attach everywhere" syndrome. Think in lists, and simple processors. You will thank yourself down the road, as will your users for more efficient code, and your maintenance programmer.
Accordingly, one thought process might be to setup an array of the elements in question, and use a single setInterval. Something like:
...
var cardList = [];
function processCards ( ) {
var i, cardEl, cardStartTime, now;
now = Date.now();
i = -1;
while ( ++i < cardList.length ) {
cardEl = cardList[ i ][ 0 ];
cardStartTime = cardList[ i ][ 1 ];
if ( cardStartTime + 6 min < now ) {
// do 6 minute thing
}
else if ( cardStartTime + 4 min < now ) {
// ...
}
}
}
$.get('/your/new/cards/call')
.done(function(...){
...
var now = Date.now();
for ( i in returnedCards ) {
cardElement = however_you_create_your_element;
attach cardElement to the DOM
// save reference to element and time created for later processing
cardList.push([cardElement, now]);
}
});
setInterval(processCards, 2*60*1000); // or whatever granularity you want.
One advantage of this approach over multiple setTimeout calls for each card is the simplicity of having a single processing function, rather than N copies lying around. It's easier to reason about and manage, and reduces the likelihood of errors if an element disappears before it's associated setTimeout function executes.
One way to do this is, after your AJAX call completes and the DOM has been updated, you can use jQuery to select your cards and for each card you can:
Get the time value and parse it to convert it to milliseconds - you can write a simple helper function for this or use something like momentjs based on how complex your requirement is
Use setTimeout with the parsed value and do any style updates/hiding as needed
Sample Code:
$('.user-w').each(function(i, el){
var $el = $(el);
var val = $el.find('div.user-date').html();
val = parseTime(val) // Assuming a function to parse time from string to milliseconds is there
setTimeout(function(){
// Do any updates here on $el (this user card)
}, val);
/*setTimeout(function(){
// Do something else when val ms is close to completion
// here on $el (this user card)
}, 0.9 * val);*/
})
If you want multiple things to happen (change bg color and then, later, hide element, for example) you can set multiple setTimeouts to happen with different time values derived from val
you want to add a function with setTimeout() for ajax success: parameter.
Ex(with jquery):-
$.ajax({
// your ajax process
success:function(){
setTimeout(function(){
$('.card-w').not('.anotherclassname').addClass('someclassname-'+i);
$('someclassname-'+i).addClass('.anotherclassname').fadeOut();
},6000);
}
})
The accepted answer can give you a lot of trouble if the ajax part
however_you_create_your_element;
attach cardElement to the DOM
Is replacing or adding elements. Ajax and processCards share cardlist and your ajax may remove items from DOM but leave them in cardlist.
You failed to mention if you replace the card list in your ajax or append new cards but the following solution would work either way.
To adjust to updating every minute and showing minutes you can change the following 2 lines:
const repeat = 1000;//repeat every second
const message = timePassed=>`${Math.round(timePassed/1000)} seconds`
to:
const repeat = 60000;//repeat every minute
const message = timePassed=>`${Math.round(timePassed/60000)} min`
(function(){//assuming cardContainer is available
const container = document.querySelector("#cardContainer");
const repeat = 1000;//repeat every second
const message = timePassed=>`${Math.round(timePassed/1000)} seconds`
const updateCards = function(){
Array.from(container.querySelectorAll(".user-w .user-date"))
.map(
(element)=>{
var started = element.getAttribute("x-started");
if(started===null){
started = Date.now()-
parseInt(element.innerText.trim().replace(/[^0-9]/g,""),10)*60000;
element.setAttribute("x-started",started);
}
return [
element,
Date.now()-parseInt(started,10)
];
}
).forEach(
([element,timePassed])=>
element.innerText = message(timePassed)
);
}
setInterval(updateCards,repeat);
}());
<div id="cardContainer">
<div class="user-w">
<div class="avatar with-status status-green">
<img alt="" src="img/avatar1.jpg">
</div>
<div class="user-info">
<div class="user-date">
12 min
</div>
<div class="user-name">
John Mayers
</div>
<div class="last-message">
What is going on, are we...
</div>
</div>
</div>
</div>

JavaScript framework/library for bi-directional relation between values

I need to implement a complicated form. For example, there are fields for summands, sum and percentage of each summand in that sum. Illustration:
Value1: 1 10%
Value2: 4 40%
Value3: 5 50%
Sum: 10 100%
The real life example is much more complicated. Everything is editable.
So, I assume several scenarios:
Editing a value updates percentages and sum.
Editing the sum updates values according to their percentages.
Editing a percentage updates all other percentages to be 100%, which itself updates all the values and sum.
I am currently trying to implement something like this using Backbone.js (just because I'm familiar with it), but the solution already looks over-engineered, and I am just in the beginning.
I wonder, is there any other approach I can look into?
I'm not an FRP person, but probably functional/reactive approach can help somehow?
If there is a framework or library designed to solve such kind of problems, I would be happy to look into it.
Seems like a task for Constraint programming. I'd recommend you to take a look at https://github.com/slightlyoff/cassowary.js and also this talk http://www.youtube.com/watch?v=72sWgwaAoyk
I implemented similar task using Kefir.js not so long ago.
Please, find my example — http://jsfiddle.net/mistakster/kwx5j8wm/
The main aproach is:
Create event stream for values, precents and sum.
var streamVal = Kefir.fromBinder(function (emitter) {
function handler() {
var values = $.map($table.find('.val'), function (ele) {
return $(ele).val();
});
emitter.emit(values);
}
$table.on('change', '.val', handler);
return function () {
$table.off('change', '.val', handler);
};
});
Implement your business logic, which apply changes to other fields based on data from the streams.
So, that’s all. As simple as draw an owl. :-)
Since you say other frameworks are acceptable, I've created a jQuery solution below.
Criterium #3 is a bit ambiguous, so let me know if this needs changing:
$('.val').change(function() {
var tot= 0;
$('.val').each(function() {
tot+= parseInt($(this).val(),10);
});
$('#sum').val(tot);
$('.pct').each(function(index) {
$(this).val(($('.val')[index].value/tot*100).toFixed(0));
$(this).data('prevValue', $(this).val());
$(this).data('index', index);
});
});
$('#sum').change(function() {
var sum= $(this).val();
$('.val').each(function(index) {
$(this).val($('.pct')[index].value*sum/100);
});
});
$('.pct').change(function() {
var index= $(this).data('index');
var prevValue= $(this).data('prevValue')
$('.val')[index].value= ($('.val')[index].value*$(this).val()/prevValue).toFixed(0);
$('.val').change();
});
$('.val').change();
.val, .pct, #sum, tpct {
width: 3em;
text-align: right;
}
th {
text-align:right;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr><td>Value1:<td><input class="val" value="1"><td><input class="pct">%
<tr><td>Value2:<td><input class="val" value="4"><td><input class="pct">%
<tr><td>Value3:<td><input class="val" value="5"><td><input class="pct">%
<tr><td>Sum: <td><input id="sum" ><th>100%
</table>

Jquery: Count node separation in xml

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

Categories

Resources