Wednesday, December 24, 2008

Quick! way to get repeated characters

want 8 spaces?


(new Array(8)).join(' ')

it's quick not only as in fewer keystrokes/less code, but is also blazingly fast to execute compared to a for-loop implementation.

Sunday, December 14, 2008

Fireworks CS4 filesize export bug

Fireworks' export wizard seems to be plagued by filesize bugs. I thought they squashed them in CS3, but CS4 seems to have re-introduced or made a new one: it reports an exported file's size as Kilobytes = bytes/1000 instead of bytes/1024.

I just exported a file, noting that FW tells me it will be 754K, when in fact, it is 737K (754,807 bytes).

Tuesday, October 21, 2008

IE6 + AlphaImageLoader + hover

the 'filter' nomenclature is unwieldy, so these lines are going to be long, you'll need to scroll. The gist is - ORDER MATTERS! - and the order is opposite from what you'd expect.


/* order matters! */
.carousel a:hover .playbutton{background:transparent url(/css/i/x.gif);filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/css/i/icons/playbutton_thumb_hover.png", sizingMethod="crop");}
.carousel .playbutton{background:transparent url(/css/i/x.gif);filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/css/i/icons/playbutton_thumb_normal.png", sizingMethod="crop");}
a:hover .playbutton{background:transparent url(/css/i/x.gif);filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/css/i/icons/playbutton_hover.png", sizingMethod="crop");}
.playbutton{background:transparent url(/css/i/x.gif);filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/css/i/icons/playbutton_normal.png", sizingMethod="crop");}

Tuesday, October 7, 2008

Asynchronously loading javascript

Asynchronous allows the page to keep loading--it's non-blocking--which can be nice. But also has gotchyas. With traditional, sequential, synchronously loaded javascript, you can be sure that something is defined if you've defined it in a preceeding script block. Not so with asynch. loaded js.

Safari 2

evidently, form.submit() bypasses any onsubmit handler that may be in play, whether it was added via form.onsubmit = function(){} or by using addEventListener.

Wednesday, September 24, 2008

Events


//will break if element.on[event] is modified directly elsewhere
//will break if the markup itself contains inline event listener functions ( <element onevent="function body"> )
//is incompatible with prototype library (Prototype library should not be used for ANY project.)

// using EVENT namespace ... could be different if needed, could possibly be Element.prototype ... could possibly be a jQuery extension/plugin (or incorporated into jQuery core?)

EVENT = {};

EVENT.elements = [];

EVENT.addListener = function(el, type, f, prepend){
    
    if(!el.eventListenerList){
        el.eventListenerList = [];
    }
    
    if(!el.eventListenerList[type]){
        el.eventListenerList[type] = [];
    }
    
    if(prepend){
        el.eventListenerList[type].splice(0,0,f);
    } else {
        el.eventListenerList[type].push(f);
    }
    
    if(!el['on'+type]){
        el['on'+type] = function(e){
            for(var i in el.eventListenerList[type]){
                el.eventListenerList[type][i](e);
            }
        };
    }
};

EVENT.removeListener = function(el, f){
    
    if(!el.eventListenerList){
        return;
    }
    
    //note: "var i in obj" is incompatible with icky prototype library
    var list = el.eventListenerList;
    for(var i in list){
        for(var j in list[i]){
            if( list[i][j] == f ){
                //remove it.
                el.eventListenerList[i].splice(j,1);
                return;
            }
        }
    }
    
};

EVENT.hasListener = function(el, f){

    if(!el.eventListenerList){
        return false;
    }
    
    //note: "var i in obj" is incompatible with icky prototype library
    var list = el.eventListenerList;
    for(var i in list){
        for(var j in list[i]){
            if( list[i][j] == f ){
                return true;
            }
        }
    }
    
    return false;
};

Wednesday, September 10, 2008

svg boundingbox of rotated ellipse

Found out last night that safari has a bug in its svg implementation (ok, probably lost most of you right there “who cares about svg?” … but there’s a possibility that this could be useful for flash/actionscript too)

Turns out that if you rotate an ellipse, the getBBox method returns a rather generous bounding box, because it’s using an implicit rectangle wrapped around the ellipse to get the bounding box.

You can see below what the browser bug is and what the correct box is. The red—buggy—implementation is created by the browser basing its calculations on the blue (implicit) rectangle that surrounds the ellipse.

The correct result is the box in green.

I created a javascript workaround for this bug.

It uses calculus. This is the first time I’ve used calculus in programming something that didn’t exist solely to demonstrate calculus.

I first tried to find a solution posted somewhere on the net, but only found a post where someone says it’s solved in the latest version, then reverses himself and says, ‘oh, no, actually it’s firefox that fixed its version of the same bug.’

If someone happens to find the bug # or a posted solution, that’d be cool.


// Trig functions (Math shortcut aliases)
var sin = Math.sin,
    cos = Math.cos,
    tan = Math.tan,
    cot = function(a){return 1/tan(a);},
    sqrt= Math.sqrt,
    acos= Math.acos,

    // our instance of an ellipse .. will become something more like this._shape (if this.type == ELLIPSE)
    ellipse = $('e').getElementsByTagName('ellipse')[0],

    // the properties of our instance of an ellipse
    a = ellipse.getAttribute('rx')*1, //x-radius (major, minor, who knows)
    b = ellipse.getAttribute('ry')*1, //y-radius (major, minor, who cares)
    cx = ellipse.getAttribute('cx')*1, //center's x-coord .. not currently used (assume the ellipse is centered at the origin)
    cy = ellipse.getAttribute('cy')*1, //center's y-coord .. not currently used (assume the ellipse is centered at the origin)
    matrix = ellipse.getCTM(), //the transform matrix
    angle = acos( matrix.a ),

    // the x coordinates where the ROTATED xmax and ymax occur on the ellipse BEFORE ROTATION.
    // these "magical" formulas represent the recombined derivative of the ellipse function
    x1 = (-1*(a*a)*tan(angle)) / sqrt(a*a*tan(angle)*tan(angle) + (b*b)),
    x2 = (   (a*a)*cot(angle)) / sqrt(a*a*cot(angle)*cot(angle) + (b*b)),

    // the actual xmax and ymax, reflecting the above, rotated into position (AFTER ROTATION).
    xmax = (cos(angle) * x2) + (sin(angle) * f(x2)),
    // (using negative-angle because positive screen y-axis is the same as 'normal maths' negative y-axis)
    ymax = (sin(-angle) * x1) + (cos(-angle) * f(x1));
///

// the f(x) that defines the ellipse (in general form)
function f(x){
    return b * Math.sqrt(1-((x*x)/(a*a)));
};

//~ console.log(ellipse.getAttribute('transform'));
//~ log(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);

// browser's idea of where the bbox is... (red box)
var box = ellipse.parentNode.getBBox();
re = $("re");
re.setAttribute("x", box.x);
re.setAttribute("y", box.y);
re.setAttribute("width", box.width);
re.setAttribute("height", box.height);
re.setAttribute("stroke", "red");

// my idea of where the bbox is... (green box)
re = $("re2");
re.setAttribute("x", -Math.abs(xmax));
re.setAttribute("y", -Math.abs(ymax));
re.setAttribute("width", Math.abs(xmax)*2);
re.setAttribute("height", Math.abs(ymax)*2);
re.setAttribute("stroke","#090");


Monday, June 9, 2008

debug

  • FF: Firebug : https://addons.mozilla.org/en-US/firefox/addon/1843
  • IE: DebugBar : http://www.debugbar.com/
  • SF: Developer: http://developer.apple.com/internet/safari/faq.html#anchor14

Debug in production?! .. when you have to .. you can target a strange userAgent string .. such as "Quality Assurance Browser" .. that you set yourself with some tool that allows you to set your userAgent.

Tuesday, June 3, 2008

YUI Compressor wishlist

Ok, so my list is currently composed of only one item: Flag for always remove. YUI Compressor already has a flag to *never* remove /** comments **/ that probably contain copyright information. I'd like it if YUI Compressor had a flag to *always* remove a block of code. Use case: remove debug statements. Perhaps it could be implemented as comments that demarcate the debug block, such as:



/** debug **/
console.dir( myObj );
/**/




and a corresponding YUI Compressor command-line argument "-rmdebug"




$ java -jar yuicompressor-2.5.2.jar -rmdebug script.js -o script-min.js





Update: Ok, so.. after I posted this, I searched the yuicompressor feature request list, and natch, somebody requested this - at least the debug-one-liner version: treat as debug any line that is prefixed with ";;". YUI Compressor Feature Request: remove debug code.




;; console.dir( myObj ); //double-semicolon prefix indicates this line is for debugging

Monday, June 2, 2008

Working with JSON

Often, on a callback that receives json, you may be tempted to do something such as:

if (data.image.length) {
    // Group of images
    for (var i = 0; i < data.image.length; i++) {
        debug("Image " + data.image[i].imageUrl + ": " + data.image[i].statusText);
    }
}
else {
    // Just a single image
    debug("Image " + data.image.imageUrl + ": " + data.image.statusText);
}

but it can be expressed more succinctly as:


if (!data.image.length) data.image = [data.image];
// Group of images
for (var i = 0; i < data.image.length; i++) {
    debug("Image " + data.image[i].imageUrl + ": " + data.image[i].statusText);
}

Tuesday, May 27, 2008

Super Simple XML-String to JSON

Yes, there are other libraries for this. Yes, they are more complete. So why this? I didn't need all the features of a complete xml->json map, I just wanted something to handle my nice, super simple use-case. And I hate wasted bandwidth. This library is 620 BYTES when YUI-compressed.

This is for a specific task. I trust the xml source. (So nobody yell "hey! - you're using eval() - you're eveel!").

Prerequisites:
  • you have a string of well-formed xml.
  • you don't have & don't care about node attributes, namespaces or CDATA (ie. your XML is 'Super Simple').

Good Ideas:
  • Don't use a library that will dirty the object prototype.
    (ie. mess up for-loops that look like this: for(var prop in obj){..})
  • Strip comments and whitespace, use safe variable-name substitution—use YUI Compressor.

Confessions:
  • It uses eval().
  • It uses regular-expressions (regexes).
  • It could probably stand to be optimized a bit.

The Code:

var xmlString = "<root><a/><b>data1</b><b>data2</b></root>";

function xmlString2json (xmlString) {
    return xmlString.replace(
            //expand empty tags
            /\<([^>]*)\/\>/g, "<$1></$1>"
        ).replace(
            //convert closing tags to closing braces
            /\<\/([^>]*)\>/g,"},"
        ).replace(
            //convert opening tags to "{NODENAME:["
            //notice that the array literal is opened, but not closed.
            /\<([^>]*)\>/g,"{'$1':[" 
        ).replace(
            //remove extraneous commas
            /,}/g,"]}"
        ).replace(
            //close array literal, begin adding needed singlequotes
            /:([^{}]*?)}/g,":'$1]'}"
        ).replace(
            //adjust singlequotes, bring inside array literal notation (left side)
            /'\[/g,"['"
        ).replace(
            //adjust singlequotes, bring inside array literal notation (right side)
            /\]'/g,"']"
        ).replace(
            //remove empty strings (which are from empty tags)
            /\[''\]/g,"[]"
        ).replace(
            //remove final, ending extraneous comma
            /},$/, '}'
    );
}

