Javascript - Unable to use Element Id as a String Parameter? - javascript

Hey I'm trying to make a function that prints out the last character of the id of the element it's called from. This is simply a test before I move forward with my actual plans but I'm stuck here. The Issue I'm getting is that I get an error saying that my parameter is 'undefined', so there is not value being passed, or the value being passed isn't a string and thus string operations (charAt) won't work on it. I have included code. Thank you.
My webpage have an undertermined amount of modal images which , when clicked, open a slideshow album of images. I got this working for 1 image then realized that to have it work for an undetermined amount of slideshows of undetermined size I should make a function that fills the slideshow div. I planned to have each modal image to have an id of "modal-1, modal-2...etc" and have a bunch of arrays with the images each named similarly "slides_1 ,slides_2...etc" and if the last character of both match up, then it will populate the slideshow with the images in the array. This way If i need to add another modal image all i need to do is give it the appropriate id and add an array of its images.this is a ,past question which sort of illustrates that larger goal.
html:
<body id="modal-2" onload="fillSlides(this.id)">
<h2 id="title" style="text-align:center"></h2>
<div class="row">
<div class="column">
<img id="modal-1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" onclick="openModal();currentSlide(1);fillSlides(this.id);" class="hover-shadow cursor">
</div>
</div>
Javascript:
function fillSlides(modalID){
var slides_1 = ["Images/LS_01.jpg", "Images/LS_02.jpg", "Images/LS_03.jpg", "Images/LS_04.jpg" ]
var modalI = modalID;
var lastCharM = modalI.charAt(modalI.length - 1);
document.getElementById("title").innerHTML = "the last char is" + lastCharM;
}

