Cause All Route Changes to Full Page Reload Angular JS

Inheriting a codebase isn’t fun. Even less fun if the code is filled with so many memory leaks that the only solution is to force a full page load on every route change. Below is a solution that works well in modern browsers, and IE9.

Note the check for changedFrom and changedTo. Without these, an infinite loop will occur.

No comments | Trackback

UserScript: Telecom NZ Broadband Stats

I finally got sick of manually calculating my remaining broadband allowance, and wrote a Greasemonkey script that to do it for me.

Update: I’ve updated this script, it now displays remaining bandwidth on the Telecom NZ initial login screen.

It waits until the relevant element has been loaded, then pulls the information it requires from the DOM, converts it into a more readable format, and appends this information into a fixed position div.

Example output (detailed):

Elapsed period:5 days
Remaining period:24 days
Remaining bandwidth:72998 MB
Remaining bandwidth per day:3042 MB

It activates only when there is information to display.

You can view it on it’s UserScripts page – Telecom NZ Usage Stats, install it, or view the source below:

// Telecom NZ Usage Stats
// 
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: https://addons.mozilla.org/en-US/firefox/addon/748
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Telecom NZ Usage Stats", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          Telecom NZ Usage Stats
// @namespace     https://www.telecom.co.nz/ebill/requesthandler
// @description   Show Telecom Broadband stats in an easy to read manner
// @include       https://www.telecom.co.nz/ebill/requesthandler*
// ==/UserScript==
 
var telecom_nz_stats = {
 
	BASIC: false,
	DETAILED: false,
	ONE_DAY: 1000 * 60 * 60 * 24,
 
	detailed_url_regex: new RegExp(/^https:\/\/www\.telecom\.co\.nz\/ebill\/requesthandler\/\?js=2&currentAccountLine.*/i),
 
	container: document.createElement('div'),
	label_css: 'style="display:hidden;width:200px;float:left;text-align:right;margin-right:10px;"',
	info_css: 'style="font-weight:bold;"',
 
	interval_id: false,
 
	output: function(content) {
 
		var message = '';
		for (var label in content) {
			message += '<span ' + this.label_css + '>' + label + '</span><span ' + this.info_css + '>' + content[label] + '</span><br/>';
		}
		this.container.innerHTML = message;
		this.container.style.display = 'block';
	},
 
	basic: function() {
		var usage = document.getElementsByClassName('tbdr')[1].getElementsByTagName('table')[0].getElementsByTagName('tr')[2].getElementsByTagName('td')[1].innerHTML.replace(/<\/?(img|nobr|br)[^>]*\/?>|<a\s.*|MB|\s/gi,'').trim().split('of');
		usage = parseFloat(usage[1]) - parseFloat(usage[0]);
		if (!isNaN(usage)) {
			this.output({'Remaining bandwidth:': usage + ' MB'});
		}
	},
 
	detailed: function() {
 
		var total_bandwidth = parseFloat(document.getElementsByClassName('usage')[0].getElementsByTagName('font')[0].children[0].innerHTML.replace(/[^0-9]/g,''));
		var usage_period = document.getElementsByClassName('tbdr')[0].getElementsByTagName('tr')[5].getElementsByTagName('td')[1].innerHTML.split('-');
		var usage_from = new Date(usage_period[0]);
		var usage_to = new Date(usage_period[1]);
		var now = new Date();
 
		var used_bandwidth = parseFloat(document.getElementsByClassName('usage')[1].getElementsByClassName('copy')[0].getElementsByTagName('td')[0].innerHTML.replace(/<\/?(img|nobr|br)[^>]*\/?>|,/gi,'').trim());
 
		var elapsed_days = Math.round((now.getTime() - usage_from.getTime()) / (this.ONE_DAY));
		var total_period = Math.round((usage_to.getTime() - usage_from.getTime()) / (this.ONE_DAY));
		var remaining_days = Math.round((usage_to.getTime() - now.getTime()) / (this.ONE_DAY));
 
		var remaining_bandwidth = (total_bandwidth - used_bandwidth);
 
		this.output({
			'Elapsed period:': elapsed_days + ' days',
			'Remaining period:': remaining_days + ' days',
			'Remaining bandwidth:': Math.round(remaining_bandwidth) + ' MB',
			'Remaining bandwidth per day:': Math.round(remaining_bandwidth / remaining_days) + ' MB'
		});
	},
 
	check: function(self) {
		if (document.getElementsByClassName('tbdr')) {
			window.clearInterval(self.interval_id);
			self.BASIC ? self.basic() : self.detailed();
		}
	},
 
	begin: function() {
		if (this.detailed_url_regex.exec(window.location.href)) this.DETAILED = true;
		else this.BASIC = true;
 
		this.container.style.position = 'fixed';
		this.container.style.top = '10px';
		this.container.style.right = '10px';
		this.container.style.backgroundColor = '#FFF';
		this.container.style.padding = '5px';
		this.container.style.display = 'none';
		document.getElementsByTagName('body')[0].appendChild(this.container);
 
		this.interval_id = window.setInterval(this.check, 100, this); 	
	}
};
 
