D3 elements being removed although I'm only adding new ones - javascript

It's been a while since I picked up d3.js and it seems I'm a bit rusty. I'm trying to do a form where I can add more input fields by clicking + and removing the existing ones by clicking -.
To try and discover what I had wrong, I started colouring the enter(), update and exit() with green, yellow and red respectively.
The original data array has two elements, so they show up as green:
Then I click on the plus sign which pushes a new element to the array, and I expected to see two yellows and one green, but instead I see all the elements deleted besides the last one, and this repeats if I click + again:
And plus again:
I've compared my code with the classic General Update Pattern and I can't see anything significant apart from the way I set the keys, in which I use the e-mail. This is code I've added to fix another underlying issue where not all the boxes were being added, just one out of each 3.
My commented code is as follows:
var renderFriends = function () {
console.log("Rendering friends:" + friendsList)
var friends = d3.select('.friends-container')
.selectAll('div')
.data(friendsList, function(d,i) {
// this was something I added when I thought the problem were the keys
return d
})
// updates will be yellow
friends.classed("update", true)
var enter = friends.enter()
// Friend box
// all the divs are because I'm using foundation css
// the new class is what marks the font green
var friendBox = enter.append('div').classed('large-12 columns new', true)
friendBox.append('div').classed('large-8 columns', true)
.append("input")
.attr("type", "text")
.attr("value", String)
// Icon box
var iconBox = friendBox.append('div').classed('large-2 left columns', true)
.append("i")
.classed('fi-minus', true)
.on("click", function(d) {
// out of scope for this question
console.log("Removing:" + d)
friendsList.remove(friendsList.indexOf(d))
renderFriends()
})
// exit state should colour the fonts red
friends.exit().classed('remove', true)
}

I did a small test with custom styles and this is what got (when I clicked minus button):
All elements have green background since they all have "new" class, the "update" elements have yellow border, and the "remove" red background.
So what I've noticed is that you have a various Divs nested, and the problem is that when you do a selectAll('div') is going to select all divs and d3 is expecting for each div element being selected to be a data element corresponding to it.
So if you want to add another element and your friendsList is:
friendsList = ['a#test.com','b#test.com','c#test.com'];
d3.selectAll('div') is going to take 6 divs (when you had 2 friends and added one), and its going to bind only 3 elements because your dataset contains only 3 friends, and its going to target the rest of elements as "exiting".
To solve this, simply change your select using a class like '.friend' and also add it to each element being inserted (only the main container div);
Like this:
http://jsfiddle.net/2codv59e/

Related

Change color of only one current element in the list

So I have a list of elements that originally have white backgrounds and my goal is when I click one of it it changes color to blue, but only one element can by chosen and have color - if another element was clicked earlier it background return to white
I was trying with this code to achive my goal - I made variable that is supposed to show if something on the list was clicked before - if yes show true, if not - show false, but when I log it on the console it show that is false since I click the same element on the list twice - then is true
var clicked = ""
table.addEventListener("click",function (event){
console.log(clicked==true)
table.style.backgroundColor="blue"
clicked=true
})
}
Assuming the table has all the elements in it,
table.addEventListener("click",function (event){
for (let i = 0;i<table.children.length;i++) {
table.children[i].style.backgroundColor = "white"
}
event.target.style.backgroundColor="blue"
})
}
This just makes all of the elements in the tables background color white, then makes the one you clicked on blue, so only one will ever be clicked

Swap Divs then swap selected option value between two select lists

The Problem:
Before I began adding the div swaps, I could only type into the left (from_value) input and the result of the calculations would be applied via ajax to the right (to_value) input.
I would like to allow the user to type into either box and have the results display in the opposite box they're typing in.
What I am doing to make this happen:
I am swapping the left div with the right div on mouseover of the to_value input. Here's the code i'm using to do the swap:
$.fn.swapWith = function (that) {
var $this = this;
var $that = $(that);
// create temporary placeholder
var $temp = $("<div>");
// 3-step swap
$this.before($temp);
$that.before($this);
$temp.after($that).remove();
return $this;
};
var leftSide = $('#left-side');
var rightSide = $('#right-side');
$('#to_value_input').on('mouseover', function () {
$(rightSide).swapWith(leftSide);
});
This effectively swaps the divs, bringing along with it ids and names of the inputs which retains functionality of my server-side script to perform calculations. As expected, the from_unit select list and to_unit select list are swapped and their values / displayed text are also swapped from one side to the other.
I would like to swap the values and text of the two select boxes either directly before or more likely, directly after the div swap so it appears as if nothing changed on screen.
Similar questions that I have reviewed:
How to swap values in select lists with jquery?
How to swap selected option in 2 select elements?
I have tried several variations of each of the provided solutions. This seems like it should be a fairly simple thing to do but I find myself stuck. Any further help would be greatly appreciated.
The full code base is available on github if you need to see it: https://github.com/pschudar/measurement-conversion
I have made minor changes to the code hosted there to accommodate the div swaps.

