A JavaScript, CSS, and XHTML Resource By Matt Snider

Understanding JavaScript and Frameworks

Monday, June 29, 2009

CSS - String Truncation with Ellipsis

Today’s article is brought to us by guest writer, Justin Maxwell. Justin will explain the technique he fine tuned for Mint.com to ellipses text using just CSS. For more information about Justin see the end of this article.

In contemporary web application interfaces, areas for single-line, user-defined labels (strings) are common. For example, Mint.com allows customers to edit transaction descriptions and account names. Other popular web applications allow customization of photo titles, folder names, sections, and much more. A designer’s primary objective should be providing appropriate, optimal space for the expected content. The secondary objective should be elegantly handling the longer strings.

Truncation on the server-side is unnecessary, complicated, and usually confusing to the user. Luckily, CSS offers a combination of properties to truncate strings and add ellipsis (…) on the client-/ side, without JavaScript.

Example 1. Starting with the text

With no width restrictions, and no ellipsis, this paragraph (<p> tag) displays as one would expect…a big block of text. We’ve given it a some visual treatment to distinguish it as an interface element in these examples:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

But that’s not what we want. We’re looking for a single line, the sizing of which we can control in our interface.

Example 2. Prevent line wrap and bring on the ellipsis!

We’ll start by adding a class “.ellipsis” to the p tag and building it by example. Add white-space: nowrap, to limit the paragraph to a single line, and overflow: hidden keeps it from making the browser window wider. We then add width: 300px to limit the size (IE6 needs a width defined, even if it’s 100%) and begin constructing our label. Also, first recommended for CSS3 back in 2003[1], the CSS Text Module includes support for text-overflow: ellipsis. So putting that all together:

.ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } p.block { width: 300px; }

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

And if it were as simple as that, this article wouldn’t be necessary. While the text-overflow: ellipsis property is supported in Internet Explorers 6 & 7, Safari 3 & 4, Chrome 1 &2, it requires special handling for IE 8 Standards Mode, Opera, and FireFox. [2]

Example 3. text-overflow in Opera and IE 8

Opera’s developers recognize that the text-overflow property isn’t part of the official spec yet, and distinguishes it as a proprietary property through the use of the -o prefix. Microsoft introduced something similar in IE 8 with the -ms prefix. So let’s add that:

.ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -o-text-overflow: ellipsis; -ms-text-overflow: ellipsis; }

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Alright, alright, no more teasing. On to the goods. Let’s deal with the 400 lb. fox in the room.

Example 4. Making it work in FireFox

While implementation of the text-overflow property in FireFox is in development[3], it has been “in development” for a long time. But FireFox does support XUL, Mozilla’s XML User Interface Language[4]. While it may be the first time you’ve heard about it, many of the popular FireFox add-ons are written in, or rely on, XUL. XUL has a crop property for the description element, which specifies “An ellipsis will be used in place of the cropped text” [5].

And this is where Rikkert Koppes, a developer in The Netherlands, figured out how to put it all together[6]. Firefox’s support for XBL (XML Binding Language) allows us to “associate elements in a document with script, event handlers, CSS, and more complex content models, which can be stored in another document” [7]. So in this case, the other document is going to be our XUL description element with tail truncation (crop), which by default in XUL will have ellipsis, and the association will be through FireFox’s support of XBL bindings in CSS. First, we’ll create the XUL, which should be saved as ellipsis.xml:

<?xml version="1.0"?> <bindings xmlns="http://www.mozilla.org/xbl" xmlns:xbl="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" > <binding id="ellipsis"> <content> <xul:window> <xul:description crop="end" xbl:inherits="value=xbl:text"><children/></xul:description> </xul:window> </content> </binding> </bindings>

At Mint.com, we store this alongside our CSS directory in a folder named xml. You may wish to do the same. Then we tell our FireFox-only (-moz prefix) CSS to bind the .ellipsis class to the XUL:Description element’s described behavior, referenced by a unique ID (that binding id=”ellipsis”) in the XML code above:

.ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -o-text-overflow: ellipsis; -moz-binding: url('assets/xml/ellipsis.xml#ellipsis'); }

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Success! If you don’t see it working on your site, double-check your path to the XML document in the CSS declaration.

That’s it, and some notes

CSS-based truncation offers much more flexibility to the designers, less work for the developers, and better information display for the user/customer. While we’d like to see native support for text-overflow in FireFox, even with a -moz prefix if needed, the XUL binding allows us to simulate it effectively with minor accessibility drawbacks.

Some notes:

Text selection in FireFox

You can’t select text that has had the XUL behavior bound to it. The -moz-user-select: text; property should allow this, but I haven’t had success with it, even inside the XUL as a style attribute.

Nested content in FireFox

You can nest a child node inside the cropped parent node as long as you bind the XUL behavior to it as well. Don’t go deeper than one child within the truncated node, and don’t use mixed content:

This is ok. This is ok. This is ok. This is ok. This is ok.

This is also ok. This is also ok. This is also ok. This is also ok.

This is not. This is not. This is not. This is not.

In the third example above, the <em> node will not display in FireFox. For more info, see Gunnar’s comment on Koppes’ original post[7].

CSS3’s incomplete draft

The last update of the W3C Working Draft for CSS Text states that “many sections intended for this module are not yet represented in this draft. In particular, the ‘text-justify-trim’, ‘text-overflow‘, ‘text-decoration’, ‘text-transformation’, ‘text-autospace’, other properties have not yet been evaluated”[8] (emphasis added locally). So keep in mind you’re using a widely-supported but not officially recommended property; its future remains uncertain.

References:

  1. http://www.w3.org/TR/2003/CR-css3-text-20030514/
  2. http://www.quirksmode.org/css/contents.html
  3. https://bugzilla.mozilla.org/show_bug.cgi?id=312156
  4. https://developer.mozilla.org/En/XUL
  5. https://developer.mozilla.org/En/XUL/Description
  6. http://www.rikkertkoppes.com/thoughts/2008/6/
  7. http://www.w3.org/TR/xbl/
  8. http://www.w3.org/TR/css3-text/


Justin Maxwell is Mint.com’s “User Experience and Design Guru,” working alongside Matt to create rich interactions and a holistic application experience. Before Mint, he spent four years at Apple, working with the Apple.com web team and the Pro Applications Design team. He designed and developed Apple’s first public uses of AJAX, Apple’s first AJAX libraries, and designed the first Apple.com product sites with dynamic content, including iTunes, iPod, iLife, and Mac OS X. His home-page on the inter-tube can be found at http://code404.com.

posted by Matt Snider at 10:25 pm  

Saturday, June 27, 2009

Development - More Thoughts On Compression

Last weeks article, Development - Optimizing JavaScript Compression and the user comments, plus Nick Zakas’ document on how to improve YUI compression performance, Helping The YUI Compressor, left me thinking about other ways to improve the compression of YUI compressor. We learned last week that the compressor will obfuscate local functions, defined as variables, into variables with names that are only single characters. Extending that idea further I realized that if an object member function is repeatedly used in a file, then it too can be reduced. This thought led to a simple equation that determines when it is more efficient (compression-wise) to replace the use of a member function with a local variables.

Example 1: Compression Optimization Equation

L + C + (K * N) = L * N, where L > K and L = the length of the function name + 1 C = the length of code to create a shortcut variable K = the length of the replacement code, per instance of the function name N = the number of times the function is used before this technique improves compression N = (L + C) / (L - K)

Let’s walk through an example, in case that is hard to follow. A frequently used function is the native DOM function ‘getElementsByTagName’. This method is 20 characters long, and since it is always proceeded with a ‘.’ when used, add 1 (ex. “_domNode.getElementsByTagName”), so ‘L’ equals 21 (eg. each time ‘getElementsByTagName’ is normally used, 21 characters are required, even after compression). Now this can be replaced with a reusable variable, using brackets and a variable storing the string name of the function.

Example 2: Optimization Technique

var GEBTN = 'getElementsByTagName'; _domNode[GEBTN]('li'); _domNode[GEBTN]('span'); _domNode[GEBTN]('small');

