1/***************************************************************************** 2 * 3 * Copyright (c) 2004-2007 Guido Wesdorp. All rights reserved. 4 * 5 * This software is distributed under the terms of the JSBase 6 * License. See LICENSE.txt for license text. 7 * 8 *****************************************************************************/ 9 10var global = this; 11global.misclib = new function() { 12 /* This lib mostly contains fixes for common problems in JS, additions to 13 the core global functions and wrappers for fixing browser issues. 14 15 See the comments in the code for further explanations. 16 */ 17 18 // reference to the lib to reach it from the functions/classes 19 var misclib = this; 20 21 var Timer = function() { 22 /* Wrapper around window.setTimeout, to solve number of problems: 23 24 * setTimeout gets a string rather than a function reference as an 25 argument 26 27 * setTimeout can not handle arguments to the callable properly, 28 since it's evaluated in the window namespace, not in the current 29 scope 30 31 Usage: 32 33 // call the global 'schedule' function to register a function 34 // called 'foo' on the current (imaginary) object, passing in 35 // a variable called 'bar' as an argument, so it gets called 36 // after 1000 msecs 37 window.misclib.schedule(this, this.foo, 1000, bar); 38 39 Note that if you don't care about 'this' inside the function 40 you register, you can just pass in a ref to the 'window' object 41 as the first argument. No lookup is done in the namespace, the 42 first argument is only used as the 'this' variable inside the 43 function. 44 */ 45 this.lastid = 0; 46 this.functions = {}; 47 48 this.registerCallable = function(object, func, timeout) { 49 /* register a function to be called with a timeout 50 51 args: 52 object - the context in which to call the function ('this') 53 func - the function 54 timeout - timeout in millisecs 55 56 all other args will be passed 1:1 to the function when called 57 */ 58 var args = new Array(); 59 for (var i=3; i < arguments.length; i++) { 60 args.push(arguments[i]); 61 } 62 var id = this._createUniqueId(); 63 this.functions[id] = new Array(object, func, args); 64 // setTimeout will be called in the module namespace, where the 65 // timer_instance variable also resides (see below) 66 setTimeout("window.misclib.timer_instance." + 67 "_handleFunction(" + id + ")", timeout); 68 }; 69 70 this._handleFunction = function(id) { 71 /* private method that does the actual function call */ 72 var obj = this.functions[id][0]; 73 var func = this.functions[id][1]; 74 var args = this.functions[id][2]; 75 this.functions[id] = null; 76 func.apply(obj, args); 77 }; 78 79 this._createUniqueId = function() { 80 /* create a unique id to store the function by */ 81 while (this.lastid in this.functions && 82 this.functions[this.lastid]) { 83 this.lastid++; 84 if (this.lastid > 100000) { 85 this.lastid = 0; 86 } 87 } 88 return this.lastid; 89 }; 90 }; 91 92 // create a timer instance in the module namespace 93 var timer_instance = this.timer_instance = new Timer(); 94 95 // this is the function that should be called to register callables 96 this.schedule = function() { 97 timer_instance.registerCallable.apply(timer_instance, arguments)}; 98 99 // cross-browser event registration 100 var events_supported = true; 101 try { 102 window; 103 } catch(e) { 104 events_supported = false; 105 }; 106 107 if (events_supported) { 108 // first some privates 109 110 // a registry for events so they can be unregistered on unload of the 111 // body 112 var event_registry = {}; 113 114 // an iterator for unique ids 115 var currid = 0; 116 117 // just to make some guy on irc happy, store references to 118 // browser-specific wrappers so we don't have to find out the type of 119 // browser on every registration (yeah, yeah, i admit it was quite a 120 // good tip... ;) 121 var reg_handler = null; 122 var unreg_handler = null; 123 if (window.addEventListener) { 124 reg_handler = function(element, eventname, handler) { 125 // XXX not sure if anyone ever uses the last argument... 126 element.addEventListener(eventname, handler, false); 127 }; 128 unreg_handler = function(element, eventname, handler) { 129 element.removeEventListener(eventname, handler, false); 130 }; 131 } else if (window.attachEvent) { 132 reg_handler = function(element, eventname, handler) { 133 element.attachEvent('on' + eventname, handler); 134 }; 135 dereg_handler = function(element, eventname, handler) { 136 element.detachEvent('on' + eventname, handler); 137 }; 138 } else { 139 reg_handler = function(element, eventname, handler) { 140 var message = 'Event registration not supported or not ' + 141 'understood on this platform'; 142 if (global.exception) { 143 throw(new exception.NotSupported(message)); 144 } else { 145 throw(message); 146 }; 147 }; 148 }; 149 150 this.addEventHandler = function(element, eventname, handler, context) { 151 /* Method to register an event handler 152 153 Works in standards compliant browsers and IE, and solves a 154 number of different problems. Most obviously it makes that 155 there's only a single function to use and memorize, but also 156 it makes that 'this' inside the function points to the context 157 (usually you'll want to pass in the object the method is 158 defined on), and it fixes memory leaks (IE is infamous for 159 leaking memory, which can lead to problems if you register a 160 lot of event handlers, especially since the memory leakage 161 doesn't disappear on page reloads, see e.g. 162 http://www.bazon.net/mishoo/articles.epl?art_id=824). 163 164 Arguments: 165 166 * element - the object to register the event on 167 168 * eventname - a string describing the event (Mozilla style, 169 so without the 'on') 170 171 * handler - a reference to the function to be called when 172 the event occurs 173 174 * context - the 'this' variable inside the function 175 176 The arguments passed to the handler: 177 178 * event - a reference to the event fired 179 180 * all arguments passed in to this function besides the ones 181 described 182 183 */ 184 var args = new Array(); 185 for (var i=4; i < arguments.length; i++) { 186 args.push(arguments[i]); 187 }; 188 var wrapper = function(event) { 189 args = [event].concat(args); 190 handler.apply(context, args) 191 args.shift(); 192 }; 193 currid++; 194 event_registry[currid] = [element, eventname, wrapper]; 195 reg_handler(element, eventname, wrapper); 196 // return the wrapper so the event can be unregistered later on 197 return currid; 198 }; 199 200 this.removeEventHandler = function(regid) { 201 /* method to remove an event handler for both IE and Mozilla 202 203 pass in the id returned by addEventHandler 204 */ 205 // remove the entry from the event_registry 206 var args = event_registry[regid]; 207 if (!args) { 208 var message = 'removeEventListener called with ' + 209 'unregistered id'; 210 if (global.exception) { 211 throw((new exception.NotFound(message))); 212 } else { 213 throw(message); 214 }; 215 }; 216 delete event_registry[regid]; 217 unreg_handler(args[0], args[1], args[2]); 218 }; 219 220 this.removeAllEventHandlers = function() { 221 for (var id in event_registry) { 222 try { 223 misclib.removeEventHandler(id); 224 } catch(e) { 225 // element must have been discarded or something... 226 }; 227 }; 228 }; 229 }; 230 231 // string representation of objects 232 this.safe_repr = function(o) { 233 var visited = {}; 234 var ret = misclib._repr_helper(o, visited); 235 delete visited; 236 return ret; 237 }; 238 239 this.repr = function(o, dontfallback) { 240 try { 241 return misclib._repr_helper(o); 242 } catch(e) { 243 // when something fails here, we assume it's because of recursion 244 // and fall back to safe_repr() 245 if (dontfallback) { 246 throw(e); 247 }; 248 return misclib.safe_repr(o); 249 }; 250 }; 251 252 this._repr_helper = function(o, visited) { 253 var newid = 0; 254 if (visited) { 255 // XXX oomph... :| 256 for (var attr in visited) { 257 if (visited[attr] === o) { 258 return '#' + attr + '#'; 259 }; 260 newid++; 261 }; 262 }; 263 var ret = 'unknown?'; 264 var type = typeof o; 265 switch (type) { 266 case 'undefined': 267 ret = 'undefined' 268 break; 269 case 'string': 270 ret = '"' + string.escape(o) + '"'; 271 break; 272 case 'object': 273 if (o instanceof Array) { 274 if (visited) { 275 visited[newid] = o; 276 }; 277 var r = []; 278 for (var i=0; i < o.length; i++) { 279 var newo = o[i]; 280 r.push(misclib._repr_helper(newo, visited)); 281 }; 282 ret = '' 283 if (visited) { 284 ret += '#' + newid + '='; 285 }; 286 ret += '[' + r.join(', ') + ']'; 287 } else if (o instanceof Date) { 288 ret = '(new Date(' + o.valueOf() + '))'; 289 } else { 290 if (visited) { 291 visited[newid] = o; 292 }; 293 var r = []; 294 for (var attr in o) { 295 var newo = o[attr]; 296 r.push(attr + ': ' + misclib._repr_helper(newo, 297 visited)); 298 }; 299 ret = ''; 300 if (visited) { 301 ret += '#' + newid + '='; 302 }; 303 ret += '{' + r.join(', ') + '}'; 304 }; 305 break; 306 default: 307 ret = o.toString(); 308 break; 309 }; 310 return ret; 311 }; 312 313 this.dir = function(obj) { 314 /* list all the attributes of an object */ 315 var ret = []; 316 for (var attr in obj) { 317 ret.push(attr); 318 }; 319 return ret; 320 }; 321 322 this.print = function(message, win) { 323 /* write output in a way that the user can see it 324 325 creates a div in browsers, prints to stdout in spidermonkey 326 327 this is here not only as a convenient way to print output, but also 328 so that it's possible to override, that way customizing prints 329 from code 330 */ 331 if (win === undefined) { 332 win = window; 333 }; 334 message = '' + message; 335 var p = null; 336 try { 337 win.foo; 338 } catch(e) { 339 // no window, so we guess we're inside some JS console, assuming it 340 // has a print() function... if there are situations in which this 341 // doesn't suffice, please mail me (johnny@johnnydebris.net) 342 print(message); 343 return; 344 }; 345 var div = document.createElement('div'); 346 div.className = 'printed'; 347 div.appendChild(document.createTextNode(message)); 348 document.getElementsByTagName('body')[0].appendChild(div); 349 }; 350}(); 351