need to modify this jquery pop menu script to work with ajax - javascript

I am using this script from: http://pop.seaofclouds.com/
The problem is if you call the script multiple times it causes a cascading effect of a pop-out within a pop-out for as many times as you call the script.
I'm trying to figure out how to prevent it from executing when the popout has already been set. Here's the script:
//
// pop! for jQuery
// v0.2 requires jQuery v1.2 or later
//
// Licensed under the MIT:
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright 2007,2008 SEAOFCLOUDS [http://seaofclouds.com]
//
(function($) {
$.pop = function(options){
// inject html wrapper
function initpops (){
$(".pop").each(function() {
var pop_classes = $(this).attr("class");
if ( $(this).find('.pop_menu').length) {
// do nothing
} else {
$(this).addClass("pop_menu");
$(this).wrap("<div class='"+pop_classes+"'></div>");
$(".pop_menu").attr("class", "pop_menu");
$(this).before(" \
<div class='pop_toggle'></div> \
");
}
});
}
initpops();
// assign reverse z-indexes to each pop
var totalpops = $(".pop").length + 100;
$(".pop").each(function(i) {
var popzindex = totalpops - i;
$(this).css({ zIndex: popzindex });
});
// close pops if user clicks outside of pop
activePop = null;
function closeInactivePop() {
$(".pop").each(function (i) {
if ($(this).hasClass('active') && i!=activePop) {
$(this).removeClass('active');
}
});
return false;
}
$(".pop").mouseover(function() { activePop = $(".pop").index(this); });
$(".pop").mouseout(function() { activePop = null; });
$("body").on("click", ".pop", function(){
closeInactivePop();
});
// toggle that pop
$("body").on("click", ".pop_toggle", function(){
$(this).parent(".pop").toggleClass("active");
});
}
})(jQuery);
now when i load this script on an ajax call the new pop-out menus work but the old ones do not react to the onclick event.

You shouldn't mess with the plugin. It works exactly like it should.
Better show us how you call this on elements that you already have.
Also I don't like this plugin. Better use something from JqueryUI
You can do such thing in much easier way.
[edit]
I tried your first code (the plugin) and it works correctly for me.
[edit]
OK. I get it. You call $.pop(); multiple times. You shouldn't! Calling $.pop(); will pin up the drop down menu to all elements that has class="pop". This is the reason why you have such funny stack.
Just use $.pop(); once.
Plugin doesn't give ability to connect NEW elements that was dynamically created on the page.

Removed pop from ajax call and just called this on success:
$(".pop").each(function() {
var pop_classes = $(this).attr("class");
if ( $(this).find('.pop_menu').length) {
// do nothing
} else {
$(this).addClass("pop_menu");
$(this).wrap("<div class='"+pop_classes+"'></div>");
$(".pop_menu").attr("class", "pop_menu");
$(this).before(" \
<div class='pop_toggle'></div> \
");
}
});
// assign reverse z-indexes to each pop
var totalpops = $(".pop").length + 100;
$(".pop").each(function(i) {
var popzindex = totalpops - i;
$(this).css({ zIndex: popzindex });
});
// close pops if user clicks outside of pop
activePop = null;
function closeInactivePop() {
$(".pop").each(function (i) {
if ($(this).hasClass('active') && i!=activePop) {
$(this).removeClass('active');
}
});
return false;
}
$(".pop").mouseover(function() { activePop = $(".pop").index(this); });
$(".pop").mouseout(function() { activePop = null; });

Related

jQuery hover function to display an overlay but closes when hover displayed content

I am a little new to JS / jQuery and have done a hover function so when hover a link it shows a hidden DIV area but when I go to then hover over the div area shown of course it closes. I would like that if I then go into the content area won't close but stay open but then if leave that area would close?
JS:
$('.mini-cart').hover(
function () {
cartOpen();
},
function () {
cartClose();
}
);
var overlay = $("#cart_slide");
var cartContainer = $("#cart_over");
function cartOpen() {
cartContainer.fadeIn("slow");
overlay.addClass("overlay");
}
function cartClose() {
cartContainer.fadeOut("medium");
overlay.removeClass("overlay");
}
HTML:
<a class="mini-cart">hover link</a>
<div id="cart_over" style="display:none;">testing</div>
You could introduce the usage of a flag (isCartOpen) variable which is going to control the whether the DIV should be displayed or not.
See this working JSFiddle example and find below the related code:
var isCartOpen = false;
$('.mini-cart, #cart_over').hover(
function() {
isCartOpen = true;
cartOpen();
},
function() {
isCartOpen = false;
setTimeout(cartClose, 1000); // after 1 sec
}
);
var overlay = $("#cart_slide");
var cartContainer = $("#cart_over");
function cartOpen() {
cartContainer.fadeIn("slow");
overlay.addClass("overlay");
}
function cartClose() {
if (isCartOpen)
return;
cartContainer.fadeOut("medium");
overlay.removeClass("overlay");
}
Looks like you'll need to separate your open/close functions . Look at the following:
$('.mini-cart').hover(
cartOpen();
);
// You'll need to create a close button.. ex. <div class="close-button"></div>
$(".close-button").click(function(){
cartClose();
});

When using the Microsoft Translator, Is there a way to remove the widget, but retain translation?

I'm using the Microsoft Translation Widget, which I'd like to use to automatically translate a webpage without user interaction.
The problem is, I can't get rid of the widget that keeps popping up or hide it on document.ready because the CSS and JS get loaded from Microsoft's own script in the widget!
Does anyone know a way around this? I've looked everywhere and cannot find a solutuion for this.
Whoa, after some time playing around with that, I've finally achieved what you want.
It's kindda ugly, because of some needed workarounds, but it works, take a look at the fiddle.
The steps were:
Firstly, we must override the default addEventListener behavior:
var addEvent = EventTarget.prototype.addEventListener;
var events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
addEvent.apply(this, [].slice.call(arguments));
events.push({
element: this,
type: type,
listener: listener
});
}
Then, we create a helper function removeEvents. It removes all the event listeners of an element.
var removeEvents = function(el, type) {
var elEvents = events.filter(function(ev) {
return ev.element === el && (type ? ev.type === type : true);
});
for (var i = 0; i < elEvents.length; i++) {
el.removeEventListener(elEvents[i].type, elEvents[i].listener);
}
}
When creating the script tag, in the way Microsoft says:
var s = d.createElement('script');
s.type = 'text/javascript';
s.charset = 'UTF-8';
s.src = ((location && location.href && location.href.indexOf('https') == 0) ? 'https://ssl.microsofttranslator.com' : 'http://www.microsofttranslator.com') + '/ajax/v3/WidgetV3.ashx?siteData=ueOIGRSKkd965FeEGM5JtQ**&ctf=True&ui=true&settings=Manual&from=';
var p = d.getElementsByTagName('head')[0] || d.dElement;
p.insertBefore(s, p.firstChild);
We must add a load event listener to that script, and the code below is fully commented:
s.addEventListener('load', function() {
// when someone changes the translation, the plugin calls the method TranslateArray
// then, we save the original method in a variable, and we override it
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
// we call the original method
translate.apply(this, [].slice.call(arguments));
// since the translation is not immediately available
// and we don't have control when it will be
// I've created a helper function to wait for it
waitForTranslation(function() {
// as soon as it is available
// we get all the elements with an attribute lang
[].forEach.call(d.querySelectorAll('[lang]'), function(item, i) {
// and we remove all the mouseover event listeners of them
removeEvents(item, 'mouseover');
});
});
}
// this is the helper function which waits for the translation
function waitForTranslation(cb) {
// since we don't have control over the translation callback
// the workaround was to see if the Translating label is visible
// we keep calling the function, until it's hidden again
// and then we call our callback
var visible = d.getElementById('FloaterProgressBar').style.visibility;
if (visible === 'visible') {
setTimeout(function() {
waitForTranslation(cb);
}, 0);
return;
}
cb();
}
});
Update 1
After re-reading your question, it seems you want to hide all the widgets at all.
So, you must add the following code as soon as the translation is got:
waitForTranslation(function() {
document.getElementById('MicrosoftTranslatorWidget').style.display = 'none';
document.getElementById('WidgetLauncher').style.display = 'none';
document.getElementById('LauncherTranslatePhrase').style.display = 'none';
document.getElementById('TranslateSpan').style.display = 'none';
document.getElementById('LauncherLogo').style.display = 'none';
document.getElementById('WidgetFloaterPanels').style.display = 'none';
// rest of the code
});
I've created another fiddle for you, showing that new behavior.
Update 2
You can prevent the widget showing at all by adding the following CSS code:
#MicrosoftTranslatorWidget, #WidgetLauncher, #LauncherTranslatePhrase, #TranslateSpan, #LauncherLogo, #WidgetFloaterPanels {
opacity: 0!important;
}
And you can even prevent the before-translated text being showed, by hiding the document.body by default, and then showing it when the page is fully translated:
(function(w, d) {
document.body.style.display = 'none';
/* (...) */
s.addEventListener('load', function() {
var translate = Microsoft.Translator.TranslateArray;
Microsoft.Translator.TranslateArray = function() {
translate.apply(this, [].slice.call(arguments));
waitForTranslation(function() {
/* (...) */
document.body.style.display = 'block';
});
}
});
});
Take a look at the final fiddle I've created.
For me, this was the solution:
on your < style > section add this class
.LTRStyle { display: none !important }
Also, if you are invoking the translation widget this way:
Microsoft.Translator.Widget.Translate('en', lang, null, null, TranslationDone, null, 3000);
then add this to your callback (in this example is TranslationDone) function:
function TranslationDone() {
Microsoft.Translator.Widget.domTranslator.showHighlight = false;
Microsoft.Translator.Widget.domTranslator.showTooltips = false;
document.getElementById('WidgetFloaterPanels').style.display = 'none';
};

