How to detect when ALL your nested ng-includes have loaded their content in angularjs

angularjs provides an $includeContentLoaded event that is fired by every instance of the ngInclude directive whenever it's content is (re)loaded.

In situations where you have multiple nested ngIncludes, the event is of limited use since it is fiddly to detect which include has loaded, and there is no out of the box way to detect when ALL of them have loaded.

Check out this jsFiddle showing how to use the $includeContentLoaded event to detect when specific includes have loaded based on their scopes.

But what if you want to know when ALL the includes beneath a given scope have been loaded?

On a recent project I needed to use $anchorScroll on a long form built out of a lot of nested includes. If I called it too early, the page would get longer and the scroll position wouldn't be correct. Obviously I didn't want to just listen for $includeContentLoaded on the main page scope and call $anchorScroll every time it was fired... Not only would this be wasteful, it might also make the page jump and jitter visibly.

Luckily, it is possible to extend on the built in angularjs directives, including ngInclude.
So, I wrote a simple extension that registers instances of ngInclude up the scope chain, and $emits a custom event at every level when all child $includeContentLoaded events have been fired.
This means I can listen at any level in the application (e.g. a form, a menu wrapper etc.) and know when angularjs has finished laying out all the nested visual elements. Of course this does not guarantee that any asynchronous calls to webservices have been completed, only that the DOM rendering is ready.

Here is a working example of the directive in action with a load of debug info so you can see what's going on: jsFiddle / gist
Here is a cleaner version with minimal code and only the allContentLoaded events being printed: jsFiddle / gist

And if you just want the code for the directive, here it is:

  1.  
  2. /**
  3.  * Extends the built-in angularjs IncludeDirective
  4.  */
  5. myApp.directive('ngInclude', function() {
  6. function recursivelyRegister(scopeToRegister, scopeToRegisterWith) {
  7. if(!scopeToRegisterWith.hasOwnProperty('includesLoading')) {
  8. scopeToRegisterWith.includesLoading = [];
  9. }
  10. if(scopeToRegisterWith.includesLoading.indexOf(scopeToRegister.$id) === -1) {
  11. scopeToRegisterWith.includesLoading.push(scopeToRegister.$id);
  12. if (scopeToRegister.hasOwnProperty('name') && scopeToRegisterWith.hasOwnProperty('name')) {
  13. scopeToRegister.logToConsole(scopeToRegister.name + ' was linked under ' + scopeToRegisterWith.name);
  14. }
  15. }
  16. if (scopeToRegisterWith.$parent) {
  17. recursivelyRegister(scopeToRegister, scopeToRegisterWith.$parent);
  18. }
  19. }
  20.  
  21. function recursivelyDeRegisterAndNotify(scopeToDeRegister, scopeToDeRegisterFrom) {
  22. var i = scopeToDeRegisterFrom.includesLoading.indexOf(scopeToDeRegister.$id);
  23. if (i !== -1) {
  24. scopeToDeRegisterFrom.includesLoading.splice(i, 1);
  25.  
  26. if (scopeToDeRegisterFrom.includesLoading.length === 0) {
  27. if (scopeToDeRegisterFrom.hasOwnProperty('name')) {
  28. scopeToDeRegister.logToConsole('all includes were loaded under ' + scopeToDeRegisterFrom.name);
  29. }
  30. scopeToDeRegisterFrom.$emit('allIncludesLoaded' + scopeToDeRegisterFrom.$id);
  31. }
  32. }
  33.  
  34. if (scopeToDeRegisterFrom.$parent) {
  35. recursivelyDeRegisterAndNotify(scopeToDeRegister, scopeToDeRegisterFrom.$parent);
  36. }
  37. }
  38.  
  39. function recursivelyReset(scope) {
  40. scope.includesLoading.length = 0;
  41. if (scope.$parent) {
  42. recursivelyReset(scope.$parent);
  43. }
  44. }
  45.  
  46. return {
  47. restrict: 'A',
  48. link: function(scope, element, attr, ngModel) {
  49. recursivelyRegister(scope, scope.$parent);
  50.  
  51. scope.$on('$routeChangeSuccess', function() {
  52. // empty the tracking arrays on route changes
  53. recursivelyReset(scope);
  54. });
  55.  
  56. scope.$on('$includeContentLoaded', function(event) {
  57. if (scope.hasOwnProperty('name') && scope.$parent.hasOwnProperty('name')) {
  58. scope.logToConsole(scope.name + ' was loaded under ' + scope.$parent.name);
  59. }
  60. recursivelyDeRegisterAndNotify(scope, scope.$parent);
  61. });
  62. }
  63. };
  64. });
  65.  