var json = xmlString2json(xmlString);
// {'root':[{'a':[]},{'b':['data1']},{'b':['data2']}]}

function cleanTree(r, p){
    if(r.length == 1 && typeof r[0] == "string"){
        //might want to trim leading and trailing whitespace from r[0] first
        // could be expressed as a series of ternary operations:
        // return r[0]=="true"?true:r[0]=="false"?false:r[0].search(/^[0-9]+$/)==0?1*r[0]:r[0]
        // expressed this way for readability:
        if(r[0] == "true"){
            return true;
        }
        if(r[0] == "false"){
            return false;
        }
        if(r[0].search(/^[0-9]+$/) == 0){
            return 1*r[0];
        }
        return r[0];
    }
    p = p || {};
    for( i in r ){
        var nn = '';
        for( name in r[i] ){
            nn = name;
        }
        var subnode = r[i][nn];
        if(p[nn]){
            if(typeof p[nn] == "object" && typeof p[nn].length == "number"){
                p[nn].push( cleanTree(subnode) );
            } else {
                p[nn] = [ p[nn], cleanTree(subnode) ];
            }
        } else {
            p[nn] = cleanTree(subnode);
        }
    }
    return p;
}

var jso = eval('('+json+')');
var cleaned = cleanTree(jso.testImages);
console.log(cleaned);