How to achieve a queue of object instances of jQuery modals, assuring only one instance is on per time?

I've a task of building a modal prompt, that's been simple so far describing its methods like "show", "hide" when it comes down just to DOM manupulation.
Now comes the hardship for me... Imagine we have a page on which there are several immediate calls to construct and show several modals on one page
//on page load:
$("browser-deprecated-modal").modal();
$("change-your-city-modal").modal();
$("promotion-modal").modal();
By default my Modal (and other libraries i tried) construct all of these modals at once and show them overlapping each other in reverse order -
i.e $(promotion-modal) is on the top, while the
$("browser-deprecated-modal") will be below all of them. that's not what i want, let alone overlapping overlays.
I need each modal to show up only when the previous one (if there'are any) has been closed. So, first we should see "browser-deprecated-modal" (no other modals underneath), upon closing it there must pop up the second one and so on.
I've been trying to work it out with this:
$.fn.modal = function(options) {
return this.each(function() {
if (Modal.running) {
Modal.toInstantiateLater.push({this,options});
} else {
var md = new Modal(this, options);
}
});
}
destroy :function () {
....
if (Modal.toInstantiateLater.length)
new Modal (Modal.toInstantiateLater[0][0],Modal.toInstantiateLater[0][1]);
}
keeping a track of all calls to construct a Modal in a array and in the "destroy" method make a check of this array is not empty.
but it seems awkward and buggy me thinks.
i need a robust and clear solution. I've been thinking about $.Callbacks or $.Deferred,
kinda set up a Callback queue
if (Modal.running) { //if one Modal is already running
var cb = $.Callbacks();
cb.add(function(){
new Modal(this, options);
});
} else { //the road is clear
var md = new Modal(this, options);
}
and to trigger firing cb in the destroy method, but i'm new to this stuff and stuck and cannot progress, whether it's right or not, or other approach is more suitable.
Besides, I read that callbacks fire all the functions at once (if we had more than one extra modal in a queue), which is not right, because I need to fire Modal creation one by one and clear the Callback queue one by one.
Please help me in this mess.
My code jsfiddle
I got rid of the counter variable, as you can use toInstantiateLater to keep track of where you are, and only had to make a few changes. Give this a try...
Javscript
function Modal(el, opts){
this.el = $(el);
this.opts = opts;
this.overlay = $("<div class='overlay' id='overlay"+Modal.counter+"'></div>");
this.wrap = $("<div class='wrap' id='wrap"+Modal.counter+"'></div>");
this.replace = $("<div class='replace' id='replace"+Modal.counter+"'></div>");
this.close = $("<span class='close' id='close"+Modal.counter+"'></span>")
if (Modal.running) {
Modal.toInstantiateLater.push(this);
}
else {
Modal.running = true;
this.show();
}
}
Modal.destroyAll = function() {
Modal.prototype.destroyAll();
};
Modal.prototype = {
show: function() {
var s = this;
s.wrap.append(s.close);
s.el.before(s.replace).appendTo(s.wrap).show();
$('body').append(s.overlay).append(s.wrap);
s.bindEvents();
Modal.currentModal = s;
},
bindEvents: function() {
var s = this;
s.close.on("click.modal",function(e){
s.destroy.call(s,e);
});
},
destroy: function(e) {
var s = this;
s.replace.replaceWith(s.el.hide());
s.wrap.remove();
s.overlay.remove();
if (Modal.toInstantiateLater.length > 0) {
Modal.toInstantiateLater.shift().show();
}
else {
Modal.running = false;
}
},
destroyAll: function(e) {
Modal.toInstantiateLater = [];
Modal.currentModal.destroy();
}
}
Modal.running = false;
Modal.toInstantiateLater = [];
Modal.currentModal = {};
$.fn.modal = function(options) {
return this.each(function() {
var md = new Modal(this, options);
});
}
$("document").ready(function(){
$("#browser-deprecated-modal").modal();
$("#change-your-city-modal").modal();
$("#promotion-modal").modal();
$("#destroy-all").on("click", function() {
Modal.destroyAll();
});
});
jsfiddle example
http://jsfiddle.net/zz9ccbLn/4/

Passing functions between RequireJS files

I've got a file which needs to run on page load (randomise_colors.js), but also needs to be called by another file as part of a callback function (in infinite_scroll.js). The randomise_colors script just loops through a list of posts on the page and assigns each one a color from an array which is used on the front-end.
Infinite Scroll loads new posts in to the DOM on a button click, but because the randomise_colors.js file has already ran on page load, new content loaded is not affected by this so I need it to run again. I'm open to other suggestions if it sounds like I could be tackling the problem in a different way, I'm no JS expert.
Currently I'm getting Uncaught ReferenceError: randomise_colours is not defined referring this line of infinite_scroll.js:
randomise_colours.init();
I'm calling all files that need be loaded on document.ready in app.js
require(['base/randomise-colours', 'base/infinite-scroll'],
function(randomise_colours, infinite_scroll) {
var $ = jQuery;
$(document).ready(function() {
infinite_scroll.init();
randomise_colours.init();
});
}
);
This is infinite_scroll.js which initialises Infinite Scroll and features the callback. The callback function runs whenever new items are loaded in via AJAX using the Infinite Scroll jQuery plugin. I've put asterix around the area where I need to run the randomise_colors.init() function from randomise_colors.js.
define(['infinitescroll'], function() {
var $ = jQuery,
$loadMore = $('.load-more-posts a');
function addClasses() {
**randomise_colours.init();**
};
return {
init: function() {
if($loadMore.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.initInfiniteScroll();
},
initInfiniteScroll: function() {
$('.article-listing').infinitescroll({
navSelector : '.load-more-posts',
nextSelector : '.load-more-posts a',
itemSelector : '.standard-post'
}, function(newItems) {
addClasses();
});
//Unbind the standard scroll-load function
$(window).unbind('.infscr');
//Click handler to retrieve new posts
$loadMore.on('click', function() {
$('.article-listing').infinitescroll('retrieve');
return false;
});
}
};
});
And this is my randomise_colors.js file which runs fine on load, but needs to be re-called again after new content has loaded in.
define([], function() {
var $ = jQuery,
$colouredSlide = $('.image-overlay'),
colours = ['#e4cba3', '#867d75', '#e1ecb9', '#f5f08a'],
used = [];
function pickRandomColour() {
if(colours.length == 0) {
colours.push.apply(colours, used);
used = [];
}
var selected = colours[Math.floor(Math.random() * colours.length)];
var getSelectedIndex = colours.indexOf(selected);
colours.splice(getSelectedIndex, 1);
used.push(selected);
return selected;
};
return {
init: function() {
if($colouredSlide.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.randomiseColours();
},
randomiseColours: function() {
console.log('randomise');
$colouredSlide.each(function() {
var newColour = pickRandomColour();
$(this).css('background', newColour);
});
}
};
});
You would have to reference randomiseColours inside the infiniteScroll file. So you need to change your define function to the following:
define(['infinitescroll', 'randomise-colours'], function(infiniteScroll, randomise_colours)
Remember that when using require you need to reference all variables through the define function, otherwise they will not be recognised.

setInterval with other jQuery events - Too many recursions

I'm trying to build a Javascript listener for a small page that uses AJAX to load content based on the anchor in the URL. Looking online, I found and modified a script that uses setInterval() to do this and so far it works fine. However, I have other jQuery elements in the $(document).ready() for special effects for the menus and content. If I use setInterval() no other jQuery effects work. I finagled a way to get it work by including the jQuery effects in the loop for setInterval() like so:
$(document).ready(function() {
var pageScripts = function() {
pageEffects();
pageURL();
}
window.setInterval(pageScripts, 500);
});
var currentAnchor = null;
function pageEffects() {
// Popup Menus
$(".bannerMenu").hover(function() {
$(this).find("ul.bannerSubmenu").slideDown(300).show;
}, function() {
$(this).find("ul.bannerSubmenu").slideUp(400);
});
$(".panel").hover(function() {
$(this).find(".panelContent").fadeIn(200);
}, function() {
$(this).find(".panelContent").fadeOut(300);
});
// REL Links Control
$("a[rel='_blank']").click(function() {
this.target = "_blank";
});
$("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
$("#content").fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
}
}
This works fine for a while but after a few minutes of the page being loaded, it drags to a near stop in IE and Firefox. I checked the FF Error Console and it comes back with an error "Too many Recursions." Chrome seems to not care and the page continues to run more or less normally despite the amount of time it's been open.
It would seem to me that the pageEffects() call is causing the issue with the recursion, however, any attempts to move it out of the loop breaks them and they cease to work as soon as setInterval makes it first loop.
Any help on this would be greatly appreciated!
I am guessing that the pageEffects need added to the pageURL content.
At the very least this should be more efficient and prevent duplicate handlers
$(document).ready(function() {
pageEffects($('body'));
(function(){
pageURL();
window.setTimeout(arguments.callee, 500);
})();
});
var currentAnchor = null;
function pageEffects(parent) {
// Popup Menus
parent.find(".bannerMenu").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
subMenu: $(this).find("ul.bannerSubmenu"),
handlerIn: function() {
this.subMenu.slideDown(300).show();
},
handlerOut: function() {
this.subMenu.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
parent.find(".panel").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
content: panel.find(".panelContent"),
handlerIn: function() {
this.content.fadeIn(200).show();
},
handlerOut: function() {
this.content.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
// REL Links Control
parent.find("a[rel='_blank']").each(function() {
$(this).target = "_blank";
});
parent.find("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
var content = $("#content");
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
content.fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
pageEffects(content);
}
}
Thanks for the suggestions. I tried a few of them and they still did not lead to the desirable effects. After some cautious testing, I found out what was happening. With jQuery (and presumably Javascript as a whole), whenever an AJAX callback is made, the elements brought in through the callback are not binded to what was originally binded in the document, they must be rebinded. You can either do this by recalling all the jQuery events on a successful callback or by using the .live() event in jQuery's library. I opted for .live() and it works like a charm now and no more recursive errors :D.
$(document).ready(function() {
// Popup Menus
$(".bannerMenu").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find("ul.bannerSubmenu").slideDown(300);
} else {
$(this).find("ul.bannerSubmenu").slideUp(400);
}
});
// Rollover Content
$(".panel").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find(".panelContent").fadeIn(200);
} else {
$(this).find(".panelContent").fadeOut(300);
}
});
// HREF Events
$("a[rel='_blank']").live("click", function(event) {
var target = $(this).attr("href");
window.open(target, "_blank");
event.preventDefault();
});
$("a[rel='share']").live("click", function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
setInterval("checkAnchor()", 500);
});
var currentAnchor = null;
function checkAnchor() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn(200);
$("#content").fadeOut(200).html(data).fadeIn(200);
$("#load").fadeOut(200);
});
}
}
Anywho, the page works as intended even in IE (which I rarely check for compatibility). Hopefully, some other newb will learn from my mistakes :p.

Categories