I hate brackets! (or rants of a JavaScript novice programmer)

Javascript is nice, I'm having fun building this Zope3/Ajax based application that I'm working on right now. What I don't enjoy is the forest of non-alphanumeric characters that has started to clog my code. I'm really a novice when it comes to Javascript, most of my professional programming has been done with other languages, but look for example at this fragment of code:

    for (i=0; i< parent.childNodes.length; i++) {
        el = parent.childNodes.item(i)
        if(el.nodeType == Node.ELEMENT_NODE) {
            if (el.classNames().find(function(s){return s == 'ajax_response'}))
            {
                parent.removeChild(parent.childNodes.item(i));
            }
        }
    }

It's a simple snippet that checks if there's already an "ajax_response" container in the parent container.  On line 4 I've stretched the code as much as I could, already reaching 80 characters (I'm using Prototype, the reasons there's an anonymous function as parameter to the find(), applied on an Enumerator object). The code is ugly and hard to read and comprehend. I'm really beginning to appreciate Python, a python version would have been something like this (trying not to differ too much from the JS version):

for i in range(parent.childNodes.length()):
    el = parent.childNodes[i]
    if el.nodeType is Node.ELEMENT_NODE) and \
         'ajax_response' in el.classNames():
                parent.removeChild(parent.childNodes.item(i))

The block of code have been reduced to almost half the number of lines and I dare say that now it is really possible to read an understand this snippet. Probably the JS code could be improved as well, but I'll keep such optimizations for later on, when my Js-Foo gets better.

Updated (again)

I wrote a Prototype extension to create a "scrollable area". I've modeled the code after the Control.Tabs extension. The control is almost ready. I feel good after understanding JS's OOP system and the Prototype extensions, its bind/bindAsEventListener quirks, but the code remains the same unreadable mess, when compared to Python code.

sizeToNumber = function(size){
//converts a style size (ex: 10px) to a number. Hackish method
return size.substring(0, size.length -2) * 1
}

if(typeof(Control) == "undefined")
var Control = {};
Control.Scroller = Class.create();
Object.extend(Control.Scroller.prototype, {
container:false,
initialize:function(area, options){

// set the options
this.options = $H({
timeslice:0.1,
amount:5,
type:'vertical',
width:'500px',
height:'400px'
}).merge(options || {});
this.area = $(area);
this.sid = 'scroller' + this.area.id;
// insert the control structures
new Insertion.After(this.area,
"<div class='scroller " + this.options.type + "' id='" + this.sid + "'>" +
"<div class='sb b_A' id='" + this.sid + "sb'></div>" +
"<div class='s_outer'>" +
"<div class='s_inner'></div>" +
"</div>" +
"<div class='sb b_B' id='" + this.sid + "sr'></div>" +
"</div>"
);

// div scroller <- container for the entire control
// div sb b_A <- scroll button. b_A = button A, b_B = button B
// div s_outer <- The restricted visible portion of the scrolled content. Has overflow:hidden
// div s_inner <- Container which holds the actual content

inner = $(this.sid).down('.s_inner');
outer = $(this.sid).down('.s_outer');

inner.appendChild(this.area);
outer.style.overflow = 'hidden';
// TODO: only apply one of height/weight

switch (this.options.type) {
case 'vertical':
this.direction = 'marginTop';
outer.style.height = this.options.height;
inner.style.height = this.area.clientHeight + 'px';
break
case 'horizontal':
this.direction = 'marginLeft';
outer.style.width = this.options.width;
inner.style.width = this.area.clientWidth + 'px';
break
default:
throw ("Unsported orientation/style type")
}

//event handlers LEFT (or TOP) scroll button
$(this.sid).down('.b_A').observe('mousedown', function(event){
this.flag=true;
new PeriodicalExecuter(function(pe){
if (this.flag) {
this.doScroll(this.options.amount);
} else {
pe.stop();
}
}.bind(this), this.options.timeslice);
}.bindAsEventListener(this));
$(this.sid).down('.b_A').observe('mouseup', function(event){
this.flag = false;
}.bindAsEventListener(this));

//event handlers RIGHT (or BOTTOM) scroll button
$(this.sid).down('.b_B').observe('mousedown', function(event){
this.flag=true;
new PeriodicalExecuter(function(pe){
if (this.flag) {
inner = $(this.sid).down('.s_inner');
cl_height = sizeToNumber(this.options.height); // the scroller control area
delta = sizeToNumber(inner.style[this.direction]); // how much the inner div was scrolled
if (this.options.type == 'horizontal') {
direction = 'clientWidth'
} else if (this.options.type == 'vertical'){
direction = 'clientHeight'
}
re_size = this.area[direction]; // the real content size (the ideal size)
if (!(((delta * -1) + cl_height) >= re_size)) {
this.doScroll(this.options.amount * -1);
}
} else {
pe.stop();
}
}.bind(this), this.options.timeslice);
}.bindAsEventListener(this));
$(this.sid).down('.b_B').observe('mouseup', function(event){
this.flag = false;
}.bindAsEventListener(this));
},

doScroll:function(amount){
// do a scroll in the direction specified
delta = $(this.sid).down('.s_inner').style[this.direction];
if (!delta) {
delta = "0px";
}
numeric_delta = sizeToNumber(delta) + amount;
if ( numeric_delta > 0) {return}
delta = numeric_delta + "px";
$(this.sid).down('.s_inner').style[this.direction] = delta;
},

scrollToElement:function(target_id){
// scroll to a specified id element inside the area

switch (this.options.type) {
case 'vertical':
offsetType = 'offsetTop';
break;
case 'horizontal':
offsetType = 'offsetLeft';
break;
}
target_offset = $(target_id)[offsetType]; //distance to top of page for the target container
outer_offset = $(this.sid).down('.s_outer')[offsetType];
offset_from_top = target_offset - outer_offset;

this._to_scroll = offset_from_top;
this._incr = Math.ceil(Math.abs(offset_from_top) / 2);
new PeriodicalExecuter(
function(pe){
if (this._to_scroll <= this._incr) {
multiplier = 1
} else {
multiplier = -1
}
this.doScroll(multiplier * this._incr);
this._to_scroll += multiplier * this._incr;
this._incr = Math.ceil(this._incr / 2);
window.console.log(this._incr);
if (this._to_scroll * multiplier * -1 <= this._incr) {
this.doScroll(-1 * this._to_scroll); //the last scroll
pe.stop()
}
}.bind(this),
this.options.timeslice
);
return false;
}
})

Comments