Compression will reduce the ‘GEBTN’ variable name to a single character and remove whitespaces, so the cost of initializing ‘GEBTN’ will be ‘C’ = 29. If you are using the “Use fewer ‘var’” technique from Example 1 in last weeks article and can instantiate the ‘GEBTN’ by piggybacking an already existing ‘var’, then the cost of initialization drops by 3 (”var ” is replaced with “,”), ‘C’ = 26. Then each usage of the compressable technique to replace ‘getElementsByTagName’ will require 3 characters, ‘K’ = 3 (”[GEBTN]” when compressed will become something like “[A]“). Putting this into the equation and solving for ‘N’, we find that for ‘getElementsByTagName’ ‘N’ is equal to 2.6 = 47 / 18 = (21 + 26) / (21 - 3). Therefore, if you use ‘getElementsByTagName’ 3 or more times in a file, this compression technique will allow YUI compressor to reduce the file size even more.

The equation in Example 1 only holds true for functions with name that are larger than 3 characters, otherwise this technique will not help. You might wonder why “_domNode.getElementsByTagName” is not saved as a variable function, instead of just the name of the member function. The reason is because when the variable function executes, the execution context (’this’) will be ‘window’ instead of the desired context (in this case the desired execution context ‘_domNode’). If you are trying to eek every byte of compression out of your JavaScript, then this equation will be a handy and easy tool to evaluate which member function replacements make sense and which do not.

Finally, the response to last weeks article was great. I would appreciate hearing about any other good techniques to help compression. Please leave a comment if you know of one.

posted by Matt Snider at 11:26 pm  

Saturday, June 20, 2009

Development - Optimizing JavaScript Compression

The power of JavaScript libraries are increasing everyday, and many more (free) tools are available now than ever before. There are so many easy to use JavaScript addons, that an unwary developer can quickly degrade the performance of their site. To decrease the performance hit when adding additional JavaScript to a site, many developers use compression tools, such as YUI Compressor. At the very least, a compressor remove all white-spaces from your code, but a good one will also obfuscate variable names into shorter ones, which can help to reduce the size of your code even more. Today we’ll cover some techniques that can help to optimize compression.

Use fewer ‘var’

The ‘var’ keyword is used to bind a variable to a function context (or window, if not inside a function) and is not compressible. However, one ‘var’ can be used to instantiate many variables. For example:

// this is the standard way of defining several variables var test1 = 1; var test2 = {}; var test3 = function() { // function code }; // this will compress better var test1 = 1, test2 = {}, test3 = function() { // function code };

Although, some compressors are attempting to remove unneeded ‘var’s, it is simple to just do it yourself. At least 4 bytes can saved per ‘var’ that is removed.

Use Local Pointers For Large Namespaces

You will see this technique throughout the YUI Library, and in much of the code that I write using YUI. When a large namespace, object, or function (more than 4 characters) is used more than twice inside inside a function context, create a local variable, such as setting “YAHOO.util.Event” to ‘YE’ (”var YE = YAHOO.util.Event”). This makes the local code easier to read, as well as allowing the compression obfuscator to rename those local variables to a single character (saving 15 bytes each time the local pointer is used).

// uncompressable way to use YAHOO.util.Event (function() { YAHOO.util.Event.on('elem1', 'click' function() {/* function code */}); YAHOO.util.Event.on('elem2', 'click' function() {/* function code */}); YAHOO.util.Event.on('elem3', 'click' function() {/* function code */}); }()); // compressable way to use YAHOO.util.Event (function() { var YE = YAHOO.util.Event; YE.on('elem1', 'click' function() {/* function code */}); YE.on('elem2', 'click' function() {/* function code */}); YE.on('elem3', 'click' function() {/* function code */}); }());

If you are in control of all your JavaScript, you may want to create global shortcuts to namespaces or functions that are used frequently.

Concatenate Files Before Compression

The compressor can only obfuscate global variables within a given file, not across all files. If you use global variables, then the compression will be better after concatenating all the files together. In this way the obfuscator can rename variables to shorter ones across all the JavaScript code.

Use Variable Names to Namespace Instead of Objects

