Ajax Bestiary: A Javascript Field Guide
 
Ajax Bestiary: A Javascript Field Guide
 
 

Entries Tagged as 'Tutorial'

Converting Between Wiki Markup & HTML with Prototype: Part 2 ListsAt

Posted by Don Albrecht

At the end of part 1 of the series, the system could easily handle direct replacement of certain html entities with their wiki markup counterparts.  Unfortunately this was a pretty limited implementation that could only handle those entities that had a direct, symmetrical relationship with html.  In the case of lists, we have to keep track of depth and better cleanup the input text.  We also need to enforce default behavior on the input stream.

Since lists are dependent on dedicated whitespace as part of their markup, we need to clear out all unnecessary white space from the html before processing it.  To do this, we simply replace all whitespace characters with an innocuous single space to remove any extra new lines.

$(textNode).innerHTML = $(textNode).innerHTML.gsub( '\s', '' );

Next we need to cleanup the recursion.  Since we need to know significantly more about the given node to properly assess it.  We can replace the Prototype templates with simple curried function calls and migrate the recursion to the curried methods.

var ConverterTable = {
strong: Converter.curry("'''", null, true),
b:  Converter.curry("'''" , null, true),
em: Converter.curry( "''"  , null, true ),
i:  Converter.curry( "''"  , null, true ),
h1: Converter.curry( '='  , null, false ),
h2: Converter.curry( '=='  , null, false ),
h3: Converter.curry( '==='  , null, false ),
h4: Converter.curry( '===='  , null, false ),
h5: Converter.curry( '====='  , null, false ),
h6: Converter.curry( '======' , null, false ),
ul: Converter.curry('', {li:Converter.curry(['* ', ''], null, false)}, false),
ol: Converter.curry(”, {li:Converter.curry(['# ', ''], null, false)}, false),
p:  Converter.curry(['','\n'], null, false)
};

The parameters passed into the individual rules are
The Markup String or an array of strings for start and end tags
An optional library of child rules.  These rules will be applied to any children of the given node before the default rules are applied.
An indication as to the ‘inline-ability’ of the given tag.  This controls the bracketing of the resulting markup with ‘\n’ characters.

The modified Converter now looks to apply rules in the following order
If the node is marked as a stopping point, no recursion proceeds on the branch.
If any over-ridden child rules exist for the given tag, those rules are applied and a memo is passed on to child nodes to denote the depth of the recursion.
Any default rules are processed as per the earlier versions of the code.  Note.  A prototype template is no longer used in favor of simple string construction.
The node itself is removed from the DOM.

function Converter( markupString, nestedRules, inline, textNode, memo ){

var startString, endString;
inline = inline ? ” : ‘\n’;
memo = memo ? memo : ”;
var children =  textNode.childElements();

if( typeof markupString == ‘object’){
startString = inline + markupString[0];
endString = markupString[1] + inline;
} else {
startString = inline + markupString;
endString = markupString + inline;
}

for( i in children){
if( typeof children[i] != ‘function’){
if( nestedRules && typeof nestedRules[children[i].tagName.toLowerCase()] == ‘function’){
startString =  memo.strip() + startString;
nestedRules[ children[i].tagName.toLowerCase() ]( children[i], startString);
} else if( typeof ConverterTable[children[i].tagName.toLowerCase()] == ‘function’){
ConverterTable[children[i].tagName.toLowerCase() ](children[i]);
} else { Converter( ”, nestedRules, true, children[i]), memo }
}
}
textNode.replace(  startString + textNode.innerHTML + endString  );
}

Converting Between Wiki Markup & HTML with Prototype

Posted by Don Albrecht

Wiki’s are amazing and powerful tools, unfortunately their dependence on specialized markup creates a huge barrier to their general adoption in many organizations.  This is a first step at building a wysiwyg editor for wiki markup.  While I will be focussing on the syntax unique to the popular MediaWiki platform, these techniques should be applicable to any wiki system.

The general flow of the converter is as follows:

  1. Converter is passed the root node of an html fragment to translate.
  2. Converter recurses through each of the child nodes and converts them.
  3. Root node tag is replaced with wiki markup.

There’s really only 2 key components involved in this first pass. A converter object and the recursive method.

The Converter Object

The converter object is little more than a collection of name value pairs.  The name corresponds to an html tag.  The value is a Prototype template to use in the direct replacement of the given node. By convention we’ll write all of the tag names for the converter object in lower case.