At this point, you might be wondering, "Why all those [extra] arrays?" My idea was to approximate the concept of the childNodes array. Really, you could just take the initial JSON and run with it, keeping in mind that it is structured with 'childNodes' arrays. But in case you'd rather have it 'cleaned' up .. I made the recursive cleanTree function to do just that.

"What if I have actual xml, not just a string representation thereof?" you may question.

Give this a shot:

var xmlString = (new XMLSerializer()).serializeToString( myXMLDoc );

Someone else's related post (reminder to self to see if his xml2json will work in actionscript / how readily adaptable it is): converting xml to json.

Saturday, April 5, 2008

consume that data

Have an array that you need to iterate over? Dying to use javascript 1.8 iterators and generators? (why?!) Anyhow... use this instead.. IF IF IF ... you don't mind CONSUMING your data :-) (this = shift)

var s=['prototype.yc','lightbox','dsn'];
while(s.length) document.write('<scr'+'ipt src="/js/'+s.shift()+'.js"></scr'+'ipt>');

Friday, April 4, 2008

directory structure = sitenav = breadcrumbs = CSS

Ok? Example:
/
home
home
home

/mainsection
mainsection
home > mainsection
<html class="mainsection">

/mainsection/subsection
mainsection > subsection
home > mainsection > subsection
<html class="mainsection"><body class="subsection">

