Bit of a noob here! I'm working in pure javascript with the assistance of HTML and CSS, no engine. I'm trying to move a displayed tank sprite composed of 3 parts: tracks, chassis and a turret.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="UTF-8">
<link rel='stylesheet' href='.css'>
<div class='tank' id='tracks'></div>
<div class='tank' id='chasis'></div>
<div class='tank' id='turret'></div>
</head>
<body>
<script src='.js'></script>
</body>
</html>
Here I created the three div elements with their unique id for the tracks, chassis and turret. They all share the common class tank.
.tank {
height: 31px;
width: 31px;
position: absolute;
}
#tracks {
background: url("tracks.png");
}
#chasis {
background: url("chasis.png");
}
#turret {
background: url("turret.png");
}
In my CSS code, I attributed all the properties that the 3 components share to their class, so they share the same size and position. Since each of the three elements has a unique image that cannot be shared, I attributed it to each element by id.
var tracks = document.getElementById('tracks');
var chasis = document.getElementById('chasis');
var turret = document.getElementById('turret');
tracks.style.top = '167px';
chasis.style.top = '167px';
turret.style.top = '167px';
This code has done the trick in the sense that it has successfully moved each component down by 167px but I want to avoid rewriting all that every time I want to move the tank by its components.
var tank = document.getElementsByClassName('tank');
tank.style.top = '167px';
So instead, I tried to move the entire tank by class but it didn't work. What am I doing wrong?
Thanks!
<div class="tank">
<div id='tracks'></div>
<div id='chasis'></div>
<div id='turret'></div>
</div>
Can you just try this? So you have TANK which has all the parts inside now.
getElementsByClassName, it returns Node list in Object format. So you can not directly apply style on it. You need to traverse the list using array functions. To get array list you can use querySelectorAll.
const tanks = document.querySelectorAll('.tank');
tanks.forEach(function(tank, index){
tank.style.marginTop = '167px';
});
<div class="tank">1</div>
<div class="tank">2</div>
try putting the divs in the body section and your js in the head section.
also add a [0] to the class as follows,
var tank = document.getElementsByClassName('tank')[0];
edited cause i realised they were not in a div with tank class as suggested by Nex
Ok, I think I found an alternate way to achieve this:
Leave the html and css as they were.
var tracks = document.getElementById('tracks');
var chasis = document.getElementById('chasis');
var turret = document.getElementById('turret');
var tank = [tracks, chasis, turret];
for (var i = 0; i < tank.length; i++) {
tank[i].style.top = '167px';
}
Scrap the js and throw in the above.
Call the elements by their ids and create an array tank to contain them. Then loop through the array while the tank.length (which is 3) is greater than i, move the tank elements by 167px. Since i can only be 0, 1 and 2, the 3 elements (labelled 0, 1 and 2) are moved.
Later if you need to access the singular elements you can call them by their ids or find them in the tank array like so: tank[0], tank[1] or tank[2].
Related
I think the title is clear, but I've some examples for you guys, here is my idea:
I've this Html5:
<html>
<head></head>
<body class="oldParent">
<div class="p1"></div>
<div class="p2"></div>
<div class="p3"></div>
<div class="p4"></div>
<div class="p5"></div>
<div class="NewParent"></div>
</body>
</html>
as you can see my NewParent is child of My OldParent
(i want to move all body elements except NewParent), and i need to move all elements except this one:
<div class="**NewParent**"></div>
I'm New to Javascript and I thought that you guys know how to add exception
for this, I've found this too:
var newParent = document.getElementById('NewParent');
var oldParent = document.getElementById('OldParent');
while (oldParent.childNodes.length > 0) {
newParent.appendChild(oldParent.childNodes[0]);
}
but i don't know how to add exception!?
I need help:(
You're pretty close with your original code. Just switch to getElementsByClassName - but be aware it returns a collection so you'll have to index into it. You might want to switch to id's if that's an option.
Then you can basically just add an if condition to test for the className of the element to exclude:
var newParent = document.getElementsByClassName('NewParent')[0];
var oldParent = document.getElementsByClassName('oldParent')[0];
while (oldParent.childNodes.length > 0) {
if (oldParent.childNodes[0].className !== 'NewParent') {
newParent.appendChild(oldParent.childNodes[0]);
}
else { break; }
}
Here's a jsfiddle you can play with. Be careful with your class names, the ones for old and new parents have different casing.
I would add while this is closest to your original code, it may not actually be the best way to solve this. This assumes your new parent is the last in the collection as it has to break out of your while loop due to the way you're looping. Cloning as the other answer shows is probably a safer solution.
You can try below method.
Get your current element which you want to retain.
Create a Clone of that element.
Empty the entire parent element.
Append the element which you have cloned earlier.
var newParent = document.getElementsByClassName('NewParent')[0];
var cln = newParent.cloneNode(true);
document.getElementsByClassName('oldParent')[0].innerHTML = '';
document.getElementsByClassName('oldParent')[0].appendChild(cln);
<html>
<head></head>
<body class="oldParent">
<div class="p1">1</div>
<div class="p2">2</div>
<div class="p3">3</div>
<div class="p4"5></div>
<div class="p5">5</div>
<div class="NewParent">77</div>
</body>
</html>
I have an image (SVG) of a human body. I would like to use JavaScript so that when I click a particular area (say, the lower leg) then all of the elements with the class "lower-leg" (even if not clicked) have their color changed -- this makes it much easier for the user.
Here is the JavaScript I currently have:
function changeclassstyle() {
var c = document.getElementsByClassName("lower-leg");
for (var i=0; i<c.length; i++) {
c[i].style.fill = "red";
}
}
The problem with this code is that it is only generalized for "lower-leg". I may have over a dozen classes I would like this to work for and don't think it is efficient to write 12 functions with the only change being the class name. Is there a way to grab what class was selected and then input that in the function?
--
Additionally, I would love to figure out how, once that section of the body is selected, I can store the class name. I would, in the end, want to store the selection, along with other inputted information in a database. But, this may be for a future question unless someone can help!
Here's how I would do it (tested on a couple of div's).
What we're doing is passing the event object to the event handler (your changeclassstyle() function). It then uses the class of the clicked-on item (the event target's class) and changes everything else on that page with that same class name to use your new desired CSS style.
function changeclassstyle(e) {
// Get all items that have the same class as the item that was clicked
var limbs = document.getElementsByClassName(e.target.className); // for div's and the like
// var limbs = document.getElementsByClassName(e.target.className.baseVal); // turns out this is needed for SVG items
// "limbs" is an HTMLCollection, not an array, so functions like .foreach won't work; C-style for-loops or modern for/let/of loops are better
for (let item of limbs) {
item.style.backgroundColor = 'red';
// item.style.fill = 'red'; // This is probably what you need for your SVG items
}
// You could still use your C-style for loop if needed/wanted
/*
for (var i=0; i<limbs.length; i++) {
limbs[i].style.fill = "red";
}
*/
}
The onchange call looks like this (using my div as the example):
<div class="upper-arm" onclick="changeclassstyle(event)">
</div>
<div class="lower-leg" onclick="changeclassstyle(event)">
</div>
The whole example with simple div's.
<html>
<head><title>stuff</title></head>
<body>
<script type="text/javascript">
function changeclassstyle(e) {
// For debugging. You may want to expand 'e' here in your browser's debug tools if you're not seeing the values you need/want
console.log(e)
var limbs = document.getElementsByClassName(e.target.className.baseVal);
for (let item of limbs) {
item.style.backgroundColor = 'red';
}
}
</script>
<style type="text/css">
div {
height: 100px;
width: 100px;
background-color: 'white';
border: 1px solid black;
}
</style>
<div class="upper-arm" onclick="changeclassstyle(event)">
</div>
<div class="upper-arm" onclick="changeclassstyle(event)">
</div>
<div class="upper-arm" onclick="changeclassstyle(event)">
</div>
<div class="lower-leg" onclick="changeclassstyle(event)">
</div>
<div class="lower-leg" onclick="changeclassstyle(event)">
</div>
<div class="lower-leg" onclick="changeclassstyle(event)">
</div>
</body>
</html>
You can use parameters in function where you pass class and color like below
function changeStyle(cls,clr) {
let elems = document.getElementsByClassName(cls);
if(!elems) return;
for (let elem of elems) {
elem.style.color = clr;
}
}
As per the iteration of many classes like i said you can store classes in array and iterate each of them.
let classes = ['one','two','three','four'];
classes.forEach(function (cls) {
changeStyle(cls,"red");
});
You can play with fiddle here if you want to test/experiment: https://jsfiddle.net/thrL5uqw/8/
Note: Change style property as you wish, For now i have used color for demo
I'm a bit late to the party, but here's my take on the problem.
Like the others told you, you'll need to use an additional parameter to your function to specify the class you want to modify your elements (or try to figure out the class from the clicked element), therefore you should have something like that:
/**
* This function will handle the click event on one of the part of the SVG.
* #param {string} lClass This the class of the element to modify
*/
function handleClick(lClass) {
for (let e of document.getElementsByClassName(lClass)) {
// Here you can do all the changes you need on the SVG element.
e.style.fill = "red";
}
}
And when it comes to the event binding, you could do like the other suggested and add the onclick event binding propery on the HTML Element, or you could bind it in you JS with the addEventListener function (that way you don't have to repeat the onclick property on each of your SVG elements).
// For each element of all the listed class, bind the "click" event to the handleClick function
const listenClass = [/*List of your classes*/];
for (let l of listenClass) {
for (let e of document.getElementsByClassName(l)) {
e.addEventListener('click', handleClick.bind(this, l));
}
}
Demo: https://plnkr.co/edit/gay2yBaVi5QD868fsTa6?p=preview
I hope it helped.
I am developing a page that will create many <div>s and appending them to a container <div>. I need to know which one of them is being clicked on. At first, I figured putting an eventListener on each one of them would be fine. However, I just read an article about using Smart Event Handlers here: Best Practices for Speeding Up Your Web Site. And it talks about using only 1 eventListener and figuring out which one of the elements the event originates from.
EDIT
I removed the previous example, here is a small example that is much closer to my target functionality and illustrates why it would be preferable to have one listener that dispatches what was clicked on. The main thing is that the <div> is what knows which index in an array needs to be grabbed for data. However, the data gets presented in <div>s that are inside the <div> that knows the array index and the event doesn't hook with him for some reason.
When you run it, you see that the log only lists the contact whenever the green "middle" <div> gets clicked but not the red "information" <div>s. How can I get the red information <div>s to trigger the listener as well without adding a zillion listeners?
JSFiddle
<!DOCTYPE html>
<html>
<head>
<title>Bubbling</title>
<meta charset="UTF-8">
<style>
div{
margin: 10px;
padding: 10px;
width: 200px;
}
#big{
background: #ccffcc;
}
.contactDiv{
background: #99ff99;
}
.nameDiv, .phoneDiv{
background: #ff9999;
}
#log{
background: #ccccff;
}
</style>
</head>
<body>
<div id="log">I log stuff</div>
<button id="adder">Add Some Div</button>
<!--Highest div that encloses multiple other div-->
<div id="big"></div>
<script>
var log = document.getElementById("log"),
adderButton = document.getElementById("adder"),
bigDiv = document.getElementById("big"),
numDivsToMake = 100,
i, contactDiv, nameDiv, phoneDiv,
contacts = [];
for (i = 0; i < numDivsToMake; i++) {
contacts.push({
name: "Bob-" + i,
phone: "555-1234"
});
}
// Make more divs whenever we click the super button
adderButton.addEventListener("click", function() {
// Don't make more
adderButton.setAttribute("disabled");
// Make the divs with data
for (i = 0; i < numDivsToMake; i++) {
// Make name and number divs
contactDiv = document.createElement("div");
nameDiv = document.createElement("div");
phoneDiv = document.createElement("div");
// Add classes
contactDiv.className = "contactDiv";
nameDiv.className = "nameDiv";
phoneDiv.className = "phoneDiv";
// Set their values
nameDiv.innerHTML = contacts[i].name;
phoneDiv.innerHTML = contacts[i].phone;
// Set the container to know how to get back to the data in the array
contactDiv.setAttribute("data-contactId", i);
// Add them to the dom
bigDiv.appendChild(contactDiv);
contactDiv.appendChild(nameDiv);
contactDiv.appendChild(phoneDiv);
}
});
// Make smart handler
bigDiv.addEventListener("click", function(e) {
// Get whether the element has the attribute we want
var att = e.target.getAttribute("data-contactId");
// Say if it does or not
if (att) {
log.innerHTML = "You clicked: " +
contacts[att].name + " " +
contacts[att].phone;
}
else {
console.log("No attribute");
}
});
</script>
</body>
</html>
I think I understand what you're doing. The event delegation you've set up in the fiddle seems to work well. The data attribute you want to select could easily be selected inside your click handler by querying the DOM. Instead of looking at the source of the click if you know you always want the data, just do another document.getElementById to retrieve your data. I think you're trying to collapse two steps into one in a way that won't work with your design.
I am new to JavaScript and would like to know how I can create multiple divs dynamically with the same class name. I have the following code but it only creates one instance of the div.
<div id="wrapper">
<div id="container">
<div id="board">
<script>
var board = document.createElement('div');
board.className = "blah";
for(x=0; x<9;x++) {
document.getElementById('board').appendChild(board);
}
</script>
</div>
</div>
</div>
Right now, you're creating the element outside the loop, and appending that element to the DOM...again and again.
What you want to do is create a new element during every iteration of the loop. To do that, move the part where you create the new div inside the loop:
for(x=0; x<9;x++) {
var board = document.createElement('div');
board.className = "blah";
document.getElementById('board').appendChild(board);
}
Now, every time the loop runs, you'll create a new element, and append that element to the element with ID #board.
It's worth pointing out that the variable you created (board) now only has scope within this loop. That means that once the loop is done, you'll need to find a different way to access the new elements, if you need to modify them.
Only a single element is created.
<script>
var board = document.createElement('div');
board.className = "blah";
for(x=0; x<9;x++) {
document.getElementById('board').appendChild(board);
}
</script>
Should be written as:
<script>
for(x=0; x<9;x++) {
var board = document.createElement('div');
board.className = "blah";
document.getElementById('board').appendChild(board);
}
</script>
Others did answer the question in a nutshell; here is one approach which addresses some issues that are present in the your and proposed code snippets, and maybe gives your some insight for further exploration. I hope it helps :)
To extend a script a little bit, this solution creates every element by using function createDiv, and references to individual divs are stored in an array, so you can modify the content of each div by modifying array elements, which are referring to DOM elements. (in this example, I modify 6th div for demonstration sake)
Notes:
All of your code is thrown in a global object, it's good
practice to encapsulate your code, here in immediately invoked
anonymous function.
x would be thrown in a global object even if encapsulated, you need
always to declare your variables with a var keyword. Here I declare
all vars needed upfront in one statement, which is also a good
practice;
It is convention to use "i" for loop iterator variable.
Avoid "magic numbers" (9), rather create a variable that will
describe what you do in your code. It is good if the code describes what
it does.
Also in this example, we avoid declaring "board" on each loop
iteration (the element where your divs get appended.)
Test your code in JSLint - great tool to validate your scripts.
(this will pass the test, given that you set indentation to 2.
"use strict" - read here.
/*jslint browser:true */
(function () {
"use strict";
function createDiv() {
var boardDiv = document.createElement("div");
boardDiv.className = "new-div";
boardDiv.innerText = "I am new DIV";
return boardDiv;
}
function createAndModifyDivs() {
var board = document.getElementById("board"),
myDivs = [],
i = 0,
numOfDivs = 9;
for (i; i < numOfDivs; i += 1) {
myDivs.push(createDiv());
board.appendChild(myDivs[i]);
}
myDivs[5].className = "modified-div";
myDivs[5].innerText = "I'm modified DIV";
}
createAndModifyDivs();
}());
.new-div {
color: gray;
}
.modified-div {
color: red;
}
<!DOCTYPE html>
<html>
<head>
<title>Inserting Divs</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<div id="board">
</div>
</div>
</div>
</body>
</html>
I need some help with something. I have 3 or 4 jScrollPanes on the same page, something like this creates them:
#foreach (var thing in Model.things)
{
<div class="scrollPanes">
<ul>
<li>Model.Item</li>
</ul>
</div>
}
My CSS is like (these panes scroll horizontally):
.scrollPanes
{
overflow: auto;
width: 100%;
}
ul
{
list-style: none;
height: 50px;
width: 1000px;
margin: 0;
padding: 0;
}
And here is how i initialize the jScrollPane
var settings = { hideFocus: true };
// Notice how all the elements with class "scrollPane" become a jscrollpane
var pane = $('.scrollPanes').jScrollPane(settings);
var api = pane.data('jsp');
api.reinitialise();
So this makes every a scrollPane, and they all work perfectly fine as I'd expect them to.
What I WANT to do is scroll ALL of the elements by an equal increment using jScrollPane().scrollToX(xxx); or jScrollPane().scrollByX(xxx);
When I call the method:
api.scrollByX(200); api.reinitialise();
That works, but it only scrolls the first list, not any after.
I'm seeking to get it to scroll all of them equally, I'd really appreciate any help on this.
Thanks
The reason your original code didn't work is because of how the .data() method works. See http://api.jquery.com/data/: "Returns value at named data store for the first element in the jQuery collection..." When you call pane.data('jsp'), it's only returning the API for the first element in the collection (which is why only the first pane scrolls for you). To run a method on the API for every element in the collection, you'll need to loop through the elements one by one. You can do that by using .each()
$(".jpanes").each(function() {
$(this).data("jsp").scrollByX(200);
});
Or, if you plan to call the APIs repeatedly, you can get an array of the APIs to call whenever you need and then use a for loop.
// Store an array of APIs for each element
var apis = $(".jpanes").map(function() {
return $(this).data("jsp");
}).get();
// Call an API method for each element
for (var i = 0, api; api = apis[i]; i++) {
api.scrollByX(200);
}
1) Add to each pane individual id
2) Write own function PanesMover(X) for changing all panes at once.
In the function move each pane on same X (recived as function argument), separetly by it's ID
3) Call in your document PanesMover(X), where X -is parameter of moving, which would be applied to all panes.
Here's what I did in simplified form, following Answer #1.
First, I attached unique IDs on all elements I wanted to scroll simultaneously, but they all use the same class.
#{ int paneID = 0; }
#foreach (var thing in Model.things)
{
<div id="#paneID" class="jpanes">
<ul>
<li>Item</li>
</ul>
</div>
paneID++;
}
Then I initialized the panes with jScrollPane as before:
$('.jpanes').jScrollPane();
Then I initialized a separate jScrollPane API for each element's unique ID using an array, something like this:
var jspAPI = new Array();
$('.jpanes').each(function(index) {
jspAPI[index] = $('#'+$(this).attr('id')).data('jsp');
}
That's it then, when I wanted to scroll them all I iterated through all in a loop
for (var i=0; i < jspAPI.length; i++) {
jspAPI[i].scrollByX(10);
}
I'm surprised I couldn't find anybody who has wanted to do this before and asked for help, but hopefully this helps someone else.