My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Wednesday, December 24, 2008

External selectors as engines and how to create your own library

With "Sizzle event", the challenge about libraries will move from the coolest/fastest selector engine into the coolest/fastest way to use it for library purpose.

Since Sizzle will be just one of them, thanks to our natural behavior ( read: re-create the wheel ) it is possible that there will be more selector engines around the net.

So, why we could not create our own library that does just what we need and nothing else, maintaining a decent size and performing like a jaguar :D ???

Here a truly basic example about how to create your own library, basing it over a generic selector engine or generic library (or just its engine, if you prefer one)

// our wonderful library constructor
function myQuery(){
// just in case we prefer another name for the constructor
// this code is "name proof"
var callee = arguments.callee;
return this instanceof callee ?
this :
callee.prototype.init.apply(new callee, arguments)
;
};

// add some native prototype to created array like instances
// a common pattern for DOM based libraries
with(Array.prototype)
myQuery.prototype = {
constructor:myQuery,
length:0,
pop:pop,
push:push,
shift:shift,
slice:slice,
sort:sort,
splice:splice,
unshift:unshift
};

// add "not standard yet array prototypes", if necessary, or our own cool proottypes
myQuery.prototype.forEach = Array.prototype.forEach || function(Function, self){
for(var i = 0, length = this.length; i < length; i++)
(i in this) && Function.call(self, this[i], i, this);
};

// define the init prototype to use the pre defined engine
myQuery.prototype.init = function(){
var self = this.engine.apply(null, arguments);
this.push.apply(this, self instanceof Array ? self : this.slice.call(self, 0));
return this;
};

// define the engine for this library
myQuery.prototype.engine = Sizzle; // or jQuery

onload = function(){

// enjoy your lib
myQuery(".foo .bar").forEach(function(HTMLElement){
HTMLElement.innerHTML = "Hello Lib!";
});

};


Summary


The main limit is the engine itself, or better, arguments that the engin could accept.
If we change engine and this accepts different arguments or same arguments in a different way, we need to change every call to our library but at least everything else will work without problems.
For this reason it is reasonble to choose a common engine arguments model, like Sizzle, for example, presuming other engine will accept the same list of options and will produce an array like result.
Am I suggesting to avoid famous/common library? Absolutely no, I simply think the selector engine is the most important thing for whatever DOM purpose library :-)

Tuesday, December 23, 2008

Visual Studio 2008 - The good part

... whatever I said in the precedent post, at least VS9 let us extend it and create macros as well.

Unfortunately, I could not create a macro in C# so I had to use a language that I do not like at all, Visual Basic, and the result is an alpha macro that allows us to create "more clever" regions and outline for brackets as well.

More clever regions


There is another macro that goes around since Visual Studio 2005 but it has some bug and/or problem with nested regions plus it does not allow us to write directly in the region what the region is about

//#region whatever
...
//#endregion

--- to obtain
//#region whatever[...]

--- instead of
// another "boring" comment over the region
[...]

At the same time that macro put every region, nested or not, at the beginning of the line. It is not such a big problem, but I prefer this version.


Brackets outline


This is absolutely experimental and there are hundreds of problems, but for a couple of files it is doing its job.


What's next


Christmas, holidays, relax, if it is possible, and a better parser (a char by char via VB, I cannot even imagine me doing it!!!) so be patience please, and enjoy this alpha release :)

' JavaScript macro for bits and bops alpha version
' by Andrea Giammarchi - WebReflection.blogspot.com

Option Strict Off
Option Explicit Off
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Imports System.Collections

Public Module JSMacros

Sub OutlineBrackets()
Outline("Brackets")
End Sub

Sub OutlineRegions()
Outline("Regions")
End Sub