telecom_nz_stats.begin();
No comments | Trackback

Scriptaculous Draggable reverteffect Example

I had a bit of a hard time finding which arguments are passed to the reverteffect function.

Scriptaculous’ documentation is somewhat opaque:

Effect, default to Effect.Move. Defines the effect to use when the draggable reverts back to its starting position.

Hmm, no mention of any arguments passed to the reverteffect function…

Here is an example of instantiating a new Draggable with a custom reverteffect:

new Draggable( element, {
    revert: true,
    reverteffect: function( element, top_offset, left_offset ){
        new Effect.Fade( element, {
            afterFinish: function(){
                $( element ).setStyle({
                    top: '0px',
                    left: '0px'
                });
                new Effect.Appear( element );
            }
        });
    }
});

Note that the arguments top_offset and left_offset are the offsets of the draggable as it was after the drag was completed. Also note that in my custom reverteffect function I’ve reset the draggable’s top and left offsets to 0, effectively shoving it back to it’s original position.

No comments | Trackback

IE8 & Assigning Classnames With new Element(‘…’)

When dynamically creating elements using Prototype’s new Element(‘…’) method, I ran into this irritating gotcha with IE8.

While the following looks great and works well in Firefox:

new Element('span', { 'class': 'someClassName' });

In IE8, not so much.

What one expects, and gets in Firefox:

<span class="someClassName"></span>

What one gets in IE8:

<span></span>

Hmmm, where did that classname go? Yeah, I don’t know either.

To avoid this, one must perform the following incantation:

new Element('span').addClassName('someClassName');

This will give the same result in Firefox and IE8. Tadaa!

No comments | Trackback

Scriptaculous Effect.Morph IE6

Ran into a weird bug the other day in IE6. Some elements, h1’s, weren’t animating with their parent when the parent was being Effect.Morphed. Every other element was moving, but the h1’s just sat there, looking really stupid. I think the use of Twin Helix’s IE PNG Fix was a contributing factor here, since writing this article I’ve noticed ( and fixed, using the solution below ) this issue on other position:relative, PNG fixed elements.

The h1’s were the parents referred to in the post Float:Right Nuke for IE6, and thus had position:relative set. Removing this caused the h1’s to animate nicely, but unfortunately I couldn’t do that because removing the position:relative had the side-effect of causing the content that I wanted to simulate float:right with to shoot up to the top-right of the screen. D’oh.

To solve it, and have everything animate acceptable in IE6 I had to do this:

/* the first morph: */
new Effect.Morph('h1_parent', { style: 'padding-top:50px', delay: 0.5 });
 
/* the fix: */
$$('.h1_className').each( function(element){ 
	new Effect.Morph(element, { 
		style: 'top:1px', duration: 0.5 
	});
});
 
/* the second morph, taking place after some user action: */
new Effect.Morph('h1_parent', { style: 'padding-top:10px', delay: 0.5 });
 
/* the fix: */
$$('.h1_className').each( function(element){ 
	new Effect.Morph(element, { 
		style: 'top:0px', duration: 0.5 
	});
});

Yeah, looks stupid I know. Works though. A bunch of h1’s moving 1px isn’t noticeable, but h1’s that didn’t animate sure would have been!

One more time, combined into a more usable function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Wiggles elements with the given classes to make sure they animate 
 * with their parent, their parent's parents, and their parent's parent's 
 * parents ( ad infinitum ) in IE6, when the elements of the given
 *  classes have the Twin Helix PNG fix + position:relative applied
 *
 * @param classes an array of css class names, prefixed with a '.'
 */
wiggle = function( classes ){
classes.each( function( className ){
     $$( className ).each( function( ele ){
          new Effect.Morph( ele, {
               style: 'top:1px', 
               duration: 0.25,
               afterFinish: function(){
                    new Effect.Morph( ele, {
                         style: 'top:0px',
                         duration: 0.25
                    });
               }
     });
}
 
/* the first morph: */
new Effect.Morph('h1_parent', { style: 'padding-top:50px', delay: 0.5 });
wiggle( [ '.classOne', '.classTwo' ] );
 
/* the second morph, taking place after some user action: */
new Effect.Morph('h1_parent', { style: 'padding-top:10px', delay: 0.5 });
wiggle( [ '.classOne', '.classTwo' ] );
No comments | Trackback