Separate desktop and mobile jQuery functions on a responsive site - javascript

I'm working on a responsive site with a specific set of jQuery functions for the desktop layout and mobile layout. They interfere with each other if they're both active at the same time.
By checking window.width, I'm able to deliver only the correct set of functions on page load, and I'd like to do the same on window.resize.
I've set up a stripped down Fiddle of where I'm at here: http://jsfiddle.net/b9XEj/
Two problems exist right now:
Either desktopFunctions or mobileFunctions will continuously fire on page resize, whether they have already been loaded or not.
If the window is resized beyond one breakpoint and then returned to the previous size, the incorrect set of functions will already have been loaded, interfering with the current set.
The window.resize function should behave in the following way:
Check if the correct set of functions currently active for the viewport size
If yes, return.
If no, fire correct set of functions and remove incorrect set of functions if they exist.
In the Fiddle example above, you would always see a single line, displaying either "Mobile Functions are active" or "Desktop Functions are active".
I'm a bit lost at this point, but I have tried using
if ($.isFunction(window.mobileFunctions))
to check if functions already exist, but I can't seem to get it working without breaking the overall function. Here's a fiddle for that code: http://jsfiddle.net/nA8TB/
Thinking ahead, this attempt also wouldn't take into account whether the incorrect set of functions exists already. So, I'm really hoping there's a way I can deal with this in a simpler way and solve both problems.
Any help would be much appreciated!

Following conquers 2 of the problems. The resize fires many times a second, so using a timeout will fix it firing your code constantly. It also adds a check to see if the same size is in effect, and return if it is
$(document).ready(function() {
var windowType;
var $wind = $(window);
var desktopFunctions = function() {
$('body').append('<p>Desktop functions are active</p>');
}
var mobileFunctions = function() {
$('body').append('<p>Mobile Functions are active</p>');
}
var mobileCheck = function() {
var window_w = $wind.width();
var currType = window_w < 940 ? 'mobile' :'desktop';
if (windowType == currType) {
$('body').append('<p>No Type Change, Width= '+window_w+'</p>');
return;
} else {
windowType = currType;
}
if (windowType == 'mobile') {
mobileFunctions();
} else {
desktopFunctions();
}
}
mobileCheck();
var resizeTimer;
$wind.resize(function() {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(mobileCheck, 300)
});
});
DEMO: http://jsfiddle.net/b9XEj/1/
Without seeing some real world differences between your 2 sets of functions it is hard to provide gudance on how to stop them conflicting. One possibility is checking the windowType in your functions

You can prevent the continuous firing by adding a delay mobileCheck. Use a setTimeout along with a checkPending boolean value.
var checkPending = false;
$(window).resize(function(){
if (checkPending === false) {
checkPending = true;
setTimeout(mobileCheck, 1000);
}
});
See here: http://jsfiddle.net/2Q3pT/
Edit
As far as the second requirement, you could use this pattern to create or use the existing one:
mobileFunctions = mobileFunctions || function() {
// mobile functions active
};
See: http://jsfiddle.net/2Q3pT/2/

Related

How to make sure that all events are unattached when called multiple times in Javascript?

