1 /* Documentation Note: 2 * Public methods and properties are commented with /** some text *\/ 3 * and private methods and properties are commented with // 4 * 5 * Please leave it that way to keep this documentation sane 6 */ 7 8 9 /* 10 * Karma Framework 11 * http://karmaeducation.org 12 * 13 * Copyright (c) 2009 14 * Bryan W Berry bryan@olenepal.org 15 * Felipe López Toledo zer.subzero@gmail.com 16 * 17 * Under MIT License: 18 * Permission is hereby granted, free of charge, to any person 19 * obtaining a copy of this software and associated documentation 20 * files (the "Software"), to deal in the Software without 21 * restriction, including without limitation the rights to use, 22 * copy, modify, merge, publish, distribute, sublicense, and/or sell 23 * copies of the Software, and to permit persons to whom the 24 * Software is furnished to do so, subject to the following 25 * conditions: 26 * 27 * The above copyright notice and this permission notice shall be 28 * included in all copies or substantial portions of the Software. 29 * 30 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 31 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 32 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 33 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 34 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 35 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 36 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 37 * OTHER DEALINGS IN THE SOFTWARE. 38 */ 39 40 /** 41 * @fileOverview Contains karma library 42 * @author Bryan Berry <bryan@olenepal.org> 43 * @author Felipe Lopez Toledo <zer.subzero@gmail.com> 44 */ 45 46 47 //common.js modules use exports object 48 if(!this.exports) { 49 exports = {}; 50 } 51 52 53 54 /** Karma is the namespace for the Karma library and Karma() is the constructor 55 * function for the Karma library object Karma. 56 * Karma() checks if the current document type is set to HTML 5, throws 57 * an error if not. Otherwise, initializes the karma object and returns 58 * a reference to that object. 59 * @namespace Global namespace for Karma library 60 * @constructor 61 * @param {Object} [options={}] options for intializing Karma library 62 * @param {String} [options.locale=''] sets current locale Not Yet Implemented 63 * @param {Array} [options.image=[]] array of images to be converted into a collection 64 * @param {Array} [options.audio=[]] array of audio to be converted into a collection 65 * @param {Array} [options.video=[]] NYI array of videos to be converted into a collection 66 * @param {Array} [options.svg=[]] array of SVG elements to be 67 * converted into a collection. Each SVG element must already exist in the html document 68 * @param {Array} [options.canvas=[]] array of canvas elements 69 * to be converted into a collection. Each canvas element must already exist in the 70 * html document and width and height of each element must be set as attributes 71 * @throws {Error} if the document type declaration is not set to HTML 5, e.g. 72 * <!DOCTYPE html> 73 * @throws {Error} If any of the initialization parameters are invalid values 74 * @returns {Object} Karma -- reference to the initialized Karma library 75 * @example 76 * 77 * var k = Karma({ 78 * image: [ 79 * {name: "ninja", file: "ninja.png"}, 80 * {name: "cowboy", file: "cowboy.png"} 81 * ], 82 * audio: [ 83 * {name: "woosh", file: "woosh.ogg"}, 84 * {name: "yeehaw", file: "yeehaw.ogg"} 85 * ], 86 * video: [ //Not Yet Implemented 87 * {name: "attack", file: "attack.ogv"}, 88 * {name: "ride", file: "ride.ogv"} 89 * ] 90 * canvas: [ 91 * {name: "ninja", domId: "ninjaCanvas"}, 92 * {name: "cowboy", domId: "cowboyCanvas"} 93 * ], 94 * svg: [ 95 * {name: "ninja", domId: "ninjaSvg"}, 96 * {name: "cowboy", domId: "cowboySvg"} 97 * ], 98 * }); 99 * Next, call the ready function with a callback to your program code 100 * 101 * k.ready(function () { ... your application code . . . } 102 * 103 * after that you can access each asset like so 104 * k.image.ninja; 105 * k.svg.cowboy; 106 * k.audio.yeehaw.play(); 107 * k.canvas.ninja.drawImage(k.image.ninja, 0, 0); 108 * 109 */ 110 var Karma = exports.Karma = function (options) { 111 Karma._isHtml5(document.doctype.nodeName); 112 113 if ( Karma._initialized === true ) { 114 return Karma; 115 } else { 116 return Karma._init(options); 117 } 118 }; 119 120 121 //helper functions 122 123 /**This emulates the Object.create method in ecmascript 5 spec 124 * This isn't a full implementation as it doesn't support an all of Object.create's features 125 * This has the same functionality as Crockford's beget method 126 * and this primary building block for prototypal inheritance in 127 * this library 128 * @param {Object} parent that the new object's prototype should point to 129 * @returns {Object} a new object whose prototype is parent 130 * @example 131 * 132 * var ninja = { weapon : "sword" }; 133 * var ninja1 = Karma.create(ninja); 134 * ninja1.weapon === "sword" 135 */ 136 Karma.create = function (parent){ 137 function F () {}; 138 F.prototype = parent; 139 return new F(); 140 }; 141 142 /** Returns a shallow copy of the passed in object 143 * @param {Object} target to be copied 144 * @returns {Object} a shallow copy of target 145 */ 146 Karma.clone = function (target){ 147 var copy = {}; 148 for ( var i in target ) { 149 if(target.hasOwnProperty(i)){ 150 copy[i] = target[i]; 151 } 152 } 153 return copy; 154 }; 155 156 /** Extends properties of the target object with those of 157 * the source object 158 * @param {Object} target object to be extended 159 * @param {Object} source whose properties will extend target 160 * @returns {Object} target extended by source 161 */ 162 Karma.objectPlus = function (target, source){ 163 for ( var i in source){ 164 if (source.hasOwnProperty(i)){ 165 target[i] = source[i]; 166 } 167 } 168 return target; 169 }; 170 171 Karma.extend = Karma.objectPlus; 172 173 /** Creates a new object that is a prototype of the first argument 174 * then extends it with the properties of the second argument 175 * @param {Object} parent1 will be prototype of returned object 176 * @param {Object} parent2 will extend properties of returned object 177 * @returns {Object} object that whose prototype is parent1 and has 178 * been extended with properties of parent2 179 */ 180 Karma.copyObjectPlus = function (parent1, parent2){ 181 function F () {}; 182 F.prototype = parent1; 183 var G = new F(); 184 return Karma.objectPlus(G, parent2); 185 }; 186 187 188 //Throws big ugly error if doctype isn't html5 189 Karma._isHtml5 = function (doctype){ 190 var regex = new RegExp('^html$', 'i'); 191 if(!regex.test(doctype)){ 192 var errorMsg = "ERROR: The doctype must be set to <!DOCTYPE html> " + 193 "in order to use Karma. Karma require you use html5"; 194 var errorElem = document.createElement('div'); 195 errorElem.setAttribute('id', 'errorDoctype'); 196 errorElem.innerText = errorMsg; 197 document.body.appendChild(errorElem); 198 throw new Error(errorMsg); 199 } 200 }; 201 202 /** 203 * Shuffles an array of items randomly 204 * @param {Array} oldList of choices to be shuffled 205 * @returns {Array} newlist of choices randomly reordered 206 */ 207 Karma.shuffle = function (oldList) { 208 var newList = oldList.slice(0); 209 for (var i = newList.length - 1; i > 0; i -= 1) { 210 var j = Karma.rand(0, i); 211 var t = newList[i]; 212 newList[i] = newList[j]; 213 newList[j] = t; 214 } 215 return newList; 216 }; 217 218 219 /** 220 * Converts a number to numerals in the specified locale. Currently only 221 * supports Nepali 222 * @param {Number} Number to be converted 223 * @param {locale} locale that number should be converted to 224 * @returns {String} Unicode string for localized numeral 225 */ 226 Karma.convertNumToLocale = function(num, locale){ 227 locale = locale || Karma.locale; 228 //48 is the base for western numerals 229 var convertDigit = function(digit){ 230 231 var numBase = 48; 232 var prefix = "u00"; 233 234 if (locale === "ne"){ 235 prefix = "u0"; 236 numBase = 2406; 237 } 238 239 return '\\' + prefix + 240 (numBase + parseInt(digit)).toString(16); 241 }; 242 243 var charArray = num.toString().split("").map(convertDigit); 244 return eval('"' + charArray.join('') + '"'); 245 }; 246 247 /** 248 * @name Karma._n 249 * @function 250 * @public 251 * Alias for Karma.convertNumToLocale. Converts a number to numerals to 252 * Karma.locale or to specified locale. Currently only supports Nepali 253 * @param {Number} Number to be converted 254 * @param {locale} locale that number should be converted to 255 * @returns {String} Unicode string for localized numeral 256 */ 257 Karma._n = Karma.convertNumToLocale; 258 259 /* Scales the dimensions of document.body to the innerHeight and innerWidth 260 * of the viewport, i.e. browser window, with a minor offset to the height to 261 * make sure the scrollbars do not appear 262 */ 263 Karma.scaleToViewport = function(){ 264 var width = window.innerWidth; 265 var height = window.innerHeight; 266 267 //hack to ensure scrollbars don't appear 268 if (height === 900){ 269 height = "" + 884 + "px"; 270 } else { 271 height = "" + (height - 13) + "px"; 272 } 273 274 document.body.style.width = "" + width + "px"; 275 document.body.style.height = height; 276 }; 277 278 // Below are geometry and math helper methods 279 280 /** 281 * Converts a value from degrees to radians. 282 * @param {Number} angle The angle in degrees 283 * @returns {Number} The angle in radians 284 */ 285 Karma.radians = function( angle ){ 286 return ( angle / 180 ) * Math.PI; 287 }; 288 289 /** 290 * Gets the square of the Euclidian (ordinary) distance between 2 points. 291 * @param {Object} Point No. 0 292 * @param {Number} Point0.x 293 * @param {Number} Point0.y 294 * @param {Object} Point No. 1 295 * @param {Number} Point1.x 296 * @param {Number} Point1.y 297 * @returns {Number} The square of the Euclidian distance 298 * @example 299 * 300 * p0 = {x:0, y:1}; 301 * p1 = {x:50, y:70}; 302 * var d = distance2(p0, p1); 303 * 304 */ 305 Karma.distance2 = function ( p0, p1 ) { 306 return (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 307 }; 308 309 /** 310 * Gets the Euclidian (ordinary) distance between 2 points.<br> 311 * <b>Warning:</b> It's slower than distance2 function 312 * @param {Object} Point No. 0 313 * @param {Number} Point0.x 314 * @param {Number} Point0.y 315 * @param {Object} Point No. 1 316 * @param {Number} Point1.x 317 * @param {Number} Point1.y 318 * @returns {Number} The Euclidian distance 319 * @example 320 * 321 * p0 = {x:0, y:1}; 322 * p1 = {x:50, y:70}; 323 * var d = distance2(p0, p1); 324 * 325 */ 326 Karma.distance = function ( p0, p1 ) { 327 return Math.sqrt( this.distance2( p0, p1 ) ); 328 }; 329 330 /** Returns a random number within the range provided 331 * @param {Number} lower limit of the range, lowest number that can be returned 332 * @param {Number} upper limit of the range, highest number that can be returned 333 * @returns {Number} number that is >= lower and <= upper 334 * @example 335 * 336 * var num = rand(0, 10); 337 * 338 * //num could be 0, 1, 2, 3 ... or 10 339 * 340 */ 341 Karma.rand = function ( lower, upper ){ 342 return Math.floor(Math.random() * (upper - lower + 1) + lower); 343 }; 344 345 346 Karma.extend(Karma, { 347 /** This is the global locale as passed to Karma(), 348 * such as "en", "es_SP" 349 * @fieldOf Karma 350 * @property {string} locale This is the global locale as passed to Karma() 351 * @default 'en' 352 */ 353 locale : 'en', 354 /** Collection of images with special helper 355 * methods added to each reference 356 * @fieldOf Karma 357 * @type object 358 * @default empty object 359 */ 360 image : {}, 361 /** Collection of audio files with special helper 362 * methods added to each reference 363 * @fieldOf Karma 364 * @type object 365 * @default empty object 366 */ 367 audio : {}, 368 /** Collection of html 5 canvases with special helper 369 * methods added to each reference 370 * @fieldOf Karma 371 * @type object 372 * @default empty object 373 */ 374 canvas : {}, 375 /** Collection of svgs with special helper 376 * methods added to each reference 377 * @fieldOf Karma 378 * @type object 379 * @default empty object 380 */ 381 svg : {}, 382 /** Collection of videos with special helper 383 * methods added to each reference 384 * @fieldOf Karma 385 * @type object 386 * @default empty object 387 */ 388 video : {}, 389 _localized : false, 390 _assetPath : "assets/", 391 _localePath : "", 392 _initialized : false, 393 _statusDiv: undefined, 394 _loaderDiv : undefined, 395 _counters : { total : 0, errors : 0, loaded : 0}, 396 397 //This constructs the Karma object per values provided by the user 398 _init: function(options) { 399 this._initialized = true; 400 401 //set up message that show count of assets loaded 402 //and has an ordered list to append error messages to 403 var _statusDiv = this._statusDiv = document.createElement('div'); 404 this._loaderDiv = this._loaderDiv = document.createElement('div'); 405 var errorList = document.createElement('ol'); 406 407 _statusDiv.setAttribute('id', 'karma-status'); 408 _statusDiv.setAttribute('style', 'position:absolute;'); 409 _statusDiv.innerHTML = 'Karma is loading ...'; 410 this._loaderDiv.setAttribute('id', 'karma-loader'); 411 this._loaderDiv.setAttribute('class', 'status'); 412 errorList.setAttribute('id', 'errorList'); 413 414 _statusDiv.appendChild(this._loaderDiv); 415 this._statusDiv.appendChild(errorList); 416 document.body.appendChild(_statusDiv); 417 418 //regular expression that matches the name of aprivate property 419 // the karma object 420 var regexPrivate = new RegExp('^_.*'); 421 422 for ( var option in options ) { 423 if (options.hasOwnProperty(option)){ 424 if (option === "image" || option === "audio" || option === 425 "svg" || option === "video" || option === "canvas"){ 426 427 if(!(options[option] instanceof Array)){ 428 throw new Error("" + option + " must be an array"); 429 } else if (options[option].length === 0){ 430 continue; 431 } 432 } else if (regexPrivate.test(option)){ 433 //don't overwrite a private property of karma object 434 continue; 435 } 436 437 switch (option){ 438 case "locale": 439 440 if (this._isValidLocale(options[option])){ 441 this.locale = this._normalizeLocale(options[option]); 442 this._localized = true; 443 this._localePath = Karma._computeLocalePath(this.locale); 444 } else { 445 throw new Error("locale provided to karma._init() is invalid"); 446 } 447 448 break; 449 case "image": 450 options[option]._type = 'image'; 451 Karma._makeCollection(options[option], 'image'); 452 break; 453 case "audio": 454 options[option]._type = 'audio'; 455 Karma._makeCollection(options[option], 'audio'); 456 break; 457 case "video": 458 options[option]._type = 'video'; 459 Karma._makeCollection(options[option], 'video'); 460 break; 461 case "svg": 462 options[option]._type = 'svg'; 463 Karma._makeCollection(options[option], 'svg'); 464 break; 465 case "canvas": 466 options[option]._type = 'canvas'; 467 Karma._makeCollection(options[option], 'canvas'); 468 break; 469 } 470 } 471 } 472 473 474 475 return this; 476 }, 477 478 /** Waits until all assets loaded(ready), then calls callback cb 479 * @memberOf Karma 480 * @param {Function} [cb] callback function 481 * @returns this 482 * @throws {Error} if Karma is not initialized with the 483 * Karma({ options }) function 484 * @example 485 * 486 * var k = Karma({ . . . your assets here . . . }); 487 * k.ready(function(){ .. your code here . . .}); 488 * 489 * your code will not be called until all assets have been loaded 490 * into collections 491 * 492 */ 493 ready : function( cb ) { 494 var that = this; 495 if (Karma._initialized !== true){ 496 throw new Error("Karma not initialized"); 497 } 498 499 if (this._counters.loaded !== this._counters.total){ 500 setTimeout(function(){ that.ready(cb);}, 5); 501 } else if (cb) { 502 //hide the "Karma is loading..." message 503 this._statusDiv.setAttribute('style', 'display:none;'); 504 505 cb(); 506 } else if (!cb) { 507 //hide the "Karma is loading..." message 508 this._statusDiv.setAttribute('style', 'display:none;'); 509 510 //if no options passed, show it works message 511 this._showStarterMessage(); 512 } 513 514 515 516 517 return this; 518 }, 519 520 //Display Apache-like "It works" message if no options 521 _showStarterMessage : function (){ 522 var starterMsg = document.createElement('div'); 523 starterMsg.setAttribute('id', 'starterMsg'); 524 starterMsg.innerHTML = "<h1>It Works</h1>"; 525 document.body.appendChild(starterMsg); 526 }, 527 528 //Updates visible counter of how many assets are loaded 529 _updateStatus : function (errorMsg) { 530 var loaded = this._counters.loaded; 531 var total = this._counters.total; 532 var errors = this._counters.errors; 533 this._loaderDiv.innerHTML = "Loaded " + loaded + " / " + total + 534 "" + (errors > 0 ? " Errors [ " + errors +" ]" : ''); 535 if (errorMsg) { 536 var liError = document.createElement('li'); 537 liError.innerHTML = errorMsg; 538 var errorList = document.getElementById('errorList'); 539 errorList.appendChild(liError); 540 } 541 }, 542 543 //matches 2 letter country code then optionally 544 //a dash or underscore followed by a country or language identifier 545 //i currently only allow a language identifier 2-3 chars long 546 _isValidLocale : function (locale) { 547 var localeRegex = new RegExp('^[a-zA-Z][a-zA-Z]([-_][a-zA-z]{2,3})?$'); 548 return localeRegex.test(locale); 549 }, 550 551 _normalizeLocale : function(locale) { 552 var lang = ""; 553 var country = ""; 554 var divider = ""; 555 556 lang = locale.slice(0, 2).toLowerCase(); 557 divider = "_"; 558 country = locale.slice(3, 6).toUpperCase(); 559 560 return locale.length > 2 ? "" + lang + divider + country : lang; 561 }, 562 563 564 565 }); 566 567 //Helper functions for creating assets 568 Karma._isLocalized = function (boolLocalized) { 569 if (typeof boolLocalized === "boolean" ) { 570 if(boolLocalized === true && 571 Karma.locale === undefined){ 572 throw new Error("You cannot localize a media asset" + 573 " if the global locale for Karma isn't set"); 574 } else { 575 return boolLocalized; 576 } 577 } else if (typeof boolLocalized === undefined){ 578 return false; 579 } else{ 580 throw new Error("This is not a valid value for the localized option"); 581 } 582 }; 583 584 Karma._computeLocalePath = function(locale) { 585 return Karma._assetPath + locale + "/"; 586 }; 587 588 589 590 591 Karma._makeCollection = function (configs, type){ 592 var makeAsset = function (config){ 593 var asset = undefined; 594 var target = undefined; 595 switch(type){ 596 case "image": 597 target = Karma.kImage; 598 break; 599 case "audio": 600 target = Karma.kAudio; 601 break; 602 case "video": 603 target = Karma.kVideo; 604 break; 605 case "svg": 606 target = Karma.kSvg; 607 break; 608 case "canvas": 609 target = Karma.kCanvas; 610 break; 611 } 612 613 asset = Karma.create(target)._init(config); 614 Karma[type][config.name] = asset; 615 }; 616 617 configs.forEach(function(config){ makeAsset(config);}); 618 }; 619 620 621 622 623 624 //Prototype objects for assets 625 626 627 /** Prototype object for images 628 * @class This object is the prototype for images submitted to Karma in the 629 * Karma() method 630 * @ throws {Error} if the image asset is set to be localized but 631 * the global locale is not set on the Karma object 632 * @ throws {Error} if the name and file properties are not supplied 633 * @example 634 * kImage is the prototype object for images. This 'media' asset is loaded 635 * in a distinctly different way from the canvas or svg assets. 636 * 637 */ 638 Karma.kImage = 639 { 640 /** file location of image 641 * @type String 642 * @default "" 643 */ 644 file : "", 645 /** media object 646 * @type Image 647 * @default undefined 648 */ 649 media : undefined, 650 //actual path to the file 651 _path : "", 652 //if using localized version of this image 653 _localized : false, 654 _type : "image", 655 //initializes kImage instance with values provided by user 656 _init : function (image) { 657 image._localized = image._localized || false; 658 Karma._counters.total++; 659 660 if (image.name === undefined || image.file === undefined){ 661 throw new Error("properties name and file have to be defined"); 662 } else { 663 this.name = image.name; 664 this.file = image.file; 665 } 666 667 this.media = new Image(); 668 669 if(Karma._isLocalized(image._localized)){ 670 this._localized = image._localized; 671 this._path = Karma._localePath + "image/"; 672 } else { 673 this._path = Karma._assetPath + "image/"; 674 } 675 676 //IMPORTANT: This one magic line loads the file 677 this.media.src = this.src = this._path + this.file; 678 679 //add event handlers 680 this._addEventHandlers(); 681 682 683 return this; 684 }, 685 //Adds event handlers to update the counters when 686 //the image is successfully or unsuccessfully loaded 687 _addEventHandlers : function () { 688 var that = this; 689 690 that.media.addEventListener( 691 "load", 692 function (e) { 693 Karma._counters.loaded++; 694 Karma._updateStatus(); 695 that.status = "loaded";}, false); 696 697 that.media.addEventListener( 698 "error", 699 function (e) { 700 Karma._counters.errors++; 701 that.status = "error"; 702 var errorMsg = "Error: " + that._type.toUpperCase() + 703 " " + that.name + " cannot be loaded."; 704 Karma._updateStatus(errorMsg); 705 }, 706 false); 707 that.media.addEventListener( 708 "abort", 709 function (e) { 710 Karma._counters.total++; 711 that.status = "aborted"; 712 var errorMsg = "ABORT: " + that._type.toUpperCase() + 713 " " + that.name + " loading was aborted."; 714 Karma._updateStatus(errorMsg); 715 716 }, false); 717 } 718 719 }; 720 721 /** Prototype object for audio files 722 * @class This object is the prototype for audio files submitted to Karma in the 723 * Karma() method 724 * @ throws {Error} if the individual audio asset is set to be localized but 725 * the globale locale is not set on the Karma object 726 * @ throws {Error} if the name and file properties are not supplied 727 * @example 728 * kAudio is the prototype object for audio 729 * The audio assets are loaded in a distinctly different way 730 * from the canvas or svg assets. They also have distinctly different 731 * helper methods 732 * 733 * You initialize the kAudio assets by passing an array of objects 734 */ 735 Karma.kAudio = { 736 /** file location of asset 737 * @type String 738 * @default "" 739 */ 740 file : "", 741 /** Media object. You can access the src, autobuffer, autoplay, loop, and 742 * controls attributes 743 * via the media property of kAudio. Read more about the properties of the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#media-element-attributes">HTML 5 media element</a> 744 * @type Audio 745 * @default undefined 746 */ 747 media : undefined, 748 //actual path to the file 749 _path : "", 750 //if using localized version of this asset 751 _localized : false, 752 _type : "audio", 753 //initializes kAudio instance with values provided by user 754 _init : function (audio) { 755 audio._localized = audio._localized || false; 756 Karma._counters.total++; 757 758 if (audio.name === undefined || audio.file === undefined){ 759 throw new Error("properties name and file have to be defined"); 760 } else { 761 this.name = audio.name; 762 this.file = audio.file; 763 } 764 765 this.media = new Audio(); 766 767 if(Karma._isLocalized(audio._localized)){ 768 this._localized = audio._localized; 769 this._path = Karma._localePath + "audio/"; 770 } else { 771 this._path = Karma._assetPath + "audio/"; 772 } 773 774 775 //IMPORTANT: This one magic line loads the file 776 this.media.src = this.src = this._path + this.file; 777 778 //add event handlers 779 this._addEventHandlers(); 780 781 this.media.autobuffer = true; 782 this.media.load(); 783 784 785 return this; 786 }, 787 //Adds event handlers to update the counters when 788 //the asset is successfully or unsuccessfully loaded 789 _addEventHandlers : function () { 790 var that = this; 791 //'canplaythrough' event is a Browser Hack recommended by chromium devs 792 //http://code.google.com/p/chromium/issues/detail?id=20251&q=loading%20audio&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20Summary%20Modified%20Owner%20Mstone%20OS#c4 793 794 that.media.addEventListener( 795 "canplaythrough", 796 function (e) { 797 Karma._counters.loaded++; 798 Karma._updateStatus(); 799 that.status = "loaded";}, false); 800 801 that.media.addEventListener( 802 "error", 803 function (e) { 804 Karma._counters.errors++; 805 that.status = "error"; 806 var errorMsg = "Error: " + that._type.toUpperCase() + 807 " " + that.name + " cannot be loaded."; 808 Karma._updateStatus(errorMsg); 809 }, 810 false); 811 that.media.addEventListener( 812 "abort", 813 function (e) { 814 Karma._counters.total++; 815 that.status = "aborted"; 816 var errorMsg = "ABORT: " + that._type.toUpperCase() + 817 " " + that.name + " loading was aborted."; 818 Karma._updateStatus(errorMsg); 819 820 }, false); 821 822 }, 823 /** Plays the audio file */ 824 play : function () { 825 this.media.play(); 826 } 827 828 }; 829 830 /** NYI:Prototype object for Video files 831 * @class Not Yet Implemented:This object is the prototype for video files submitted 832 * to Karma in the Karma() method 833 * @ throws {Error} if the individual video asset is set to be localized but 834 * the globale locale is not set on the Karma object 835 * @ throws {Error} if the name and file properties are not supplied 836 */ 837 Karma.kVideo = { 838 /** file location of asset 839 * @type String 840 * @default "" 841 */ 842 file : "", 843 /** media object 844 * @type Video 845 * @default undefined 846 */ 847 media : undefined, 848 //actual path to the file 849 _path : "", 850 //if using localized version of this asset 851 _localized : false, 852 _type : "video", 853 //initializes kVideo instance with values provided by user 854 _init : function (video) { 855 //Not Yet Implemented 856 Karma._counters.errors++; 857 throw new Error("Video is not Yet Implemented"); 858 859 video._localized = video._localized || false; 860 Karma._counters.total++; 861 862 if (video.name === undefined || video.file === undefined){ 863 throw new Error("properties name and file have to be defined"); 864 } else { 865 this.name = video.name; 866 this.file = video.file; 867 } 868 869 this.media = new Video(); 870 871 if(Karma._isLocalized(video._localized)){ 872 this._localized = video._localized; 873 this._path = Karma._localePath + "video/"; 874 } else { 875 this._path = Karma._assetPath + "video/"; 876 } 877 878 879 //IMPORTANT: This one magic line loads the file 880 this.media.src = this.src = this._path + this.file; 881 882 //add event handlers 883 this._addEventHandlers(); 884 885 return this; 886 }, 887 //Adds event handlers to update the counters when 888 //the asset is successfully or unsuccessfully loaded 889 _addEventHandlers : function () { 890 var that = this; 891 //'canplaythrough' event is a Browser Hack recommended by chromium devs 892 //http://code.google.com/p/chromium/issues/detail?id=20251&q=loading%20audio&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20Summary%20Modified%20Owner%20Mstone%20OS#c4 893 894 that.media.addEventListener( 895 "canplaythrough", 896 function (e) { 897 Karma._counters.loaded++; 898 Karma._updateStatus(); 899 that.status = "loaded";}, false); 900 901 that.media.addEventListener( 902 "error", 903 function (e) { 904 Karma._counters.errors++; 905 that.status = "error"; 906 var errorMsg = "Error: " + that._type.toUpperCase() + 907 " " + that.name + " cannot be loaded."; 908 Karma._updateStatus(errorMsg); 909 }, 910 false); 911 that.media.addEventListener( 912 "abort", 913 function (e) { 914 Karma._counters.total++; 915 that.status = "aborted"; 916 var errorMsg = "ABORT: " + that._type.toUpperCase() + 917 " " + that.name + " loading was aborted."; 918 Karma._updateStatus(errorMsg); 919 920 }, false); 921 922 } 923 924 }; 925 926 927 928 /** Prototype object for each canvas element submitted to Karma in the 929 * Karma() method 930 * @throws {Error} if the name and domId for the canvas element are not specified 931 * @thows {Error} if the supplied domId does not match an element in the DOM 932 * @class This object is the prototype for each canvas element submitted to Karma in the 933 * Karma() method 934 */ 935 Karma.kCanvas = { 936 /** Name of the canvas, used internally by karma.js 937 * @type String 938 * @default '' 939 */ 940 name : '', 941 /** Width of canvas element 942 * @type Number 943 * @default 0 944 */ 945 width: 0, 946 /** Height of canvas element 947 * @type Number 948 * @default 0 949 */ 950 height: 0, 951 /** Whether canvas is visible 952 * @type boolean 953 * @default true 954 */ 955 visible: true, 956 /** Element ID for canvas element in html document. This value is read-only 957 * @type String 958 * @default undefined 959 */ 960 domId: undefined, 961 /** Reference to the DOM element 962 * @type DOMElement 963 * @default undefined 964 * @example 965 * //You can access all properties and methods of the underlying DOM element 966 * //using the 'node' property 967 * Karma.canvas.someCanvas.node.dispatchEvent( ... some event ...); 968 * var stuff = Karma.canvas.someCanvas.node.innerHTML; 969 * 970 */ 971 node: undefined, 972 /** The 2 Dimensional Rendering context property for this canvas 973 * @type 2DRenderingContext 974 * @default undefined 975 * @example 976 * //Almost all of the context attributes and methods are wrapped in helper functions 977 * //but you can also access them directly using the ctx property 978 * Karma.canvas.someCanvas.ctx.drawImage(someImage, x, y); 979 * Karma.canvas.someCanvas.ctx.fillStyle = "#ffffff"; 980 */ 981 ctx: undefined, 982 983 //initializes object with values provides by user 984 _init: function (config) { 985 for (var option in config){ 986 if (config.hasOwnProperty(option)){ 987 switch (option){ 988 case "name": 989 this.name = config[option]; 990 break; 991 case "domId": 992 this.domId = config[option]; 993 break; 994 case "width": 995 if(!this.height){ 996 throw new Error("If you specify a width you must also" + 997 "specify a height"); 998 } 999 this.width = config[option]; 1000 break; 1001 case "height": 1002 if(!this.width){ 1003 throw new Error("If you specify a height you must also" + 1004 "specify a width"); 1005 } 1006 this.height = parseInt(config.option, 10); 1007 break; 1008 case "fps": 1009 this.fps = parseInt(config.option, 10); 1010 break; 1011 } 1012 } 1013 } 1014 1015 if(this.domId && document.getElementById(this.domId)){ 1016 this.node = document.getElementById(this.domId); 1017 this.ctx = this.node.getContext('2d'); 1018 } else { 1019 throw new Error('you must specify a valid domId that' + 1020 'is in your html page'); 1021 } 1022 1023 if(!config.height && !config.width){ 1024 this.width = parseInt(this.node.getAttribute('width'), 10); 1025 this.height = parseInt(this.node.getAttribute('height'), 10); 1026 } 1027 1028 return this; 1029 }, 1030 /** Clear area of canvas element specified by parameters, if no 1031 * parameters supplied, clears entire canvas 1032 * @param {Number} [x=0] x coordinate, defaults to zero if left blank 1033 * @param {Number} [y=0] y coordinate, defaults to zero if left blank 1034 * @param {Number} [width=0] width of area to be cleared, defaults 1035 * entire width of canvas 1036 * @param {Number} [height=0] height of area to be cleared, defaults 1037 * entire height of canvas 1038 * @returns this 1039 * @example 1040 * 1041 * k.canvas.ninja.clear(); 1042 * // clears the entire ninja canvas 1043 * 1044 * k.canvas.ninja.clear(0, 10, 20, 30); 1045 * //clears a specific portion of the ninja canvas 1046 * 1047 */ 1048 clear : function ( x, y, width, height ) { 1049 var that = this; 1050 that.ctx.clearRect( 1051 x || 0, 1052 y || 0, 1053 width || that.width, 1054 height || that.height 1055 ); 1056 return that; 1057 }, 1058 1059 /** The globalAlpha attribute gives an alpha value that is applied to shapes 1060 * and images before they are composited onto the canvas 1061 * @param {Number} number in the range from 0.0 to 1.0 1062 * @returns this 1063 */ 1064 globalAlpha : function (attribute){ 1065 var name = 'globalAlpha'; 1066 this.ctx[name] = attribute; 1067 return this; 1068 }, 1069 1070 /** Sets the globalCompositeOperation attribute, which sets how shapes and images 1071 * are drawn onto the existing bitmap, once they have had globalAlpha and the 1072 * current transformation matrix applied. 1073 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1074 * @param {String} globalCompositeOperation source-atop, 1075 * source-in, source-out, 1076 * source-over, destination-atop, destination-in, destination-out, destination-over, 1077 * lighter 1078 * @returns this 1079 */ 1080 globalCompositeOperation: function (attribute){ 1081 var name = ' globalCompositeOperation'; 1082 this.ctx[name] = attribute; 1083 return this; 1084 }, 1085 1086 /** Sets the lineWidth attribute which gives the width of lines, in coordinate space 1087 * units. 1088 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1089 * @param {Number} lineWidth 1090 * @returns this 1091 */ 1092 lineWidth: function (attribute){ 1093 var name = 'lineWidth'; 1094 this.ctx[name] = attribute; 1095 return this; 1096 }, 1097 /** The lineCap attribute defines the type of endings that UAs will place on 1098 * the end of lines. 1099 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1100 * @param {String} type butt, round, square 1101 * @returns this 1102 */ 1103 lineCap: function (attribute){ 1104 var name = 'lineCap'; 1105 this.ctx[name] = attribute; 1106 return this; 1107 }, 1108 /** The lineJoin attribute defines the type of corners that UAs will place 1109 * where two lines meet. The three valid values are bevel, round, and miter. 1110 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1111 * @param {String} type 1112 * @returns this 1113 */ 1114 lineJoin: function (attribute){ 1115 var name = 'lineJoin'; 1116 this.ctx[name] = attribute; 1117 return this; 1118 }, 1119 1120 /** Sets the miter limit 1121 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1122 * @param {Number} number 1123 * @returns this 1124 */ 1125 miterLimit: function (attribute){ 1126 var name = 'miterLimit'; 1127 this.ctx[name] = attribute; 1128 return this; 1129 }, 1130 /** Sets the font property and takes the same syntax as setting the font property 1131 * in CSS 1132 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1133 * @param {String} 1134 * @returns this 1135 */ 1136 font: function (attribute){ 1137 var name = 'font'; 1138 this.ctx[name] = attribute; 1139 return this; 1140 }, 1141 1142 /** Changes the text alignment. The possible values are start, end, left, right, 1143 * and center. The default is start. Other values are ignored. 1144 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1145 * @param {string} alignment 1146 * @returns this 1147 */ 1148 textAlign: function (attribute){ 1149 var name = 'textAlign'; 1150 this.ctx[name] = attribute; 1151 return this; 1152 }, 1153 1154 /** Changes the baseline alignment. If the value is one of top, hanging, middle, 1155 * alphabetic, ideographic, or bottom, then the value must be changed to the new value. 1156 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1157 * @param {String} alignment 1158 * @returns this 1159 */ 1160 textBaseline: function (attribute){ 1161 var name = 'textBaseline'; 1162 this.ctx[name] = attribute; 1163 return this; 1164 }, 1165 1166 /** Save the current state of the context 1167 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1168 * @param 1169 * @returns this 1170 */ 1171 save : function ( ){ 1172 var name = 'save'; 1173 this.ctx[name].apply(this.ctx, arguments); 1174 return this; 1175 }, 1176 /** Restore the saved context 1177 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1178 * @param 1179 * @returns this 1180 */ 1181 restore : function ( ){ 1182 var name = 'restore'; 1183 this.ctx[name].apply(this.ctx, arguments); 1184 return this; 1185 }, 1186 /** Perform a scale transformation 1187 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1188 * @param 1189 * @returns this 1190 */ 1191 scale : function ( ){ 1192 var name = 'scale'; 1193 this.ctx[name].apply(this.ctx, arguments); 1194 return this; 1195 }, 1196 /** Perform a rotation transformation 1197 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1198 * @param 1199 * @returns this 1200 */ 1201 rotate : function ( ){ 1202 var name = 'rotate'; 1203 this.ctx[name].apply(this.ctx, arguments); 1204 return this; 1205 }, 1206 /** Performa a translation transformation 1207 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1208 * @param 1209 * @returns this 1210 */ 1211 translate : function ( ){ 1212 var name = 'translate'; 1213 this.ctx[name].apply(this.ctx, arguments); 1214 return this; 1215 }, 1216 1217 /** Transform the identity matrix 1218 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1219 * @param 1220 * @returns this 1221 */ 1222 transform : function ( ){ 1223 var name = 'transform'; 1224 this.ctx[name].apply(this.ctx, arguments); 1225 return this; 1226 }, 1227 /** Set the transform 1228 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1229 * @param 1230 * @returns this 1231 */ 1232 setTransform : function ( ){ 1233 var name = 'setTransform'; 1234 this.ctx[name].apply(this.ctx, arguments); 1235 return this; 1236 }, 1237 /** Clear a rectangular area 1238 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1239 * @param 1240 * @returns this 1241 */ 1242 clearRect : function ( ){ 1243 var name = 'clearRect'; 1244 this.ctx[name].apply(this.ctx, arguments); 1245 return this; 1246 }, 1247 /** Fill a rectangular area 1248 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1249 * @param 1250 * @returns this 1251 */ 1252 fillRect : function ( ){ 1253 var name = 'fillRect'; 1254 this.ctx[name].apply(this.ctx, arguments); 1255 return this; 1256 }, 1257 1258 /** Draw the outline of the rectangle 1259 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1260 * @param 1261 * @returns this 1262 */ 1263 strokeRect : function ( ){ 1264 var name = 'strokeRect'; 1265 this.ctx[name].apply(this.ctx, arguments); 1266 return this; 1267 }, 1268 /** Begin a path 1269 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1270 * @param 1271 * @returns this 1272 */ 1273 beginPath : function ( ){ 1274 var name = 'beginPath'; 1275 this.ctx[name].apply(this.ctx, arguments); 1276 return this; 1277 }, 1278 /** End a path 1279 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1280 * @param 1281 * @returns this 1282 */ 1283 closePath : function ( ){ 1284 var name = 'closePath'; 1285 this.ctx[name].apply(this.ctx, arguments); 1286 return this; 1287 }, 1288 /** Move to specified coordinates 1289 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1290 * @param 1291 * @returns this 1292 */ 1293 moveTo : function ( ){ 1294 var name = 'moveTo'; 1295 this.ctx[name].apply(this.ctx, arguments); 1296 return this; 1297 }, 1298 1299 1300 /** Draw a line to the given coordinates 1301 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1302 * @param 1303 * @returns this 1304 */ 1305 lineTo : function ( ){ 1306 var name = 'lineTo'; 1307 this.ctx[name].apply(this.ctx, arguments); 1308 return this; 1309 }, 1310 1311 /** Draw a quadratic curve to given coordinates 1312 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1313 * @param 1314 * @returns this 1315 */ 1316 quadraticCurveTo : function ( ){ 1317 var name = 'quadraticCurveTo'; 1318 this.ctx[name].apply(this.ctx, arguments); 1319 return this; 1320 }, 1321 /** Draw a bezier curve to given coordinates 1322 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1323 * @param 1324 * @returns this 1325 */ 1326 bezierCurveTo : function ( ){ 1327 var name = 'bezierCurveTo'; 1328 this.ctx[name].apply(this.ctx, arguments); 1329 return this; 1330 }, 1331 /** Draw an arc to the given points 1332 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1333 * @param 1334 * @returns this 1335 */ 1336 arcTo : function ( ){ 1337 var name = 'arcTo'; 1338 this.ctx[name].apply(this.ctx, arguments); 1339 return this; 1340 }, 1341 /** Create an arc 1342 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1343 * @param 1344 * @returns this 1345 */ 1346 arc : function ( ){ 1347 var name = 'arc'; 1348 this.ctx[name].apply(this.ctx, arguments); 1349 return this; 1350 }, 1351 1352 /** Create a rectangle 1353 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1354 * @param 1355 * @returns this 1356 */ 1357 rect : function ( ){ 1358 var name = 'rect'; 1359 this.ctx[name].apply(this.ctx, arguments); 1360 return this; 1361 }, 1362 /** fill in the current subpaths with the current fillstyle 1363 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1364 * @param 1365 * @returns this 1366 */ 1367 fill : function ( ){ 1368 var name = 'fill'; 1369 this.ctx[name].apply(this.ctx, arguments); 1370 return this; 1371 }, 1372 /** Stroke the subpaths 1373 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1374 * @param 1375 * @returns this 1376 */ 1377 stroke : function ( ){ 1378 var name = 'stroke'; 1379 this.ctx[name].apply(this.ctx, arguments); 1380 return this; 1381 }, 1382 1383 /** description 1384 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1385 * @param 1386 * @returns this 1387 */ 1388 clip : function ( ){ 1389 var name = 'clip'; 1390 this.ctx[name].apply(this.ctx, arguments); 1391 return this; 1392 }, 1393 /** description 1394 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1395 * @param 1396 * @returns this 1397 */ 1398 fillText : function ( ){ 1399 var name = 'fillText'; 1400 this.ctx[name].apply(this.ctx, arguments); 1401 return this; 1402 }, 1403 /** description 1404 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1405 * @param 1406 * @returns this 1407 */ 1408 strokeText : function ( ){ 1409 var name = 'strokeText'; 1410 this.ctx[name].apply(this.ctx, arguments); 1411 return this; 1412 }, 1413 /** description 1414 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1415 * @param 1416 * @returns this 1417 */ 1418 measureText : function ( ){ 1419 var name = 'measureText'; 1420 this.ctx[name].apply(this.ctx, arguments); 1421 return this; 1422 }, 1423 /** description 1424 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1425 * @param 1426 * @returns this 1427 */ 1428 isPointInPath : function ( ){ 1429 var name = 'isPointInPath'; 1430 this.ctx[name].apply(this.ctx, arguments); 1431 return this; 1432 }, 1433 1434 /** Sets the stroke style 1435 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1436 * @param 1437 * @returns this 1438 */ 1439 strokeStyle: function (attribute){ 1440 var name = 'strokeStyle'; 1441 this.ctx[name] = attribute; 1442 return this; 1443 }, 1444 1445 /** Sets the fill style 1446 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1447 * @param 1448 * @returns this 1449 */ 1450 fillStyle: function (attribute){ 1451 var name = 'fillStyle'; 1452 this.ctx[name] = attribute; 1453 return this; 1454 }, 1455 /** description 1456 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1457 * @param 1458 * @returns this 1459 */ 1460 createLinearGradient : function ( ){ 1461 var name = 'createLinearGradient'; 1462 this.ctx[name].apply(this.ctx, arguments); 1463 return this; 1464 }, 1465 /** description 1466 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1467 * @param 1468 * @returns this 1469 */ 1470 createRadialGradient : function ( ){ 1471 var name = 'createRadialGradient'; 1472 this.ctx[name].apply(this.ctx, arguments); 1473 return this; 1474 }, 1475 /** description 1476 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1477 * @param 1478 * @returns this 1479 */ 1480 createPattern : function ( ){ 1481 var name = 'createPattern'; 1482 this.ctx[name].apply(this.ctx, arguments); 1483 return this; 1484 }, 1485 /** description 1486 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1487 * @param 1488 * @returns this 1489 */ 1490 shadowOffsetX: function (attribute){ 1491 var name = 'shadowOffsetX'; 1492 this.ctx[name] = attribute; 1493 return this; 1494 }, 1495 /** description 1496 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1497 * @param 1498 * @returns this 1499 */ 1500 shadowOffsetY: function (attribute){ 1501 var name = 'shadowOffsetY'; 1502 this.ctx[name] = attribute; 1503 return this; 1504 }, 1505 /** description 1506 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1507 * @param 1508 * @returns this 1509 */ 1510 shadowBlur: function (attribute){ 1511 var name = 'shadowBlur'; 1512 this.ctx[name] = attribute; 1513 return this; 1514 }, 1515 /** description 1516 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1517 * @param 1518 * @returns this 1519 */ 1520 shadowColor: function (attribute){ 1521 var name = 'shadowColor'; 1522 this.ctx[name] = attribute; 1523 return this; 1524 }, 1525 /** description 1526 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1527 * @param 1528 * @returns this 1529 */ 1530 drawImage : function ( ){ 1531 var name = 'drawImage'; 1532 this.ctx[name].apply(this.ctx, arguments); 1533 return this; 1534 }, 1535 /** description 1536 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1537 * @param 1538 * @returns this 1539 */ 1540 getImageData : function ( ){ 1541 var name = 'getImageData'; 1542 this.ctx[name].apply(this.ctx, arguments); 1543 return this; 1544 }, 1545 /** description 1546 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1547 * @param 1548 * @returns this 1549 */ 1550 putImageData : function ( ){ 1551 var name = 'putImageData'; 1552 this.ctx[name].apply(this.ctx, arguments); 1553 return this; 1554 }, 1555 /** description 1556 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1557 * @param 1558 * @returns this 1559 */ 1560 createImageData : function ( ){ 1561 var name = 'createImageData'; 1562 this.ctx[name].apply(this.ctx, arguments); 1563 return this; 1564 }, 1565 /** description 1566 * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a> 1567 * @param 1568 * @returns this 1569 */ 1570 drawWindow : function ( ){ 1571 var name = 'drawWindow'; 1572 this.ctx[name].apply(this.ctx, arguments); 1573 return this; 1574 }, 1575 1576 1577 1578 1579 }; 1580 1581 1582 /** Prototype object for each svg element submitted to Karma in the 1583 * Karma() method 1584 * @throws {Error} if the name and domId for the svg element are not specified 1585 * @thows {Error} if the supplied domId does not match an element in the DOM 1586 * @class This object is the prototype for each svg element submitted to Karma in the 1587 * Karma() method 1588 */ 1589 Karma.kSvg = { 1590 /** name of instance, used internally 1591 * @typeof string 1592 * @default "" 1593 */ 1594 name : "", 1595 /** width of element 1596 * @type number 1597 * @default 0 1598 */ 1599 width: 0, 1600 /** height of element 1601 * @type number 1602 * @default 0 1603 */ 1604 height: 0, 1605 /** Status of element, either "loaded" or "error" 1606 * @type string 1607 * @default "" 1608 */ 1609 status: "", 1610 /** Whether canvas is visible. This value is read-only 1611 * @type boolean 1612 * @default true 1613 */ 1614 visible: true, 1615 /** Element ID for canvas element in html document. 1616 * @type String 1617 * @default undefined 1618 */ 1619 domId: undefined, 1620 /** Reference to the DOM element. 1621 * @type DOMElement 1622 * @default undefined 1623 * @example 1624 * //You can access all properties and methods of the underlying DOM element 1625 * //using the 'node' property 1626 * Karma.svg.someSvg.node.dispatchEvent; 1627 * Karma.svg.someSvg.node.addEvenListener(...); 1628 */ 1629 node: undefined, 1630 /** Reference to the SVGDocument. You can use the this.doc to manipulate 1631 * the SVG document 1632 * @type SVGDocument 1633 * @default undefined 1634 * @example 1635 * var myElem = Karma.svg.someSvg.doc.getElementById('foobar'); 1636 * Karma.svg.someSvg.doc.createElement(...); 1637 * Karma.svg.someSvg.doc.removeChild(someNode); 1638 * 1639 */ 1640 doc: undefined, 1641 /** Reference to the root element of the SVG Document 1642 * @type DocumentElement 1643 * @default undefined 1644 * @example 1645 * // The root element is equivalent to "document" in a regular html document 1646 * // The root attribute is used frequently with the jQuery SVG plugin for CSS selectors 1647 * $('#someId', Karma.svg.someSvg.root).css(.. manipulate css attributes ...); 1648 */ 1649 root: undefined, 1650 _localized : undefined, 1651 _init: function (config) { 1652 Karma._counters.total++; 1653 1654 for (var option in config){ 1655 if (config.hasOwnProperty(option)){ 1656 switch (option){ 1657 case "name": 1658 this.name = config[option]; 1659 break; 1660 case "domId": 1661 this.domId = config[option]; 1662 break; 1663 case "width": 1664 if(!this.height){ 1665 throw new Error("If you specify a width you must also" + 1666 "specify a height"); 1667 } 1668 this.width = parseInt(config[option], 10); 1669 break; 1670 case "height": 1671 if(!this.width){ 1672 throw new Error("If you specify a height you must also" + 1673 "specify a width"); 1674 } 1675 this.height = config[option]; 1676 break; 1677 } 1678 } 1679 } 1680 1681 if(this.domId && document.getElementById(this.domId)){ 1682 this.node = document.getElementById(this.domId); 1683 } else { 1684 throw new Error('you must specify a valid domId that' + 1685 'is in your html page'); 1686 } 1687 1688 if(!config.height && !config.width){ 1689 this.width = parseInt(this.node.getAttribute('width'), 10); 1690 this.height = parseInt(this.node.getAttribute('height'), 10); 1691 } 1692 1693 var that = this; 1694 that._addEventHandlers(); 1695 1696 return this; 1697 1698 1699 }, 1700 _addEventHandlers : function () { 1701 var that = this; 1702 that.doc = that.node.getSVGDocument(); 1703 that.node.addEventListener( 1704 "load", 1705 function (e) { 1706 that.doc = that.node.getSVGDocument(); 1707 that.root = that.doc.documentElement; 1708 Karma._counters.loaded++; 1709 Karma._updateStatus(); 1710 that.status = "loaded"; 1711 }, false); 1712 1713 that.node.addEventListener( 1714 "error", 1715 function (e) { 1716 Karma._counters.loaded--; 1717 Karma._counters.errors++; 1718 that.status = "error"; 1719 var errorMsg = "Error: " + that._type.toUpperCase() + 1720 " " + that.name + " cannot be loaded."; 1721 Karma._updateStatus(errorMsg); 1722 }, 1723 false); 1724 that.node.addEventListener( 1725 "abort", 1726 function (e) { 1727 that.status = "aborted"; 1728 var errorMsg = "ABORT: " + that._type.toUpperCase() + 1729 " " + that.name + " loading was aborted."; 1730 Karma._updateStatus(errorMsg); 1731 1732 }, false); 1733 1734 } 1735 }; 1736