JavaScript framework/library for bi-directional relation between values - javascript

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>

Related

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>

How to write a neat JS if else statement to dynamically add a nested Rails simple_form via Cocoon?

I am building a nested simple_form_for in rails using cocoon to dynamically add and remove nested elements. The main model object is a quote and a quote has many employees. I've reached the limit of my amateur code skills and would like some guidance on writing a neat js script so to achieve the following:
if nested_object.count <= 2 then remove_empee_link.hide
if nested_object.count > 2 then remove_empee_link.show, but not on the first two nested_objects.
if nested_object.count > 10 then add_empee_link.hide, otherwise always add_empee_link.show
Adapted from a really helpful post here courtesy of #nathanvda I've got to here;
$(document).ready(function() {
function check_to_hide_or_show_add_empee_link() {
if ($('#empee-form .nested-fields:visible').length == 5) {
$('#empee-form .links a').hide();
} else {
$('#empee-form .links a').show();
}
}
$('#empee-form').on('cocoon:after-insert', function() {
check_to_hide_or_show_add_empee_link();
});
$('#empee-form').on('cocoon:after-remove', function() {
check_to_hide_or_show_add_empee_link();
});
check_to_hide_or_show_add_empee_link();
});
$(document).ready(function() {
function check_to_hide_or_show_remove_empee_link() {
if ($('#empee-form .nested-fields:visible').length <= 2) {
$('.remove_fields').hide();
} else {
$('.remove_fields').show();
}
}
$('#empee-form').on('cocoon:after-insert', function() {
check_to_hide_or_show_remove_empee_link();
});
$('#empee-form').on('cocoon:after-remove', function() {
check_to_hide_or_show_add_remove_empee_link();
});
check_to_hide_or_show_add_remove_empee_link();
});
But I'm struggling to put a strategy together on how I can I achieve what I outlined in my biullets above in a neat solution, any guidance would be really appreciated after starting and playing with this for hours. Thanks
The updated code that I've now written, but the behavior is unexpected;
If 1, 2 or 3 nested elements on page, then all remove_links hidden.
If 4 nested elements on page then 1st, 2nd & 4th have remove_link hidden
If 5 nested elements on page then 1st, 2nd & 3rd have remove_link hidden
Intended behaviour, 1st and 2nd remove_links hidden always, anoy others shown:
// Hiding the 1st 'remove employee' link for the first two employee fields.
$(document).ready(function() {
function hide_first_and_second_remove_empee_links() {
var remove_links = $('.remove_fields')
$(remove_links[0]).hide();
$(remove_links[1]).hide();
// $('a.remove_fields:first-child').hide();
// $('a.remove_fields:nth-child(2)').hide();
}
$('#empee-form').on('cocoon:after-insert', function() {
hide_first_and_second_remove_empee_links();
});
$('#empee-form').on('cocoon:before-insert', function() {
hide_first_and_second_remove_empee_links();
});
hide_first_and_second_remove_empee_links();
});
How can this be? There's one method, it collects all .remove_fields' into the remove_links var, then wraps the[0]element of that collection in a jQuery object and callshideon it. Then the same to the1element. That method is called on page ready and then again oncocoon:before-insertandafter-insert. I don't see how the definition of the[0]and the1` elements changes?
Your logic becomes easier when you write:
always hide the first two remove links
hide the add association link if there are more than 10 employees
The second is already covered in your answer.
The first one is actually pretty easy using jquery:
$('.remove-fields:first-child').hide()
$('.remove-fields:nth-child(2)').hide()

Colors not being set correctly via Javascript