I'm using dragswipe, a plugin from here. It works perfectly fine. However, I have a requirement to do dynamic carousels. So, every time a user changes something on a page, the carousels get updated dynamically. I thought I could just recall the plugin to update the element, but somehow when I dragged the carousel the functional gets called multiple times.
For example, I do this to initialise the plugin.
function init() {
$('#carousel').dragswipe({
width: 320,
current_page_element: '#current_page',
total_pages_element:'#total_pages'
});
}
So after the page is updated via ajax I have a callback to call this method again like this
function callback() {
init();
}
Everything should is updated perfectly fine, but when I started dragging. The carousel skips some pages. I thought I had to unbind all the events so I tried this. $('#carousel').unbind() but the problem still persists.
Here's the source code when the plugin is initialised.
$.fn.dragswipe = function(options) {
options = $.extend({
offset: 0,
turn_threshold: 0.1,
current_page: 0
},options)
this.each(function() {
option = $(this).hammer({
drag_vertical: false,
swipe_time: 20
});
// start a drag.
// if we're moving left or right, set our initial distance to 0.
$(this).bind('dragstart',function(ev) {
console.log('dragstart');
if (ev.direction=='left' || ev.direction=='right') {
drag_distance = 0;
}
});
// while dragging, change the dragging distance based on how far a user has dragged.
$(this).bind('drag',function(ev) {
console.log('drag');
if (ev.direction=='left' || ev.direction=='right') {
drag_distance = ev.distanceX;
var options = CONFIGS[$(this).data('dragswipe_id')];
$(this).updateOffset(options.offset+drag_distance);
}
});
$(this).bind('dragend',function(ev) {
console.log('dragend');
if (ev.direction=='left' || ev.direction=='right') {
var options = CONFIGS[$(this).data('dragswipe_id')];
if (Math.abs(drag_distance / options.width) > options.turn_threshold) {
if (ev.direction=='left') {
options.current_page++;
}
if (ev.direction=='right') {
options.current_page--;
}
}
// store modified options
CONFIGS[$(this).data('dragswipe_id')] = options;
console.log(options.current_page);
$(this).gotoPage(options.current_page,true);
}
});
// set the dragswipe ID used to look up config options later.
$(this).data('dragswipe_id',CONFIGS.length);
// store config options.
CONFIGS[$(this).data('dragswipe_id')] = options;
});
}
Which I see nothing wrong with the plugin, but maybe I'm missing something obvious.
UPDATED
I have created the example in jsfiddle, but it's not working just in case anyone can fix the problem. Also, on the plugin site itself. The problem can be reproduce by running this code to initialise the plugin multiple times. After running the code twice, when you drag the page it goes to the last page instead of the second page.
$('#carousel').dragswipe({
width: 320,
current_page_element: '#current_page',
total_pages_element:'#total_pages'
});
Have you tried turning it off and on again?
Dragswipe has a removeDragswipe() method that does a little bit more than just unbinding, and it also explicitly only unbinds events that are related to dragswipe itself so it won't mess up any other events that may have been bound.
So instead of unbind(), do this:
function callback() {
$('#carousel').removeDragswipe();
init();
}
As discussed in the comments, this doesn't work because it seems that even after unbinding all the events, after re-binding them by activating the plugin again the events seem to fire an extra time.
What does seem to be a workaround is to actually rebuild the entire element so that no events can linger:
function callback() {
$('#carousel').removeDragswipe();
rebuildCarousel();
init();
}
function rebuildCarousel() {
var wrapper = $('#carousel').parent;
var html = parent.html();
$('#carousel').remove();
wrapper.html(html);
}

Why is this onClick event not being applied correctly?

I'm using late-binding to assign onClick events to checkboxes (styled as dots). My first assignment [located at https://github.com/farfromunique/vampirrePoints/blob/master/events.js#L264 ] goes perfectly, and reacts as expected - all dots have this as their onClick:
/* https://github.com/farfromunique/vampirrePoints/blob/master/main.js#L440 */
allDots[i].onclick = function() {
if (this.checked) {
decrementCounter(1);
} else {
incrementCounter(1);
};
}
However, when Step11() is triggered, and freebieDotSetup() is called, only some of the checkboxes get their onClicks updated. Specifically, sta1, sta2, sta3, sta4, sta5 (possibly others, too) keep their initial value.
I have tried putting console.log() statements in during the assignment process, and it looks like the assignment happens, but it doesn't "stick". Why doesn't this work?
Code reference (whole site): https://github.com/farfromunique/vampirrePoints
By request, a non-working JSFiddle: http://jsfiddle.net/farfromunique/mS4Lp/
Note: the menu is on the wrong side, doesn't advance properly, and none of the "dots" (checkboxes) are clickable, so functionality is not testable. I strongly suspect that my code is not cross-browser compatible, but that isn't a priority for me (yet).
The solution: In the function freebieDotSetup(), I was doing this:
for (i=0;i<attributeDots.length;i++) {
attributeDots[i] = allDots[i];
}
...
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+attributeDots.length];
}
...
for (i=0;i<disciplineDots.length;i++) {
disciplineDots[i] = allDots[i+abilityDots.length];
}
...
Since I was using the previous group's length, it would occasionally overwrite the previous values (due to groups being shorter). I corrected this with this:
startPos = startPos + attributeDots.length;
for (i=0;i<abilityDots.length;i++) {
abilityDots[i] = allDots[i+startPos];
}
Adding the previous group's length to startPos each time. Making this change resolved my issue.
... But the JSFiddle is totally busted, still.

