March 21st, 2008
There was an interesting post the other day which criticized Safari's choice to completely overhaul their event system and, in the process, seemingly cripple the keypress event for non-character keys.
At first glance to me, and to many others, it appeared as if a dramatic flaw had been introduced into Safari's event system - completely castrating its practical use. It was at this point that I contacted Yehuda Katz (Merb and jQuery contributor) - he knows more about the inanity of cross-browser keyboard events than anyone I know. Here's his responses to a few questions that I bounced off him:
John: At first glance through WebKit's changes it appears as if they've significantly crippled the keypress event, is this the case?
Yehuda: People should not have been using the keypress event to get the character that was pressed. That's because the keydown event only provides information about the actual key that was pressed (the A key, the right arrow, etc.), but does not tell the user what character will get added to (for instance) an input box.
Since arrow keys do not get added as text, keydown provides all the information that is needed. There were a couple of quirks with using keydown in certain cases previously which are resolved by their changes that prevent keypress from getting called if keydown's default handling is blocked.
What this means is that if people were using keypress before (and relying on Safari's native results for arrow keys, such as 63232), their code will break. However, this was a bad idea all along; people should use keydown to detect and block non-character keys. My mantra has always been: keydown/keyup if you want to know the key that was pressed; keypress if you want to know what text was detected.
John: Being able to access non-character keys must be useful - granted Safari gave strange results previously - but at least some value is better than none, right?
Yehuda: The keypress event does not provide any additional benefits over keydown for these non-character keys. Keep in mind that IE does not fire the keypress event for some non-character keys (e.g. delete, end, enter, escape, fkeys, etc.).
Because Safari appears to have fully resolved issues with suppressing default events from keydown, there should be no problem with using keydown for non-character keys.
John: So we have charCode, keyIdentifier, and keyCode along with keyup, keydown, and keypress - is there a solution that we can safely use in a cross-browser manner?
Yehuda: There's a reasonably good writeup at: http://unixpapa.com/js/key.html.
Effectively, you can use e.keyCode for all modern browsers in keydown/keyup (although you'll need to consult the chart at the bottom of that page to determine whether the keyCodes returned by the browsers are consistent).
For keypress, e.charCode || e.keyCode should work (Mozilla and Safari define e.charCode, while IE defines e.keyCode which returns the ASCII value). What that means is that you have a close-to-cross-browser keyCode for keyup/keydown and a pretty reasonable charCode for keypress.
As a result, keypress can be used for key events that produce text for which you need to know its ASCII value (A vs. a). keyup/keydown can be used for events for which you care about the physical key that was pressed. Again, there are some quirks, which can be summed up in the tables at http://unixpapa.com/js/key.html.
I'd like to thank Yehuda for taking the time to clear these points up. Effectively if you were doing key-related development, previously, in a non-cross-browser manner then you hit a wall with this recent change. If nothing else this should encourage you to keep a good separation, as Yehuda notes: "keydown/keyup if you want to know the key that was pressed; keypress if you want to know what text was detected."
Tags: safari, javascript, dom, events, webkit
9 Comments on 'Keypress in Safari 3.1'
July 18th, 2007
It is with much happiness that I think I can finally say, without seeming like a fool, that: "JavaScript Getters and Setters are now prevalent enough to become of actual interest to JavaScript developers." Wow, I've been waiting a long time to be able to say that.
I want to start by giving a whirlwind tour of Getters and Setters and why they're useful. Followed by a look into what platforms now support Getters and Setters as to make them relevant.
Getters and Setters
Getters and Setters allow you to build useful shortcuts for accessing and mutating data within an object. Generally, this can be seen as an alternative to having two functions with an object that are used to get and set a value, like so:
{
getValue: function(){
return this._value;
},
setValue: function(val){
this._value = val;
}
}
The obvious advantage to writing JavaScript in this manner is that you can use it obscure values that you don't want the user to directly access. A final result looking something like the following (using a closure to store the value of a newly constructed Field):
function Field(val){
var value = val;
this.getValue = function(){
return value;
};
this.setValue = function(val){
value = val;
};
}
Some example results:
var field = new Field("test");
field.value
// => undefined
field.setValue("test2")
field.getValue()
// => "test2"
Now, centered around this concept, is where getters and setters come into play. They allow you to bind special functions to an object that look like normal object properties, but actually execute hidden functions instead. The end result looks something like this:
var field = new Field("test");
field.value
// => test
field.value = "test2";
field.value
// => "test2"
So let's look at how you would go about setting something like that up. Mimicking the "hidden value property" style of before, our code would look something like this:
function Field(val){
var value = val;
this.__defineGetter__("value", function(){
return value;
});
this.__defineSetter__("value", function(val){
value = val;
});
}
Now, if we wanted to, instead, define getters and setters within the context of our object prototype (and where having "private" data is less of a concern) we can then use an alternative object syntax for that.
function Field
(val
){
this.
value = val;
}
Field.prototype = {
get value(){
return this._value;
},
set value(val){
this._value = val;
}
};
The syntax for getters and setters is typically what scare people the most about the feature. But after a little bit of use, it's easy to get over.
Here's another example, allowing a user to access an array of usernames (but denying them access to the original, underlying user objects.
function Site(users){
this.__defineGetter__("users", function(){
// JS 1.6 Array map()
return users.map(function(user){
return user.name;
});
};
}
As a bonus, here's a method that I've written that can help you to extend one object with another (a common JavaScript operation) while still taking into account getters and setters:
// Helper method for extending one object with another
function extend(a,b) {
for ( var i in b ) {
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
if ( g || s ) {
if ( g )
a.__defineGetter__(i, g);
if ( s )
a.__defineSetter__(i, s);
} else
a[i] = b[i];
}
return a;
}
This code is from the Server-Side Browser Environment that I wrote about a week ago. You'll notice that, when looking through the code that I make liberal use of getters and setters to create this mock environment.
Additionally, in my custom extend() method you'll notice two new methods: __lookupGetter__ and __lookupSetter__. These are immensely useful, once you start dealing with getters and setters.
For example, when I did my first pass at writing an extend() method, I started getting all sorts of errors - I was thoroughly confused. That's when I realized that two things were happening with the simple statement: a[i] = b[i];
If a setter existed in object a, named i, and a getter existed in object b, named i, a[i]'s value was being set not to the other setter function, but to the computed value from b's getter function. The two __lookup*__ methods allow you to access the original functions used for the methods (thus allowing you to write an effective extend method, for example).
A couple things to remember:
- You can only have one getter or setter per name, on an object. (So you can have both one value getter and one value setter, but not two 'value' getters.)
- The only way to delete a getter or setter is to do: 'delete object[name];' Be aware, that this command is capable of deleting normal properties, getters and setters. (So if all you want to do is remove a setter you need to backup the getter and restore it afterwards.)
- If you use __defineGetter__ or __defineSetter__ it will silently overwrite any previous named getter or setter - or even property - of the same name.
Platforms
Now, here is where things get really interesting. Up until just recently, talking about actually using getters and setters (outside of the context of Firefox/Gecko) was pretty much not happening.
However, look at this line up:
Browsers
- Firefox
- Safari 3+ (Brand New)
- Opera 9.5 (Coming Soon)
I used the following snippet to test browsers:
javascript:foo={get test(){ return "foo"; }};alert(foo.test);
Additionally, the following JavaScript environments support Getters and Setters
And as do the following ECMAScript implementations
- ActionScript
- JScript.NET 8.0
Tags: programming, opera, safari, ecmascript, firefox, mozilla, javascript
7 Comments on 'JavaScript Getters and Setters'
May 3rd, 2005
A good new feature in Safari 2.0 is the RSS reader, which I'm sure everyone has heard about by now. However, Apple feels compelled to change all feed URLs from their original http: to a new feed:, making it annoying to use that address elsewhere. The one area that is particularly frustrating is trying to subscribe to a web site using a bookmarklet (such as one provided by most of the major news aggregator services). So, in a nutshell, here are a bunch of modified bookmarklets that will work in Safari, handling the wonky feed URLs easily.
Drag the following link(s) your bookmark bar:
Note: The above links will still work in Firefox, IE, Opera, et. al.
Tags: apple, osx, safari, rss, feed, blogs, bookmarklet
1 Comment on 'Feed Links in Safari'
April 28th, 2005
When using the new Safari RSS last night, I was surpised to see that it detected the embedded FOAF data within the feed (My LJ Feed). I think this is the first instance of a corporation releasing a product that utitlizes FOAF (as far as I know). I haven't seen this mentioned anywhere, as of yet, so here it is.
Update: Ok, so I've done some more research into it and it's much more 'interesting' then I previously suspected. I thought that there was a link (of some sort) pointing to the FOAF file within the RSS/ATOM feed itself. There isn't The only external link (besides what is present in each of the items) is the one to that of the channel livejournal.com/users/jeresig/ - it is on that page that a link to my FOAF file exists. So, it appears that Safari is:
1) Requesting the RSS feed.
2) Requesting the channel link.
3) Requesting the FOAF file from the channel link.
4) Placing the friends data parsed from the foaf file into the sidebar of the viewer.
And now that I look at it, it does take a few seconds extra for the FOAF data to load completely, and this is probably why.
And once you expand the list of usernames, there is no way to collapse them again, kind of odd.
On another note, poking through the HTML of this RSS page: Safari supports both the 'search' and 'range' input types! So you can now add in a slider to your (Safari-only) page, if you so desired. This is good to know.
Tags: foaf, safari, rss
7 Comments on 'FOAF Support in Safari RSS'