Tagged with: , , ,
Posted in JavaScript

How to stop Twitter Bootstrap modal dialogs breaking on browser history navigation in angularjs

Twitter's Bootstrap modal dialogs are susceptible to a problem when used in conjunction with angularjs... when your users navigate using their browser's history buttons or keyboard shortcuts (the Backspace key for example), they stay open.

Because the scope from which they were opened has been destroyed, I was ending up with orphaned, broken dialogs that could no longer be closed.

If you are targeting modern browsers that support the History API, a simple fix is available using the popstate Window event:

  1.  
  2. /* Watches the HTML5 History API history changed event to avoid the bug with
  3.  * modal dialogs being catastrophically broken when backspace or the
  4.  * browser back button is clicked.
  5.  */
  6. window.onpopstate = function() {
  7. // hideDialog is a method on my rootScope that looks after cleanly
  8. // closing any open dialog ...
  9. // it basically boils down to $('#someModalElement').modal('hide');
  10. // I'll write a post about it sometime.
  11. $rootScope.hideDialog();
  12.  
  13. // The hiding animation was causing a race condition that resulted
  14. // in the overlay div that is loaded under dialogs being left behind
  15. // sometimes, so I had to explicitly remove it.
  16. $('.modal-backdrop').remove();
  17. };
  18.  
Tagged with: , , ,
Posted in JavaScript

Yet more JS wierdness – how to zero out (or just set) the time part of a date object

I love JavaScript, warts and all, but occasionally I stumble across quirks that are so unintuitive that I feel the need to write them down.

Today, I needed to compare two dates, but without taking into account the time element (so only  day, month and year). Obviously there are plenty of more verbose ways to achieve this, but I just wanted to do a straight comparison between two Date objects. So, I dove into the specs looking for a way to set an existing Date object's time element to exactly midnight (00:00).

I found setHours, setMinutes, setSeconds and setMilliseconds... but surely simple old setTime would do the job? Of course not, that would be far too logical:

The setTime() method sets a date and time by adding or subtracting a specified number of milliseconds to/from midnight January 1, 1970.

  1. var d = new Date();
  2. d.setTime(1332403882588);

But a closer look at the documentation for the other methods reveals that each one is not only capable of setting it's implied unit of time, but also all those below it.

For example:

The setMinutes() method sets the minutes of a date object.
This method can also be used to set the seconds and milliseconds.

  1. Date.setMinutes(min,sec,millisec)

and:

The setHours() method sets the hour of a date object.
This method can also be used to set the minutes, seconds and milliseconds.

  1. Date.setHours(hour,min,sec,millisec)

Therefore, to achieve the goal of setting the time part of an existing date object, you can use the setHours() method:

  1. var d = new Date();
  2. console.log(d); // Thu Dec 13 2012 12:48:32 GMT+0100 (CET)
  3.  
  4. d.setHours(0,0,0,0);
  5. console.log(d); // Thu Dec 13 2012 00:00:00 GMT+0100 (CET) ​​​​

Logical, right?

Source: w3schools.com - JavaScript Date Object

Posted in JavaScript

1989 Jeep Wrangler YJ radio / stereo wiring

My '89 Jeep had no wiring harness / block so I needed to research how to connect it up to a modern ISO head unit.
For anyone else in the same situation, here is what I found:

removing the dash fascia to remove / install the head unit
Source: Autozone free repair docs (free site membership required).

* Disconnect the battery ground cable.
* Remove the gauge cluster bezel as outlined under Gauge Cluster removal and installation.
* Remove the control knobs, nuts, and bezel.
* If equipped with air conditioning, remove the screws and lower the assembly.
* Disconnect the radio bracket from the instrument panel.
* Tilt the radio down and remove it toward the steering wheel.
* Detach the antenna, speaker, and power wires.
* Reverse the procedure for installation.

removing a Jeep YJ dash / fascia

removing a Jeep YJ dash / fascia


1990 Jeep Wrangler Car Stereo Radio Wiring
Source: http://www.modifiedlife.com/1990-jeep-wrangler-car-stereo-radio-wiring-diagram/

