matt good

musing coding

Entries tagged "Web"

Simple JavaScript Namespaces

written by Matt, on May 31, 2009 12:52:00 PM.

“Namespaces are one honking great idea — let's do more of those!” - Tim Peters, “The Zen of Python”

After a few years of mostly server-side development I'm getting back into JavaScript, and for the first time I feel like I'm approaching it as an actual engineering challenge rather than a bunch of quick hacks. As our JS code base is growing I'm breaking things up into namespaces to keep things manageable.

Though JavaScript has no built-in notion of "modules" like many other languages you can use objects to create namespaces, which in their simplest form would look something like:

util = {
  my_func: function() {
    alert('my_func');
  }
};
util.my_func();

However, creating nested namespaces like "myapp.util.text" gets a bit ugly with that approach. I liked the basic API of Namespace.js, though I didn't want any of the additional features like remote loading, so I figured I could make something much simpler and smaller.

You can first "declare" a namespace and then assign attributes like:

Namespace('myapp.ui');
myapp.ui.ToggleButton = function() {
  // ...
};

Or pass in the attributes to include in that namespace:

Namespace('myapp.util.text', {
  some_func: function() {
  }
});

Here's the code:

function Namespace(name, attributes) {
  var parts = name.split('.'),
      ns = window,
      i = 0;
  // find the deepest part of the namespace
  // that is already defined
  for(; i < parts.length && parts[i] in ns; i++)
    ns = ns[parts[i]];
  // initialize any remaining parts of the namespace
  for(; i < parts.length; i++)
    ns = ns[parts[i]] = {};
  // copy the attributes into the namespace
  for (var attr in attributes)
    ns[attr] = attributes[attr];
}

