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

Entries from December 2008

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}));
} } }

A Fast & Easy Prototype Based Curry

Posted by Don Albrecht

in Javascript the Good Parts, Douglas Crockford presents a simple method to curry functions.  It’s a great tool that allows you to build functions with arguments predefined in them dynamically.  In his implementation Array.splice & an indirect method declaration is used.  In my version, I use prototype’s enhanced Array object & a direct call to the Function object to the same effect.

Function.curry = function() {
var args = $A(arguments), that = this;
return function() {
return that.apply( null, args.concat(arguments));
};
};

I’ve been using this method for a few months now in both its prototype & jquery forms and find it invaluable, especially when faced with a need to manipulate a large number of similar objects.