initialising an onload from an external source

I was trying to break down a script I have which was approaching 20k in size. This involved breaking it down to seven separate scripts:

shoppingcart.js
shoppingcart_cartdisplay.js
shoppingcart_cartediting.js
shoppingcart_catdisplay.js
shoppingcart_catediting.js
shoppingcart_itemdisplay.js
shoppingcart_itemediting.js

In shoppingcart.js, the other scripts are loaded through a function:

function addJS(name){
 if(window.addEventListener){
  var el=newEl('script');el.src='/admin/j/'+name;
  document.getElementsByTagName('head')[0].appendChild(el);
 }else document.write('');
}

addJS('shoppingcart_cartdisplay.js');

in the above, newEl() is just a shortcut to document.createElement()

The problem is in the following code:

if(window.attachEvent)window.attachEvent('onload',shoppingcart_init);
else if(window.addEventListener)window.addEventListener('load',shoppingcart_init,false);

shoppingcart_init() is defined in shoppingcart_cartdisplay.js.

IE runs the above fine, but Firefox has trouble with it. It looks like that Firefox replaces shoppingcart_init with a pointer to the actual function. This only seems to work if the function has either been defined already, or is in the local file (when you set something equal to something else, the something else must exist so its value can be read – this is different in IE, it seems).

The solution is to create a pseudofunction which will then call the initialisation function. The internal code of the function will not be parsed until it is called, so it doesn’t matter that the initialisation function does not exist yet.

if(window.attachEvent)window.attachEvent('onload',shoppingcart_init);
else if(window.addEventListener)window.addEventListener('load',function(){shoppingcart_init();},false);

how the border-radius hack works

Okay; very late on Sunday, I posted about a curved borders hack to end all (or most) curved border hacks. I was a bit bushed after all the work, so was too tired to explain the hack.

First, a short description of it, for all you people that didn’t read the post:

This hack is a javascript & php script which you simply link to in your <head> section as you would any other external javascript. From then on, you write plain CSS. If you want to give an element a curved corner, then simply specify it in the CSS!

p{
 background:#666;
 padding:10px;
 border:2px solid #333;
 border-radius:30px;
 border-top-left-radius:0;
}

The above would render such as this:

example image

This will work in practically every browser. Don’t believe me? Here’s the test page, and here’s the CSS for that page.

As for native ability – as far as I know, there is only one single browser that can do curved borders.

That browser is, of course, Firefox; the greatest browser in the world.

Unfortunately, not even that great god of a browser gets it all right:

example image

In the example image (which is rendered using Firefox’s native rendering engine – try for your self (Mozilla browsers only)), you can see that the background image is leaking.

I’m currently compiling a build of Mozilla to see if I can get working on a fix for that (unlikely, as my C++ is woeful, but I will try…).

Anyway – look in the example with your own browser and you will see that your own browser can now display it properly.

So, how was it done?

It was a three-step process:

  1. get the css and parse it.
  2. for each element which has a border-radius, remove the border from the element, correcting the size of the element by adding the missing border widths to the elements, and giving the element a custom background image which fakes the border.
  3. create the image

It’s surprising (and there’s probably a universal law that explains it), but the shorter a problem’s description, the more difficult it usually is. Of the three items above, the easiest was the second.

Grabbing the CSS

I spent a week researching how to grab CSS straight from the source before the browser parses it. I gave up after a good read through the W3C’s DOM specs turned up nothing.

Suddenly, the answer was clear: XMLHttpRequest! Why not just grab the actual source, instead of trying to access it through some non-existant DOM interface.

Actually, once I thought of the solution, it was pretty simple… it’s just that I spent so long trying to figure it out that I was damned frustrated.

Parsing the CSS and fixing the elements

Nyeh – read the source and figure it out for yourself 🙂 It’s pretty self-explanatory.

Dean Edwards, as usually, was a life saver. His cssQuery code made it a cinch to grab arrays of each affected element.

The background image

I’m not really a graphical coder, so writing the background painter involved a bit of thought for me.

Basically, the solution, though, was pretty simple again, after I’d worked it out.

You need to paint two images – the first one is a mask, and the second is the border. The mask is for figuring out what should be transparent and what should not.

First, you build the mask. This involves painting a rectangle in a certain colour (eg; black), and then painting the border in another color (white). If the element is supposed to have a background image or colour, then you need to fill in the space between the borders.

Then, you paint the real image. This involves first painting a rectangle in the element’s background colour (if specified), then tiling the background (again if specified).

Once the two images are done, you just go through the mask, a pixel at a time, and whenever a “transparent” indicator (black, in this example) is found, the corresponding pixel in the real image is painted “clear”.

Conclusion

As far as I know, this is a pretty foolproof method – if JavaScript is not enabled, then the browser simply relies on its own rendering capabilities.

There are improvements that could be made – the script has not been tested with multiple colours, and I haven’t coded any border styles (dashed, dotted, etc) into it.

If there is interest in the script, then I may improve on it.