Unfortunately, most obfuscators cannot reduce the names of properties added to an object (this is a really hard problem to solve). For this reason, compression will be better when not storing values in parameters on an object. I am often guilty of this, using objects such as ‘_D’ to store all DOM pointers and ‘_E’ to store all event callbacks for function context. In the future I will be prefixing DOM pointers with ‘_dom’ and event callback functions with ‘_evt’. This will make the code more verbose, when uncompressed, but each variable will be compressible to a single character.

// using objects to organize variables (function() { var $ = YAHOO.util.Dom.get; var _D = { test1: $('test1'), test2: $('test2') }; var _E = { callback1: function() {/* function code */}, callback2: function() {/* function code */} }; }()); // using variable names to orgnaize (function() { var $ = YAHOO.util.Dom.get; var _domTest1 = $('test1'), _domTest2 = $('test2'), _evtCallback1 = function() {/* function code */}, _evtCallback2 = function() {/* function code */}; }());

This can be the most tedious optimization, and may go against your coding conventions. Use this technique only if you need compress the JavaScript as small as possible.

Nick Zakas of YUI also recommends the following, Helping The YUI Compressor.

posted by Matt Snider at 4:28 pm  

Sunday, June 14, 2009

Architecture - Pattern - Augmented Configuration

When a JavaScript object can be configured in a variety of ways, it is best-practice to include a configuration object in the constructor, where all configuration values are optional. This way, the developer can configure an object when it is instantiated, without having to provide parameters that are common to most instances of an object.

Example 1: Using A Configuration Object