Car Radio Constant 12V+ Wire: Pink
Car Radio Switched 12V+ Wire: Purple/White
Car Radio Ground Wire: Black
Car Radio Illumination Wire: Blue/Red
Car Radio Dimmer Wire: Red/Black
Car Radio Antenna Trigger: N/A
Car Radio Amp Trigger Wire: N/A
Front Speakers Size: 4″ x 6″ Speakers
Front Speakers Location: Dash
Left Front Speaker Wire (+): Gray
Left Front Speaker Wire (-): Yellow
Right Front Speaker Wire (+): White/Red
Right Front Speaker Wire (-): Black/Red
Rear Speakers Size: 4″ Speakers
Rear Speakers Location: Rear Roof
Left Rear Speaker Wire (+): Gray/White
Left Rear Speaker Wire (-): Brown/White
Right Rear Speaker Wire (+): White/Black
Right Rear Speaker Wire (-): Brown

OR…

1988-89 Jeep Wrangler Stereo Wiring
Source: http://www.the12volt.com/installbay/stereodetail/1232.html

Constant 12V+ Red/White
Switched 12V+ Purple/White
Ground Black
Illumination Blue/Red
Dimmer Red/Black
Antenna Right Front
Front Speakers 4" x 6" Dash
Left Front (+) Gray
Left Front (-) Yellow
Right Front (+) White/Red
Right Front (-) Brown/Red
Rear Speakers 4" Rear Roof
Left Rear (+) Gray/White
Left Rear (-) Brown/White
Right Rear (+) White/Black
Right Rear (-) Brown


In case yours does have the wiring block, here is the pinout for a YJ radio connector:

Jeep Wrangler YJ radio pinout

Jeep Wrangler YJ radio pinout

(source: some forum post I have now lost - will try to find it back)


And here is the ISO pinout (viewed from the back of the plugs that go into the head unit - the side the wires go in):

ISO radio / stereo pinout

ISO radio / stereo pinout

(source: http://en.wikipedia.org/wiki/File:ISO_10487_connector_pinout.svg)


And finally, the standard ISO colours:

Power plug
Yellow - Permanent power
Red - Switched power
Orange - Illumination
Blue - Remote (electric aerial/amplifier switch on)
Black - Earth

Speaker plug
Grey - Front right speaker
White - Front left speaker
Purple - Rear right speaker
Green - Rear left speaker

These wires are in pairs. The speaker wires with the tracers are usually the returns.

Note that these colours might not be adhered to by all manufacturers, so be careful!
The wires themselves often have their purpose printed on them in small text, otherwise RTFM!

Posted in Cars

Last day at Philips

A special task sticky added by my team for my last day.

A special task sticky added by my team for my last day.

I am going to really miss all these people.

I am going to really miss all these people.

Posted in Scrum

What? You tattooed code on your arm?

[OPPORTUNISTIC UPDATE]
Since I'm seeing so many hits for this page, and since most of you are developers, I thought it might be worth trying to do a little blatant recruiting :)
If you are an exceptional, passionate front end developer (HTML, CSS, JS) and you want to work in an awesome SCRUM team at a huge multinational company in the South of the Netherlands, please drop me a line (DM my Twitter account).
There are also opportunities for developers (front and/or back end) and UNIX gurus at another brilliant company (Competa) in the Randstad. Basically, if you love tech and you're looking for work in Holland, get in touch!

[UPDATE]
This code isn't meant to do anything - it's purely symbolic.
And yes, I know it will cause a stack overflow if you run it - that's the point that I die, "run out" of memory or suffer some other system failure. Clearly I'm not immortal, so there needs to be some end to the recursion eventually :)

(function(){var k=[];return function j(){k.push(i);j();}})()();

That's what I got tattooed on my forearm last week. Reactions have ranged from incredulity to hilarity, but I love it.

So why did I decide to spend the rest of my life with an obscure snippet of JavaScript on my body? Well, because I wanted a tattoo that meant something to me, not anyone else. I wanted something linked to my work and my passion: web development. And I wanted something no one else would have.

I briefly Googled "developer tattoos" and "programmer tattoos" looking for inspiration, but the only good thing that came up was the (admittedly brilliant) </head> <body> tattoo that you've doubtless already seen:

head-body-tattoo

ingenious

Having been let down by the Interwebs, I thought "how better to commemorate my love for code than in code?".