This is of course much smaller than the 16kb of Namespace.js, so as an additional challenge I decided see if I could compact it to fit the 140 character limit of a Twitter status message :) (Note: the Twitter version used jQuery's $.extend function to copy the attributes into the namespace which I later replaced with the framework-independent version above.)

I'll follow up later with some tricks I've been thinking about to "import" stuff from other namespaces.

Safari 3

written by Matt, on Aug 28, 2007 6:55:17 AM.

I think I've tried just about every web browser available for the Mac. The included Safari 2 didn't feel quite right, Firefox was too slow and a memory hog, Flock, Shiira and some others just didn't really fit what I was looking for. Camino was nice and light, and better integrated with the Mac than Firefox, but I was missing a good search box and it felt a little too limited in features. Now I'm using the Safari 3 beta and couldn't be happier. There are lots of little things that just feel "right" and it fits with the Mac OS nicely. After downloading an application you are warned that it contains an app, and if you approve the DMG file is mounted so you can run or install the app. But, the one thing that seems simple, but I've felt missing in browsers for a while is the tab management. On Linux I used Epiphany which has supported rearranging tabs for a while, and Firefox now supports it, but Safari gets one more thing right: dragging tabs between windows. So far I haven't seen another browser do this, but in Safari you can drag a tab up or down to detach it from the current window and either drag it to a different window, or out into a new window. It's not a feature I use constantly, but I do really like being able to keep my tabs organized in a logical manner instead of having a random assortment of tabs all open in the same window. Thanks Apple for getting this right.

Trac Nominated for Linux User Awards

written by Matt, on Oct 16, 2006 12:56:03 PM.

Trac has been nominated in the Linux User Awards for "Best Linux/OSS Developer Tool". Launchpad and Mono are the other nominations in this category, so we're among some pretty big competition. The Trac community has grown tremendously in the past year, so it's nice to see that it's so highly regarded.

Disabling HTML <option>s

written by Matt, on May 16, 2006 8:30:54 PM.

If browsers worked as they should it would be pretty simple to disable some of the options in a select list:

<select>
    <option>Enabled</option>
    <option disabled>Disabled</option>
</select>

Of course IE doesn't support this part of the spec, and it's not included in the IE 7 beta, so it's unlikely it will be supported in the near future. So, one option that makes IE behave similarly to the disabled options is to replace them with <optgroup> tags. The <optgroup>s cannot be selected and with a little CSS they look like disabled options. However, the static solution suggested didn't work for me since I needed to be able to disable or enable options at runtime. So, with a little JavaScript (and the help of MochiKit's DOM functions) the options can be enabled or disabled dynamically:

var selectStyleDisabled = function(select) {
    forEach(select.options, function(opt) {
        if (opt.disabled) {
            var optgroup = OPTGROUP();
            updateNodeAttributes(optgroup, {
                label: opt.innerHTML,
                style: { color: 'graytext' }});
            optgroup._option = opt;
            swapDOM(opt, optgroup);
        }
    });
    forEach(select.getElementsByTagName("OPTGROUP"), function(optgroup) {
        var option = optgroup._option;
        if (option && !option.disabled) {
            swapDOM(optgroup, option);
        }
    });
};

addLoadEvent(function() {
    var selects = currentDocument().getElementsByTagName("SELECT");
    forEach(selects, function(select) {
        select._onfocus = select.onfocus;
        select.onfocus = function() {
            selectStyleDisabled(this);
            if (this._onfocus)
                return this._onfocus();
            return true;
        }
        selectStyleDisabled(select);
    });
});

The display of disabled options is updated in the "onfocus" method to make sure that changes in the disabled state of the options is reflected before the <select> is displayed again. Disabling options can be done directly to any of the option elements such as:

select.options[0].disabled = true;

However, a little care needs taken to re-enable options, since the <optgroup>s aren't in the "options" attribute of the <select>. The original option needs reenabled by accessing it through the "_option" attribute added to the <optgroup> element:

// reenable all disabled options in standards-compliant browsers
forEach(select.options, function(option) {
    option.disabled = false;
});

// reenable all disabled options in IE
forEach(select.getElementsByTagName("OPTGROUP"), function(optgroup) {
    optgroup._option.disabled = false;
});

To make sure that this IE hack only gets loaded in IE, put it in a .js file and wrap it in an IE conditional comment:

<!--if lt IE 8]>
<script type="text/javascript" src="select-ie-disabled-options.js"></script>
<![endif]-->

Fun with Evolution Filters

written by Matt, on Jan 3, 2006 7:49:09 PM.

I've fixed up a couple of things in Evolution that have been irritating me for a while using filters to pipe the messages through external apps.

New message notification

I've wanted to have popup notifications when I get new messages, so I wrote a little Python script that parses the message and displays a summary using Galago:

http://matt-good.net/files/post-related/mail-notify.png

Installation

  • Download Ka-Ping's scrape.py library to somewhere on your PYTHONPATH (usually /usr/lib/python2.4/site-packages; change the version number if necessary)
  • Download mail-notify.py and make it executable (chmod a+x mail-notify.py)
  • Add your Evolution filter (use Browse to pick the file if it's not on your PATH):
http://matt-good.net/files/post-related/mail-notify-filter.png

Working spam filtering

Spam filtering in Evolution used to work, but in recent versions I have not seen it catch a single spam. At first it seemed like this was due to not having SpamAssassin installed. After installing that it starts the spamd process and thinks for a little bit when I try to mark messages as junk, but still nothing gets caught.

So, I gave up on the built-in filtering and set up an Evolution filter using Bogofilter:

http://matt-good.net/files/post-related/bogo-filter.png

Unfortunately Evolution doesn't provide a way to just specify a program to run when you click the Junk and Not Junk buttons, so I'm using Spam Trainer to train Bogofilter. You can drag messages from Evolution into Spam Trainer to mark them as "spam" or "ham".

Set your Spam Trainer preferences to use the Bogofilter program:

http://matt-good.net/files/post-related/bogo-prefs.png

Unclogging RSS Bandwidth

written by Matt, on May 1, 2004 2:33:00 PM.

Wired's article Will RSS Readers Clog the Web? starts off with some interesting points about the bandwidth problems with aggregators, but I don't really agree with some of their conclusions.

Like stated in the article, it's definitely important for aggregators to support time-stamp checking to make sure they don't keep grabbing the same feed over and over. Of course the flip-side is it's also important for servers to be able to provide this information. Pyblosxom (which powers this blog) doesn't seem to have this ability.

One of my problems with the article is that they don't mention that grabbing an RSS feed is most likely going to consume less bandwidth than loading the page in a browser.

Whereas a human reader may scan headlines on The New York Times website once a day, aggregators check the site hourly or even more frequently.

While this may be true for some people, I imagine that these are not the people who are using aggregators. Before using a feed reader I would check sites like Slashdot repeatedly during the day for updates. Using an aggregator I can check the site less frequently now since I only need to visit it when I see an article I'm interested in.

I decided to do a little math to figure out how the bandwidth compares between loading the RSS and the actual page. Slashdot's RSS feed is weighing in at 1.9K. Now this didn't really seem fair since it's only the titles. Adding in the story bodies shown on the front page brings it up to 9.1K. Updating once an hour gives us 218.4K per day.

So how does that compare to reading the front page. Well, to be conservative I'll assume that your browser has cached all the images and is only loading the HTML, which comes to ... 63K! So, if I read Slashdot's front page 4 or more times a day I'm using more bandwidth than if I was using an RSS feed that contained the article content. (To be fair Slashdot could really benefit from a CSS-based overhaul, but that's for a different article)

Ok, so I'm just going to assume that there are sites out there where RSS is eating up their bandwidth. Wired (or at least some of the people they talked to) seem to think P2P would help alleviate these problems. I'm not so sure I agree with this solution. While protocols like BitTorrent can reduce the strain of file distribution on a server, they're really best at distributing large files. There is an overhead when distributing files via P2P with distributing the information about what peers are available for each requested file (and what portions of the file they have, etc.). This may not be too bad when you're distributing files that are a few megs or more, but with RSS we're only looking at maybe 10K. I haven't figured out the numbers, but it seems like it would be quite hard to make the overhead of P2P small enough to justify using it for RSS distribution.

Now, I don't understand why Wired has ignored the RSS Cloud specification. This allows an aggregator to subscribe to a feed only once a day. Then, the server sends a notification to the aggregator when new content is published (thus publish/subscribe). This would allow aggregators to only refresh the content when it had been updated, eliminating the redundant updates. I'd also like to see this implemented more since it would also give more timely notification of updates.