How to unselect a class when clicking on another class with javascript

I have a map of the US with selectable counties that when clicked change their background to red. What I want to happen is when the user clicks on another county it deselects the current county and then selects the new one only. Currently right now when clicking on the county class it changes the class which gives it a background of red, but when you click another county then both are red.
Here is the code where I draw the map and change classes when clicked:
//DRAW MAP
d3.json("js/map.json", function(error, mapData){
if (error) throw error;
//draw counties
edit.map.append("g")
.selectAll("path")
.data(topojson.feature(mapData, mapData.objects.counties).features)
.enter().append("path")
.attr("class", "counties")
.attr("d", edit.path)
.on("click", function(d){
sFips = d.properties.STATEFP;
cFips = d.properties.COUNTYFP;
//display values in text boxes
$("#locationCountySelect").val(cFips);
$("#locationStateSelect").val(sFips);
//change clicked county class name
if (this.className.baseVal == "counties") {
this.className.baseVal = "selectedCounty";
//send new county to db
} else {
this.className.baseVal = "counties";
}
});
});
Again, how can I only have one county selected at a time?
For this purpose I suggest you ditch jQuery in favor of D3. The following two lines in your click listener will do the job:
d3.select(".selectedCounty").attr("class", "counties");
d3.select(this).attr("class", "selectedCounty");
The first statement selects the element having class .selectedCounty and sets the class attribute to counties instead. The second one selects the element clicked upon and set its class to selectedCounty.
It might also be worth considering to keep a reference to the currently selected element in a variable in the outer scope to not having to reselect on every click:
var selectedCounty = d3.select(".selectedCounty");
edit.map.append("g")
// ...
.on("click", function(d) {
selectedCounty.attr("class", "counties");
selectedCounty = d3.select(this).attr("class", "selectedCounty");
}
As requested by Teton-Coder's comment there might be the need to toggle the class instead of just replacing it. Using selection.attr("class", "selectedCounty") will set the value of the class attribute, whereby replacing any other classes set on the element. Although you are allowed to pass a space-separated list to the attribute via this function the easiest way to toggle a specific class on an element is the use of selection.classed(). The second argument to that function is a boolean value determining if the class should either be assigned to the element or be removed from it while leaving all other classes intact. The above code can thus be rewritten as:
var selectedCounty = d3.select(".selectedCounty");
edit.map.append("g")
// ...
.on("click", function(d) {
selectedCounty.classed("selectedCounty", false);
selectedCounty = d3.select(this).classed("selectedCounty", true);
}

How to make my D3 lines Split?

I have recently created a node map which has multiple nodes with line/arrows going between each node. I have recreated my code here: https://jsfiddle.net/GarrettUK/51j2rx1t/. I was wondering if anyone could show me how I could go about creating an on-click function on the lines so that when a line is clicked... 2 more lines are shown. So when its clicked we would have the line we just clicked in the middle and the 2 new lines either side.
I've already started creating the on-click function as seen in the example. I had a theory that you would create the 2 extra lines that connect to each node but have them hidden. Then when you click on the line. The function switches these lines from hidden to show.
My on-click function looks like this in the example:
.on("click", function(){
// Determine if current line is visible
var active = redLine.active ? false : true ,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#redLine").style("opacity", newOpacity);
// Update whether or not the elements are active
redLine.active = active;
});

Simpler function to reset classes, add to target class 'active', and filter map based on different parameters

I'm working on a map project where I built my own UI. Part of it is a navigation where you can filter the map by clicking a div button in a column floating at the edge of the map. It works perfectly fine, currently, but it is overly complicated and will be a major issue to explain how to add new buttons as the data expands.
The gist is that it first defines a variable as the button id, and on click resets all the classes to a default set, except for the button which is clicked gains the class 'active', and executes a function which sets filtering based on an assigned id in the dataset or return true for All.
The way I have it, which is functional but extremely cluttered
Edit: I've made a few changes that substantially reduce the lines, but I still don't know how to combine multiple similar copies of these into a single function
//define variable to point to a specific button
var navigation = document.getElementById('navigation').children;
var all = document.getElementById('filter-all');
....
//[Currently] nine additional variables of nearly the same thing (now just for selecting the button pressed)
//Show all
all.onclick = function() {
navigation.className = navigation.className.replace(new RegExp('\b' + active + '\b'),'');
this.className += ' active'; //for some reason this doesn't actually add the space
map.featureLayer.setFilter(function(f) {
// Returning true for all markers shows everything.
return true;
});
map.fitBounds(map.featureLayer.getBounds());
return false;
};
....
//Followed by nine nearly identical functions that just reset classes, and sets filtering parameters
I know there is a way to combine the functions to do the same thing without so much bulk, but my search is so far fruitless.

Categories