As per suggestion from a user, I decided to redo my question.
What I'm trying to do is change foreground and background colors of several elements without using a stylesheet.
Next to each colourIt call just under the script tag is the style I'm trying to reproduce Javascript style. This is because my stylesheet is too large and is taking up more than 1/2 my website code, especially with all those background color declarations.
This is my new code:
<table>
<tr>
<td ID="CELL">
GREENonYELLOW
<p>BLUEonRED<i>REDonGREEN<b>YELLOWonBLUE</b>REDonGREEN</i>BLUEonRED</p>
<p>BLUEonRED<i>REDonGREEN<b>YELLOWonBLUE</b>REDonGREEN</i>BLUEonRED</p>
GREENonYELLOW
</td>
</tr>
</table>
<script>
colorIt('CELL','','#FF0','#0F0'); //#CELL {background:#F00;color:#0F0}
colorIt('CELL','P','#F00','#00F'); //#CELL P{background:#00F;color:#F00}
colorIt('CELL','P I','#0F0','#F00'); //#CELL P I{background:#F00;color:#0F0}
colorIt('CELL','P I B','#00F','#FF0'); //#CELL P I B{background:#FF0;color:#00F}
//populate array with pointers as parents to the children are found
function getKidValues(parent,children){
var allKids=[],childrenSet=document.getElementsByTagName(children);
childrenSetSize=childrenSet.length;
for(kidsIndex=0;kidsIndex < childrenSetSize;kidsIndex++){
if(childrenSet[kidsIndex].parentNode==parent){
allKids.push(childrenSet[kidsIndex])
}
}
return allKids;
}
function colorIt(parent,kids,backgroundColor,foregroundColor){
if(parent){
parentPtr=document.getElementById(parent);
if(kids){
kidSet=kids.split(' ');
kidSetSize=kids.length;
for (index=0;index < kidSetSize;index++){
//get proper kids. I think I need to pass new parameters but idk which??
currentKids=getKidValues(parentPtr,kidSet[index]);
//go through the kids setting the colors.
for (thisKidIndex in currentKids){
setColor(currentKids[thisKidIndex],backgroundColor,foregroundColor);
}
}
}
}
}
//just sets color of element: This function isn't problematic.
function setColor(item,back,fore){
if(item){
item=item.style;
if(item){
if(back){item.backgroundColor=back}
if(fore){item.color=fore}
}
}
}
</script>
I feel I need to add recursion to solve this, but if there's another way to solve it without killing a computer processor and that works in Internet Explorer 7, then I'm looking forward to that answer.
So far, this javascript was only able to satisfy one child to the ColorIt function correctly, It does not work with more than one child.
What can I do to fix this?
UPDATE
I changed my javascript code to:
function setValues(parent,children,depth,b,f){
var childrenSet=document.getElementsByTagName(children[depth]),childrenSetSize=childrenSet.length;
for(kidsIndex=0;kidsIndex < childrenSetSize;kidsIndex++){
var m=childrenSet[kidsIndex];
if(m.parentNode==parent){
if ((depth+1) < children.length){
setValues(m,children,depth+1,b,f);
}else{
setColor(m,b,f);
}
}
}
}
function colorIt(parent,kids,backgroundColor,foregroundColor){
if(parent){
parentPtr=document.getElementById(parent);
if(kids){
kidSet=kids.split(' ');
setValues(parentPtr,kidSet,0,backgroundColor,foregroundColor);
}
}
}
function setColor(item,back,fore){
if(item){
item=item.style;
if(item){
if(back){item.backgroundColor=back}
if(fore){item.color=fore}
}
}
}
and everything works but only the first line of text is being affected. I want both lines to be the same way, not one being just one color. Remember, I'm trying to rewrite some of my CSS. see above.

Optimizing code to define variables only once, code only works when the vars are in change function and for the code outside change I redefine?

