From jQuery

Facebook JavaScript SDK “Uncaught RangeError: Maximum call stack size exceeded” error

volcano-ecuador-825x558

I’ve been dealing with an issue for a couple of months where the Facebook JavaScript SDK wouldn’t function properly on my local development instance, even though it was working fine in our testing and production environments. I tried all the obvious things–confirmed the correct URLs in the Facebook App settings, made sure I was using the right App ID and Secret, etc. The weird thing is, according to the console FB was an object, and XFBML was an object, but parse() was not a method of XFBML.

I wasn’t seeing any of the usual JS errors in the console in Chrome either, which was a bit confusing, at least until I opened Safari and saw this:

Uncaught RangeError: Maximum call stack size exceeded

According to this question on StackOverflow, the problem is caused by running the old Facebook SDK (FeatureLoader.js) alongside the new one (all.js). I was positive that FeatureLoader.js wasn’t loading anywhere in my codebase, and a quick check with ack didn’t show anything either. FeatureLoader.js definitely was getting loaded though, and when I double-checked I saw that it was being loaded by a locally-installed dev plugin that I have running (but that isn’t on our dev or production sites). Plugin removed, problem solved.

On Safari Mobile for iOS 5, event.pageY is the new event.clientY

safari-logo-lg

We have some tooltips running at my work that are used to render sharing buttons when a user clicks or taps on them. When the recent upgrade to iOS came around, the tooltips stopped being rendered properly in iOS 5.

After running into a few problems with jQuery Tools and the iPad, I came up with a solution for getting the tooltips to appear next to the anchor element like they were supposed to. By using the event.clientY value from the touch event, I was able to detect where in the DOM the touch had happened, and simply position the tooltip right next to it, with something like this:

$('#tooltip').css('position', 'absolute !important').css('top', event.clientY);

In iOS 4.3, event.clientY was reporting the absolute position of the touch event relative to the entire document. In iOS 5, I discovered that it was reporting the position of the touch event relative to the window. So, if you tapped a tooltip way down on the page, but near the top of the current viewport window, the tooltip would appear right near the top of the document, completely off screen.

A little digging on Google yielded this page on the Apple site. The reference to an event.pageY property made me think that maybe that would do the trick, and it seems to work.

$('#tooltip').css('position', 'absolute !important').css('top', event.pageY);

Now, with iOS 5, the touch event was properly setting the tooltip’s top value to the position of the touch event within the entire DOM, not just the viewport. I’m not exactly sure what’s changed between iOS 4.3 and 5, but at least now I have something that works for both.

Lazy-load a LinkedIn Sharing button after the JavaScript window.load event

Adding social networking sharing buttons to your site has become almost a ubiquitous step in Web development, to the point when some designers have stopped thinking about the performance impact of rendering multiple buttons via JavaScript while a page is still loading. The delay might not be noticeable for, say, 1 or 2 buttons, but when you’re rendering multiple buttons per page (when you have a button to share individual Tweets on a page, for example), it can get out of hand (turns out JavaScript crashing the browser creates a user-unfriendly experience for most people).

The solution is to lazy-load the buttons when you need them, either when a user clicks to expose a previously hidden div, or at the very least after the window.load JavaScript event, to prevent slowing your pageload times down. Here’s an example of a simple way to lazy-load a LinkedIn Share button on window.load:

First, include the necessary scripts (LinkedIn’s in.js and jQuery). You can do this in the footer if you want…after all, you’re not doing anything with them until much later in JavaScript-time:



Next, add some jQuery in a script tag that looks for any script tag with a type of ‘unparsed-IN/Share’ (the name doesn’t matter, as long as it’s NOT IN/Share, since the whole point here is you don’t want the in.js script to parse the tag). Depending on the size of your DOM, you may want to be more specific with your jQuery selector…a div or a section of content is fine, and you can bind to a click event, a scroll event, or whatever else you’d like to initiate the parsing of your LinkedIn buttons:

 jQuery(window).load(function(){
    jQuery('body').find('script[type="unparsed-IN/Share"]').each(function(){
      jQuery(this).attr('type', 'IN/Share');
      IN.parse(document); //you can also use getElementById('id') in place of document here
    });
  });

Finally, you just need to add LinkedIn sharing tags in the following manner. The key here is to change the JS script type from IN/Share to unparsed-IN/Share (or whatever you chose in the jQuery above), which will cause the tag not to be rendered when in.js is loaded, allowing you to control with the JS when the tag is actually parsed, using IN.parse (which can be applied to the document as a whole, or an element as retrieved by the built-in JS document.getElementById method.


Update: As Howard points out in the comment section, if you don’t need to load the in.js script to render any LinkedIn buttons or content earlier, you can always accomplish lazy-loading by simply deferring the script load until you want to render the buttons. This allows you to avoid parsing and replacing the ‘type’ on each JavaScript snippet. If you need LinkedIn content to render both before the onload event as well as after, though, you’ll still need to do the replacement.

Refresh Twitter and Facebook social icons after an Ajax request

I recently found myself dealing with the following problem: I wanted to lazy-load as much of the social networking components of a site as possible, which meant deferring the load of both the Twitter and Facebook JS until after the document finished rendering. This process was relatively straightforward, but in the course of implementing the buttons I discovered something interesting: when you do an Ajax request to refresh a portion of the page, buttons in there are not dynamically refreshed. This makes sense–there’s nothing to re-run the appropriate JS methods to render them–but it’s not good.

My quest on Google first took me to this forum, where the exact same problem I was having was discussed back in Sept-Oct. Since a new version of the Twitter @anywhere API came out today, and doesn’t include a TweetButton, that wasn’t going to be an option either.

Thankfully, I found this post, which outlined the best workaround I’d seen yet for interacting with Twitter’s widgets.js file. It’s worth a quick aside here: the problem is created because the @anywhere API, which has all named functions, does not have a method to create a TweetButton. That is handled instead within widgets.js, which is all wrapped in one big anonymous function. There’s a render() method hiding pitifully inside, but it took someone with JS skills beyond mine to coax it out of hiding.

Both of the examples in the previous link work great, and can be cut-and-paste. In my case, however, I didn’t want to mess with the actual $.get request, which was happening in a completely different script file, and was written years ago. Thankfully, a few quick tweaks resulted in this:

$('.topic-term-link').ajaxComplete(function(){
   //re-render the facebook icons (in a div with id of 'content')     
   FB.XFBML.parse(document.getElementById('content'));

  //re-render twitter icons by calling the render() method on each
  $('#content').find('a.twitter-share-button').each(function(){
     var tweet_button = new twttr.TweetButton( $( this ).get( 0 ) );
     tweet_button.render();
  });
});

Since jQuery lets me bind directly to the ajaxComplete event, I didn’t even need control over the Ajax call, which is good, because it wasn’t an option. Now, maybe Twitter will name the main function in widgets.js and we can just call a single line like for Facebook in the above example.