Issue with execution order in javascript

I'm relatively new to javascript so please hold it against me.
I have a bit of code which should give the user a little time to reach the submenu from the base-menu.
My problem is that the code keeps executing in a weird order.
Here is the code:
function onFocusOut() {
var tester = 0;
setTimeout(function(){menuReset(tester)},1000);
}
function menuReset(tester) {
var hoverCheck = function (event) {
alert("#navBase a has focus"); //is fired, but to late...
var tester = event.data.varTester;
var tester = 1;
};
jQuery('#navBase').on('mousemove', 'a', { varTester: tester }, hoverCheck);
jQuery('#navBase').off('mousemove', 'a', { varTester: tester }, hoverCheck);
alert(tester); //This keeps firing first, before the alert in hoverCheck
if(tester == 1){
alert("tester = 1");
return;
}
else {
jQuery('#navBase ul').hide();
jQuery('#navBase').css({'width': ''});
jQuery('#navBaseAnchor').css({
'width': '', 'color': '',
'font-size': '',
'border-bottom-style': '',
'border-bottom-width': '',
'border-bottom-color': ''});
tester = 0;
}
}
Now I keep getting the alert that "tester" is 0, before the hoverCheck function is executed (which should set "tester" to 1) and fires the alert within that function.
Could someone tell me what I'm doing wrong?
I am also fairly new to JS, but should you also be watching out for variable scope errors too?
You have declared tester locally in onFocusOut() and in menuReset(tester), and then called it as a global var outside?
From answers.oreilly.com
LOCAL - Are those that are specific to a function and only work on it.
GLOBAL - Are those that are not defined within a function and may also serve to functions unless the function has not required that
variable.
Nevermind people...
I found a way around it all.
Currently i'm setting a .focus() to the anchor involved on mouseOver. (and of course blur() on mouseleave)
Then it's real easy to check the currently focussed element using document.activeElement.
So problem solved, altough in a bit different way.
alert(tester) is the first line of code that is executing something you notice as a user. The two function calls jQuery().on() and jQuery().off() are only attaching event handlers. If you want to see a "1" in the alert, you have to quickly move your mouse before hoverCheck is executed. But probably you cannot move your hand faster than JavaScript reaching the next line, which is the alert() with tester equals "0".
A little bit different approach would be to set a Javascript timeout() to make the submenu disappear after a certain amount of time if a certain condition isn't met.
Check out this JSFiddle example
Best of luck!

if/else statement in a function: using onclick as a switch