The load event is actually a window event, not actually an event on body, so this within your onload-attribute-style handler text is window. To access body, use document.body instead of this:
<body id="modal-2" onload="fillSlides(document.body.id)">
Live example on JSBin (since Stack Snippets want to supply the body markup for you).
In general, I'd recommend avoiding onxyz-attribute-style event handlers. For one thing, other than methods on the element or (sometimes) it's containing element, the only functions they can call have to be globals, and the global namespace on browsers is already really crowded.
Instead, I'd recommend at least using the onxyz properties on elements and on window. In this case, that would look like this in your JavaScript code:
window.onload = function() { loadSlides(document.body.id); };
But even better would be to use modern (DOM2+) event handling via addEventListener (with fallback to Microsoft's proprietary attachEvent if you need to support obsolete versions of IE; details here):
window.addEventListener("load", function() {
loadSlides(document.body.id);
};

I would approach this using data attributes instead of parsing a string. This will allow you to use a class instead of id's on your HTML img elements, and then determine which element was clicked on via the data attribute.
HTML
<img data-num="1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" class="hover-shadow cursor img">
JS
var classname = document.getElementsByClassName("img");
var myFunction = function() {
var attribute = this.dataset.num;
//attribute has the data-num value
};
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', myFunction, false);
}
I'm also binding the click event in JS rather than an inline onclick in order to preserve separation of concerns (not mixing JS and HTML).
Update, in response to comment by kakamg0:
Using document.querySelectorAll('img[data-num]') would omit the need to use a class for targeting.
HTML
<img data-num="1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" class="hover-shadow cursor">
JS
var images = document.querySelectorAll('img[data-num]');
var myFunction = function() {
var attribute = this.dataset.num;
//attribute has the data-num value
};
for (var i = 0; i < images.length; i++) {
images[i].addEventListener('click', myFunction, false);
}

Related

Drawing 3D objects

I'm looking to draw a 3D cylinder with javascript by copying the layers and applying an increased margin to these elements. I have tried to set the height of the element in my input and run the copy function while total margin of the copied elements is lower than the set height of elements.
http://jsfiddle.net/yuX7Y/3/
<form>
<input type="number" id="userHeight" />
<button type="submit" onclick="circleHeight">Submit</button>
</form>
<div class="circle">
</div>
<div class="slice">
</div>
$(document).ready(function(){
var initMargin = 4;
var totalMargin = 0;
var i = 0;
function copy(){
$(".slice").clone().appendTo( ".content" ).css({'margin-top': totalMargin + "px"});
console.log("cloned");
i++;
totalMargin = initMargin + 4;
}
function setH(){
while(i < (document.getElementById("userHeight").value)){
copy();
}
if(i>100){
initMargin = 4;
i=0;
}
}
});
Jump To The Result: http://jsfiddle.net/yuX7Y/15/
Notes
This Fiddle/question intrigued me so I went ahead and looked at it for a little while. There were actually a number of issues, some obvious and some less obvious. Somewhat in the order I noticed them these are some of the issues:
jQuery wasn't included in the fiddle
The click event wasn't wired up correctly - it was actually trying to submit the form. You need to use e.preventDefault to stop the form from submitting. Since you were already using jQuery I just wired up with the jQuery click event:
$("#recalculateHeight").click(function (e) {
e.preventDefault();
setH();
});
Because of the use of "global" variables (variables not initialized within the routines), the submit would only work once. Instead of this, I moved variable declarations to the appropriate routine.
Calling $(".slice").clone() clones ALL slice elements on the page. The first time you clone, this is fine. But after that you are cloning two elements, then three elements, etc (as many slice elements as are on the page). To solve this I created a slice template like:
<div class="slice" id="slice-template" style="display: none"></div>
Then you can clone to your hearts content like $("#slice-template").clone(). Just don't forget to call the jQuery show() method or set display back to block on the cloned element.
Finally, if you want to repeat the process many times you need to be able to clear previous elements from the page. I find this easiest to do by creating containers, then clearing the contents of the container. So I put all of the "slices" into this container:
<div class="content">
Now when you want to clear the content node you can just call $(".content").empty();.
I also made a few style based changes in my Fiddle, but those don't make it work or not work, they just help me read the code! So, there you have it! Best of luck!

while loop in Javascript with concatenation

I'm trying to make a while-loop in my Javascript.
So far, I've got this:
<script>
$(document).ready(function(){
var i=0;
while (i<9999) {
$(".add_new_item_field" + i).hide();
$(".add_new_item_button" + i).click(function(){
$(".add_new_item_field" + i).slideToggle("slow");
});
i++;
}
});
</script>
Goal is to make this work:
<div class="add_new_item_button1"></div>
<div class="add_new_item_button2"></div>
<div class="add_new_item_button3"></div>
...
<div class="add_new_item_field1">Show me something</div>
<div class="add_new_item_field2">Show me something</div>
<div class="add_new_item_field3">Show me something</div>
...
But for some reason, it's not working. Am I missing something here?
Your problem is that the concatenation in the line $(".add_new_item_field" + i).slideToggle("slow"); happens when you click one of the divs. Yet, the loop that had set up the handlers was run long ago then and i already has a value of 9999. Use a closure as #David demonstrated to avoid this.
However, I feel this is the wrong approach. Setting up 10000 click handlers, and executing 20000 jQuery selection does make your page very, very slow. Use one common class for the button, and one common class for the fields. If you can't depend on a certain document order, give them unique ids to refer to each other - but not classes.
Then hide all the fields with one single line of CSS, and use event delegation for the buttons to fire 1 single function that looks up the field by id from the data attached to the clicked button.
<style>
.add_new_item_field { display:none; }
</style>
<!-- placing the stylesheet here also avoids flickering.
Even better would be of course if it was written dynamically by JS, for not
hiding the fields in clients that do not support JavaScript -->
<script src=/"jquery.js"></script>
<script>
jQuery(function($) {
$(document).on("click", ".add_new_item_button", function(e) {
var id = "field"+$(this).data("field");
$('#'+id).show();
});
});
</script>
<div class="add_new_item_button" data-field="1"></div>
<div class="add_new_item_button" data-field="2"></div>
<div class="add_new_item_button" data-field="3"></div>
...
<div class="add_new_item_field" id="field1">Show me something</div>
<div class="add_new_item_field" id="field2">Show me something</div>
<div class="add_new_item_field" id="field3">Show me something</div>
...
I’m guessing that i is not what you expect it to be in the handler, because when the handler executes i has already maxed out to 9999. To fix that, you need to bring the variable into the handler closure, something like:
var i=0;
while (i<9999) {
$(".add_new_item_field" + i).hide();
$(".add_new_item_button" + i).click((function(i) {
// i is now saved in this closure
return function() {
$(".add_new_item_field" + i).slideToggle("slow");
};
}(i)));
i++;
}
Sidenote: I’m not really sure this is the best way to solve your actual task here though, looping and attaching 9999 event handlers seems unnecessary...
I think you are using the class the wrong way. You can assign a click handler to all objects of the same class. But you are trying to use the class specifier as an ID and are trying to assign the handler to each seperate object. You wouldn't do the same for the behavior and layout of a link/url? (link) would you?
Read this: jQuery: How do I add a click handler to a class and find out which element was clicked?
You want to setup the handler for a class, specify your divs as that class. I not abusing the class specifier as some sort of ID.
You are creating a closure in your click handler which captures the i variable, not it's value. This means all the click functions will contain the value 9999 which the value of the variable at the end of the while loop.
You can fix it by creating a function that set's the click handler.
var setClick = function(index) {
$(".add_new_item_button" + index).click(function(){
$(".add_new_item_field" + index).slideToggle("slow");
}
}
And use it in your while loop.
while (i<9999) {
$(".add_new_item_field" + i).hide();
setClick(i);
i++;
}
Now the value of i is correctly captured by your click handler

Speed up page load by deferring images

I am creating a page which will contain a lot of large sized images, so naturally I want to make sure the page loads without too much trouble. I read this article here http://24ways.org/2010/speed-up-your-site-with-delayed-content
The method of deferring is as follows (pulled from page, don't mind the URL)
<div>
<h4>
<a href="http://allinthehead.com/" data-gravatar-hash="13734b0cb20708f79e730809c29c3c48">
Drew McLellan
</a>
</h4>
</div>
then later a snippet of js takes care of the image loading
$(window).load(function() {
$('a[data-gravatar-hash]').prepend(function(index){
var hash = $(this).attr('data-gravatar-hash')
return '<img width="100" height="100" alt="" src="http://www.gravatar.com/avatar.php?size=100&gravatar_id=' + hash + '">'
});
});
I don't plan on doing this for every image but definitely for some image which I don't need it to show up at page load time.
Is this the best way to go or are there better ways to achieve faster page load by deferring images?
Thanks
A little late, but in case it benefits others, there is a great article on this topic by Patrick Sexton
https://varvy.com/pagespeed/defer-images.html
He basically is suggesting the same thing, only by using tiny base 64 encoded images, he can place his image tags directly in the HTML which has the benefit of being able to control attributes like height, width, alt, etc individually. It will be a lot easier to maintain your HTML this way as opposed to creating the entire image tag in a script.
<img src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="image1.jpg" alt="image 1">
<img src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="image2.jpg" alt="image 2">
Then your script is simple and generic for all images
<script>
function init() {
var imgDefer = document.getElementsByTagName('img');
for (var i = 0; i < imgDefer.length; i++) {
if (imgDefer[i].getAttribute('data-src')) {
imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));
}
}
}
window.onload = init;
</script>
This seems to be pretty clean way of deferring images. The only potential problem is if images carry important information as "Data attributes are a new feature in HTML5".
Another option could be to put images to end of body and use CSS to position them. Personally I would stick to javascript.
Here's a version showcasing .querySelectorAll:
function swapSrcAttributes(source) {
return function(element) {
element.setAttribute('src', element.getAttribute(source));
}
}
function forEach(collection, partial) {
for (var i = 0; i < collection.length; i++) {
partial(collection[i]);
}
}
function initDeferImages() {
// for images
var deferImages = document.querySelectorAll('img[data-src]');
// or you could be less specific and remove the `img`
deferImages = document.querySelectorAll('[data-src]');
forEach(deferImages, swapSrcAttributes('data-src'));
}
window.onload = function() {
initDeferImages();
}
Here is the compatibility table for .querySelector and .querySelectorAll via https://caniuse.com/#feat=queryselector
Html
<img
width="1024"
src="https://placehold.it/64x48.jpg"
data-src-defer="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80"
alt="image 1"
/>
<img
width="1024"
src="https://placehold.it/64x48.jpg"
data-src-defer="https://images.unsplash.com/photo-1557053964-d42e8e29cb27?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80"
alt="image 2"
/>
JS
function deferImgs() {
Array
.from(document.querySelectorAll("img[data-src-defer]"))
.forEach((element) => {
element.setAttribute("src", element.dataset.srcDefer);
});
}
window.addEventListener("load", deferImgs());
================================================================
I'm trying to comply with Farrukh's request with this edit.
I try to do my best, but English is unfortunately only the third language I speak. And I am not a language genius. :D
This js code snippet illustrates a delayed load of some big pictures.
This is not a practical implementation.
The size difference between the images is intentionally huge.
This is because the test must be illustrative.
You can monitor its operation through a browser development tool page.
F12 > Network tab > Speed settings dropdown
The ideal network speed for the test is between 1 - 3MB/s (Some slow network speed).
You may want to run the test several times, so you can see, that the order in which the images are loaded is not controlled in this case, but depends on the transmission.
Because it is not regulated, it is not possible to predict, which image will arrive first.
We load first a small image into a large placeholder.
(image: 64x48.jpg > placeholder width="1024").
The querySelectorAll() method returns a static nodelist. This list of nodes at first glance looks like an array, but it's not.
This is an array-like object:
document.querySelectorAll("img[data-src-defer]")
The Array.from() method can creates a new array instance from this object.
The forEach method can now be executed on this array.
The forEach() method executes a provided function once for each element of the array.
In this case, each element of the array is passed once to this function:
(element) => {
element.setAttribute("src", element.dataset.srcDefer);
}
and this function sets the value of the src="" attribute of the image tag, to the value of the dataset of the same image tag.
src="https://placehold.it/64x48.jpg";
data-src-defer="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80";
src = data-src-defer;
So finally:
src="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80";
You can do it as simple as the example below:
All images have data-src attribute where you put the file path. And src attribute with a fake transparent 1x1px png image. You can also add loading attribute setted to lazy, it tells modern browsers to avoid to load immediately the images that are out of viewport (visible site zone)
<img data-src="path/to/image.jpg" loading="lazy"
src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
Add this script to make all images get the src attribute once your site is loaded (you need jQuery to make it work)
$(function(){
$("img[data-src]").attr("src", function(){ return $(this).data("src"); });
});

make javascript repeat for each row in a view

I have created a view in Drupal. I'm using JavaScript to modify the CSS in each row. The script runs on the first row, but does not make the changes on the rest of rows from the view.
This is the script:
<script language="javascript" type="text/javascript">
window.onload = floatbr;
function floatbr() {
var f = document.getElementById('firstright') // Get div element
var s = document.getElementById('secondright') // Get div element
var w = document.getElementById('catwrapper') // Get div element
var sh = s.offsetHeight // secondright div height
var wh = w.offsetHeight // catwrapper div height
f.style.height = wh - sh + 'px'; }
</script>
I'm using it from this page: http://agsone.100webcustomers.com/floatbottom.php
having the script in the page once does not do the trick.
having the script in the view footer and repeating the script does not work.
The link to the jSfiddle with HTML, CSS and JavaScript is the following one: http://jsfiddle.net/YTN3K/.
Drupal provides and already uses jQuery, so you should use it too. Drupal has its own way to manage JavaScript and comes with some additional JavaScript API, mainly to handle passing variables from PHP to JavaScript properly, register script to run on page load and content addition, etc.
jQuery is well documented and popular, so finding documentation, tutorial and howto is easy. Its own documentation page is a good start. But it requires basic understanding of what an XHTML document is and how it is structured.
It's hard to tell from your question and the markup you've linked to exactly what you're trying to do, so here's some general information to get you going:
The function you're currently using, getElementById, returns a single element: The one on the page with that id value. (id values must be unique on the page.)
To deal with multiple elements, you have several options. Two of the most popular:
You can start with a given element and then use its childNodes, firstChild, nextSibling, and similar properties to navigate from it to other elements nearby.
You can use getElementsByTagName (on the document or on an element) to find all elements within that container (including ones several levels down) that have a given tag. For instance, document.getElementsByTagName("p") gives you a NodeList of all paragraphs on the page.
These are properties and methods of the "DOM" (the Document Object Model), which is the tree of elements and associated information the browser creates when parsing and rendering your HTML.
References:
DOM2 Core
DOM2 HTML bindings
DOM3 Core
HTML5 Specification's DOM info
Here's an example showing some very basic operations (live copy):
HTML:
<div id="justOneDiv">I'm the <code>justOneDiv</code> element. I'm unique on the page. JavaScript code on the page turned me red.</div>
<div id="container">I'm a container called "container" with <span>various</span> <code>span</code> elements. <span>Code</span> on the <span>page</span> has made all of the <code>span</code> elements in this container <span>bold</span>.</div>
<div>I'm a container with <span>various</span> <code>span</code> elements. <span>Note</span> that the <code>span</code> elements are <span>not</span> bold, because I'm <span>not</span> in the container above.</div>
<div>I'm a <code>div</code> with no class.</div>
<div class="foo">I'm a <code>div</code> with class "foo". Code on the page turned me blue.</div>
<div class="bar">I'm a <code>div</code> with class "bar". Code on the page turned me green.</div>
<div>Another classless <code>div</code></div>
<div class="foo other">Another "foo", also with class "other"</div>
<div class="bar">Another "bar"</div>
<div>Another classless <code>div</code></div>
<div class="foo">Another "foo"</div>
<div class="bar test">Another "bar", also with class "test"</div>
<div>Another classless <code>div</code></div>
<div class="foo">Another "foo"</div>
<div class="bar">Another "bar"</div>
<div>Another classless <code>div</code></div>
<div class="foo">Another "foo"</div>
<div class="bar">Another "bar"</div>
JavaScript:
(function() {
hookEvent(window, "load", go);
function go() {
var list, index, div, container;
// Get just the one element, turn it red
document.getElementById("justOneDiv").style.color = "red";
// Get the spans within the specific container
container = document.getElementById("container");
list = container.getElementsByTagName("span");
// Loop through making those spans bold
for (index = 0; index < list.length; ++index) {
list.item(index).style.fontWeight = "bold";
}
// Get a NodeList of all divs on the page
list = document.getElementsByTagName("div");
// Loop it, turning "foo"s blue and "bar"s green
for (index = 0; index < list.length; ++index) {
div = list.item(index);
if (/\bfoo\b/.test(div.className)) {
div.style.color = "blue";
}
else if (/\bbar\b/.test(div.className)) {
div.style.color = "green";
}
}
}
function hookEvent(element, eventName, handler) {
// Very quick-and-dirty, recommend using a proper library,
// this is just for the purposes of the example.
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(eventName, handler, false);
}
else if (typeof element.attachEvent !== "undefined") {
element.attachEvent("on" + eventName, handler);
}
else {
element["on" + eventName] = handler;
}
}
})();
Side note: The operations above can be made dramatically simpler by leveraging the utility functionality provided by any decent JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others.
For example, using that same HTML, here's the JavaScript code using jQuery for the same result (live copy):
jQuery(function($) {
// Get just the one element, turn it red
$("#justOneDiv").css("color", "red");
// Get the spans within the specific container
// Loop through making those spans bold
$("#container span").css("font-weight", "bold");
// Turn all divs with the class "foo" blue
$("div.foo").css("color", "blue");
// Turn all divs with the class "bar" green
$("div.bar").css("color", "green");
});
The DOM is the official API; libraries like jQuery provide alternate or enhanced APIs. They're very useful and powerful, but I would recommend having some understanding of the DOM itself, even if you use a library and end up rarely writing code directly to the DOM API.