var Converter = {
strong: new Template("'''#{body}'''"),
b:  new Template("'''#{body}'''"),
em: new Template("''#{body}''"),
i:  new Template("''#{body}''"),
h1: new Template('=#{body}='),
h2: new Template('===#{body}=='),

h3: new Template(’===#{body}===’),
h4: new Template(’====#{body}====’),
h5: new Template(’=====#{body}=====’),
h6: new Template(’======#{body}======’)  }

The Converter Function

The Converter function always performs 2 checks before attempting to convert a given node.  First it ensures that the node is in fact a node and not a stray function from the Prototype enhanced object.  Next it verifies that a converter exists for the tag.  The toLowerCase() on the tagName is necessary due to the inconsistent behavior browsers demonstrate with this attribute.  While all browsers return the variable in all caps for traditional html, they are not reliable about returning lower case values for xhtml markup.

function convertToWiki( textNode ){
//make sure textNode isn't a function on the object
if( typeof textNode != 'function'){

//provide a way to stop execution on select sub trees
if( !textNode.hasClassName( 'stop')){
$(textNode).childElements().each( convertToWiki );
}

//make sure a converter exists for the given tag
if( liteConverter[ textNode.tagName.toLowerCase() ] ){

//replace the text node with a converted version of itself
textNode.replace( liteConverter[textNode.tagName.toLowerCase()]
.evaluate({body:textNode.innerHTML}));
} } }

Implicit Vs. Explicit Conversion in Javascript

Posted by Don Albrecht

A question emerged out of my boredom on my flight yesterday.  Which is faster, implicit vs explicit conversion.  Most javascript developers use implicit conversion out of habit. For example:

!!x; instead of Boolean( x ); and x + “”; instead of String( x);. 

I decided to try an experiment for myself and record the performance of casting a number to a Boolean or String on my Windows box in Safari 3, Firefox 2, Opera 9 and IE 7.

The Verdict:

Implicit conversion wins handily, demonstrating over a 7 fold performance increase in one test. Overall, the performance gain for using implicit conversion averaged out to 53% across browsers after 10 tests.

The Numbers:

  Implicit Boolean Explicit Boolean Implicit String Explicit String
Firefox 2 0.162 0.312 0.248 0.358
IE 7 0.042 0.100 0.074 0.152
Opera 9 0.030 0.088 0.020 0.142
Safari 3 0.028 0.036 0.074 0.100
Cross Browser Average 0.066 0.134 0.104 0.188

You can find the code I used after the jump.

Keep reading →

Javascript Best Practices: parseInt( x, 10 );

Posted by Don Albrecht

ParseInt is one of the handiest universal functions in javascript. Even though javascript’s ability to cast most any primitive to any other primitive on the fly is handy to say the least, sometimes we need to explicitly parse a string to make sure we have a legitimate number to work with.

ParseInt does this for us. What most developers don’t realize is that parseInt is base agnostic. While it typically assumes base ten, any base between 2 and 36 can be used and is indicated by the optional second argument.

Why is this a problem you might ask? Because octal numbers can be represented by a leading zero. In practice this can cause some interesting effects in your code.

For example:

var x = "010";

console.log( x );

console.log( x - 0 );

console.log( parseInt( x ) );

console.log( parseInt( x , 10 ) );

Returns:


"010"

10

8

10

Note: “010″ is equivalent to 10 in  “x - 0″!

!! You’re True (or False)

Posted by Don Albrecht

Ok, so you probably know that Javascript like most other programming languages treats “0″ as false and any other numerical value as true in logic statements. Usually, this is sufficient, unfortunately, sometimes, you need a bit more consistent behavior. For example, ( 2 && 3 && true ) = 2.

Luckily there is a simple fix. Since, ! only operates on boolean data. !(0) is true and !( any other non-null value is false). Therefore, !! returns the boolean value of the original expression.

Another way of coding it, would be “Boolean ( expression );” and directly casting the expression to a Boolean. All things being equal, this would honestly be my preferred way of doing it. After all, legibility in code is one of the utmost considerations for most development, but since bandwidth is also a major consideration in javascript and !! is reasonably intuitive, I personally use it wherever necessary.

Handling Nested Clicks with jQuery Revisited

Posted by Don Albrecht

Yesterday, I posted instructions for handling a series of nested clicks with jquery. Today I’m going to follow up that post with a more sophisticated solution to handle a corner case we discovered in testing. I will be rehashing some but not all of yesterdays solution in this tutorial, but you may want to review it.

The problem:

We have nested clickable divs created by using the jQuery $().click( function … ) function. If the user clicks on the nested div, the click event fires for the parent div as well. Since this isn’t traditional event bubbling, we aren’t able to restrict the event to the innermost div by returning false from the click function or using event.stopPropagation(); & event.cancelBubble = true;.

We handled this yesterday by the simple expedience of using a status flag to verify that a child div hasn’t already been clicked on and erasing the status flags globally when ever an element is selected. This works beautifully for most cases, unfortunately it makes it impossible to select the parent of a previously selected element.

The Solution:

We needed to build a solution that could deal with 5 cases:

  1. Target div is innermost clicked div
  2. Target div is not innermost clicked div but selection hasn’t been processed for innermost clicked div
  3. Target div is not innermost clicked div and selection has been processed for innermost clicked div
  4. Target div is innermost clicked div and parent of previously selected div
  5. Target div is child of innermost clicked div and was previously selected.

Our previous solution handled cases 1 - 3 well. We now need a fix for cases 4 & 5. We accomplish this by providing a tracking a bit more information than the selected / not selected flag. We declare a global variable clickedDivStack that maintains an array of the currently selected div & its ancestors.

First we determine if the target is the innermost div of a selected stack (none of its children have the selected flag set on them). In this case, we know we don’t have to worry about situations 4 & 5 so we simply set the div as selected. Then we reset the clickedDivStack array to contain only the target.

if (!$(target).find(".selected").size()) {
$('.selected').removeClass("elected");
$(target).addClass("selected");
clickedDivStack = [target];
return; }

Next, we check for case 4 & 5 by comparing the target to the contents of the clickedDivStack Array. If the length of the array is greater than 1 & the target is already in the array, we know that this was an attempt to click on the parent of a previously selected element & the current target is the innermost div of the new selection. We can then process this as a new innermost selection and return.

for (var i = 1; i < selecteddiv.length; i++) {
if (selecteddiv[i] == target) {
$(’.selected’).removeClass(”elected”);
$(target).addClass(”selected”);
clickedDivStack = [target];
return; }}

If the target is not a new innermost selection, it must be a parent div. Since we need to track parent divs, we add it to the array of parent divs.

clickedDivStack = [target];
return;

It’s a bit more complex, but the perfomance is excellent and it smoothly handles the click selection of divs, even coping with nested elements in all corner cases found.

Handling Nested Clicks With jQuery.

Posted by Don Albrecht

Alright, so here’s the problem. I need to apply the ability to select any div inside an ajax app. Including divs nested inside other nestable divs. ex.

<div id="outer" class="clickable">
<div id="inner" class="clickable"></div>
</div>

In jQuery, if I simply call $('.clickable').click(...); I wind up with a click event that fires twice when inner is clicked. Once with target inner and once with target outer. Since I only care about the inner most div selected, I need some way to ignore the click event on the outer div.

I achieve this by assigning an attribute to the currently selected div. In the case of my most recent project, I was able to piggyback on a class change, but any attribute would do.

Step 1: Determine if this is the innermost selected div

There are two possible scenarios for any target div. It is either the innermost clicked div or it isn’t. If it isn’t the innermost div it will either already have a child div with a set “selected” attribute or it won’t. If it has a selected child div, we escape.

if ($(target).find(".selected").size() ) { return; }

Otherwise, we remove all elements with set selected attributes and set the selected attribute for the target div.

$('.selected').removeClass( "selected");
$(target).addClass( "selected" );

Note: If the containing div was the first processed, we don’t have a problem because we will clean up the current selection when the function is called on the innermost div.

The system is working like a charm for me in my current project.  If anyone knows a more efficient way to do it, please share it in the comments below.

Streamline Your Javascript with Shorthand

Posted by Don Albrecht

D’bug has published a wonderful list of techniques for abbreviating Javascript and improving performance.

You can find the article here:

http://blog.reindel.com/2007/11/01/javascript-shorthand-tips-and-tricks/

LightBox, ThickBox & Framework Conflicts

Posted by Don Albrecht

Lightbox & CodaSlider aren’t compatible.  A situation that was brought to my attention while participating in the comments on a blog post at http://www.ndoherty.com/.  Conflicts like this are a common experience for many of us and I thought this was a good opportunity to explore the cause of one such conflict.

In the case of Lightbox & CodaSlider, the fundamental frameworks were at odds with each other.  Lightbox is built with the Prototype / Scriptaculous framework stack, CodaSlider uses the jQuery framework.  Superficially, this doesn’t seem like a big deal.  While it’s usually bad form to load down a web page with multiple frameworks, I know it’s a crime many of us have been guilty of in the name of expediency or through multiple vendor integrations.  Usually this works, but in this case, the developer was attempting to integrate two widgets that were instantiated at different times and by different methods.

Luckily, there’s a simple solution to the problem.  I directed her towards the popular “ThickBox” widget instead.  In fact, that’s the greatest strength of the Ajax Bestiary we all have at our disposal.  There’s usually another tool available for us to use and in the case of popular frameworks like jQuery, a native solution is out there.  So if you ever find yourself in this situation, try to find the native solution.

jQuery $ unleash the power of selectors

Posted by Don Albrecht

While jQuery is many powerful things, Selectors are quite possibly its most capable and useful feature. So here’s the jQuery Selector crash course.

The Ground Rules:

  1. Selectors work just like CSS (1-3) selectors # for ID’s, . for classes p, div, ul, li etc.
  2. XPath an also be used.
  3. CSS & XPATH selectors can be combined

The $ wrapper.

Selectors + $ = jQuery Nirvana. The $() function accepts any selector and returns an object that can be manipulated.
Filters 

jQuery filters enhance jQuery Selectors by providing additional logic.  Here’s a list of supported filters:

  • Not (selector)
  •  first
  • last
  • even
  • odd
  • eq( index) matches an elements index in returned array
  • gt( index) matches all elements after given index in an array
  • lt( index ) matches all elements before given index in an array
  • header matches all h elements (h1 h2 h3 etc)
  • animated (matches all elements that are currently being animated)
  • contains( text) matches all elements which contain given text
  • empty matches all empty elements
  • has(selector) matches all elements containing an element that matches the given selector
  • parent matches all elements that are parents / have child element. (opposite of empty)
  • hidden matches all elements of type hidden
  • visible (opposite of hidden)