Memory management in javascript jquery mobile with phonegap - javascript

I'm building a jquery mobile + phonegap app to bundle for iOS. The JQM site/app works as it should on a web browser. However, when bundled with phonegap and tested on a phone, it seems to forget javascript functions.
For example, I open/close panels on swipe. After a couple of swipes, ~10 opens/closes, it no longer responds to a swipe. I cannot open a panel. Other buttons are still functional, but I cant get the panel.
On a computer or webapp, I can do it all day long without it freezing up. Is there possibly something clearing functions from my javascript? Or should I define them in a different way?
$(document).on('pageinit', '#page', function() {
$(document).on("swipeleft swiperight", "#page", function(e) {
console.log('swiped!!')
});
});
Any ideas?
UPDATE:
Apparently it only "forgets" the function when I do it consistently back-and-forth for the ~10 tries. If I leave a ~2-3 second pause between each swipe, it seems to work fine for a lot longer. Maybe the new swipe events are occurring while the older swipe event is still completing the function??? And that is causing them to get tangled up and freeze? I've been kind of stuck on this. Any help/insight on memory management of phonegap app's javascript would be nice.

So, I found a fix.
$(document).on('pageinit', '#page', function() {
$(document).on("swipeleft swiperight", "#page", function(e) {
console.log('swiped!!')
});
});
This was the psuedo code I posted. It turns out, the console.log msg was getting called on every swipe, but the panel open/close calls that were omitted in the above code, were not.
Here's the complete old code:
$(document).on('pageinit','#page', function(){
$(document).on("swipeleft swiperight", "#page", function(e) {
console.log('swiped!!')
// We check if there is no open panel on the page because otherwise
// a swipe to close the left panel would also open the right panel (and v.v.).
// We do this by checking the data that the framework stores on the page element (panel: open).
if ($.mobile.activePage.jqmData( "panel" ) !== "open") {
if ( e.type === "swipeleft" ) {
$( "#right-panel" ).panel( "open" );
} else if ( e.type === "swiperight" ) {
$( "#left-panel" ).panel( "open" );
}
}
else if ($.mobile.activePage.jqmData( "panel" ) == "open"){
$( "#left-panel" ).panel( "close" );
$( "#right-panel" ).panel( "close" );
}
});
}
These changes fixed the code:
took the selector off the swipeleft swiperight function
$(document).on("swipeleft swiperight", "#page", function(e) {} became $(document).on("swipeleft swiperight", function(e) {}
and I added e.stopPropagation() on the event. I think it must've been JQM event propagation bubbling up the DOM and breaking everything.
$(document).on('pageinit', '#page', function() {
$(document).on("swipeleft swiperight", function(e) {
e.stopPropagation();
console.log('swiped!!')
// We check if there is no open panel on the page because otherwise
// a swipe to close the left panel would also open the right panel (and v.v.).
// We do this by checking the data that the framework stores on the page element (panel: open).
if ($.mobile.activePage.jqmData( "panel" ) !== "open") {
if ( e.type === "swipeleft" ) {
$( "#right-panel" ).panel( "open" );
} else if ( e.type === "swiperight" ) {
$( "#left-panel" ).panel( "open" );
}
}
else if ($.mobile.activePage.jqmData( "panel" ) == "open"){
$( "#left-panel" ).panel( "close" );
$( "#right-panel" ).panel( "close" );
}
});
}

Related

Resize function events fire 2 times more each time the window was resized

On my site, I am trying to fix the navigation so that when the browser is getting resized from desktop to mobile size, the mobile menu works. I have the mobile menu working on initial load, and the desktop navigation working on initial load, but when I run the script in a $(window).on('resize', function() {} and click an item as depicted in my script, the event fires always +1 each time the window was rested after a resize.
What I mean is, if I load the page, scale it into mobile size, click the menu and a dropdown item, the click event will fire once. Resize the window out and then back in, the click event will fire now 2 times, then 3, and so on, depending on how many times the browser was resized.
I'm not sure exactly what is going on in my resize script that is screwing everything up and I'm at my wits end at trying to figure it out. Normally people aren't sitting there resizing their browser from desktop to mobile, but my boss does when he show's clients a beta of their site and wants this to never be an issue.
Here is my resize script:
(function( $ ) {
var id,
$body = $('body'),
$window = $( window ),
$navSlider = $('.nav-slider'),
$navMask = $( '.nav-mask' ),
$navToggler = $( '.navbar-toggler' ),
$parent = $( '.menu-item-has-children' ),
$parentLink = $( '.dropdown-toggle' ),
$childContainer = $( '.dropdown-menu' );
$window.on( 'resize', function( e ) {
clearTimeout(id);
id = setTimeout(function() {
close();
var width = $window.width();
if ( width < 992 ) {
setHeightToNav();
$navMask.on( 'click', function() { close() } );
$navToggler.on( 'click', function() { open() } );
$parentLink.on( 'click', function( e ) {
e.preventDefault();
var $this = $( this );
$this.data( 'clicked', true );
console.log( $this.parent() );
} )
}
if ( width >= 992 ) {
resetNavHeight();
console.clear();
}
}, 500 );
} );
function setHeightToNav() {
if ( $body.hasClass( 'logged-in' ) ) {
var $wpAdminBar = $( '#wpadminbar' ).outerHeight();
$navSlider.css( { top: $wpAdminBar + 'px' } );
}
var $navHeight = $( '#header-container' ).outerHeight();
$navSlider.css( { marginTop: $navHeight + 'px' } );
}
function resetNavHeight() {
if ( $body.hasClass( 'logged-in' ) ) {
$navSlider.css( { top: 0 + 'px' } );
}
$navSlider.css( { marginTop: 0 + 'px' } );
}
function close() {
$body.removeClass( 'has-active-menu' );
setTimeout( function() {
$navSlider.removeClass( 'toggling' );
$parent.removeClass( 'show' );
$parentLink.attr( 'aria-expanded', false );
$childContainer.removeClass( 'show' ).removeAttr( 'style' );
$parentLink.data('clicked', false);
}, 250 );
console.log('close()');
}
function open() {
$body.addClass( 'has-active-menu' );
$navSlider.addClass( 'toggling' );
}
})( jQuery );
I've tried my script both with AND without the setTimeout function and it happens exactly the same.
On the project, we are using Bootstrap 4, with the Bootstraps Dropdown._clearMenus(); function commented out in the right places as it was causing conflicts with the functionality I wanted with the navigation.
A link to a site where you can see this is here. It's a WordPress site as well if that matters for anything.
Any help is appreciated. I've been at this for several hours and am at my wits end.
.on( 'click', function ) does not set the event listener, it adds an event listener. Try doing off('click') before setting it if you really need to set this listener here.
But note that any other 'click' listener for this element will also be removed.
That's for the quick fix. You could do better, but that would require more work (track with a boolean if you just changed "display mode", and add or remove the event listeners only then, for example).

How can I reuse this JQM function instead of copying it 5 times?

I saw this demo page and I build it in my page using JQM. However, to open the panel on swipe, the following function is needed:
$( document ).on( "pagecreate", "#demo-page", function() {
$( document ).on( "swipeleft swiperight", "#demo-page", function( e ) {
// We check if there is no open panel on the page because otherwise
// a swipe to close the left panel would also open the right panel (and v.v.).
// We do this by checking the data that the framework stores on the page element (panel: open).
if ( $( ".ui-page-active" ).jqmData( "panel" ) !== "open" ) {
if ( e.type === "swipeleft" ) {
$( "#right-panel" ).panel( "open" );
} else if ( e.type === "swiperight" ) {
$( "#left-panel" ).panel( "open" );
}
}
});
});
But I've got 4 other pages. How can I reuse the function for every page instead of copying it?
I'd recommend using an external panel that can be accessed from any page. An external panel should be placed outside any page, i.e. should be a sibling of all pages, a child of page container.
<!-- external panel -->
<div data-role="panel" id="myPanel">
<!-- content -->
</div>
<!-- pages -->
<div data-role="page" id="p1">
<!-- content -->
</div>
<div data-role="page" id="p1">
<!-- content -->
</div>
And then initialize it manually and enhance its' contents.
$(function () {
$("#myPanel").panel().enhanceWithin();
});
To add swipe listener, run the code once .one() on pagecreate.
$(document).one("pagecreate", function () {
$(document).on("swipeleft", function (e) {
if ($(".ui-page-active").jqmData("panel") !== "open") {
$("#myPanel").panel("open");
}
});
});
However, if you want to use a different panel for each page, then you need to run code whenever pagecreate triggers by utilizing event.target. Moreover, to target the panel in the page where swipe event was triggered, you need to use activePage method.
I forgot to mention that pagecreate event fires once per page, hence, the below code will be executed one time per page.
$(document).on("pagecreate", function (e) {
$(e.target).on("swipeleft", function (e) {
var activePage = $.mobile.pageContainer.pagecontainer("getActivePage");
if (activePage.jqmData("panel") !== "open") {
$("[data-role=panel]", activePage).panel("open");
}
});
});
Well it's a anonimouse function, just give it a name, save it in a *.js file and include that file in your four pages. Here the fuction with a name:
function withName( e ) {
// We check if there is no open panel on the page because otherwise
// a swipe to close the left panel would also open the right panel (and v.v.).
// We do this by checking the data that the framework stores on the page element (panel: open).
if ( $( ".ui-page-active" ).jqmData( "panel" ) !== "open" ) {
if ( e.type === "swipeleft" ) {
$( "#right-panel" ).panel( "open" );
} else if ( e.type === "swiperight" ) {
$( "#left-panel" ).panel( "open" );
}
}
}
function moreName() {
$( document ).on( "swipeleft swiperight", "#demo-page", withName);
}
$( document ).on( "pagecreate", "#demo-page", moreName);

Modernizr.mq not working in Safari & multiple event triggers?

I've got a sub navigation that has 3 different styles and functions based on browser width. See it live here.
$(function() {
$(window).resize(function() {
if(Modernizr.mq('(min-width:641px) and (max-width: 1200px)')) {
$(".subnav-menu").click(function() {
$('.primary-subnav').slideToggle();
$('.subnav-close').show();
});
$(".subnav-close").click(function() {
$('.primary-subnav').slideToggle();
$('.subnav-close').hide();
});
$(".tablet-subnav li").click(function() {
$('.primary-subnav').slideToggle();
$('.subnav-close').hide();
});
}
else if(Modernizr.mq('(max-width: 640px)')) {
$(".subnav").click(function() {
$('.primary-subnav').slideToggle();
$('.subnav-mobile-open').toggle();
$('.subnav-mobile-close').toggle();
});
}
}).trigger('resize');
});
The menu that should be showing up on click is not showing up at all on Safari on my desktop or phone, tested on several other macs and got nothing. It's working fine in Chrome/Firefox other than sometimes it will fire the menu's slideToggle multiple times. What am I doing wrong?
Edit:: The menu appears to not expand in mobile safari or mobile chrome at all, whereas I thought this was purely a safari issue. The little arrow on mobile and the menu button tablet do change though, and if I tap under there were the menu should be, it reacts as if going to the link, but the menu is not visible... Really strange.
Try using .off( "click", "**" ) on the click event handlers that were added from one media query and replace them with the .on( "click" ) event handlers that you need for the current media query.
Also, you can create a nice fade effect without the common problem of multiple queued animations by adding .stop( true, true ) to the chain.
Without your html, I couldn't really test it, but here's about what it should look like:
$( window ).on( "resize", function ()
{
if( Modernizr.mq( "(min-width:641px) and (max-width: 1200px)" ))
{
$( ".subnav" ).off( "click", "**" );
$( ".subnav-menu" ).on( "click", function ()
{
$( ".primary-subnav" )
.stop( true, true)
.slideToggle();
$( ".subnav-close" )
.stop( true, true )
.show();
});
$( ".subnav-close, .tablet-subnav li" ).on(" click", function ()
{
$( ".primary-subnav" )
.stop( true, true )
.slideToggle();
$( ".subnav-close" )
.stop( true, true )
.hide();
});
}
else if ( Modernizr.mq( "(max-width: 640px)" ))
{
$( ".subnav-menu, .subnav-close, .tablet-subnav li" ).off( "click", "**" );
$( ".subnav" ).on( "click", function ()
{
$( ".primary-subnav" )
.stop( true, true )
.slideToggle();
$( ".subnav-mobile-open, .subnav-mobile-close" )
.stop(true, true)
.toggle();
});
}
})
.resize();

jquery mobile collapse if mobile device

I have a web application built on JQuery Mobile.
I've been looking for a way to have certain divs (which have data-role="collapsible") be collapsed when viewed on a mobile device, but expanded if viewed on a desktop browser.
I know there are a lot of similar questions, but I haven't been able to get any of those solutions to work. Here's what I've tried.
The divs have a class of mobileHide
<script type="text/javascript">
/*collapse non-crucial sections when viewed on mobile device*/
/* this works while resizing desktop browser, but can't resize mobile browser*/
$(window).on('resize', function(){
var w = $(window).width();
if(w >= 540 ) {
$( ".mobileHide" ).trigger( "expand" );
}else{
$( ".mobileHide" ).trigger( "collapse" );
}
});
/* Tried setting #media to make .hide-element {display:none} if screen size less than 540px (which worked in hiding that element) then set is_mobile to true. Also tried doing the same without the is_mobile and just using "if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) )"*/
$(window).resize(function() {
var is_mobile = false;
if( $('.hide-element').css('display')=='none' {
is_mobile = true;
}
if(!is_mobile) {
$( ".mobileHide" ).trigger( "expand" );
}else{
$( ".mobileHide" ).trigger( "collapse" );
}
});
/*Also tried this without being in a window.resize function. The alert works when viewed on mobile device, but the collapse and expand don't*/
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
/*alert("I am mobile!");*/
$( ".mobileHide" ).trigger( "collapse" );
} else {
$( ".mobileHide" ).trigger( "expand" );
}
</script>
I didn't have all of those running at once, but each piece that I commented on was the only part that was active at the time.
Any insight into why the last one detects mobile device and displays the alert (when it's not commented out) but won't trigger the collapse when it detects mobile device would be much appreciated!
Here is a working DEMO
The function CollapsiblesOnSize() checks the window width and then either collapses or expands all elements with class of mobileHide. You can then call this on window resize, orientation change and jQM's pageshow:
$(document).on("pageinit", "#page1", function () {
CollapsiblesOnSize();
$(window).on("resize orientationchange", function(){
CollapsiblesOnSize();
});
});
function CollapsiblesOnSize(){
var w = $(window).width();
if(w >= 540 ) {
$( ".mobileHide" ).trigger( "expand" );
}else{
$( ".mobileHide" ).trigger( "collapse" );
}
}

Detect scroll event on Android browser

I'm trying to detect the scroll event in Android browser (my specific version is 2.1, but U want it to work also on older versions). This seems impossible!
I first tried this:
document.addEventListener('scroll', function(){ alert('test'); }, false);
But nothing is triggered (except when the page load).
I thought: well, let's be crazy and emulate it by :
1. Detecting touchend
2. Polling the window.pageYOffset so we know when the window stops scrolling
3. Manually trigger a user function I want on scroll.
Unfortunately, the touchend event doesn't look to be triggered either... in fact, when we don't scroll and only tap the screen (touchstart + touchend), it works. As soon as we scroll the page in between (touchstart + touchmove + touchend), it breaks everything.
Now my most basic example only contains this:
document.addEventListener('touchend', function(){ alert('test'); }, false);
But the alert doesn't show up when we scroll with the finger and release the touch...
Does anyone has a suggestion?
Thanks.
You may want to crawl the source for JQuery Mobile, it supports android browsers and has scroll event listeners.
Or at least they say it does in the docs. :p
Here's the source
$.event.special.scrollstart = {
enabled: true,
setup: function() {
var thisObject = this,
$this = $( thisObject ),
scrolling,
timer;
function trigger( event, state ) {
scrolling = state;
var originalType = event.type;
event.type = scrolling ? "scrollstart" : "scrollstop";
$.event.handle.call( thisObject, event );
event.type = originalType;
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this.bind( scrollEvent, function( event ) {
if ( !$.event.special.scrollstart.enabled ) {
return;
}
if ( !scrolling ) {
trigger( event, true );
}
clearTimeout( timer );
timer = setTimeout(function() {
trigger( event, false );
}, 50 );
});
}
};

Categories