root/trunk/core.js

Revision 232, 22.6 kB (checked in by whitaker, 2 years ago)

Bug ID: 58521

Testing post-commit

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2Core JavaScript Library
3$Id$
4
5Copyright (c) 2005, Six Apart, Ltd.
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are
10met:
11
12    * Redistributions of source code must retain the above copyright
13notice, this list of conditions and the following disclaimer.
14
15    * Redistributions in binary form must reproduce the above
16copyright notice, this list of conditions and the following disclaimer
17in the documentation and/or other materials provided with the
18distribution.
19
20    * Neither the name of "Six Apart" nor the names of its
21contributors may be used to endorse or promote products derived from
22this software without specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36*/
37
38/* stubs */
39
40log = function() {};
41log.error = log.warn = log.debug = log;
42
43
44/* utility functions */
45
46defined = function( x ) {
47    return x === undefined ? false : true;
48}
49
50
51/**
52 * Utility method.
53 * @param x <code>any</code> Any JavaScript value, including <code>undefined</code>.
54 * @return boolean <code>true</code> if the value is not <code>null</code> and is not <code>undefined</code>.
55 */
56exists = function( x ) {
57   return (x === undefined || x === null) ? false : true;
58}
59
60
61finite = function( x ) {
62    return isFinite( x ) ? x : 0;
63}
64
65
66finiteInt = function( x, base ) {
67    return finite( parseInt( x, base ) );
68}
69
70
71finiteFloat = function( x ) {
72    return finite( parseFloat( x ) );
73}
74
75
76max = function() {
77    var a = arguments;
78    var n = a[ 0 ];
79    for( var i = 1; i < a.length; i++ )
80        if( a[ i ] > n )
81            n = a[ i ];
82    return n;
83}
84
85
86min = function() {
87    var a = arguments;
88    var n = a[ 0 ];
89    for( var i = 1; i < a.length; i++ )
90        if( a[ i ] < n )
91            n = a[ i ];
92    return n;
93}
94
95
96/* try block */ 
97 
98Try = {
99    these: function() {
100        for( var i = 0; i < arguments.length; i++ ) {
101            try {
102                return arguments[ i ]();
103            } catch( e ) {}
104        }
105        return undefined;
106    }
107}
108
109
110/* unique id generator */
111
112Unique = {
113    length: 0,
114   
115    id: function() {
116        return ++this.length;
117    }
118}
119
120
121/* event methods */
122
123if( !defined( window.Event ) )
124    Event = {};
125
126
127Event.stop = function( event ) {
128    event = event || this;
129    if( event === Event )
130        event = window.event;
131
132    // w3c
133    if( event.preventDefault )
134        event.preventDefault();
135    if( event.stopPropagation )
136        event.stopPropagation();
137
138    // ie
139    try {
140        event.cancelBubble = true;
141        event.returnValue = false;
142    } catch( e ) {}
143
144    return false;
145}
146
147
148Event.prep = function( event ) {
149    event = event || window.event;
150    if( !defined( event.stop ) )
151        event.stop = this.stop;
152    if( !defined( event.target ) )
153        event.target = event.srcElement;
154    if( !defined( event.relatedTarget ) ) 
155        event.relatedTarget = event.toElement;
156    return event;
157}
158
159
160try { Event.prototype.stop = Event.stop; }
161catch( e ) {}
162
163
164/* object extensions */
165
166Function.stub = function() {};
167
168
169if( !Object.prototype.hasOwnProperty ) {
170    Object.prototype.hasOwnProperty = function( p ) {
171        if( !(p in this) )
172            return false;
173        try {
174            var pr = this.constructor.prototype;
175            while( pr ) {
176                if( pr[ p ] === this[ p ] )
177                    return false;
178                if( pr === pr.constructor.prototype )
179                    break;
180                pr = pr.constructor.prototype;
181            }
182        } catch( e ) {}
183        return true;
184    }
185}
186
187
188Object.prototype.extend = function() {
189    var a = arguments;
190    for( var i = 0; i < a.length; i++ ) {
191        var o = a[ i ];
192        for( var p in o ) {
193            try {
194                if( !this[ p ] &&
195                    (!o.hasOwnProperty || o.hasOwnProperty( p )) )
196                    this[ p ] = o[ p ];
197            } catch( e ) {}
198        }
199    }
200    return this;
201}
202
203
204Object.prototype.override = function() {
205    var a = arguments;
206    for( var i = 0; i < a.length; i++ ) {
207        var o = a[ i ];
208        for( var p in o ) {
209            try {
210                if( !o.hasOwnProperty || o.hasOwnProperty( p ) )
211                    this[ p ] = o[ p ];
212            } catch( e ) {}
213        }
214    }
215    return this;
216}
217
218
219Object.prototype.extend( {
220    init: Function.stub,
221    destroy: Function.stub
222} );
223
224
225
226/* function extensions */
227
228Function.prototype.extend( {
229    bind: function( object ) {
230        var method = this;
231        return function() {
232            return method.apply( object, arguments );
233        };
234    },
235   
236   
237    bindEventListener: function( object ) {
238        var method = this; // Use double closure to work around IE 6 memory leak.
239        return function( event ) {
240            try {
241                event = Event.prep( event );
242            } catch( e ) {}
243            return method.call( object, event );
244        };
245    }
246} );
247
248
249/* class helpers */
250
251indirectObjects = [];
252
253
254Class = function( superClass ) {
255
256    // Set the constructor:
257    var constructor = function() {
258        if( arguments.length )
259            this.init.apply( this, arguments );
260    };   
261    //   -- Accomplish static-inheritance:
262    constructor.override( Class );  // inherit static methods from Class
263    superClass = superClass || Object; 
264    constructor.override( superClass ); // inherit static methods from the superClass
265    constructor.superClass = superClass.prototype;
266   
267    // Set the constructor's prototype (accomplish object-inheritance):
268    constructor.prototype = new superClass();
269    constructor.prototype.constructor = constructor; // rev. 0.7   
270    //   -- extend prototype with Class instance methods
271    constructor.prototype.extend( Class.prototype );   
272    //   -- override prototype with interface methods
273    for( var i = 1; i < arguments.length; i++ )
274        constructor.prototype.override( arguments[ i ] );
275   
276    return constructor;
277}
278
279
280Class.extend( {
281    initSingleton: function() {
282        if( this.singleton )
283            return this.singleton;
284        this.singleton = this.singletonConstructor
285            ? new this.singletonConstructor()
286            : new this();
287        this.singleton.init.apply( this.singleton, arguments );
288        return this.singleton;
289    }
290} );
291
292
293Class.prototype = {
294    destroy: function() {
295        try {
296            if( this.indirectIndex )
297                indirectObjects[ this.indirectIndex ] = undefined;
298            delete this.indirectIndex;
299        } catch( e ) {}
300       
301        for( var property in this ) {
302            try {
303                if( this.hasOwnProperty( property ) )
304                    delete this[ property ];
305            } catch( e ) {}
306        }
307    },
308   
309   
310    getBoundMethod: function( methodName ) {
311        return this[ name ].bind( this );
312    },
313   
314   
315    getEventListener: function( methodName ) {
316        return this[ methodName ].bindEventListener( this );
317    },
318   
319   
320    getIndirectIndex: function() {
321        if( !defined( this.indirectIndex ) ) {
322            this.indirectIndex = indirectObjects.length;
323            indirectObjects.push( this );
324        }
325        return this.indirectIndex;
326    },
327   
328   
329    getIndirectMethod: function( methodName ) {
330        if( !this.indirectMethods )
331            this.indirectMethods = {};
332        var method = this[ methodName ];
333        if( typeof method != "function" )
334            return undefined;
335        var indirectIndex = this.getIndirectIndex();
336        if( !this.indirectMethods[ methodName ] ) {
337            this.indirectMethods[ methodName ] = new Function(
338                "var o = indirectObjects[" + indirectIndex + "];" +
339                "return o." + methodName + ".apply( o, arguments );"
340            );
341        }
342        return this.indirectMethods[ methodName ];
343    },
344   
345   
346    getIndirectEventListener: function( methodName ) {
347        if( !this.indirectEventListeners )
348            this.indirectEventListeners = {};
349        var method = this[ methodName ];
350        if( typeof method != "function" )
351            return undefined;
352        var indirectIndex = this.getIndirectIndex();
353        if( !this.indirectEventListeners[ methodName ] ) {
354            this.indirectEventListeners[ methodName ] = new Function( "event",
355                "try { event = Event.prep( event ); } catch( e ) {}" +
356                "var o = indirectObjects[" + indirectIndex + "];" +
357                "return o." + methodName + ".call( o, event );"
358            );
359        }
360        return this.indirectEventListeners[ methodName ];
361    }
362}
363
364
365/* string extensions */
366
367String.extend( {
368    escapeJSChar: function( c ) {
369        // try simple escaping
370        switch( c ) {
371            case "\\": return "\\\\";
372            case "\"": return "\\\"";
373            case "'":  return "\\'";
374            case "\b": return "\\b";
375            case "\f": return "\\f";
376            case "\n": return "\\n";
377            case "\r": return "\\r";
378            case "\t": return "\\t";
379        }
380       
381        // return raw bytes now ... should be UTF-8
382        if( c >= " " )
383            return c;
384       
385        // try \uXXXX escaping, but shouldn't make it for case 1, 2
386        c = c.charCodeAt( 0 ).toString( 16 );
387        switch( c.length ) {
388            case 1: return "\\u000" + c;
389            case 2: return "\\u00" + c;
390            case 3: return "\\u0" + c;
391            case 4: return "\\u" + c;
392        }
393       
394        // should never make it here
395        return "";
396    },
397   
398   
399    encodeEntity: function( c ) {
400        switch( c ) {
401            case "<": return "&lt;";
402            case ">": return "&gt;";
403            case "&": return "&amp;";
404            case '"': return "&quot;";
405            case "'": return "&apos;";
406        }
407        return c;
408    },
409
410
411    decodeEntity: function( c ) {
412        switch( c ) {
413            case "amp": return "&";
414            case "quot": return '"';
415            case "gt": return ">";
416            case "lt": return "<";
417        }
418        var m = c.match( /^#(\d+)$/ );
419        if( m && defined( m[ 1 ] ) )
420            return String.fromCharCode( m[ 1 ] );
421        m = c.match( /^#x([0-9a-f]+)$/i );
422        if(  m && defined( m[ 1 ] ) )
423            return String.fromCharCode( parseInt( hex, m[ 1 ] ) );
424        return c;
425    }
426} );
427
428
429String.prototype.extend( {
430    escapeJS: function() {
431        return this.replace( /([^ -!#-\[\]-~])/g, function( m, c ) { return String.escapeJSChar( c ); } )
432    },
433   
434   
435    escapeJS2: function() {
436        return this.replace( /([\u0000-\u0031'"\\])/g, function( m, c ) { return String.escapeJSChar( c ); } )
437    },
438   
439   
440    escapeJS3: function() {
441        return this.replace( /[\u0000-\u0031'"\\]/g, function( m ) { return String.escapeJSChar( m ); } )
442    },
443   
444   
445    escapeJS4: function() {
446        return this.replace( /./g, function( m ) { return String.escapeJSChar( m ); } )
447    },
448   
449   
450    encodeHTML: function() {
451        return this.replace( /([<>&"])/g, function( m, c ) { return String.encodeEntity( c ) } );
452    },
453
454
455    decodeHTML: function() {
456        return this.replace( /&(.*?);/g, function( m, c ) { return String.decodeEntity( c ) } );
457    },
458   
459   
460    cssToJS: function() {
461        return this.replace( /-([a-z])/g, function( m, c ) { return c.toUpperCase() } );
462    },
463   
464   
465    jsToCSS: function() {
466        return this.replace( /([A-Z])/g, function( m, c ) { return "-" + c.toLowerCase() } );
467    },
468   
469   
470    firstToLowerCase: function() {
471        return this.replace( /^(.)/, function( m, c ) { return c.toLowerCase() } );
472    },
473   
474       
475    rgbToHex: function() {
476        var c = this.match( /(\d+)\D+(\d+)\D+(\d+)/ );
477        if( !c )
478            return undefined;
479        return "#" +
480            finiteInt( c[ 1 ] ).toString( 16 ).pad( 2, "0" ) +
481            finiteInt( c[ 2 ] ).toString( 16 ).pad( 2, "0" ) +
482            finiteInt( c[ 3 ] ).toString( 16 ).pad( 2, "0" );
483    },
484   
485   
486    pad: function( length, padChar ) {
487        var padding = length - this.length;
488        if( padding <= 0 )
489            return this;
490        if( !defined( padChar ) )
491            padChar = " ";
492        var out = [];
493        for( var i = 0; i < padding; i++ )
494            out.push( padChar );
495        out.push( this );
496        return out.join( "" );
497    },
498
499
500    trim: function() {
501        return this.replace( /^\s+|\s+$/g, "" );
502    }
503
504} );
505
506
507/* extend array object */
508
509Array.extend( { 
510    fromPseudo: function ( args ) {
511        var out = [];
512        for ( var i = 0; i < args.length; i++ )
513            out.push( args[ i ] );
514        return out;
515    }
516});
517
518
519/* extend array object */
520
521Array.prototype.extend( {
522    copy: function() {
523        var out = [];
524        for( var i = 0; i < this.length; i++ )
525            out[ i ] = this[ i ];
526        return out;
527    },
528
529
530    first: function( callback, object ) {
531        var length = this.length;
532        for( var i = 0; i < length; i++ ) {
533            var result = object
534                ? callback.call( object, this[ i ], i, this )
535                : callback( this[ i ], i, this );
536            if( result )
537                return this[ i ];
538        }
539        return null;
540    },
541
542
543    fitIndex: function( fromIndex, defaultIndex ) {
544        if( !defined( fromIndex ) || fromIndex == null )
545            fromIndex = defaultIndex;
546        else if( fromIndex < 0 ) {
547            fromIndex = this.length + fromIndex;
548            if( fromIndex < 0 )
549                fromIndex = 0;
550        } else if( fromIndex >= this.length )
551            fromIndex = this.length - 1;
552        return fromIndex;
553    },
554
555
556    scramble: function() {
557        for( var i = 0; i < this.length; i++ ) {
558            var j = Math.floor( Math.random() * this.length );
559            var temp = this[ i ];
560            this[ i ] = this[ j ];
561            this[ j ] = temp;
562        }
563    },
564   
565   
566    add: function() {
567        var a = arguments;
568        for( var i = 0; i < a.length; i++ ) {
569            var index = this.indexOf( a[ i ] );
570            if( index < 0 ) 
571                this.push( arguments[ i ] );
572        }
573        return this.length;
574    },
575       
576   
577    remove: function() {
578        var a = arguments;
579        for( var i = 0; i < a.length; i++ ) {
580            var j = this.indexOf( a[ i ] );
581            if( j >= 0 )
582                this.splice( j, 1 );
583        }
584        return this.length;
585    },
586
587
588    /* javascript 1.5 array methods */
589    /* http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array#Methods */
590
591    every: function( callback, object ) {
592        var length = this.length;
593        for( var i = 0; i < length; i++ ) {
594            var result = object
595                ? callback.call( object, this[ i ], i, this )
596                : callback( this[ i ], i, this );
597            if( !result )
598                return false;
599        }
600        return true;
601    },
602
603
604    filter: function( callback, object ) {
605        var out = [];
606        var length = this.length;
607        for( var i = 0; i < length; i++ ) {
608            var result = object
609                ? callback.call( object, this[ i ], i, this )
610                : callback( this[ i ], i, this );
611            if( result )
612                out.push( this[ i ] );
613        }
614        return out;
615    },
616   
617   
618    forEach: function( callback, object ) {
619        var length = this.length;
620        for( var i = 0; i < length; i++ ) {
621            object
622                ? callback.call( object, this[ i ], i, this )
623                : callback( this[ i ], i, this );
624        }
625    },
626   
627   
628    indexOf: function( value, fromIndex ) {
629        fromIndex = this.fitIndex( fromIndex, 0 );
630        for( var i = 0; i < this.length; i++ ) {
631            if( this[ i ] === value )
632                return i; 
633        }
634        return -1;
635    },
636
637
638    lastIndexOf: function( value, fromIndex ) {
639        fromIndex = this.fitIndex( fromIndex, this.length - 1 );
640        for( var i = fromIndex; i >= 0; i-- ) {
641            if( this[ i ] == value )
642                return i;
643        }
644        return -1;
645    },
646
647
648    some: function( callback, object ) {
649        var length = this.length;
650        for( var i = 0; i < length; i++ ) {
651            var result = object
652                ? callback.call( object, this[ i ], i, this )
653                : callback( this[ i ], i, this );
654            if( result )
655                return true;
656        }
657        return false;
658    },
659
660
661    /* javascript 1.2 array methods */
662
663    concat: function() {
664        var a = arguments;
665        var out = this.copy();
666        for( i = 0; i < a.length; i++ ) {
667            var b = a[ i ];
668            for( j = 0; j < b.length; j++ )
669                out.push( b[ j ] );
670        }
671        return out;
672    },
673   
674
675    push: function() {
676        var a = arguments;
677        for( var i = 0; i < a.length; i++ )
678            this[ this.length ] = a[ i ];
679        return this.length;     
680    },
681
682
683    pop: function() {
684        if( this.length == 0 )
685            return undefined;
686        var out = this[ this.length - 1 ];
687        this.length--;
688        return out;
689    },
690   
691   
692    unshift: function() {
693        var a = arguments;
694        for( var i = 0; i < a.length; i++ ) {
695            this[ i + a.length ] = this[ i ];
696            this[ i ] = a[ i ];
697        }
698        return this.length;     
699    },
700   
701   
702    shift: function() {
703        if( this.length == 0 )
704            return undefined;
705        var out = this[ 0 ];
706        for( var i = 1; i < this.length; i++ )
707            this[ i - 1 ] = this[ i ];
708        this.length--;
709        return out;
710    }
711} );
712
713
714/* date extensions */
715
716Date.extend( {
717    /*  iso 8601 date format parser
718        this was fun to write...
719        thanks to: http://www.cl.cam.ac.uk/~mgk25/iso-time.html */
720
721    matchISOString: new RegExp(
722        "^([0-9]{4})" +                                                     // year
723        "(?:-(?=0[1-9]|1[0-2])|$)(..)?" +                                   // month
724        "(?:-(?=0[1-9]|[12][0-9]|3[01])|$)([0-9]{2})?" +                    // day of the month
725        "(?:T(?=[01][0-9]|2[0-4])|$)T?([0-9]{2})?" +                        // hours
726        "(?::(?=[0-5][0-9])|\\+|-|Z|$)([0-9]{2})?" +                        // minutes
727        "(?::(?=[0-5][0-9]|60$|60[+|-|Z]|60.0+)|\\+|-|Z|$):?([0-9]{2})?" +  // seconds
728        "(\.[0-9]+)?" +                                                     // fractional seconds
729        "(Z|\\+[01][0-9]|\\+2[0-4]|-[01][0-9]|-2[0-4])?" +                  // timezone hours
730        ":?([0-5][0-9]|60)?$"                                               // timezone minutes
731    ),
732   
733   
734    fromISOString: function( string ) {
735        var t = this.matchISOString.exec( string );
736        if( !t )
737            return undefined;
738
739        var year = finiteInt( t[ 1 ], 10 );
740        var month = finiteInt( t[ 2 ], 10 ) - 1;
741        var day = finiteInt( t[ 3 ], 10 );
742        var hours = finiteInt( t[ 4 ], 10 );
743        var minutes = finiteInt( t[ 5 ], 10 );
744        var seconds = finiteInt( t[ 6 ], 10 );
745        var milliseconds = finiteInt( Math.round( parseFloat( t[ 7 ] ) * 1000 ) );
746        var tzHours = finiteInt( t[ 8 ], 10 );
747        var tzMinutes = finiteInt( t[ 9 ], 10 );
748
749        var date = new this( 0 );
750        if( defined( t[ 8 ] ) ) {
751            date.setUTCFullYear( year, month, day );
752            date.setUTCHours( hours, minutes, seconds, milliseconds );
753            var offset = (tzHours * 60 + tzMinutes) * 60000;
754            if( offset )
755                date = new this( date - offset );
756        } else {
757            date.setFullYear( year, month, day );
758            date.setHours( hours, minutes, seconds, milliseconds );
759        }
760
761        return date;
762    }
763} );
764
765
766Date.prototype.extend( {
767    getISOTimezoneOffset: function() {
768        var offset = -this.getTimezoneOffset();
769        var negative = false;
770        if( offset < 0 ) {
771            negative = true;
772            offset *= -1;
773        }
774        var offsetHours = Math.floor( offset / 60 ).toString().pad( 2, "0" );
775        var offsetMinutes = Math.floor( offset % 60 ).toString().pad( 2, "0" );
776        return (negative ? "-" : "+") + offsetHours + ":" + offsetMinutes;
777    },
778
779
780    toISODateString: function() {
781        var year = this.getFullYear();
782        var month = (this.getMonth() + 1).toString().pad( 2, "0" );
783        var day = this.getDate().toString().pad( 2, "0" );
784        return year + "-" + month + "-" + day;
785    },
786
787
788    toUTCISODateString: function() {
789        var year = this.getUTCFullYear();
790        var month = (this.getUTCMonth() + 1).toString().pad( 2, "0" );
791        var day = this.getUTCDate().toString().pad( 2, "0" );
792        return year + "-" + month + "-" + day;
793    },
794
795
796    toISOTimeString: function() {
797        var hours = this.getHours().toString().pad( 2, "0" );
798        var minutes = this.getMinutes().toString().pad( 2, "0" );
799        var seconds = this.getSeconds().toString().pad( 2, "0" );
800        var milliseconds = this.getMilliseconds().toString().pad( 3, "0" );
801        var timezone = this.getISOTimezoneOffset();
802        return hours + ":" + minutes + ":" + seconds + "." + milliseconds + timezone;
803    },
804
805
806    toUTCISOTimeString: function() {
807        var hours = this.getUTCHours().toString().pad( 2, "0" );
808        var minutes = this.getUTCMinutes().toString().pad( 2, "0" );
809        var seconds = this.getUTCSeconds().toString().pad( 2, "0" );
810        var milliseconds = this.getUTCMilliseconds().toString().pad( 3, "0" );
811        return hours + ":" + minutes + ":" + seconds + "." + milliseconds + "Z";
812    },
813
814
815    toISOString: function() {
816        return this.toISODateString() + "T" + this.toISOTimeString();
817    },
818
819
820    toUTCISOString: function() {
821        return this.toUTCISODateString() + "T" + this.toUTCISOTimeString();
822    }
823} );
824
825
826/* ajax */
827
828if( !defined( window.XMLHttpRequest ) ) {
829    window.XMLHttpRequest = function() {
830        var types = [
831            "Microsoft.XMLHTTP",
832            "MSXML2.XMLHTTP.5.0",
833            "MSXML2.XMLHTTP.4.0",
834            "MSXML2.XMLHTTP.3.0",
835            "MSXML2.XMLHTTP"
836        ];
837       
838        for( var i = 0; i < types.length; i++ ) {
839            try {
840                return new ActiveXObject( types[ i ] );
841            } catch( e ) {}
842        }
843       
844        return undefined;
845    }
846}
Note: See TracBrowser for help on using the browser.