Pretty sure I know the solution... would write .on('change','load', function(){}
correct? <-- Tested didn't work? so I am up to your solutions :)
Sushanth -- && adeneo both came up with great solutions, this is a good lesson in optimizing code... It's gonna be hard to choose which answer to go with, but I know this is going to help me rethink how I write... I dont know what I do without this forum, id have to learn this stuff in college.
This is purely a question out of curiosity and bettering my skills, as well as giving you guys a chance to display your knowledge on jQuery. Also to prevent any sloppy writing.
I have a radio based switch box, the markup looks like this, the id's and on/off values are generated by the values in my array with PHP...
<span class="toggle-bg">//This color is the background of the toggle, I need jQuery to change this color based on the state on/off
<input type="radio" value="on" id="_moon_page_header_area1" name="_moon_page_header_area">//this is my on value generated by the array
<input type="hidden" value="_moon_page_header_area" class="switch-id-value">// I create this input because I have multiple checkboxes that have the ID _moon_ARRAYVALUE_area1
<input type="radio" value="off" id="_moon_page_header_area2" name="_moon_page_header_area">// off value
<input type="hidden" value="_moon_page_header_area" class="switch-id-value">//_moon_ARRAYVALUE_area2
<span class="switch"></span>// the switch button that changes
</span>
Hope that makes sense and the comments are clear
Here is the jQuery
var value = $('.toggle-bg input.switch-id-value').val()
var moon1 = $('#'+value+'1').is(':checked');
var moon2 = $('#'+value+'2').is(':checked');
var static_slide = $('._moon_staticarea_height');
var toggle = $('.toggle-bg');
if(moon1){
toggle.css({'background-color': '#46b692'});
static_slide.hide()
} else
if (moon2){
toggle.css({'background-color': '#333'});
static_slide.show()
}
$('.toggle-bg').change(function () {
var value = $('.toggle-bg input.switch-id-value').val()
var moon1 = $('#'+value+'1').is(':checked');
var moon2 = $('#'+value+'2').is(':checked');
var static_slide = $('._moon_staticarea_height');
var toggle = $('.toggle-bg');
if(moon1){
toggle.css({'background-color': '#46b692'});
static_slide.slideUp()
} else
if (moon2){
toggle.css({'background-color': '#333'});
static_slide.slideDown()
}
});
it looks longer than it really is, its just repeating it self, one is on load so that it gives the correct color on load of the page, and then inside the change function we need to change colors..
How do I write it so I only have to use variables one time (so its cleaner) is there a better way to optimize it... Just NOW thinking after writing this I could put it in one function .on('load', 'change', function() {}
I just now thought of that, but I wrote all this so I am going to see what others think...
You'd do that by having the function in the change event handler, and on the end you chain on a trigger('change') to make it work on pageload :
$('.toggle-bg').on('change', function () {
var value = $('.toggle-bg input.switch-id-value').val(),
moon1 = $('#' + value + '1').is(':checked'),
slider = $('._moon_staticarea_height'),
toggle = $('.toggle-bg');
toggle.css('background-color', (moon1 ? '#46b692' : '#333'));
slider[moon1?'slideUp':'slideDown']();
}).trigger('change');
As radiobuttons can't be unchecked, it's either moon1 or moon2, which means checking one of them should be enough.
.on('change','load',
supposed to be
// Remove the comma separator if you want to bind the same handler to
// multiple events.
.on('change load',
And you can remove the one separately written out and enclose it in a function (if multiple instances of the class toggle-bg)
or just trigger the change event.(If there is a single instance of a class)
This will just run the same functionality when the page loads.
var toggle = $('.toggle-bg');
toggle.change(function () {
var value = $('input.switch-id-value', this).val(),
moon1 = $('#' + value + '1').is(':checked'),
moon2 = $('#' + value + '2').is(':checked'),
static_slide = $('._moon_staticarea_height');
if (moon1) {
toggle.css({
'background-color': '#46b692'
});
static_slide.slideUp()
} else if (moon2) {
toggle.css({
'background-color': '#333'
});
static_slide.slideDown()
}
}).change();

How to migrate a .change function from jQuery to plain Javascript

I'm not a JS expert but i think what i'm trying to do is pretty simple (at least in jQuery)
I've got 3 select
<select id="faq" class="onchance_fill">...</select>
<select id="pages" class="onchance_fill">...</select>
<select id="faq" class="onchance_fill">...</select>
and an input (it's a tinyMCE one in advlink plugin)
<input type="text" onchange="selectByValue(this.form,'linklisthref',this.value);" value="" class="mceFocus" name="href" id="href" style="width: 260px;">
I want that each time i change a value in one of the 3 select, that this value of the option, will be placed in the input.
In Jquery, it would be something like :
$('.ajax_onchance_fill').change(function() {
data = $('.ajax_onchance_fill').val();
$('#href').text(data);
});
But i can't use it. So what is the equivalent in plain Javascript ?
Thanks
I would advice you keep using Jquery as it speeds up this kind of thing but in pure JavaScript i think what you want looks something like this...
<script type="text/javascript">
function load() {
var elements = document.getElementsByClassName('onchance_fill');
for(e in elements){
elements[e].onchange = function(){
document.getElementById('href').value = this.value;
}
}
}
</script>
document.getElementsByClassName("ajax_onchance_fill").onchange = function() {
getElementById('href').value = this.options[this.selectedIndex].text;
};
Though I am not sure exactly if it'll work since getElementsByClassName returns more than 1 element.
Try this:
$('.ajax_onchance_fill').change(function() {
var data = $(this).val();
$('#mytextboxid').val(data);
});
Okay, so I realize this thread is well over 8 years old, so this answer isn't so much for the OP (who probably figured it out long ago) as it is for someone else who might be curious about this particular topic.
All that said, here's a relatively simple and reliable way you could pull it off in vanilla JS:
/**
* Since we need to listen to all three ajax_onchance_fill elements,
* we'll use event delegation.
*
*/
const targetLink = document.getElementById('href');
document.addEventListener('change', function(e) {
if (!!Element.prototype.matches) { // Let's make sure that matches method is supported...
if (e.target.matches('.ajax_onchance_fill')) {
targetLink.textContent = e.target.value;
}
} else { // and if not, we'll just use classList.contains...
if (e.target.classList.contains('ajax_onchance_fill')) {
targetLink.textContent = e.target.value;
}
}
});

Categories

Resources