I have looked for solutions to this on google for what seems like an eternity, but I can't seem to formulate my search correctly, or nobody has posted the code I'm looking for earlier.
I am currently trying to make a function that will modify one or several margins of a div element. I want to use an if/else statement within the function, so that the onclick event will switch between the two conditions. This is what I have been working on so far;
function facebookToggle()
{
if($('#facebooktab').style.margin-left == "-250px";)
{
document.getElementById("facebooktab").style.marginLeft="0px";
}
else
{
document.getElementById("facebooktab").style.marginLeft="-250px";
}
}
I have tried twisting it around a little, like switching between "marginLeft" and "margin-left", to see if I was just using the wrong terms.. I'm starting to wonder if it might not be possible to combine jQuery and regular javascript? I don't know.. It's all just guesses on my part at this point.
Anyway, I have a div, which is now positioned (fixed) so almost all of it is hidden outside the borders of the browser. I want the margin to change onclick so that it will be fully shown on the page. And when it is shown, I want to be able to hide it again by clicking it.
I might be approaching this in the wrong way, but I really hope someone can help me out, or even tell me another way to get the same results. Thank you for any help you can give me.
You can see it in action at: http://www.torucon.no/test/
(EDIT: By the way, I am a complete javascript novice, I have no experience with javascript prior to this experiment. Please don't be too harsh, as I am aware I probably made some really stupid mistakes in this short code.)
Fixed problem:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
A jQuery object doesn't have a property called style, so
if($('#facebooktab').style.margin-left == "-250px";)
// also remove this semi-colon! ^
is going to throw an error. Some options for accessing CSS properties are (1)
document.getElementById("facebooktab").style.marginLeft;
which you have correctly used, or (2)
$('#facebooktab').css('margin-left');
Consider being consistent and using the same approach for all three cases. You can assign css properties with jQuery like
$('#facebooktab').css('margin-left', '-250px');
With these things in mind, here's a suggested rewrite:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
and here's another that uses a predefined CSS class:
#facebooktab {
margin-left: -250px; /** default state */
}
.no-left-margin {
margin-left: 0px;
}
function facebookToggle() {
$('#facebooktab').toggleClass('no-left-margin');
}
toggleClass
jQuery is just a JavaScript library. It is written in JavaScript and its API is in JavaScript. Your event handler could be rewritten as follows:
function facebookToggle() {
var el = document.getElementById('facebooktab');
if (el)
el.style.marginLeft = (el.style.marginLeft == '250px' ? 0 : -250) + 'px';
}
Since you are mixing jQuery with javascript, you got mixed up. Apart from what paislee's advice. you are do this too.
if($('#facebooktab')[0].style.margin-left == "-250px";){
document.getElementById("facebooktab").style.marginLeft="0px";
}
else {
var fb = document.getElementById("facebooktab");
fb.style.marginLeft="-250px";
}

SetInterval inside scrolling events on iPhone - Why this code doesn't work?

Please, take a look at this code (I'm using Zepto http://zeptojs.com/ BTW)...
var timer = false;
$(window).bind('touchstart touchmove scroll', function (e) {
if (timer === false) {
timer = setInterval(function () {
$('footer').css('top', (this.pageYOffset + this.innerHeight - 40) + 'px');
console.log('Adjusted...');
}, 100);
}
}).bind('touchend', function () {
clearInterval(timer);
timer = false;
console.log('Cleaned it up...');
});
As you can see, I have a footer element that I'm trying to keep fixed on the bottom of the iPhone screen. I know that there are libraries that helps us make this quite easily like iScroll 4 http://cubiq.org/iscroll-4, but I was trying to see if I could make it simpler.
It turns out that the code above doesn't work properly. While I'm actually scrolling the page, for some reason setInterval doesn't execute but instead seems to pile up on the background to run every call at the same time.
At the end it doesn't do what I wanted it to do, which is to "animate" the footer and have it in place during scroll not only after. Does anyone has any idea on how such effect could be achieved on some similar manner?
Thanks!
When you pass a method to setInterval() (or any other function, for that matter), it will be invoked with a wrong this value. This problem is explained in detail in the JavaScript reference.
MDC docs
Inside your outer callback, this will be the DOM element you care about, but inside the setInterval callback, this will be window. Keep in mind that this is a keyword, not a variable, and that it is highly context sensitive.
The usual approach is to capture the value of this in a variable and then use that variable instead of this:
if(timer === false) {
var self = this; // "_that" is also a common name for the variable.
timer = setInterval(function () {
$('footer').css('top', (self.pageYOffset + self.innerHeight - 40) + 'px');
console.log('Adjusted...');
}, 100);
}
Similar issues apply to all callbacks in JavaScript, always make sure you know what this is and grab its value and build a closure over that value when it won't be what you want.

Categories

Resources