The function outputs correctly on online code editors but I am not successful in replicating the output on my browser. What's the correct way of outputting it to my browser? I have tried numerous methods. Here is the function I want to output.
function countdown(i) {
console.log(i);
if (i <= 1) { // base case
return;
} else { // recursive case
countdown(i - 1);
}
}
countdown(5); // This is the initial call to the function.
Here is my most recent attempt at output on my web browser
function countDown(i) {
document.getElementById("recursiveFuncAttempt").innerHTML = i;
if (i <= 1) {
return;
} else {
cat = countDown(i - 1);
return document.getElementById("recursiveFuncAttempt").innerHTML = cat;
}
}
countDown(5);
<div>
countdown attempt
<button onclick="countDown()">click me</button>
<p id="recursiveFuncAttempt"></p>
</div>
Grouping your code and the comments together...
Your original code was correct but instead of logging to the console you should add the value to the text content of a page element.
Logging the different values in the console - line by line - gives an appearance of time passing which updating the text content of a DOM element wouldn't give you. All you would see is the last digit in the sequence because the function would work faster than your eyes can see.
Therefore a a timeout is needed to pause execution for n time before calling the function again.
You can simplify the code a little by eliminating the else part of the condition.
// Cache the element
const div = document.querySelector('div');
// Add a default value to count if a value
// is not passed into the function
function countdown(count = 5) {
// If count is zero just return
if (count < 1) return;
// Otherwise update the text content
// of the cached element
div.textContent = count;
// Wait one second (1000ms), and call the function
// with a decremented count
setTimeout(countdown, 1000, --count);
}
countdown();
div { font-size: 5em; color: blue; font-weight: 700;}
<div></div>
Related
I want to use image-maps inside fancybox 3. Goal is to display mountain panoramas, where the user could point on a summit and get name and data. The usual recommendation is to use a SVG based image map for this like in this pen. Due to the size of the images the fancybox zoom functionality is important.
While fancybox will display SVGs as an image like in this pen it is not possible to use the <image> tag with an external source inside the SVG file. Even worse: SVG files used as source of an <img> tag would not show the map functionality (see this question).
I tried to replace the <img> tag in fancybox with an <object> tag using the SVG file as data attribute. This shows the image with the map functionality correctly but fancybox won't zoom it any more.
So eventually the question boils down to how I can make an <object> (or an inline SVG or an iframe) zoomable just like an image in fancybox 3.
I'm open to other solutions as well. I only want to use fancybox to keep the appearance and usage the same as other image galleries on the same page. I'd even use an old style <map>, where I would change the coords using jquery to have it responsive. I tried that, attaching the map manually in developer tools as well as programmatically in afterLoad event handler, but apparently this doesn't work in fancybox 3 either.
The areas are polygons, so using positioned div's as overlays is no solution.
Edit: I just discovered that I can replace <img> with a <canvas> in fancybox without loosing the zoom and drag functionality. So in theory it would be possible to use canvas paths and isPointInPath() methode. Unfortunately I need more than one path, which requires the Path2D object, which is not available in IE...
Since all options discussed in the question turned out to be not feasible and I found the pnpoly point in polygon algorithm, I did the whole thing on my own. I put the coordinates as percentages (in order to be size-independent) in an array of javascript objects like so:
var maps = {
alpen : [
{type:'poly',name:'Finsteraarhorn (4274m)',vertx:[56.48,56.08,56.06,56.46], verty:[28.5,28.75,40.25,40.25]},
{type:'rect',name:'Fiescherhörner (4049m)',coords:[58.08,29.5,59.26,43.5]},
{type:'poly',name:'Eiger (3970m)',vertx:[61.95,61.31,61.31,60.5,60.5], verty:[43,35.25,30.25,30.25,45.5]}
]
}; // maps
Since the pnpoly function requires the vertices for x and y separately I provide the coordinates this way already.
The Id of the map is stored in a data attribute in the source link:
<a href="/img/bilder/Alpen.jpg" data-type='image' data-Id='alpen' data-fancybox="img" data-caption="<h5>panorama of the alps from the black forest Belchen at sunset</h5>">
<img src="/_pano/bilder/Alpen.jpg">
</a>
CSS for the tooltip:
.my-tooltip {
color: #ccc;
background: rgba(30,30,30,.6);
position: absolute;
padding: 5px;
text-align: left;
border-radius: 5px;
font-size: 12px;
}
pnpoly and pnrect are provided as simple functions, the handling of that all is done in the afterShow event handler:
// PNPoly algorithm checkes whether point in polygon
function pnpoly(vertx, verty, testx, testy) {
var i, j, c = false;
var nvert = vertx.length;
for(i=0, j=nvert-1; i<nvert; j=i++) {
if (((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
c = !c;
}
}
return c;
}
// checks whether point in rectangle
function pnrect(coords,testx,testy) {
return ((testx >= coords[0]) && (testx <= coords[2]) && (testy >= coords[1]) && (testy <= coords[3]));
}
$("[data-fancybox]").fancybox({
afterShow: function( instance, slide ) {
var map = maps[$(slide.opts.\$orig).data('id')]; // Get map name from source link data-ID
if (map && map.length) { // if map present
$(".fancybox-image")
.after("<span class='my-tooltip' style='display: none'></span>") // append tooltip after image
.mousemove(function(event) { // create mousemove event handler
var offset = $(this).offset(); // get image offset, since mouse coords are global
var perX = ((event.pageX - offset.left)*100)/$(this).width(); // calculate mouse coords in image as percentages
var perY = ((event.pageY - offset.top)*100)/$(this).height();
var found = false;
var i;
for (i = 0; i < map.length; i++) { // loop over map entries
if (found = (map[i].type == 'poly') // depending on area type
?pnpoly(map[i].vertx, map[i].verty, perX, perY) // look whether coords are in polygon
:pnrect(map[i].coords, perX, perY)) // or coords are in rectangle
break; // if found stop looping
} // for (i = 0; i < map.length; i++)
if (found) {
$(".my-tooltip")
.css({bottom: 'calc(15px + '+ (100 - perY) + '%'}) // tooltip 15px above mouse coursor
.css((perX < 50) // depending on which side we are
?{right:'', left: perX + '%'} // tooltip left of mouse cursor
:{right: (100 - perX) + '%', left:''}) // or tooltip right of mouse cursor
.text(map[i].name) // set tooltip text
.show(); // show tooltip
} else {
$(".my-tooltip").hide(); // if nothing found: hide.
}
});
} else { // if (map && map.length) // if no map present
$(".fancybox-image").off('mousemove'); // remove event mousemove handler
$(".my-tooltip").remove(); // remove tooltip
} // else if (map && map.length)
} // function( instance, slide )
});
Things left to do: Find a solution for touch devices, f.e. provide a button to show all tooltips (probably rotated 90°).
As soon as the page is online I'll provide a link here to see it working...
I have a div in my html:
.row(id="div_amount")
.col-sm-2
label(for="amount") Amount:
.col-sm-1
input(type="text", name="amount")
which is perfect as I expected. It displays in one line for both elements.
I have a radio button which have an onclick event for toggle the display of this div section.
function div_toggle() {
div_amount = document.getElementById('div_amount');
if (radio_value === 0) {
div_amount.style.display = "block";
} else {
div_amount.style.display = "none";
}
}
However, after click on the radio button, it always displays as two lines.
How could I fix it to always display in one line?
Please take a look at https://stackoverflow.com/help/mcve and try to include a reproducible example.
In bootstrap4, rows are displayed as flex, i.e., "display: flex;". So in your JavaScript if you change it to display as block, the columns within the row would be broken.
You can set its display to flex, like
if (radio_value === 0) {
div_amount.style.display = "flex";
} else {
div_amount.style.display = "none";
}
although setting HTML element styles in JavaScript is considered as bad practice IMO.
I want to get the element through javascript based on attribute value, as per the example I want to get element through attribute "doc-cid" and the value of "doc-cid" is dynamic.
<p align="center" style="font-family:times;" doc-cid="11303">
<font size="2" doc-cid="11304">55</font></p>
<p id="demo">Click the button to change the text of a list item.</p>
<button onclick="myFunction()">Try it</button>
<script>
function myFunction()
{
var list = document.getElementsByTagName("doc-cid=\"11303\"")
alert(list.getAttribute("doc-cid"));
}
</script>
I'll start with a few pointers about your above code, and follow through with a solution.
POINTERS
doc-cid is not a "TagName", it is a custom attribute. hence, your function trying to getElementsByTagName will always fail for "doc-cid".
doc-cid can by dynamic (or not) it doesn't matter. A lookup function will always get the CURRENT DOM value of your element (unless you specifically make it do otherwise).
I suggest you use the new "data-*" attribute in html, it keeps your markup valid (if that is important to you). The use would be as follows:
your content
SOLUTION
function getElementByAttribute (attribute, value, start) {
var start = start || document,
cache, //store found element
i;
function lookup(start) {
// if element has been found and cached, stop
if (cache) {
return;
} else if (start.getAttribute(attribute) === value) { // check if current element is the one we're looking for
cache = start;
return;
} else if (start.childElementCount > 0) { // if the current element has children, loop through those
lookup(start.children[0]);
} else if (start.nextElementSibling) { // if the current element has a sibling, check it
lookup(start.nextElementSibling);
} else {
return;
}
}
lookup(start);
return cache;
}
You simply give the function the attribute name you are looking up, the value you need to match and the starting point of the lookup (if no starting point is specified it'll start at the very beginning of your page (much slower).
Below is an example for your markup:
// you have no easy to get starting point, so we'll traverse the DOM
getElementByAttribute('doc-cid', '11303');
If you want to start at a better node, you can add a wrapper div element and give it id="wrapper" then you could call the function as follows:
var start = document.getElementById('wrapper');
getElementByAttribute('doc-cid', '11303', start);
Hope this helps.
I am trying to do 2 actions on one click on the element using Masonry library. It should work the way that when I click on the element it expands (I am adding class to have box bigger) and in the meantime the page is scrolling to that box. The problem is that when you expand the box it may drop a line down and that means the function called to scroll will scroll to wrong place. It should do the layout and get new position of the element and then move...
I got it almost working. It is expanding the box and doing new layout and when finished it scrolls the page to the box... but it looks a little bit strange and it first moves all the boxes for new layout, then stops and strars moving the page. I would prefer that to be done in one move. It that possible.
Here is my code:
$(document).ready(function() {
var $container = $('#container');
// initialize
var msnry = new Masonry( container, {
columnWidth: 280,
itemSelector: '.item'
} );
$('.openBoks').on('click', function() {
// this is my element
var thisBox = $(this).parent().parent();
// adding the class to expand it
thisBox.addClass('opened');
// calling method to do new layout
msnry.layout();
msnry.on( 'layoutComplete', function( msnryInstance, laidOutItems )
{
$('html, body').animate({
scrollTop: (thisBox.offset().top-10)
}, 700);
return true;
});
});
});
In case you or someone else is still looking for an answer to this, I did a little workaround this problem. The idea was basically the same, to do the scroll and the masonry repositioning simultaneously. It's not the most elegant solution since I added a couple of lines inside the masonry script.
Inside masonry.pkgd.js, there is a function _processLayoutQueue that positions the masonry items stored in the queue array, calling the _positionItem for each of the items.
Outlayer.prototype._processLayoutQueue = function( queue ) {
for ( var i=0, len = queue.length; i < len; i++ ) {
var obj = queue[i];
this._positionItem( obj.item, obj.x, obj.y, obj.isInstant );
}
};
What I did was check through the iteration for the item with the identifying masonry class, in your case ".opened". So when that item is found, I immediately activate the page scroll using a cointainer div ("#container_div_id" for this example) for the offset and adding the items "y" position (obj.y). At the end, the code looks something like this:
Outlayer.prototype._processLayoutQueue = function( queue ) {
for ( var i=0, len = queue.length; i < len; i++ ) {
var obj = queue[i];
this._positionItem( obj.item, obj.x, obj.y, obj.isInstant );
/* ADDED CODE */
if( $(obj.item.element).hasClass('opened')){
$('html, body').animate({
scrollTop: $('#container_div_id').offset().top+obj.y
}, 600);
}
/* END OF ADDED CODE */
}
};
This makes the scrolling start right after the positioning of the item, without having to wait for the layoutComplete event. As I said, its not the most elegant solution, but it is pretty solid, It works fine for my case.
Hope it helps someone!
I've got an issue where I'm using template.render to render an array of items based on a html template. Each item in the array also contains another array, that I want to bind to another template, within the parent element for the area. I know I can use a grid layout for groups, but I'm trying to accomplish this another way, so please, no suggestions to use a different control, I'm just curious as to why the following doesn't work correctly.
//html templates
<div id="area-template" data-win-control="WinJS.Binding.Template">
<h1 class="area-title" data-win-bind="innerHTML:title"></h1>
<div class="items">
</div>
</div>
<div id="item-template" data-win-control="WinJS.Binding.Template">
<h2 class="item-title" data-win-bind="innerHTML:title"></h2>
</div>
// JS in ready event
var renderer = document.getElementsByTagName('section')[0];
var area_template = document.getElementById('area-template').winControl;
var item_template = document.getElementById('item-template').winControl;
for (var i = 0; i < areas.length; i++) {
var area = areas.getAt(i);
area_template.render(area, renderer).done(function (el) {
var item_renderer = el.querySelector('.items');
for (var j = 0; j < area.items.length; j++) {
var item = area.items[j];
item_template.render(item, item_renderer).done(function (item_el) {
});
}
});
}
So what should happen, is that after it renders the area, in the "done" function the newly created element (el) gets returned, I'm then finding it's ".items" div to append the items to. However, this appends all the items to the first div created. If it was the last div, it might make more sense due to closures, but the fact it happens on the first one is really throwing me off!
What's interesting, is that if I replace my template render function using document.createElement and el.appendChild, it does display correctly e.g: (in the done of area render)
area_template.render(area, renderer).done(function (el) {
var item = area.items[j];
var h2 = document.createElement('h2');
h2.innerText = item.title;
el.appendChild(h2);
}
although I've realised this is el it is appending it to, not the actual .items div of the el
I'm not quite sure what could be going on here. It appears the value of el is getting updated correctly, but el.querySelector is either always returning the wrong ".items" div or it's getting retained somewhere, however debugging does show that el is changing during the loop. Any insight would be greatly appreciated.
thanks
I've worked out what is going on here. The "el" returned in the render promise is not the newly created element as I thought. It's the renderer and the newly created html together. Therefore el.querySelector('.items') is always bringing back the first '.items' it finds. I must have misread the docs, but hopefully someone else will find this information useful in case they have the same error.
I guess one way around this would be to do item_rendered = el.querySelectorAll('.items')[i] and return the numbered '.items' based on the position in the loop
e.g
for (var i = 0; i < areas.length; i++) {
var area = areas.getAt(i);
area_template.render(area, renderer).done(function (el) {
var item_renderer = el.querySelectorAll('.items')[i];
for (var j = 0; j < area.items.length; j++) {
var item = area.items[j];
var h2 = document.createElement('h2');
h2.innerText = item.title;
item_renderer.appendChild(h2);
}
});
}