1 /*global window, document*/ 2 /*jslint bitwise: true, unparam: true, regexp: true*/ 3 4 (function (window, document) { 5 'use strict'; 6 var BytePushers; 7 8 if (window.BytePushers !== undefined && window.BytePushers !== null) { 9 BytePushers = window.BytePushers; 10 } else { 11 window.BytePushers = {}; 12 BytePushers = window.BytePushers; 13 } 14 /**************************************************************************************************** 15 * BEGIN Array Extensions */ 16 if (!Array.prototype.every) { 17 Array.prototype.every = function (fun, funParameter) { 18 var t = Object.create(this), 19 len = t.length >>> 0, 20 i; 21 22 if (this === null) { 23 throw new TypeError(); 24 } 25 26 if (typeof fun !== "function") { 27 throw new TypeError(); 28 } 29 30 for (i = 0; i < len; i = i + 1) { 31 if (t.hasOwnProperty(i) && !fun.call(funParameter, t[i], i, t)) { 32 return false; 33 } 34 } 35 36 return true; 37 }; 38 } 39 40 // Production steps of ECMA-262, Edition 5, 15.4.4.18 41 // Reference: http://es5.github.com/#x15.4.4.18 42 if (!Array.prototype.forEach) { 43 44 Array.prototype.forEach = function forEach(callback, thisArg) { 45 46 var T, k, O, len, obj = {}, kValue; 47 48 if (this === null) { 49 throw new TypeError("this is null or not defined"); 50 } 51 52 // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 53 O = Object.create(this); 54 55 // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 56 // 3. Let len be ToUint32(lenValue). 57 len = O.length >>> 0; // Hack to convert O.length to a UInt32 58 59 // 4. If IsCallable(callback) is false, throw a TypeError exception. 60 // See: http://es5.github.com/#x9.11 61 if (obj.toString.call(callback) !== "[object Function]") { 62 throw new TypeError(callback + " is not a function"); 63 } 64 65 // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 66 if (thisArg) { 67 T = thisArg; 68 } 69 70 // 6. Let k be 0 71 k = 0; 72 73 // 7. Repeat, while k < len 74 while (k < len) { 75 // a. Let Pk be ToString(k). 76 // This is implicit for LHS operands of the in operator 77 // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 78 // This step can be combined with c 79 // c. If kPresent is true, then 80 if (Object.prototype.hasOwnProperty.call(O, k)) { 81 82 // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 83 kValue = O[k]; 84 85 // ii. Call the Call internal method of callback with T as the this value and 86 // argument list containing kValue, k, and O. 87 callback.call(T, kValue, k, O); 88 } 89 // d. Increase k by 1. 90 k = k + 1; 91 } 92 // 8. return undefined 93 }; 94 } 95 96 if (!Array.prototype.some) { 97 Array.prototype.some = function (fun, functionParameter) { 98 var t = Object.create(this), 99 len = t.length >>> 0, 100 i; 101 102 if (this === null) { 103 throw new TypeError(); 104 } 105 106 if (typeof fun !== "function") { 107 throw new TypeError(); 108 } 109 110 for (i = 0; i < len; i = i + 1) { 111 if (t.hasOwnProperty(i) && fun.call(functionParameter, t[i], i, t)) { 112 return true; 113 } 114 } 115 116 return false; 117 }; 118 } 119 120 if (!Array.prototype.isArray) { 121 Array.prototype.isArray = function (arg) { 122 var targetArray = (arg === true) ? arg : this; 123 return Object.prototype.toString.call(targetArray) === "[object Array]"; 124 }; 125 } 126 127 /* END Array Extensions * 128 ****************************************************************************************************/ 129 130 /**************************************************************************************************** 131 * BEGIN Date Extensions */ 132 /** 133 * <p>Function that is used to determine if two dates objects have the same date.</p> 134 * @function 135 * @param {@link Date} The date to evaluate against this object. 136 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True if the date passed in is equal the date object; otherwise return false. 137 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 138 */ 139 Date.prototype.isDateEqualTo = function (date) { 140 if (this.getFullYear() === date.getFullYear()) { 141 if (this.getMonth() === date.getMonth()) { 142 if (this.getDate() === date.getDate()) { 143 return true; 144 } 145 } 146 } 147 return false; 148 }; 149 150 /** 151 * <p>Function that is used to determine if two dates objects have the same date and time.</p> 152 * @function 153 * @param {@link Date} The date to evaluate against this object. 154 * @return {@link <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a>} True if the date passed in is equal the date object; otherwise return false. 155 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 156 */ 157 Date.prototype.isDateEqualToDateAndTime = function (date) { 158 if (this.getFullYear() === date.getFullYear()) { 159 if (this.getMonth() === date.getMonth()) { 160 if (this.getDate() === date.getDate()) { 161 if (this.getHours() === date.getHours()) { 162 if (this.getMinutes() === date.getMinutes()) { 163 return true; 164 } 165 } 166 } 167 } 168 } 169 return false; 170 }; 171 172 /** 173 * <p>Function that is used to determine a date is the day after another date.</p> 174 * @function 175 * @param {@link Date} The date to evaluate against this object. 176 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True if the date is the day after the original date; otherwise return false. 177 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 178 */ 179 Date.prototype.isDateEqualToTomorrow = function (date) { 180 if (this.getFullYear() === date.getFullYear()) { 181 if (this.getMonth() === date.getMonth()) { 182 if (this.getDate() + 1 === date.getDate()) { 183 return true; 184 } 185 } else if (this.getMonth() + 1 === date.getMonth()) { 186 if (this.isLastDayInMonth() && date.getDate() === 1) { 187 return true; 188 } 189 } 190 } else if (this.getFullYear() + 1 === date.getFullYear()) { 191 if (this.getMonth() === 11 && date.getMonth() === 0) { 192 if (this.getDate() === 31 && date.getDate() === 1) { 193 return true; 194 } 195 } 196 } 197 return false; 198 }; 199 200 /** 201 * <p>Function that is used to determine a date is the day before another date.</p> 202 * @function 203 * @param {@link Date} The date to evaluate against this object. 204 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True if the date is the day before the original date; otherwise return false. 205 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 206 */ 207 Date.prototype.isDateEqualToYesterday = function (date) { 208 if (this.getFullYear() === date.getFullYear()) { 209 if (this.getMonth() === date.getMonth()) { 210 if (this.getDate() - 1 === date.getDate()) { 211 return true; 212 } 213 } else if (this.getMonth() === date.getMonth() + 1) { 214 if (this.getDate() === 1 && date.isLastDayInMonth()) { 215 return true; 216 } 217 } 218 } else if (this.getFullYear() - 1 === date.getFullYear()) { 219 if (this.getMonth() === 0 && date.getMonth() === 11) { 220 if (this.getDate() === 1 && date.getDate() === 31) { 221 return true; 222 } 223 } 224 } 225 return false; 226 }; 227 228 /** 229 * <p>Tells you whether it is the last day in a month or not.</p> 230 * @private 231 * @returns <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True if it is the last day of the month, otherwise false. 232 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 233 */ 234 Date.prototype.isLastDayInMonth = function () { 235 var lastDayInMonth = this.getCurrentMonthTotalDays(); 236 if (this.getDate() === lastDayInMonth) { 237 return true; 238 } 239 return false; 240 }; 241 242 /** 243 * <p>Function that is used to get calendar total calendar days of the previous month.</p> 244 * @function 245 * @returns <a href="http://www.w3schools.com/jsref/jsref_obj_number.asp">Number</a> The total days in the previous month. 246 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 247 */ 248 Date.prototype.getPreviousMonthTotalDays = function () { 249 if (this.getMonth() === 0) { 250 return Date.monthNames[11].getTotalDays(this.getFullYear()); 251 } 252 253 return Date.monthNames[this.getMonth() - 1].getTotalDays(this.getFullYear()); 254 }; 255 256 /** 257 * <p>Function that is used to get the total calendar days of the next month.</p> 258 * @function 259 * @returns <a href="http://www.w3schools.com/jsref/jsref_obj_number.asp">Number</a> The total days in the next month. 260 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 261 */ 262 Date.prototype.getNextMonthTotalDays = function () { 263 if (this.getMonth() === 11) { 264 return Date.monthNames[0].getTotalDays(this.getFullYear()); 265 } 266 267 return Date.monthNames[this.getMonth() + 1].getTotalDays(this.getFullYear()); 268 }; 269 270 /** 271 * <p>Function that is used to get the total calendar days of the next month.</p> 272 * @function 273 * @returns <a href="http://www.w3schools.com/jsref/jsref_obj_number.asp">Number</a> The total days in the next month. 274 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 275 */ 276 Date.prototype.getCurrentMonthTotalDays = function () { 277 if (this.getMonth() === 11) { 278 return Date.monthNames[0].getTotalDays(this.getFullYear()); 279 } 280 281 return Date.monthNames[this.getMonth()].getTotalDays(this.getFullYear()); 282 }; 283 284 /** 285 * <p>Adds time to a date object.</p> 286 * @param <a href="http://www.w3schools.com/jsref/jsref_obj_number.asp">Number</a> time Represents the time you want to add to the date. 287 * 288 * @returns {String} A new Date object with the specified time added. 289 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 290 */ 291 Date.prototype.addTime = function (time) { 292 var newDate = new Date(), 293 wholeNumber = (time > 0) ? Math.floor(time) : Math.ceil(time), 294 fraction = ((time - wholeNumber).toFixed(2) * 100), 295 hourInMilliseconds = 1000 * 60 * 60 * wholeNumber, 296 minutesInMilliseconds = 1000 * 60 * fraction; 297 298 newDate.setTime(this.getTime()); 299 newDate.setTime(newDate.getTime() + hourInMilliseconds); 300 newDate.setTime(newDate.getTime() + minutesInMilliseconds); 301 302 return newDate; 303 }; 304 /** 305 * <p>Static function that tells you whether a date is the last day in a month or not.</p> 306 * @private 307 * @static 308 * @param <a href="http://www.w3schools.com/jsref/jsref_obj_date.asp">Number</a> time Represents the time you want to add to the date. 309 * @returns <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True if it is the last day of the month, otherwise false. 310 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 311 */ 312 Date.isLastDayInMonth = function (date) { 313 var lastDayInMonth = date.getCurrentMonthTotalDays(); 314 if (date.getDate() === lastDayInMonth) { 315 return true; 316 } 317 return false; 318 }; 319 320 /** 321 * <p>Static function that gets month name.</p> 322 * @private 323 * @static 324 * @param <a href="http://www.w3schools.com/jsref/jsref_obj_number.asp">Number</a> index Represents the position of the month in a month array. 325 * @param <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> useAbbr An optional boolean flag that governs whether the 326 * full name of the month is returned or its abbreviation. 327 * @returns {String} The name of the month. 328 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 329 */ 330 Date.getMonthName = function (index, getAbbr) { 331 if (getAbbr) { 332 return this.monthNames[index].abbr; 333 } 334 335 return this.monthNames[index].name; 336 }; 337 338 /** 339 * <p>Static field for the list of month.</p> 340 * @static 341 * @field 342 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 343 */ 344 Date.monthNames = [ 345 {"name": "January", "abbr": "Jan", "getTotalDays": function () { return 31; } }, 346 {"name": "February", "abbr": "Feb", "getTotalDays": function (year) { 347 if (year) { 348 return (year % 4 === 0) ? 29 : 28; 349 } 350 351 throw ("Expected parameter(Year) is not defined."); 352 }}, 353 {"name": "March", "abbr": "Mar", "getTotalDays": function () { return 31; }}, 354 {"name": "April", "abbr": "Apr", "getTotalDays": function () { return 30; }}, 355 {"name": "May", "abbr": "May", "getTotalDays": function () { return 31; }}, 356 {"name": "June", "abbr": "Jun", "getTotalDays": function () { return 30; }}, 357 {"name": "July", "abbr": "Jul", "getTotalDays": function () { return 31; }}, 358 {"name": "August", "abbr": "Aug", "getTotalDays": function () { return 31; }}, 359 {"name": "September", "abbr": "Sep", "getTotalDays": function () { return 30; }}, 360 {"name": "October", "abbr": "Oct", "getTotalDays": function () { return 31; }}, 361 {"name": "November", "abbr": "Nov", "getTotalDays": function () { return 30; }}, 362 {"name": "December", "abbr": "Dec", "getTotalDays": function () { return 31; }} 363 ]; 364 /* END Date Extensions * 365 ****************************************************************************************************/ 366 367 /**************************************************************************************************** 368 * BEGIN Object Extensions */ 369 /** 370 * <p>Static function that tells you whether an object is an array or not.</p> 371 * @static 372 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is an array. 373 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is an array, otherwise false. 374 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 375 */ 376 Object.isArray = function (someArray) { 377 var result = false; 378 if (Object.isDefined(someArray)) { 379 if (someArray.constructor.toString().indexOf("Array") > -1) { 380 result = true; 381 } 382 } 383 384 return result; 385 }; 386 387 /** 388 * <p>Static function that tells you whether an object is a date or not.</p> 389 * @static 390 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is a date. 391 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is an date, otherwise false. 392 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 393 */ 394 Object.isDate = function (someDate) { 395 var result = false; 396 if (Object.isDefined(someDate)) { 397 if (typeof someDate === "object" && someDate instanceof Date) { 398 result = true; 399 } 400 } 401 402 return result; 403 }; 404 /** 405 * <p>Static function that tells you whether an object is a string or not.</p> 406 * @static 407 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is a string. 408 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is an string, otherwise false. 409 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 410 */ 411 Object.isString = function (someString) { 412 var result = false; 413 if (Object.isDefined(someString)) { 414 if (typeof someString === "string" || (typeof someString === "object" && someString instanceof String)) { 415 result = true; 416 } 417 } 418 419 return result; 420 }; 421 422 /** 423 * <p>Static function that tells you whether an object is numeric or not.</p> 424 * @static 425 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is numeric. 426 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is numeric, otherwise false. 427 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 428 */ 429 Object.isNumeric = function (someNumber) { 430 var result = false; 431 if (Object.isDefined(someNumber)) { 432 if (typeof someNumber === "number" || (typeof someNumber === "object" && someNumber instanceof Number)) { 433 result = true; 434 } 435 } 436 437 return result; 438 }; 439 440 /** 441 * <p>Static function that tells you whether an object is a boolean or not.</p> 442 * @static 443 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is a boolean. 444 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is a boolean, otherwise false. 445 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 446 */ 447 Object.isBoolean = function (someBoolean) { 448 var result = false; 449 if (Object.isDefined(someBoolean)) { 450 if (typeof someBoolean === "boolean" || (typeof someBoolean === "object" && someBoolean instanceof Boolean)) { 451 result = true; 452 } 453 } 454 455 return result; 456 }; 457 458 /** 459 * <p>Static function that tells you whether an object is defined or not.</p> 460 * @static 461 * @param <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures">Object of some type</a> The object that will be tested to see if it is defined. 462 * @returns <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean">Boolean</a> True if an object is defined, otherwise false. 463 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 464 */ 465 Object.isDefined = function (target) { 466 var result = false; 467 if (target !== undefined && target !== null) { 468 result = true; 469 } 470 return result; 471 }; 472 473 Object.getProperty = function (target, property) { 474 var result = false, tmp; 475 if (Object.isDefined(target)) { 476 if (Object.isString(target)) { 477 tmp = JSON.parse(target); 478 } else { 479 tmp = target; 480 } 481 result = tmp[property]; 482 } 483 return result; 484 }; 485 /* END Object Extensions * 486 ****************************************************************************************************/ 487 488 /**************************************************************************************************** 489 * BEGIN String Extensions */ 490 /** 491 * <p>Function that is used to trim the white spaces from the beginning and end of the string.</p> 492 * @function 493 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_string.asp">String</a> The value of the string after it has been trimmed. 494 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 495 */ 496 String.prototype.trim = function () { 497 return this.replace(/^\s+|\s+$/g, ''); 498 }; 499 500 /** 501 * <p>Function that is used to determine if a string includes a certain character or string.</p> 502 * @function 503 * @param <a href="http://www.w3schools.com/jsref/jsref_obj_string.asp">String</a> The string we are checking if is included. 504 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_boolean.asp">Boolean</a> True of the string is included, otherwise false. 505 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 506 */ 507 if (!String.prototype.includes) { 508 String.prototype.includes = function () { 509 return String.prototype.indexOf.apply(this, arguments) !== -1; 510 }; 511 } 512 513 /** 514 * <p>Function that is used to format a sentence to camel case. (e.g. Hello world => helloWorld).</p> 515 * @function 516 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_string.asp">String</a> The value of the string after it has been formatted to camel case. 517 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 518 */ 519 String.prototype.toCamelCase = function () { 520 return this.replace(/^([A-Z])|\s(\w)/g, function (match, p1, p2) { 521 if (p2) { 522 return p2.toUpperCase(); 523 } 524 return p1.toLowerCase(); 525 }); 526 }; 527 528 /** 529 * <p>Function that is used to turn a string that is in camel case format to a Normal sentence format. (e.g. helloWorld => Hello World)</p> 530 * @function 531 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_string.asp">String</a> The value of the string after it has been formatted to a normal sentence format. 532 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 533 */ 534 String.prototype.toNormalCase = function () { 535 return this.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z])([A-Z])/g, '$1 $2').replace(/^./, function (str) {return str.toUpperCase(); }); 536 }; 537 538 /** 539 * <p>Convenience function that will format a string with dynamic variables.</p> 540 * @static 541 * @param {...string} string - first argument is the string to be formatted. The remaining arguments are the format items (e.g. "{0}") 542 * @function 543 * @return <a href="http://www.w3schools.com/jsref/jsref_obj_string.asp">String</a> The value of the string after it has been formatted. 544 * @author <a href="mailto:pouncilt.developer@gmail.com">Tonté Pouncil</a> 545 */ 546 String.format = function (someString) { 547 // The string containing the format items (e.g. "{0}") 548 // will and always has to be the first argument. 549 var theString = someString, i, regEx; 550 551 // start with the second argument (i = 1) 552 for (i = 0; i < arguments.length; i = i + 1) { 553 // "gm" = RegEx options for Global search (more than one instance) 554 // and for Multiline search 555 regEx = new RegExp("\\{" + i + "\\}", "gm"); 556 theString = theString.replace(regEx, arguments[i]); 557 } 558 559 return theString; 560 }; 561 /* END String Extensions *****************************************************************************************************/ 562 563 564 BytePushers.namespace = function (ns_string) { 565 var parts = ns_string.split('.'), parent = BytePushers; 566 // strip redundant leading global 567 if (parts[0] === "BytePushers") { 568 parts = parts.slice(1); 569 } 570 parts.forEach(function (part, index) { 571 // create a property if it doesn't exist 572 if (parent[part] === undefined) { 573 parent[part] = {}; 574 } 575 parent = parent[part]; 576 }); 577 return parent; 578 }; 579 580 /** 581 * inherit() returns a newly created object that inherits properties from the prototype object p. 582 * It uses the ECMAScript 5 function Object.create() if it is defined, and otherwise falls. 583 * 584 * @param p 585 * @returns {*} 586 */ 587 BytePushers.inherit = function (p) { 588 var t; 589 if (p === null) { // p must be non-null object 590 throw new TypeError(); 591 } 592 if (Object.create) { // if Object.create() is defined... 593 return Object.create(p); // then just use it. 594 } 595 596 t = typeof p; // Otherwise do some more type checking 597 598 if (t !== "object" && t !== "function") { 599 throw new TypeError(); 600 } 601 602 function F() {// Define a dummy constructor function. 603 return; 604 } 605 F.prototype = p; // Set its prototype property to p. 606 return new F(); // Use f() to create an "heir" of p. 607 }; 608 609 /** 610 * defineClass() -- a utility function for defining JavaScript classes. 611 * 612 * This function expects a single object as its only argument. It defines 613 * a new JavaScript class based on the data in that object and returns the 614 * constructor function of the new class. This function handles the repetitive 615 * tasks of defining classes: setting up the prototype object for correct 616 * inheritance, copying methods from other types, and so on. 617 * 618 * The object passed as an argument should have some or all of the 619 * following properties: 620 * 621 * name: the name of the class being defined. 622 * If specified, this value will be stored in the classname 623 * property of the prototype object. 624 * 625 * extend: The constructor of the class to be extended. If omitted, 626 * the Object() constructor will be used. This value will 627 * be stored in the superclass property of the prototype object. 628 * 629 * construct: The constructor function for the class. If omitted, a new 630 * empty function will be used. This value becomes the return 631 * value of the function, and is also stored in the constructor 632 * property of the prototype object. 633 * 634 * methods: An object that specifies the instance methods (and other shared 635 * properties) for the class. The properties of this object are 636 * copied into the prototype object of the class. If omitted, 637 * an empty object is used instead. Properties named 638 * "classname", "superclass", and "constructor" are reserved 639 * and should not be used in this object. 640 * 641 * statics: An object that specifies the static methods (and other static 642 * properties) for the class. The properties of this object become 643 * properties of the constructor function. If omitted, an empty 644 * object is used instead. 645 * 646 * borrows: A constructor function or array of constructor functions. 647 * The instance methods of each of the specified classes are copied 648 * into the prototype object of this new class so that the 649 * new class borrows the methods of each specified class. 650 * Constructors are processed in the order they are specified, 651 * so the methods of a class listed at the end of the array may 652 * overwrite the methods of those specified earlier. Note that 653 * borrowed methods are stored in the prototype object before 654 * the properties of the methods object above. Therefore, 655 * methods specified in the methods object can overwrite borrowed 656 * methods. If this property is not specified, no methods are 657 * borrowed. 658 * 659 * provides: A constructor function or array of constructor functions. 660 * After the prototype object is fully initialized, this function 661 * verifies that the prototype includes methods whose names and 662 * number of arguments match the instance methods defined by each 663 * of these classes. No methods are copied; this is simply an 664 * assertion that this class "provides" the functionality of the 665 * specified classes. If the assertion fails, this method will 666 * throw an exception. If no exception is thrown, any 667 * instance of the new class can also be considered (using "duck 668 * typing") to be an instance of these other types. If this 669 * property is not specified, no such verification is performed. 670 **/ 671 BytePushers.defineClass = function (data) { 672 // Extract the fields we'll use from the argument object. 673 // Set up default values. 674 var classname = data.name, 675 Superclass = data.extend || Object, 676 constructor = data.construct || function () {return; }, 677 methods = data.methods || {}, 678 statics = data.statics || {}, 679 borrows, 680 provides, 681 proto, 682 i1, 683 i2, 684 c1, 685 c2, 686 p1, 687 p2, 688 p3, 689 p4, 690 p5; 691 692 // Borrows may be a single constructor or an array of them. 693 if (!data.borrows) { 694 borrows = []; 695 } else if (data.borrows instanceof Array) { 696 borrows = data.borrows; 697 } else { 698 borrows = [ data.borrows ]; 699 } 700 701 // Ditto for the provides property. 702 if (!data.provides) { 703 provides = []; 704 } else if (data.provides instanceof Array) { 705 provides = data.provides; 706 } else { 707 provides = [ data.provides ]; 708 } 709 710 // Create the object that will become the prototype for our class. 711 proto = new Superclass(); 712 713 // Delete any noninherited properties of this new prototype object. 714 for (p1 in proto) { 715 if (proto.hasOwnProperty(p1)) { 716 delete proto[p1]; 717 } 718 } 719 720 // Borrow methods from "mixin" classes by copying to our prototype. 721 for (i1 = 0; i1 < borrows.length; i1 = i1 + 1) { 722 c1 = data.borrows[i1]; 723 borrows[i1] = c1; 724 // Copy method properties from prototype of c to our prototype 725 for (p2 in c1.prototype) { 726 if (typeof c1.prototype[p2] === "function") { 727 proto[p2] = c1.prototype[p2]; 728 } 729 } 730 } 731 732 // Copy instance methods to the prototype object 733 // This may overwrite methods of the mixin classes 734 for (p3 in methods) { 735 if (methods.hasOwnProperty(p3)) { 736 proto[p3] = methods[p3]; 737 } 738 } 739 740 // Set up the reserved "constructor", "superclass", and "classname" 741 // properties of the prototype. 742 proto.constructor = constructor; 743 proto.Superclass = Superclass; 744 // classname is set only if a name was actually specified. 745 if (classname) { 746 proto.classname = classname; 747 } 748 749 // Verify that our prototype provides all of the methods it is supposed to. 750 for (i2 = 0; i2 < provides.length; i2 = i2 + 1) { // for each class 751 c2 = provides[i2]; 752 for (p4 in c2.prototype) { // for each property 753 if (typeof c2.prototype[p4] === "function" && (p4 === "constructor" || p4 === "superclass")) { //methods only 754 // Check that we have a method with the same name and that 755 // it has the same number of declared arguments. If so, move on 756 if (proto.hasOwnProperty(p4) && typeof proto[p4] !== "function" && proto[p4].length !== c2.prototype[p4].length) { 757 // Otherwise, throw an exception 758 throw new Error("Class " + classname + " does not provide method " + c2.classname + "." + p4); 759 } 760 } 761 } 762 } 763 764 // Associate the prototype object with the constructor function 765 constructor.prototype = proto; 766 767 // Copy static properties to the constructor 768 for (p5 in statics) { 769 if (statics.hasOwnProperty(p5)) { 770 constructor[p5] = statics[p5]; 771 } 772 } 773 774 // Finally, return the constructor function 775 return constructor; 776 }; 777 778 BytePushers.isArrayLike = function (x) { 779 if (x instanceof Array) { // Real arrays are array-like 780 return true; 781 } 782 if (!x.hasOwnProperty("length")) { // Arrays must have a length property 783 return false; 784 } 785 if (typeof x.length !== "number") { // Length must be a number 786 return false; 787 } 788 if (x.length < 0) { // and nonnegative 789 return false; 790 } 791 if (x.length > 0) { 792 // If the array is nonempty, it must at a minimum 793 // have a property defined whose name is the number length-1 794 if (!x.hasOwnProperty((x.length - 1))) { 795 return false; 796 } 797 } 798 return true; 799 }; 800 801 // Return true if O has methods with the same name and arity as all 802 // methods in I.prototype. Otherwise, return false. Throws an exception 803 // if I is a built-in type with nonenumerable methods. 804 BytePushers.provides = function (O, I) { 805 var proto = I.prototype, 806 p6; 807 // If O actually is an instance of I, it obviously looks like I 808 if (O instanceof I) { 809 return true; 810 } 811 812 // If a constructor was passed instead of an object, use its prototype 813 if (typeof O === "function") { 814 O = O.prototype; 815 } 816 817 // The methods of built-in types are not enumerable, and we return 818 // undefined. Otherwise any object would appear to provide any of 819 // the built-in types. 820 if (I === Array || I === Boolean || I === Date || I === Error || I === Function || I === Number || I === RegExp || I === String) { 821 return undefined; 822 } 823 824 for (p6 in proto) { // Loop through all properties in I.prototype 825 // Ignore properties that are not functions 826 if (typeof proto[p6] === "function") { 827 // If O does not have a property by the same name return false 828 if (!(O.hasOwnProperty(p6))) { 829 return false; 830 } 831 // If that property is not a function, return false 832 if (typeof O[p6] !== "function") { 833 return false; 834 } 835 // If the two functions are not declared with the same number 836 // of arguments return false. 837 if (O[p6].length !== proto[p6].length) { 838 return false; 839 } 840 } 841 } 842 // If all the methods check out, we can finally return true. 843 return true; 844 }; 845 846 // This function creates a new enumerated type. The argument object specifies // the names and values of each instance of the class. The return value 847 // is a constructor function that identifies the new class. Note, however 848 // that the constructor throws an exception: you can't use it to create new 849 // instances of the type. The returned constructor has properties that // map the name of a value to the value itself, and also a values array, // a foreach() iterator function 850 BytePushers.enumeration = function (namesToValues) { 851 // This is the dummy constructor function that will be the return value. 852 var name, 853 e, 854 i3, 855 enumeration = function () { throw "Can't Instantiate Enumerations"; }, 856 proto; 857 858 enumeration.prototype = { // Enumerated values inherit from this object. 859 constructor: enumeration, // Identify type 860 toString: function () { return this.name; }, // Return name 861 valueOf: function () { return this.value; }, // Return value 862 toJSON: function () { return this.name; } // For serialization 863 }; 864 proto = enumeration; 865 enumeration.values = []; // An array of the enumerated value objects 866 867 // Now create the instances of this new type. 868 for (name in namesToValues) { // For each value 869 if (namesToValues.hasOwnProperty(name)) { 870 e = BytePushers.inherit(proto); // Create an object to represent it 871 e.name = name; // Give it a name 872 e.value = namesToValues[name]; // And a value 873 enumeration[name] = e; // Make it a property of constructor 874 enumeration.values.push(e); // And store in the values array 875 } 876 } 877 878 // A class method for iterating the instances of the class 879 enumeration.foreach = function (f, c) { 880 for (i3 = 0; i3 < this.values.length; i3 = i3 + 1) { 881 f.call(c, this.values[i3]); 882 } 883 }; 884 // Return the constructor that identifies the new type 885 return enumeration; 886 }; 887 }(window, document));