MooTools Fx.Slide throwing this.element is null

The following code is throwing the error "this.element is null". However, the wid_cont is definitely grabbing an element.
window.addEvent('domready',function(){
var min = $(document.body).getElements('a.widget_minimize');
min.addEvent('click',
function(event){
event.stop();
//var box = ;
var wid_cont = ($(this).getParents('.widget_box').getElement('.widget_box_content_cont'));
var myVerticalSlide = new Fx.Slide(wid_cont);
myVerticalSlide.slideOut();
}
);
});
It's moo tools 1.2.4 and has the fx.slide included....
it does not return a single element but an array due to getParents() and possible other similarly marked up elements, Fx.Slide requires you pass it a single element.
here it is at least partially working when passing first item of the array: http://www.jsfiddle.net/KFdnG/
however, this is imo ineffective and difficult to manage if you have a long list of items and need a particular content layer to unfold only, you want to keep the lookup to the content layer more local.
something like this:
http://www.jsfiddle.net/KFdnG/4/
// store an instance into each content div and set initial state to hidden.
$$("div.widget_box_content_cont").each(function(el) {
el.store("fxslide", new Fx.Slide(el).hide());
});
$$('a.widget_minimize').addEvent('click', function(event) {
event.stop();
// can't use this.getNext() due to wrapper by Fx.Slide which does not have the instance.
this.getParent().getElement("div.widget_box_content_cont").retrieve("fxslide").toggle();
});
which works on the markup of:
<div class="widget_box">
<div class="widget_box_content">
link
<div class="widget_box_content_cont">
some content
</div>
</div>
<div class="widget_box_content">
link 2
<div class="widget_box_content_cont">
some content 2
</div>
</div>
</div>
this is also better as you won't be making a new instance of the Fx.Slide class on every click but will reference the ones already attached to the element.

Categories