var YL = YAHOO.lang; var testFunction = function(conf) { var cfg = YL.isObject(conf) ? conf : {}; // default configuration if (! YL.isArray(cfg.rows)) {cfg.rows = [];} if (! YL.isNumber(cfg.size)) {cfg.size = 10;} // etc... this._cfg = cfg; // so configuration is available to prototype attached functions } testFunction.prototype = { _cfg: null, doSomething: function() { for (var i = 0, j = this._cfg.size; i < j; i += 1) { // ... } }, // ... }

Sometimes, however, a configuration is defined when the object is instantiated, but on occasion certain configuration options need to be temporary overwritten when a member function executes. From Example 1, lets assume that occasionally the ’size’ parameter needs to be changed. To achieve this is the most generic way, the ‘doSomething’ function must now except a contextual configuration object as its last parameter.

Example 2: Augmented Configuration

testFunction.prototype = { _cfg: null, doSomething: function(conf) { var cfg = conf; if (YL.isObject(cfg)) { YL.augmentObject(cfg, _cfg, false); } else { cfg = _cfg; } for (var i = 0, j = cfg.size; i < j; i += 1) { // ... } }, // ... }

The new configuration object passed into the function will be used for that function’s context only. If the configuration object is not provided, then the object’s default configuration object will be used. However, when a new configuration object is used, it is then augmented by the default configuration, adding any values to the context configuration that do not already exist. Since the third parameter of ‘augmentObject’ is false (falsy values are fine as well) no values on the context configuration object are overridden, thereby preserving any configuration modifications, while sharing all other configuration options.

In a simple example, such as Example 2, one could pass the new size as an optional parameter into the ‘doSomething’ method, instead of an object. However, if there were 2 or more overridable configuration options, then passing optional parameters would become cumbersome and the Augmented Configuration pattern becomes much more useful.

posted by Matt Snider at 10:49 am  

Tuesday, June 9, 2009

Tools - Google Page Speed

Google recently released their internal page speed analysis tool. On the surface this tool appears a lot like ySlow, but looking deeper into the analysis reveals many more ways to improve the performance of a website beyond the ySlow suggestions.

posted by Matt Snider at 10:17 am  

Saturday, June 6, 2009

Framework - YUI - Using EventProvider

For the Client-Side Storage problem that I have been working on with YUI, I was introduced to the EventProvider Interface, which provides a better way of handling the CustomEvents attached to an object. In Yahoo!’s own words:

EventProvider is designed to be used with YAHOO.augment to wrap CustomEvents in an interface that allows events to be subscribed to and fired by name. This makes it possible for implementing code to subscribe to an event that either has not been created yet, or will not be created at all.

By using this interface, developers no longer need to worry about whether or not a given CustomEvent exists when subscribing to it. Thereby, making coding easier, while allowing a developer the opportunity to not create CustomEvents that never fire. Additionally, EventProvider normalizes interactions with CustomEvents attached to objects, since the same methods will be attached to each object. Lastly, the events are bound to the augmented object, so although you might instantiate hundreds of EventProvider augmented object, each sharing an event named ‘update’, only the callback functions subscribed to the current object will execute when the ‘fireEvent’ function of that object is called. In summary, when using EventProvider the event names need only be unique on a single object, not across all objects (as with CustomEvent).

Example 1: Methods Augmented to an Object

createEvent - used to create an event; should be created before calling fireEvent fireEvent - fires the event, executing the proper subscribed functions hasEvent - evaluates if an event exists subscribe - associates a callback function with a given event unsubscribe - unassociate a callback function with a given event unsubscribeAll - unassociates all callback functions for a given event

Each of these method is explained in more detail in the YUI EventProvider API Docs. They all require an event name as the first parameter, otherwise behaving like their counterparts on the CustomEvent object. I recommend storing the event names as constants attached to the function prototype of the augmented object, that way they are easy to change and easy to use since they are attached to each instantiated object.

Example 2: Augmenting an Object with EventProvider

var MyObject = function() { this.createEvent(this.CE_UPDATE); }; MyObject.prototype = { CE_UPDATE: 'update', doSomething: function() { /* your code */ this.fireEvent(this.CE_UPDATE); } }; YAHOO.lang.augmentProto(MyObject , YAHOO.util.EventProvider); var myObject = new MyObject(); myObject.subscribe(myObject.CE_UPDATE, function() { /* your callback code */ });

Example 2 shows how to create a CE_UPDATE event, and fire a CE_UPDATE event, augment the object ‘MyObject’ with EventProvider (without this ‘fireEvent’ nad ‘createEvent’ will fail), and subscribe a callback function to the CE_UPDATE (’update’) event. In the future I plan to use this event model for all my CustomEvents.

posted by Matt Snider at 1:28 am  

Saturday, May 30, 2009

Project - Secure Konami Code JavaScript

One of the comments from the original Konami Code Project was if there was a way to protect the code so that users do not know what the code is. This is an interesting problem and inspired today’s revisions to the Konami Code widget. Using a server-side one-way transformation, such as MD5, and then doing the same transformation on the client-side, allows the developer to specify a secret code. The code was also revised to use an algorithm that is faster with larger sets of char codes (ex. the user has been interacting with the page for a while and pressed many keys), by using the Array.length and Array.slice the smallest set of char codes can be compared instead of the whole list.

Example 1: KonamiCode Singleton

(function() { // constants var Y = YAHOO.util, YE = Y.Event, YL = YAHOO.lang, // local namespace _F = function() {}, _keyPressed = [], _code = '38,38,40,40,37,39,37,39,66,65,13', _cryptoFx = function(typedCode, storedCode) {return typedCode=== storedCode;}, _length = 11, _that = null, // event namespace _E = { onKey: function(e) { var keyCode = YE.getCharCode(e); _keyPressed.push(keyCode); if (_keyPressed.length >= _length && _cryptoFx(_keyPressed.slice(_keyPressed.length - _length).join(','), _code)) { _keyPressed = []; _that.fireEvent(_that.EVENT_CODE_ENTERED); } } }; // public namespace YL.augmentObject(_F.prototype, { /** * The event to fire after the Konami code is successfully entered. * @event onCodeEntered */ EVENT_CODE_ENTERED: 'KonamiCode.CodeEntered', /** * Change the code; use a string of comma separated key codes. * @method changeCode * @param code {String} Required. The key codes. * @public */ changeCode: function(code) { if (YL.isString(code)) { _code = code; _length = code.split(',').length; } }, /** * Change the code and apply a cryptography method. * @method useCrypto * @param code {String} Required. The encrypted key codes. * @param cryptoFx {Function} Required. The cryptography function, see cryptoFx above for function signature. * @param length {Number} Required. The number of characters that originated the encrypted key codes. * @public */ useCrypto: function(code, cryptoFx, length) { if (YL.isString(code) && YL.isFunction(cryptoFx) && YL.isNumber(length)) { _code = code; _cryptoFx = cryptoFx; _length = length; } } }); YL.augmentProto(_F, Y.EventProvider); YE.on(document, 'keydown', _E.onKey); _that = new _F(); _that.createEvent(_that.EVENT_CODE_ENTERED); Core.Controller.KonamiCode = _that; }());

The code has also been restructured to leverage YUI EventProvider. Instead of explicitly defining a CustomEvent (’onCodeEntered’) on the KonamiCode object, the EventProvider augments the object with a subscribe function. Then developers just need to use a static key to reference the event:

Example 2: Subscribing to the Konami Code

Core.Controller.KonamiCode.subscribe(Core.Controller.KonamiCode.EVENT_CODE_ENTERED, function() { alert('test'); });

Now there is two new functions on KonamiCode: ‘changeCode’ and ‘useCrypto’. The ‘changeCode’ method simply replaces the konami code with the comma-delimited string of char codes provided. The ‘useCrypto’ method requires three parameters: the encrypted code, the JavaScript function to encrypt with, and the length of the code (this is required, because there is know way of knowing the code length from the encrypted code). The encrypted code parameter should be encrypted server-side and passed to the JavaScript. The encryption function should accept two parameters: the currently typed code and the stored code respectively. Here is a PHP example of how to implement an encrypted konami code:

Example 3: Encrypted PHP KonamiCode

<script type="text/javascript">Core.Controller.KonamiCode.useCrypto('<?php echo(md5('38,38,40,40,37,37')); ?>', function(typedCode, storedCode) { return hex_md5(typedCode) === storedCode; }, 6);<.script>

The code inside of “<?php echo(md5(’38,38,40,40,37,37′)); ?>” is actually executed on the server and will appear client-side as ‘4c85bea4f7f7842da72a152b18b45636′. The ‘cryptoFx’ simply compares the ‘md5′ value of the typed code to the stored code, returning true when the values are equal (this causes the ‘EVENT_CODE_ENTERED’ event to fire). The code is Example 3 is Up, Up, Down, Down, Left, Left.

For additional JavaScript cryptographic algorithms, see the work by Paj Cryptography - JavaScript MD5.

posted by Matt Snider at 4:12 pm  

Monday, May 25, 2009

CSS - Imageless Rounded Corners

Over the past few years, many web designers have decided rounded-corners improve the layout/usability of their sites. Typically, two techniques are used: using background images with layered elements or simulating rounded corners with elements inserted via JavaScript. Both techniques have a drawback, requiring extra load time for images to download or for JavaScript to execute. It would be nice if browsers had built in CSS support handling the rounding of corners. Fortunately, CSS3 will include a “border-radius” rule to specify how to handle corner rounding. Although no browsers fully support CSS3 yet, there are browser specific rules implementing the CSS3 rounded corners.

Please note however, this technique does not work in IE (not even 8), and it is not yet supported by Opera (latest version is 9.64), although it should be supported soon. I recommend gracefully degrading (no rounded corners) in these browsers, as site performance is paramount to maintaining users and today’s technique is much faster and more versatile than other methods. However, if rounded corners are a must in IE, then I suggest reading, Rounded Corners in Internet Explorer, and implementing an IE version using SVG or PNG.

That said, in CSS3 developers will be able to use the following rounded corners rules:

Example 1: CSS3 Rounded Corners Rule

border: 1px solid #F00; border-radius: 5px; /** specify horizontal and vertical radii */ border-radius: 5px 10px; /** below target a specific corner */ border-top-left-radius: 5px 10px; border-top-right-radius: 5px 10px; border-bottom-left-radius: 5px 10px; border-bottom-right-radius: 5px 10px;

First, with all the rounded corner rules discussed today, the ‘border’ rule is optional. If the developer does not specify a border, the element will still be rounded but with an invisible border. When the border is specified then then a line of the provided color and style will surround the element and round as well.

The border radius can be defined in two different ways: a single value indicates that the corner(s) should be rounded evenly, while two values indicates that the corner(s) will be rounded with the first value being the horizontal distance and the second value the vertical from the center of the curve. A developer may also target a specific corner by using “border-top/bottom-left/right-radius” instead of “border-radius”. For more information on the border radius, see the W3C specification.

That said, “border-radius” does not work as defined in any of the major browsers. Instead you will need to use browser specific versions:

Example 2: Browser Specific Border Radius Rules

border: 1px solid #F00; /** web-kit (safari/chrome) */ -webkit-border-radius: 5px; -webkit-border-radius: 5px 10px; -webkit-border-top-left-radius: 5px 10px; -webkit-border-top-right-radius: 5px 10px; -webkit-border-bottom-left-radius: 5px 10px; -webkit-border-bottom-right-radius: 5px 10px; /** konquerer */ -khtml-border-radius: 5px; -khtml-border-radius: 5px 10px; -khtml-border-top-left-radius: 5px 10px; -khtml-border-top-right-radius: 5px 10px; -khtml-border-bottom-left-radius: 5px 10px; -khtml-border-bottom-right-radius: 5px 10px; /** possibly opera */ -opera-border-radius: 5px; -opera-border-radius: 5px 10px; -opera-border-top-left-radius: 5px 10px; -opera-border-top-right-radius: 5px 10px; -opera-border-bottom-left-radius: 5px 10px; -opera-border-bottom-right-radius: 5px 10px; /** gecko (firefox/) */ -moz-border-radius: 5px; -moz-border-radius: 5px 10px; /* !IMPORTANT! rule doesn't work */ -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; -moz-border-radius-bottomleft: 5px; -moz-border-radius-bottomright: 5px;

For the most part the browsers have chosen to append their identifier to the front of “border-radius” and mirror the W3C specifications. Mozilla chose not to do this, changing the way developers target corners (’topleft’, instead of ‘top-left’) and not supporting the use of two parameters for a rule; all corners must be uniformly rounded in Mozilla. I recommend rounding corners uniformly in all browsers, anyway, because they look better.

I have written a Demo Page to show how to use these rules. The outer element has the upper left corner rounded to “10px 20px” (notice that the rule is not applied in FireFox) and the bottom right corner rounded to “10px”; no border style is specified. The inner element has all corners rounded uniformly to “10px” and a dashed, red border.

posted by Matt Snider at 9:32 pm  

Tuesday, May 12, 2009

project - ajax poller

A friend of mine needed a simple AJAX polling system, so I developed the AjaxPoller widget for him. The object is instantiated with a configuration object, which should include at the very least the URL to poll (’url’). Additionally, the developer can optionally specify: the length of time before the AJAX message times out (’timeout’), the length of time to wait between polls (’period’), a function to trigger when to stop polling dymanically (’stopfx’), and assign subscribers to any of the three CustomEvents (’onPoll’, ‘onStart’, ‘onStop’).

Example: AjaxPoller

(function() { // constants var _YU = YAHOO.util, _YL = YAHOO.lang, _CE = _YU.CustomEvent, _YC = YAHOO.util.Connect; Core.Controller.AjaxPoller = function(conf) { var _cfg = _YL.isObject(conf) ? conf : {}; if (! _YL.isNumber(_cfg.period)) {_cfg.period = 5000;} // default is 5 seconds if (! _YL.isNumber(_cfg.timeout)) {_cfg.timeout = 10000;} // default is to abort AJAX after 10 seconds if (! _YL.isFunction(_cfg.stopfx)) {_cfg.stopfx = function() {};} if (! _YL.isString(_cfg.url)) {_cfg.url = '';} this._cfg = _cfg; this.onPoll = new _CE('AjaxPoller.poll', null, false, _CE.FLAT); this.onStart = new _CE('AjaxPoller.start', null, false, _CE.FLAT); this.onStop = new _CE('AjaxPoller.stop', null, false, _CE.FLAT); if (_YL.isFunction(_cfg.onPoll)) {this.onPoll.subscribe(_cfg.onPoll);} if (_YL.isFunction(_cfg.onStart)) {this.onStart.subscribe(_cfg.onStart);} if (_YL.isFunction(_cfg.onStop)) {this.onStop.subscribe(_cfg.onStop);} }; Core.Controller.AjaxPoller.prototype = { onPoll: null, onStart: null, onStop: null, _cfg: null, _timeoutId: null, _transaction: null, start: function() { var _that = this; _that.onStart.fire(this); var fx = function() { _that._transaction = _YC.asyncRequest('GET', _that._cfg.url, {success: function(o) { _that.onPoll.fire(o); if (_that._cfg.stopfx(o)) { _that.stop(o); } else { _that._timeoutId = setTimeout(fx, _that._cfg.period); } }, failure: fx, timeout: _that._cfg.timeout}); }; fx(); }, stop: function() { if (this._transaction && this._transaction) {this._transaction.conn.abort();} if (this._timeoutId) {clearTimeout(this._timeoutId);} this.onStop.fire.apply(this.onStop, arguments); }, updateUrl: function(url) { this._cfg.url = url; } }; })();

Once the poller has been instantiated, simply call the ’start’ method to begin the system polling. Immediately upon calling the ’start’ method, the ‘onStart’ CustomEvent fires, allowing developers to make any setup changes. Then the first AJAX request is made, and the ‘onPoll’ CustomEvent fires after each response, passing the response object to any subscribers. The same response object is then passed to the ’stopfx’, which should return true if the polling should be terminated. Otherwise, the system continues to poll until the developer/user triggers a ’stop’. There the ‘onStop’ CustomEvent fires, passing the response object, if terminated by the ’stopfx’ or null if terminated by the developer. If the ‘url’ changes over time, then the ‘updateUrl’ method should be called to modify the internal URL pointer.

Although the configuration object is exposed by the pseudo-private variable ‘_cfg’, modifying it or any of the other pseudo-private variables directly should be avoided. I have put together a demo page so you can play with the system.

posted by Matt Snider at 10:37 pm  

Friday, May 8, 2009

projects - konami code javascript

Recently, many sites (such as espn.com and facebook.com) have added Easter eggs to their JavaScript libraries that are triggered by the old Konami video game code: up up down down left right left right b a enter. While this does not necessarily improve the web architecture, it does make it more fun. To promote more easter eggs like that, I decided to write a simple script that makes integrating the Konami code into your site trivial.

Example 1: KonamiCode Object

/** * Konami Code widget, modified from work done by John Resig. */ Core.Controller.KonamiCode = (function() { // constants var _YU = YAHOO.util, _CE = _YU.CustomEvent, _YE = _YU.Event; // local namespace var _F = function() {}, _keyPressed = [], _konamiCode = '38,38,40,40,37,39,37,39,66,65,13', _that = null; // event namespace var _E = { onKey: function(e) { var keyCode = _YE.getCharCode(e); _keyPressed.push(keyCode); if (0 < = _keyPressed.toString().indexOf(_konamiCode)) { _keyPressed = []; _that.onCodeEntered.fire(); } } }; // public namespace _F.prototype = { /** * The event to fire after the Konami code is successfully entered. * @event onCodeEntered */ onCodeEntered: new _CE('KonamiCode.CodeEntered', null, false, _CE.FLAT) }; _YE.on(document, 'keydown', _E.onKey); _that = new _F(); return _that; })();

This requires including YUI for event handling and custom events. The code listens for key strokes on the ‘document’, recording the desired keys in the appropriate order. The developer need only subscribe to the public CustomEvent ‘onCodeEntered’, allowing any number of callback functions.

Example 2: using onCodeEntered

Core.Controller.KonamiCode.onCodeEntered.subscribe(function() { alert('Guten Tag!'); });

This is very simple to use, just include the code snippet in example 1 after including YUI, so no demo page today.
———–
Modified to use John Resig’s logic, as it was simpler.
A new cryptographic version of this widget is available.

posted by Matt Snider at 4:46 pm  
Next Page »

Powered by WordPress