The obvious choice of language was JavaScript. It's the language I've been using the longest (although I've only been seriously into it for the past year or so since I got to work on JigLibJS). JavaScript is also one of core foundations of the web, and looking at recent developments that's not likely to change anytime soon. Also, it's an open standard, which is what I believe the web should be all about.

So, what should I say with my code? I decided I wanted something to remind me of the values and philosophies I strive to apply every day in my work, and in my life.

(function(){var k=[];return function j(){k.push(i);j();}})()();

This code structure is called a closure. If you don't know what a closure is, you can read about it here: via Wikipedia. The short version: it's a function that returns another function. The returned function has access to anything created in the scope of the parent function - just like private class members in full blown object oriented languages like C# or Java. I decided to go for this structure for 2 reasons: firstly because I find it very beautiful, and secondly because I wanted the code to support growth or accumulation in some way.

(function(){var k=[];return function j(){k.push(i);j();}})()();

The k array represents knowledge or experience.

(function(){var k=[];return function j(){k.push(i);j();}})()();

The function j returned by the closure is recursive. This is to remind me that I should maintain my own drive and motivation - that I should always expect more of myself.

(function(){var k=[];return function j(){k.push(i);j();}})()();

On every call, the function j attempts to add the contents of variable i out of the global scope to the enclosed array k. The eagle eyed will notice that I have not included a safety check here so that if i is undefined, the code will error out and the recursion of j will fail. This is intentional. The variable i represents input from other people, and I want to remember that I should always seek the advice and opinions of those around me. I know that I cannot grow in isolation, hence the catastrophic consequences of failing to garner the contributions of others. As I mentioned above, the k array represents knowledge or experience, and this part of the code also reminds me that I should always learn from the input I elicit. This doesn't mean to say that I should always accept or act upon advice I receive, just that I should learn from it. Even poor opinions can teach us a lot about the people who offer them ;)

(function(){var k=[];return function j(){k.push(i);j();}})()();

Both the closure and the returned function j are self invoking. That means that the code will run itself: the functions do not need to be invoked from elsewhere. This is to remind me that I should take the initiative in my life and work, not rely on external influences for the impetus to start new things.

So, that's it. Every time I look at my arm I will be reminded of the values that are important to me, and of my love for code.

Much better than some Chinese characters that may or may not mean "luck" or "strength" I think, and every bit as cryptic.

My new tattoo

I wear my geek-ness with pride

Posted in JavaScript, Rants

IE, images and labels

Internet Explorer doesn't handle clicks on images in labels:

  1. <label for="someCheckbox">
  2. <input name="someCheckbox" type="checkbox" />
  3. <img src="aPrettyPicture.png" />
  4. </label>

So, I did a quick Google to see how others have solved this issue, and the first hit was this site which included this reader-submitted jQuery solution:

  1. $("label img").live("click", function() {
  2. $("#" + $(this).parents("label").attr("for")).click();
  3. });

This solution was pretty buggy for me (required double clicks on the images) and uses jQuery.live() which seems overkill to me since the problem presented in the original article does not specify a need to support dynamically added labels. Also, use of jQuery.live() can be considered inadvisable, especially if it's not actually necessary to achieve the required behaviour. Finally, I didn't understand why the code didn't just select the image's direct parent.

So, I settled on this simplified version which works perfectly for me:

  1. $("label img").bind("click", function() {
  2. $(this).parent().click();
  3. });
Posted in JavaScript

Can Google see JavaScript content?

I made this simple page to test if Googlebot can parse content loaded or generated via JavaScript.

There are two divs on the page - one has it's content loaded via AJAX using jQuery.load() and the other using jQuery.html().

[update] I have added an extra content block whose AJAX request is triggered by a link in case Googlebot treats onload JS differently to onclick.

I then used Google's Fetch as Googlebot tool to see if the content would be visible during spidering.

This is how Googlebot saw the page:

  1. HTTP/1.1 200 OK
  2. Content-Length: 639
  3. Content-Type: text/html
  4. Content-Location: http://www.sangwine.net/js_seo_poc/index.html
  5. Last-Modified: Thu, 14 Jul 2011 17:53:30 GMT
  6. Accept-Ranges: bytes
  7. ETag: W/"ec7d4f14e42cc1:7d83e"
  8. Server: Microsoft-IIS/6.0
  9. X-Powered-By: ASP.NET
  10. Date: Thu, 14 Jul 2011 17:54:16 GMT
  11.  
  12. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  13. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  14.  
  15. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  16. <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  17. <title>Sample Title</title>
  18. <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
  19. <script type="text/javascript" src="script.js"></script>
  20. </head>
  21.  
  22. <h1>simple AJAX content loading (jQuery.load();):</h1>
  23. <div id="contentBlock1"></div>
  24.  
  25. <h1>Dynamically built content:</h1>
  26. <div id="contentBlock2"></div>
  27.  
  28. </body>
  29. </html>

So, it would seem that Google cannot spider content created after the original HTTP response.

I will be reviewing how Google really spiders my test page in a day or two to confirm if the "Fetch as Googlebot" tool is really accurate.

If anyone can shed any light on techniques for writing SEO client-side dynamic content, please shout out in the comments!

Tagged with: , ,
Posted in JavaScript

MacBook Pro SSD Upgrade: is it worth it?

I've been trying to find ways to make my late 2008 unibody MacBook Pro run faster because I want to hold out on upgrading at least until the end of the year, and preferably after the next range refresh.

I run a lot of applications simultaneously, especially at work. I also use Windows virtual machines a lot for cross-browser testing (I'm a web developer), and so a lot of my day is spent waiting for stuff to load.

I also run a lot of persistent menubar widgets for everything from managing my VPN connections to quickly accessing my code snippets:

My menubar

My menubar

The first step was to up my RAM to 6Gb (Apple says it will only take 4, but 6 has been found to be the real stable maximum by the community). This helped a little, especially when running VM's, but I still found myself spending way too much time on the Apple site with my cursor hovering over the buy button for the 17 inch i7.

The next obvious upgrade was an SSD drive, and after seeing this video I decided to take the plunge:

After quite a bit of research I settled on the 240Gb OCZ Vertex 2 as my drive of choice. Big enough to not need a second internal drive (I didn't want to trade the increased battery drain and heat for extra space) but not so big as to break the bank. It still wasn't cheap (£285 GBP / ~ €325 EUR from Amazon UK), but having installed it this morning and used it at work for a day, I have to say it was money well spent!

In fact the results are nothing short of stunning. I will never go back to rotational media again for my main workhorse machine.
I put together a series of tests while I was waiting for the drive to turn up so I could empirically compare it with my old 5200rpm stock drive.

First, the specs of my system and the two drives:

My late 2008 unibody MacBook Pro specs

My late 2008 unibody MacBook Pro specs

my original 320Gb 5400RPM hdd

my original hard drive

OCZ Vertex 2 SSD specs

OCZ Vertex 2 SSD specs


cold boot test

HDD:
Dock appears: 1:20
Desktop loads: 1:40
Finder and menubar loaded: 2:10

SSD:
Dock appears: 0:30
Desktop loads: 0:31
Finder and menubar loaded: 0:36


Multiple application loading test

I made a simple AppleScript that loads a selection of my most commonly used work apps: Photoshop CS5, iTerm2, CSSEdit, Espresso, Versions, EclipseJEE and Firefox.

HDD: 1:15
SSD: ~ 14 seconds


grep test

I recursively grepped for "wp" in the root of a WordPress site

grep -ri "wp*" ./

HDD: ~ 9 seconds
SSD: ~ 5 seconds


tar test

I timed how long it took to tar up a joomla application (5283 files, 69.2Mb) from the commandline.

tar -cvf testdump.tar sangwine.net_2010

HDD: ~ 13 seconds
SDD: ~ 5 seconds


XBench test

XBench results with the original hdd

XBench results with the original hdd

OCZ Vertex 2 SSD XBench results

OCZ Vertex 2 SSD XBench results


The test results really don't convey the feeling of working on a system that loads applications and accesses data so fast.

To put it in perspective, here are a few application load times I am getting with the new SSD drive:

  • Mac Mail: < 1 second
  • Outlook 2011: ~ 2 seconds
  • Windows XP VM running under Parallels: ~ 30 seconds

I downloaded a tarball earlier today, and Archiver unpacked it so quickly that it didn't even get a chance render to the screen.

If you are considering making the switch, I highly recommend it.

Posted in Hardware

Really? A glory hole?

I have just encountered my first (and probably last) video game glory hole. And not just any glory hole... one surrounded by graffiti depicting a woman (the hole is in her mouth) along with the words "yum" and "slurp".

And yes, it's interactive.

What game hero would dare openly use a glory hole? Why Duke Nukem of course!

What is less Duke-ish is the fact that when you peek through the hole it is a man you see sitting in the neighboring stall, although the voice is a woman's. Bizarre. Is this some kind of hidden hint that Duke's bread is buttered on both sides, or just another symptom of an incomplete game?

Regardless, it kind of puts GTA San Andreas' infamous hot coffee mod in perspective.

Posted in Games