I'm using jQuery to show / hide a div which happens to contain an iframe. It works great with just standard 'show' and 'hide' methods.
So now I want to get a little fancy and add some effects from jQuery UI (http://jqueryui.com/effect/) but suddenly my iframes are getting reloaded every time I show / hide them.
Here is a fiddle to demonstrate: http://jsfiddle.net/BnZzk/1/
And here is the code since SO is forcing me to add it:
<style>
div {
height: 200px
}
span {
display: block;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid black;
}
</style>
<div>
<iframe src="http://www.wikipedia.org/"></iframe>
</div>
<input type="button" value="Go"/>
<hr/>
<div id="msgs"></div>
<script>
$(function () {
var container = $('div'),
ifrm = $('iframe'),
msgs = $('#msgs')
delay = 10; //change me to adjust delay in seconds between actions
$('input').on('click', normal);
log('First let the iframe load and then clear your network console -- click the "Go" button to get started.');
function log (msg) {
msgs.append('<span>' + msg + '</span>');
}
function normal () {
ifrm.
hide(400, function () {
log('That was a standard hide with no effect -- is your network console still empty? OK we\'ll pause for ' + delay + ' seconds and then do a standard show.');
ifrm.
delay(delay * 1000).
show(400, function () {
log('That was a show with no effect -- is you network console *still* empty? Great! Let\'s try some effects.');
log('------------------------<br/>' +
'-- Begin Effects --<br/>' +
'------------------------<br/>');
withEffect();
});
}); //hide
} //normal
function withEffect () {
log('We\'ll pause for another ' + delay + ' seconds -- get ready to watch your network console.');
ifrm.
delay(delay * 1000).
hide('fold', {mode:'hide'}, 400, function () {
log('That was a hide with effect -- is your network console flooded? Mine too :-( We\'ll wait ' + delay + ' seconds while you clear your console again.');
ifrm.
delay(delay * 1000).
show('fold', {mode:'show'}, 400, function () {
log('That was a show with effect -- is your network console flooded again? Bummer ...');
});
}); //hide
} //withEffect
});
</<script>
Any idea how I can keep the fancy effects but not refresh the content of my iframes?
It's happening because this effect reorganizes the DOM, putting a DIV wrapper around the IFRAME, so when the IFRAME is "reappended" the reload happens! You can see this behavior using the Google Chrome elements inspector.
To solve I suggest you apply the effect in a parent DIV from your IFRAME but not using the effect plugin. Check out the http://api.jquery.com/animate/, manipulating the width and height style properties.
As #Jamillo Santos's answer, 'reappend' issues of iFrame.
if you are using dialog widget or its extension of jQueryUI and want to prevent this situation,
just redefine _moveToTop() function of your widget implementation as below.
_moveToTop: function() {
return null;
},
Related
Synopsis:
The main idea behind this is to auto smooth scroll to the testimonials section of my html after a certain amount of time.
Achieved so far:
I'm able to scroll to the testimonials section of the page after X seconds without any problems using a simple script in my main index.html page. The script code is given below.
Auto scroll in the page after 5secs
<script>
setTimeout(function(){
window.location.hash = '#testimonials';
},5000);
</script>
Problem facing:
I've smooth scrolling in the entire page, but for the timer scrolling in my page, I'm unable to use smooth scroll. The uncertain transition is looking awkward, hence, I want to make it smooth scroll.
Also, I want this to happen only for the first time on page loading, i.e., if any operation is done in the page, this will not happen if visited index.html again.
TIA guys!
function goTo(selector, timeout, cb) {
var $el = $(selector);
if (!$el[0]) return;
var $par = $el.parent();
if ($par.is("body")) $par = $("html, body");
setTimeout(() => {
$par.stop().animate({scrollTop: $el.offset().top}, 1000, cb && cb.call($el[0]));
}, timeout || 0);
}
// USE LIKE:
goTo("#testimonials", 3000, function() {
// You can use `this` to reference #testimonials! yey
$(this).append("<br>Going to #contact in 3sec!");
goTo("#contact", 3000);
});
// Alternatively, without using callbacks you can do
// goTo("#testimonials", 3000);
// goTo("#contact", 6000);
// Reuse function for elements click!
$("[href^='#']").on("click", function(e) {
e.preventDefault();
goTo($(this).attr("href"));
});
/*QuickReset*/
*{margin:0;box-sizing:border-box;} html,body{height:100%;font:14px/1.4 sans-serif;}
article {
height: 150vh;
}
<article style="background:#0bf;" id="top">WELCOME (wait 3 sec)</article>
<article style="background:#f0b;" id="about">ABOUT</article>
<article style="background:#b0f;" id="work">OUR WORK</article>
<article style="background:#0fb;" id="testimonials">TESTIMONIALS</article>
<article style="background:#fb0;" id="contact">
CONTACT TO TOP
</article>
<script src="//code.jquery.com/jquery-3.1.0.js"></script>
I got js from FAQ module where nodes are loaded collapsed, with "faq-answer ... collapsable collapsed" and when toggled "faq-answer ... collapsable ("collapsed" disappears).
I suspect that since .collapsed includes 'display: none;' the lazy loading script for images is not triggered.
Therefore, I'm trying to execute that script lazy.js after any faq is toggled but can't make it work.
A) Inside lazy.js i gave it a name (function runblazy($) { and inside faq.js I inserted runblazy(); just after $(this).next('div.faq-dd-hide-answer').toggleClass("collapsed"); but this interrupts the entire script, which prevents the page from properly loading ie. render links etc.
B) I also tried to run it when the class "collapsed" disappeared, but it's only being run once (during page load) and I neither could figure out how to possibly make it work with a while loop
var x = document.getElementsByClassName("faq-answer");
if (x[0].classList.contains("collapsed")) { //do nothing }
else { runblazy(); }
Which would be the better / correct approach, what am I missing?
Here is the js from faq.js :
(function ($) {
Drupal.behaviors.initFaqModule = {
attach: function (context) {
// Hide/show answer for a question.
var faq_hide_qa_accordion = Drupal.settings.faq.faq_hide_qa_accordion;
$('div.faq-dd-hide-answer', context).addClass("collapsible collapsed");
if (!faq_hide_qa_accordion) {
$('div.faq-dd-hide-answer:not(.faq-processed)', context).addClass('faq-processed').hide();
}
$('div.faq-dt-hide-answer:not(.faq-processed)', context).addClass('faq-processed').click(function() {
if (faq_hide_qa_accordion) {
$('div.faq-dt-hide-answer').not($(this)).removeClass('faq-qa-visible');
}
$(this).toggleClass('faq-qa-visible');
$(this).parent().addClass('faq-viewed');
$('div.faq-dd-hide-answer').not($(this).next('div.faq-dd-hide-answer')).addClass("collapsed");
if (!faq_hide_qa_accordion) {
$(this).next('div.faq-dd-hide-answer').slideToggle('fast', function() {
$(this).parent().toggleClass('expanded');
});
}
$(this).next('div.faq-dd-hide-answer').toggleClass("collapsed");
//ADD "runblazy();" HERE?
// Change the fragment, too, for permalink/bookmark.
// To keep the current page from scrolling, refs
// http://stackoverflow.com/questions/1489624/modifying-document-location-hash-without-page-scrolling/1489802#1489802
var hash = $(this).find('a').attr('id');
var fx, node = $('#' + hash);
if (node.length) {
fx = $('<div></div>')
.css({position: 'absolute', visibility: 'hidden', top: $(window).scrollTop() + 'px'})
.attr('id', hash)
.appendTo(document.body);
node.attr('id', '');
}
document.location.hash = hash;
if (node.length) {
fx.remove();
node.attr('id', hash);
}
// Scroll the page if the collapsed FAQ is not visible.
// If we have the toolbar so the title may be hidden by the bar.
var mainScrollTop = Math.max($('html', context).scrollTop(), $('body', context).scrollTop());
// We compute mainScrollTop because the behaviour is different on FF, IE and CR
if (mainScrollTop > $(this).offset().top) {
$('html, body', context).animate({
scrollTop: $(this).offset().top
}, 1);
}
return false;
});
I almost can't believe it, after many hours of trying, I found a very simple fix that worked.
I remembered that I suspected display:none to be the issue for lazy loading not working and read here that this should be avoided, thus changing the .css to use the below instead worked:
.faq .collapsed {
/*display: none;*/
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
}
I would be still interested if there is another way as per question to run the lazy.js after the toggle?
I am making a website with a splash screen that I want to make disappear after 3 seconds. I can successfully do it when I include jQuery, but this takes time to load (especially if it's not cached) and so the splash still displays for a small time.
I am also using cookies so that it will only show on the first load of the page (so it's not overly irritating).
Here's my HTML:
<div class="splash">
splash content
</div>
Here's the working jQuery (that I want to avoid):
if(document.cookie.indexOf("visited=true") === -1) {
$(".splash").delay(3000).queue(function(){
$(this).addClass("hidden").dequeue();
});
} else {
$(".splash").addClass("hidden");
}
Here's what I have come up with regarding javascript, but it doesn't work:
document.getElementsByClassName("splash").addEventListener("load",
function() {
if(document.cookie.indexOf("visited=true") === -1) {
setTimeout(function(){
this.classList.add("hidden");
}, 3000);
} else {
this.classList.add("hidden");
}
});
I don't think you want to add the function as the load event listener of the splash. You should add it to the load event of the page.
See comments inline for more details on reorganizing the code. Unfortunately, it won't work with cookies here in the Stack Overflow snippet environment.
Note that the splash is set to be hidden (via CSS) by default. This is a better practice than showing it by default and then hiding it. If, after reading the cookie, it is determined that the splash should not be shown, some users may wind up seeing the splash momentarily on their screens due to processing limitations, or worse if there is any kind of error in your code, the splash may wind up being shown and never taken away because the JS stops executing at the error.
// Get a reference to the splash dialog
var splash = document.querySelector(".splash");
// When the window is loaded....
window.addEventListener("load", function() {
// Check to see if the cookie indicates a first-time visit
if(document.cookie.indexOf("visited=true") === -1) {
// Reveal the splash (remember: splash is hidden by default by CSS)
splash.classList.remove("hidden");
// .5 seconds later, hide the splash
setTimeout(function(){
splash.classList.add("hidden");
// >> Set cookie to visited here <<
}, 500);
}
});
.splash {
height:200px;
width:200px;
background:yellow;
}
.hidden {
display:none;
}
<div class="splash hidden">S P L A S H !</div>
document.getElementsByClassName("splash").addEventListener("load", //not possible as ByClassName returns a collection not an element
function() {
if(document.cookie.indexOf("visited=true") === -1) {//why not simply !...
setTimeout(function(){
this.classList.add("hidden");//this is window as setTimeout is a window function...
}, 3000);
} else {
this.classList.add("hidden");//the only that work
}
});
The right way:
document.getElementsByClassName("splash").forEach(el=>{el.addEventListener("load",function() {
if(!document.cookie.indexOf("visited=true")) {
setTimeout(function(){
this.classList.add("hidden");
}.bind(this), 3000);//fix of context
} else {
this.classList.add("hidden");
}
})});
You can include this IIFE at the bottom of your page so that it will be executed when the splash DOM element is ready. This way you can avoid the event listener.
I also converted your splash to use the ID splash rather than a class. If you prefer the class, when you use document.getElementsByClassName("splash") it returns an array of elements. In that case you'll have to specify which elements of the returned array you want to use (i.e. document.getElementsByClassName("splash")[0] or iterate through them).
(function() {
var splash = document.getElementById("splash");
if (document.cookie.indexOf("visited=true") === -1) {
splash.classList.remove("hidden"); // Display the splash
setTimeout(function() {
splash.classList.add("hidden"); // Hide it after the timeout
}, 500);
}
})();
#splash { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background: #ddd; }
.hidden { display: none; }
Not splashed!
<div id="splash" class="hidden">Splashed!</div>
I'm trying to get a spinner working properly in my grails app. The way I understand it, is it should work out of the box while waiting for an action to complete. It is not doing this.
I was able to get a spinner working based on some suggestions I found from google, and modified this solution: http://grails.1312388.n4.nabble.com/spinner-or-progress-indicator-td1363802.html , however this seems rather hacky to me, and not an optimal solution.
Which indicated I needed the following script:
<script type="text/javascript">
function showSpinner() {
document.getElementById('spinner').style.display = 'inline';
document.getElementById('error').style.display = 'none';
}
function hideSpinner() {
document.getElementById('spinner').style.display = 'none';
document.getElementById('error').style.display = 'none';
}
function showError(e) {
var errorDiv = document.getElementById('error')
errorDiv.innerHTML = '<ul><li>'
+ e.responseText + '</li></ul>';
errorDiv.style.display = 'block';
}
</script>
Which I employed with a grails button as such:
<g:submitToRemote value="Validate Address"
url="[action: 'standardizedList', controller: 'address']"
update="addressList" after="showSpinner();" onSuccess="hideSpinner()"
class="btn btn-primary"/><br>
<img id="spinner" style="display:none;"
src="${createLinkTo(dir: 'images', file: 'spinner.gif')}"
alt="Spinner"/>
Now I put the javascript snippet into /layouts/main.gsp, but it appears I have to add the spinner image into each page where I want it, and if I put it in the div where I want it to show, it will be overwritten when the action completes and updates the div so I have to add the spinner in the page that is responding as well as inside the response body.
When I look at the given main.gsp it has the following code in it:
<div id="spinner" class="spinner" style="display:none;">
<g:message code="spinner.alt" default="Loading…"/>
</div>
furthermore, found inside the web-app/js/ directory there is a file application.js which contains the code that I saw frequently which is supposed to add the spinner.
if (typeof jQuery !== 'undefined') {
(function($) {
$('#spinner').ajaxStart(function() {
$(this).fadeIn();
}).ajaxStop(function() {
$(this).fadeOut();
});
})(jQuery);
}
Now I have several places where I believe actions may cause a delay, and I want the spinner to tell the user it's working. So my question is two fold: 1) Am I understanding how it is supposed to be working? If so, 2) how can I make the out of the box code work properly?
Here's how I do it: make sure the following JavaScript is included in every page, e.g. by putting it in a .js file which is included in the layout:
$(document).ready(function () {
var showSpinner = function() {
$("#spinner").fadeIn('fast');
};
// Global handlers for AJAX events
$(document)
.on("ajaxSend", showSpinner)
.on("ajaxStop", function() {
$("#spinner").fadeOut('fast');
})
.on("ajaxError", function(event, jqxhr, settings, exception) {
$("#spinner").hide();
});
});
The functions above will be called every time an AJAX request is stopped, started, or returns with an error. Also include the following in the layout:
<div id="spinner" style="display:none;">
<g:img uri="/images/spinner.gif" alt="Loading..."/>
</div>
This is the content which is shown/hidden when an AJAX request starts/stops. In my case I apply the following styles, so that the spinner is shown in the center of the screen on top of any other content:
#spinner {
position: fixed;
top: 50%;
left: 50%;
margin-left: -50px; // half width of the spinner gif
margin-top: -50px; // half height of the spinner gif
z-index: 5000;
overflow: auto;
}
Pleasantries
I've been playing around with this idea for a couple of days but can't seem to get a good grasp of it. I feel I'm almost there, but could use some help. I'm probably going to slap myself right in the head when I get an answer.
Actual Problem
I have a series of <articles> in my <section>, they are generated with php (and TWIG). The <article> tags have an image and a paragraph within them. On the page, only the image is visible. Once the user clicks on the image, the article expands horizontally and the paragraph is revealed. The article also animates left, thus taking up the entire width of the section and leaving all other articles hidden behind it.
I have accomplished this portion of the effect without problem. The real issue is getting the article back to where it originally was. Within the article is a "Close" <button>. Once the button is clicked, the effect needs to be reversed (ie. The article returns to original size, only showing the image, and returns to its original position.)
Current Theory
I think I need to retrieve the offset().left information from each article per section, and make sure it's associated with its respective article, so that the article knows where to go once the "Close" button is clicked. I'm of course open to different interpretations.
I've been trying to use the $.each, each(), $.map, map() and toArray() functions to know avail.
Actual Code
/*CSS*/
section > article.window {
width:170px;
height:200px;
padding:0;
margin:4px 0 0 4px;
position:relative;
float:left;
overflow:hidden;
}
section > article.window:nth-child(1) {margin-left:0;}
<!--HTML-->
<article class="window">
<img alt="Title-1" />
<p><!-- I'm a paragraph filled with text --></p>
<button class="sClose">Close</button>
</article>
<article class="window">
<!-- Ditto + 2 more -->
</article>
Failed Attempt Example
function winSlide() {
var aO = $(this).parent().offset()
var aOL = aO.left
var dO = $(this).offset()
var dOL = dO.left
var dOT = dO.top
var adTravel = dOL-aOL
$(this).addClass('windowOP');
$(this).children('div').animate({left:-(adTravel-3)+'px', width:'740px'},250)
$(this).children('div').append('<button class="sClose">Close</button>');
$(this).unbind('click', winSlide);
}
$('.window').on('click', winSlide)
$('.window').on('click', 'button.sClose', function() {
var wW = $(this).parents('.window').width()
var aO = $(this).parents('section').offset()
var aOL = aO.left
var pOL = $(this).parents('.window').offset().left
var apTravel = pOL - aOL
$(this).parent('div').animate({left:'+='+apTravel+'px'},250).delay(250, function() {$(this).animate({width:wW+'px'},250); $('.window').removeClass('windowOP');})
$('.window').bind('click', winSlide)
})
Before you go scratching your head, I have to make a note that this attempt involved an extra div within the article. The idea was to have the article's overflow set to visible (.addclass('windowOP')) with the div moving around freely. This method actually did work... almost. The animation would fail after it fired off a second time. Also for some reason when closing the first article, the left margin was property was ignored.
ie.
First time a window is clicked: Performs open animation flawlessly
First time window's close button is clicked: Performs close animation flawlessly, returns original position
Second time SAME window is clicked: Animation fails, but opens to correct size
Second time window's close button is clicked (if visible): Nothing happens
Thank you for your patience. If you need anymore information, just ask.
EDIT
Added a jsfiddle after tinkering with Flambino's code.
http://jsfiddle.net/6RV88/66/
The articles that are not clicked need to remain where they are. Having problems achieving that now.
If you want to go for storing the offsets, you can use jQuery's .data method to store data "on" the elements and retrieve it later:
// Store offset before any animations
// (using .each here, but it could also be done in a click handler,
// before starting the animation)
$(".window").each(function () {
$(this).data("closedOffset", $(this).position());
});
// Retrieve the offsets later
$('.window').on('click', 'button.sClose', function() {
var originalOffset = $(this).data("originalOffset");
// ...
});
Here's a (very) simple jsfiddle example
Update: And here's a more fleshed-out one
Big thanks to Flambino
I was able to create the effect desired. You can see it here: http://jsfiddle.net/gck2Y/ or you can look below to see the code and some explanations.
Rather than having each article's offset be remembered, I used margins on the clicked article's siblings. It's not exactly pretty, but it works exceptionally well.
<!-- HTML -->
<section>
<article>Click!</article>
<article>Me Too</article>
<article>Me Three</article>
<article>I Aswell</article>
</section>
/* CSS */
section {
position: relative;
width: 404px;
border: 1px solid #000;
height: 100px;
overflow:hidden
}
article {
height:100px;
width:100px;
position: relative;
float:left;
background: green;
border-right:1px solid orange;
}
.expanded {z-index:2;}
//Javascript
var element = $("article");
element.on("click", function () {
if( !$(this).hasClass("expanded") ) {
$(this).addClass("expanded");
$(this).data("originalOffset", $(this).offset().left);
element.data("originalSize", {
width: element.width(),
height: element.height()
});
var aOffset = $(this).data("originalOffset");
var aOuterWidth = $(this).outerWidth();
if(!$(this).is('article:first-child')){
$(this).prev().css('margin-right',aOuterWidth)
} else {
$(this).next().css('margin-left',aOuterWidth)
}
$(this).css({'position':'absolute','left':aOffset});
$(this).animate({
left: 0,
width: "100%"
}, 500);
} else {
var offset = $(this).data("originalOffset");
var size = $(this).data("originalSize");
$(this).animate({
left: offset + "px",
width: size.width + "px"
}, 500, function () {
$(this).removeClass("expanded");
$(this).prev().css('margin-right','0')
$(this).next().css('margin-left','0')
element.css({'position':'relative','left':0});
});
}
});​