Rounded borders for IE, Konqueror and Firefox

In short: this is a method for applying rounded borders with one single line of CSS. No messy tables, no messy :before{content()} stuff – just plain old border-radius.

I had an idea last week – a border can easily be faked by giving an element a background which has a border painted into it…

This set off a chain of thoughts, which basically ended with this:

if all existing browsers have trouble rendering rounded borders (yes, even firefox), then why not just remove them completely from the CSS and render them as a background image in PHP?

In other words, scan the CSS, grab any CSS relating to rounded borders, and render the borders as a background image.

This thought was electric! Why hadn’t I thought of it before? Why hadn’t anyone else thought of it?

Before you get bored, here’s the example. Note that the source brings in two external items – the CSS (which is very plain), and the script for rendering it.

Here’s the source. Just link it into your page and then add some border-radius magic to your CSS. It should work straight out.

The script grabs external CSS using XMLHttpRequest – there does not seem to be an ECMAScript method to extract un-parsed CSS (and I’ve scoured the DOM standards documentation!).

A major advantage this method has over previous methods is that you can have a background image, which is clipped properly by the server-side script. Other methods involve adding images to the corners, which, if background images are involved, mean very careful planning to make sure the corners don’t clash.

installing a wg511v2 in gentoo

I lost my system drive during the week so thought I’d take the time to install gentoo, as I tend to prefer installing things from source, and… …and… you know – I’m not going to attempt to justify it – I just felt like it.

So anyway – everything installed fine, even if it took forever to install the system in the first place. Everything, that is, except the Netgear WG511v2 that I use for my wireless network.

As mentioned in an earlier post, where I installed the card in Fedora, the version I’m using is the Chinese one, with the Marvell chipset.

Check the version of ndiswrapper that Gentoo will install:

emerge -p ndiswrapper

If the version is greater than, or equal to 1.1 (as of today, the version installed by portage is 0.12-r3 – not good enough), then do this:

emerge ndiswrapper

Otherwise, you’ll have to go to the ndiswrapper site, download the newset version, unzip it, enter the directory, then sudo -c "make && make install" to install it.

If you go ahead with the emerge anyway (yeah – ignore me… I’m just the person trying to give help here 🙂 ), then you will get weird errors like “unknown symbol ntoskernel” in the log when you try to install the module.

So, ndiswrapper is installed; time to install the card driver. Type the following into the console.

wget http://opencurve.org/~sunny/linux/files/WG511v2.tar.gz
tar xzf WG511v2.tar.gz
cd "Windows XP"
sudo -c "ndiswrapper -i WG511v2.INF && ndiswrapper -l"

The last line installs the driver, and then displays all installed ndiswrapper drivers:

wg511v2 driver present, hardware present

If your console doesn’t say “hardware present”, then plug your card in and type sudo -c "ndiswrapper -l" to check again. If it fails again, or the wg511v2 bit doesn’t appear at all, then go join the ndiswrapper mailinglist and help out.

So now, type su to gain root rights (too many commands to bother with su -c), and type the following:

modprobe ndiswrapper
iwconfig

If your wlan0 interface rears its pretty little face, then all is well. If not, see above about the mailing list.

iwlist wlan0 scan

this gives me the following output:

wlan0     Scan completed :
          Cell 01 - Address: 00:0F:B5:0F:D0:91
                    ESSID:"NETGEAR"
                    Protocol:IEEE 802.11b
                    Mode:Managed
                    Frequency:2.462 GHz (Channel 11)
                    Quality:0/100  Signal level:-41 dBm  Noise level:-256 dBm
                    Encryption key:off
                    Bit Rate:1 Mb/s
                    Bit Rate:2 Mb/s
                    Bit Rate:5.5 Mb/s
                    Bit Rate:11 Mb/s
                    Bit Rate:6 Mb/s
                    Bit Rate:9 Mb/s
                    Bit Rate:12 Mb/s
                    Bit Rate:18 Mb/s
                    Bit Rate:24 Mb/s
                    Bit Rate:36 Mb/s
                    Bit Rate:48 Mb/s
                    Bit Rate:54 Mb/s
                    Extra:bcn_int=100
                    Extra:atim=0

That gives us info such as the AP‘s ESSID, the channel it broadcasts on, and its MAC address (the address that looks like an IPv6 number).

IMPORTANT My system had a kernel panic a moments after the following step. Make sure to save whatever work you’re doing before proceeding. The panic only happened once, so it may be related to something different (I have a naughty habit of massively multi-tasking).

iwconfig wlan0 channel 11 essid "NETGEAR" ap "00:0f:b5:0f:d0:91"

We’re almost there. Now you need to bring the card up. Change the numbers below as appropriate:

ifconfig wlan0 192.168.1.106
ifconfig wlan0 up
route add default gw 192.168.1.254 dev wlan0

I’m not sure what the Gentoo equivalent of rc.local is yet, but when I find it, the two code blocks above will be going into it, so the network interface is brought up immediately upon boot. (I don’t know how to do it any other way 🙂 )