/mainsection/subsection/leaf
mainsection > subsection > leaf
home > mainsection > subsection > leaf
<html class="mainsection"><body class="subsection"><div id="siteContainer" class="leaf">

Monday, March 31, 2008

dom nodes + for-loops THAT WORK

Imagine you have a collection of DOM nodes (html element nodes, whatever you want to call them) stored in the variable ‘elements’, and that var len = elements.length. The following for-loop will not work. Every element will have an onclick event handler function that calls clicked(len) – not the respective clicked(i)



WRONG

    for( var i = 0; i < len; i++){
        
        var el = elements[i];
        el.addListener('click', function(){clicked(i);}, false);
        
    }



RIGHT

    (function loop( I ){
        if (I == len) return;
        var i = I;
        
        
        var el = elements[i];
        el.addListener('click', function(){clicked(i);}, false);
        
        
        loop(++I);
    })(0);

Note: addListener is not any browser’s implementation. It’s just my way of saying addEventListener (or, for IE, attachEvent). Also, for brevity, the function ‘clicked’ is not here defined. Yes, this is documented in a couple places around the web. But those places are not obvious or easily searchable for everyone.

Tuesday, February 26, 2008

If

Have you ever seen code like this?

if(condition){
   stuff
}

This won't work for all cases, but I often enjoy writing it like this:

if(!condition) return;
stuff

Note: if you are NOT minifying your code, the bottom method is probably leaner, but if you ARE minifying, stick with the top method.

Reduce the Verbosity of Prototypal Class Definitions

I ran across some code like this recently:


function SomeClass(args){
...
}
SomeClass.prototype.firstMethod = function(args){ ... };
SomeClass.prototype.secondMethod = function(args){ ... };
SomeClass.prototype.thirdMethod = function(args){ ... };
...
...


Maybe, like me, you cringe when you see repetitious code. Maybe not. If not, return. So I got to thinking of a way to streamline this class.


function SomeClass(args){};
SomeClass.prototype = new (function(args){
   var me = this;
   me.firstMethod = function(args){ ... };
   me.secondMethod = function(args){ ... };
   me.thirdMethod = function(args){ ... };
   ...
})();

/* to test it, we'll create an instance and check if it has it's own [copy of] firstMethod */

var myClass = new SomeClass(args);
alert(myClass.hasOwnProperty('firstMethod'));
/* false, it does not have a local copy of the method */