Private Function Outline(ByVal what As String)
Dim REGION_START As String = "//#region"
Dim REGION_END As String = "//#endregion"
If what = "Brackets" Then
REGION_START = "{"
REGION_END = "}"
ElseIf what = "Regions" Then
REGION_START = "//#region"
REGION_END = "//#endregion"
End If
Dim selection As EnvDTE.TextSelection = DTE.ActiveDocument.Selection
Dim selections As ArrayList = New ArrayList
Dim regions As Stack
Dim current As Stack
Dim endIndex As Integer
Dim startIndex As Integer
Dim text As String
selection.StartOfDocument()
selection.SelectAll()
text = selection.Text
If text.Chars(text.Length - 1) <> vbCr Then
selection.EndOfDocument()
selection.Insert(vbCr)
text += vbCr
End If
regions = FindRegions(text, REGION_START, REGION_END)
If what = "Brackets" Then
OutlineBracketsLoop(REGION_START, REGION_END, regions, selections, text, 0, 0, 0, regions.Count(), New Stack)
ElseIf what = "Regions" Then
OutlineRegionsLoop(REGION_START, REGION_END, regions, selections, text, 0, 0, 0, regions.Count(), New Stack)
End If
SetOutline(selection, selections)
selection.StartOfDocument()
End Function

Private Function CalcLineNumber(ByRef text As String, ByVal i As Integer)
Dim lineNumber As Integer = 1
While 0 < i
i -= 1
If text.Chars(i) = vbCr Then
lineNumber += 1
End If
End While
Return lineNumber
End Function

Private Function FindPosition(ByRef text As String, ByVal i As Integer)
Dim position As Integer = 0
While 0 < i
i -= 1
position += 1
If i = 0 Or text.Chars(i) = vbCr Then
Exit While
End If
End While
Return position
End Function

Private Function FindRegions(ByRef text As String, ByRef REGION_START As String, ByRef REGION_END As String)
Dim result As Stack = New Stack()
Dim pos As Stack = New Stack(2)
Dim startIndex As Integer = 0
Dim endIndex As Integer
Dim index As Integer = 0
Dim subValue As String = ""
Do
startIndex = text.IndexOf(REGION_START, startIndex)
If startIndex = -1 Then
Exit Do
End If
pos.Push(startIndex)
startIndex = startIndex + REGION_START.Length
index = startIndex
endIndex = text.IndexOf(REGION_END, startIndex)
If endIndex = -1 Then
MsgBox("Endless Region")
Exit Do
End If
Do
index = text.IndexOf(REGION_START, index)
If index = -1 Or endIndex < index Then
pos.Push(endIndex)
result.Push(pos)
pos = New Stack(2)
Exit Do
Else
endIndex = text.IndexOf(REGION_END, endIndex + REGION_END.Length)
index = index + REGION_START.Length
End If
Loop
Loop
Return result
End Function

Private Function OutlineBracketsLoop(ByRef REGION_START As String, ByRef REGION_END As String, ByRef regions As Stack, ByRef selections As ArrayList, ByRef text As String, ByVal startIndex As Integer, ByVal endIndex As Integer, ByVal i As Integer, ByVal length As Integer, ByVal current As Stack)
Dim startLine As Integer
Dim endLine As Integer
While i < length
current = regions.Pop
endIndex = current.Pop
startIndex = current.Pop
startLine = CalcLineNumber(text, startIndex)
endLine = CalcLineNumber(text, endIndex)
If startLine < endLine Then
selections.Add(startLine)
selections.Add(FindPosition(text, startIndex) - 1)
selections.Add(endLine)
selections.Add(FindPosition(text, endIndex))
End If
i += 1
End While
End Function

Private Function OutlineRegionsLoop(ByRef REGION_START As String, ByRef REGION_END As String, ByRef regions As Stack, ByRef selections As ArrayList, ByRef text As String, ByVal startIndex As Integer, ByVal endIndex As Integer, ByVal i As Integer, ByVal length As Integer, ByVal current As Stack)
Dim tmpIndex As Integer
While i < length
current = regions.Pop
endIndex = current.Pop
startIndex = current.Pop
tmpIndex = startIndex + REGION_START.Length
selections.Add(CalcLineNumber(text, startIndex))
selections.Add(1 + FindPosition(text, startIndex) + REGION_START.Length + text.Substring(tmpIndex, text.IndexOf(vbCr, startIndex) - tmpIndex).Length)
selections.Add(CalcLineNumber(text, endIndex) + 1)
selections.Add(1)
i += 1
End While
End Function