tracking external links with AJAX

There was a bit of chat on the #linux chatroom about how Donncha’s linktracker used a method which could be misread as an attempt to spam google.

So, we had a bit of a brainstorm, and Stewie suggested that I try using XMLHTTPRequest to track the link before it is followed.

…which I did.

On the example page, I have three links – two external, and one internal. I guessed that it’s silly to record internal links, when your web logs already do that, so I deliberately told the script to ignore internal links.

The script automatically searches the document for links, and attaches an ‘onclick’ event which calls the link tracker before the link is followed.

Donncha says he’ll be using it in his WPMU WordPress enhancement. I’ll be writing it into a plugin probably tonight, if someone else doesn’t do it first 🙂

update 2005-09-23: noticed this fantastic example of how to avoid all the AJAX malarkey to do this trick. I’m surprised I didn’t think of it myself! I’m blinded by science, I suppose – applying new tricks to everything, when a simple gimmick will do the trick even better. Well done, Martin!

collapsible menus updated

Through the encouragement of “Helen” (full name, location, website etc – unknown), I’ve been prodded into reviving an old chestnut of mine called the Collapsible Menu.

The idea of this script was that it takes menus that you create in the form of unordered lists, then it dynamically converts them into a collapsible menu.

For instance, if you have the following menu:

  <ul>
   <li><a href="/">home</a>
    <ul>
     <li><a href="/aboutme">about me</a></li>
     <li><a href="/contact">contact me</a></li>
    </ul>
   </li>
   <li><a href="/gallery">photo gallery</a></li>
  </ul>

You can add in the script:

  <script type="text/javascript" src="cm.js"></script>

And, with no extra work, you have a collapsible menu (example).

Yes yes, I know its not new – there are many people that do that these days. But back in the day, I believe I was one of the first to do it.

Anyway – the request I had from Helen was to change the script so it was possible to remove the [+] links and instead use the entire parent link as the opener/closer.

After much tardiness on my part (been busy and sick, in turns), I’ve done that requested work.

To use the parent link as the opener/closer, you need to override a variable. After the tag which includes the JavaScript, place this line:

 <script type="text/javascript">parentLinkIsOpener=1;</script>

An example of that in action can be seen here.

That’s about it really. But, before I submit this article, here’s a nice goody – A while back, Nathan Young took the script and made a bookmarklet out of it. Try it out! Bookmark the ul collapser, go to this page, then click on the bookmark in your bookmarks list. Violin!

getElementById bug in IE6

This one had me scratching my head for a few minutes.

Look at the code below, and tell me what’s wrong with it.

 <div id='testdiv'>test</div>
 <script type='text/javascript'>
  testdiv=document.getElementById('testdiv');
  testdiv.appendChild(document.createTextNode('. all is well'));
 </script>

There is nothing wrong with the above, in sane browsers, but IE is not known for its sanity.

Trying the above in IE will result in an “object does not support this method” error.

The reason for this seems to be that IE creates a global variable for each id that it comes across. In this case, that means that testdiv in the JavaScript above is predefined as the <div> in the HTML section.

IE’s error message system is hopelessly useless. The error applies to the ‘=‘ in the line, and not the getElementById, which is what I was scratching my head about.

So what’s the solution? There are two workarounds:

  • use a unique variable name.
  • pay attention to scope.

The unique variable name approach is not ideal, as it may make less readable in your code than you’d like, so the solution is to tell the browser that the variable name you’re using applies just to that local scope.

 <div id='testdiv'>test</div>
 <script type='text/javascript'>
  var testdiv=document.getElementById('testdiv');
  testdiv.appendChild(document.createTextNode('. all is well'));
 </script>

The above should work. Surprising, how simple a workaround can be…

news.google.ie improved

I learn what’s going on outside the computer world by reading google’s news site. This morning, I found that they’ve improved the interface for the page.

You can now rearrange the news categories how you want, increase/decrease the number of articles reported per category, and even remove categories you find completely boring. For example, I no longer have to hear about how some crappy tennis or football “star” is doing in some tournament somewhere.

Now, they just need to find some way if providing localised news… There is a “US” section, but every other country is lumped under “World”.

It would be nice if they could spell “customised” right as well, but I’m not greedy…

multiselect updates

Thanks to all the people who have tested and used the multiselect before. I’ve taken the improvements, suggestions, and bug-fixes and am now happy with a new version of it.

Multiselect takes a normal <select multiple="multiple"> and converts it dynamically into a more user-friendly interface.

An example of the new and improved version can be found here.

Points of note

  • you can now “select all” and “select none”
  • a new “reset” link brings the select box back to how it was originally
  • the script works even if you have more than one multiselect box

caveats: the name of the <select> must end in ‘[]‘. While this is specifically against W3C rules, there does not seem to be any alternative method to pass on multiple values for one variable.

To use, simple download the script and link to it in your webpage.

This script should work in all major browsers. If it doesn’t work in the one you’re testing in, then please alert me.