Private Function SetOutline(ByRef selection As EnvDTE.TextSelection, ByRef selections As ArrayList)
Dim i As Integer = 0
Dim length As Integer = selections.Count()
While i < length
selection.MoveToLineAndOffset(selections.Item(i), selections.Item(i + 1))
selection.MoveToLineAndOffset(selections.Item(i + 2), selections.Item(i + 3), True)
selection.OutlineSection()
i += 4
End While
End Function

End Module

Saturday, December 20, 2008

Visual Studio 2008 and JavaScript: the worst couple ever

We are using Visual Studio 2008 Team Edition at work and it is great for our C# plus SQLServer application, but I am working entirely with JavaScript and every time my files grow up "too much" I start having headache scrolling up and down thousands of line of code.

A basic function that even a small editor like Notepad++ has, the plus/minus sign to close functions closures, does not exists.

You should try to create your own macro, in VB "of course", and call it every time.
As alternative, I added the file extension js in the option with HTML Editor as default editor to let me do something like this

//#region MyConstructor <script>

... my code

//#endregion </stript>

Above start and end comment allows me to close code blocks and everything inside will be highlighted because the editor consider it as an HTML JavaScript tag:
it's true, HTML has open/close blocks while JavaScript does not.

The "fantastic" debugger is nothing that better than the one you can find inside Internet Explorer 8, the only point is that it is integrated with the rest of the project so you can debug both JavaScript and C# at the same time ... without this big effort.

I use FireBug and/or Chrome console indeed, since I do not need to monitor C# that much, and that's it, client side debug feature completely useless under IE ( and I downloaded the version 8 beta 2 yesterday so I can use its debugger instead of launching another local server for client side debug ... )

The intellisense sucks, it does not work automatically as is for C# or other languages or as is for Eclipse or Aptana, you have to put useless syntax in the top of the file to tell the big IDE where are other files.

The suggestion is so clever that if you have a property whose name starts with el, the IDE will autmatically put an else

myObject.else // by the ide


The internal parser does not recognize properly functions as objects, and inheritance is not part of the suggestion ... you should try to disable it if you want to code faster instead of correct IDE suggestions.

In few words, I still wonder why Microsoft is doing everything to discourage the usage of JavaScript and I can't wait to find a valid alternative to integrate my JavaScript projects into source control without being constricted to use the best IDE for C# and the worst ever for JavaScript: Visual Studio 2008

Kind Regards

Wednesday, December 17, 2008

outerHTML for almost every browser ... if you need it ...

We all have to deal with memory leaks problem and apparently a good way to be sure that an HTMLElement has been removed from its "flow" is the outerHTML assignemnt (thanks Ariel for the suggestion)

element.outerHTML = "";
element = null;

If the element is not a document.body or another body parent node, Internet Explorer will "extract" that element from its context, if any, and if there are no other references for that element the null assignment will, theoretically, complete the opera, hopefully solving memory leaks problem for that node as well ...


To remove ... but to replace too ...


Every library has a "swap" or replace method to quickly change an element, but even if we all know that innerHTML is the fastest way to insert content, few libraries use outerHTML to replace nodes, that as far as I know, should be "that fast" in IE as innerHTML is:

// traditional swap, the DOM way
function swap(oldNode, newNode){
var parentNode = oldNode.parentNode;
parentNode.insertBefore(newNode, oldNode);
parentNode.removeChild(oldNode);
};

// spaghetti swap, the outerHTML way
function swap(oldNode, newNode){
oldNode.outerHTML = newNode.outerHTML;
};



outerHTML ... both get and set



try{
HTMLElement.prototype.__defineGetter__.length;
(function(body, removeChild){
HTMLElement.prototype.__defineGetter__(
"outerHTML",
function(){
var self = body.appendChild(this.cloneNode(true)),
outerHTML = body.innerHTML;
body.removeChild(self);
return outerHTML;
}
);
HTMLElement.prototype.__defineSetter__(
"outerHTML",
function(String){
if(!String)
removeChild(this);
else if(this.parentNode){
body.innerHTML = String;
while(body.firstChild)
this.parentNode.insertBefore(body.firstChild, this);
removeChild(this);
body.innerHTML = "";
};
}
);
})(
document.createElement("body"),
function(HTMLElement){if(HTMLElement.parentNode)HTMLElement.parentNode.removeChild(HTMLElement);}
);
}catch(e){};



Conclusion


pro and cons are the same of innerHTML and the reference is lost as is for Internet Explorer, whenever we decide to use this dirty approach to remove or change elements in the DOM. A last example?

function change(strong){
strong.outerHTML = "not strong anymore";
strong = null;
};
onload = function(){
document.body.innerHTML = "click";
};

Saturday, December 06, 2008

A fast and crossbrowser function to make an Array

An absolutely common task present in almost every library, is to transform a generic collection/list of objects or DOM Elements into a friendly Array.

Cases Scenario



  • It is possible to apply directly an Array.prototype.slice call to quickly return an Array

  • It is not possible at all apply every kind of Array prototype to the list



Cases scenario detection


When we execute a piece of JavaScript in a web page, we can assume that we have at least one DOM element in that page, as the script itself for example, so it is always possible to obtain a collection of elements calling document.getElementsByTagName("script") or a generic ("*") in this case probably superflous considering the latter assumption.

Cross browser and fast makeArray proposal



var makeArray = function(push, slice){
try{
// Andrea Giammarchi proposal
slice.call(document.getElementsByTagName("script"), 0);
return function(array, results){
array = array instanceof Array ? array : slice.call(array, 0);
results ? push.apply(results, array) : results = array;
return results
}
}catch(e){
return function(array, results){
if(!(array instanceof Array))
for(var ret = [], i = 0, length = array.length; i < length; i++)
ret[i] = array[i];
else
var ret = array;
results ? push.apply(results, ret) : results = ret;
return results
}
}
}(Array.prototype.push, Array.prototype.slice);


Summary


The try catch is executed only once, the Array prototypes are cached once as well to guarantee cross libraries compatibility. The function is assigned differently for those browsers that can apply the slice prototype to the collection and those that cannot (mainly Internet Explorer).
Accepted parameters are two, inspired by Sizzle function, to allow us to concatenate elements into a collection.

The collection is transformed into an Array only if necessary, since it does not make sense to perform a slice call in every case.

Compatiblity? It should be every browser that supports try and catch statement :-)

Wednesday, December 03, 2008

Stressfull procedure? Distribute your task!

Have you never been in troubles with frozen GIFs or unresponsive HTML?
Sometimes JavaScript could be used to perform really stressful task and a loop, a for in, or an each, could not be fast enough to make your DOM responsive.

What we need in this case is simply a closure to make sure references are consistent and our job will end up in the correct order.

This is a probably silly but I hope interesting function to make the DOM and generally the page more responsive:

Time = {
setTimeout:function(Stack, delay){
var self = this;
if(!delay)
delay = 1;
if(!(Stack instanceof Array))
Stack = [Stack];
setTimeout(function(){
Stack.shift().call(self);
if(0 < Stack.length)
setTimeout(arguments.callee, delay);
}, delay);
}
};

We can call this functon in different ways, stating from the demo:

Time.setTimeout([
function(){
alert("Hello");
},
function(){
alert("Distributed");
},
function(){
alert("Work");
}
]);

untill its more meaningful usage:

var distributed = [];
$("whatever").each(function(i, dom){
distributed.push(function(){
// your stuff to do with i or dom element
});
});
distributed.push(function(){
// your stuff to do after the each call
});
Time.setTimeout(distributed);

Another trick? The usage of the scope injected by each function:

$("whatever").each(function(i, dom){
var self = this;
distributed.push(function(){
return function(){
// your stuff to do with i or dom element
}.call(self);
});
});

Closures against Responsiveness


People expect usability, we expect performances ... at the same time we would like to be able to show a progress, something, that indicate that the page is not completely blocked.

This is a simple way to solve the problem, portable enough, and customizable via closures ... I hope you'll enjoy it :)