declare module EightBittr { /** * A basic representation of an in-game Thing. Size, velocity, and position * are stored, as well as a reference to the parent IEightBittr. */ export interface IThing { EightBitter: IEightBittr; top: number; right: number; bottom: number; left: number; width: number; height: number; xvel: number; yvel: number; } /** * Settings to initialize a new instance of the IEightBittr interface. */ export interface IEightBittrSettings { /** * How much to expand each pixel from raw sizing measurements to in-game. */ unitsize?: number; /** * A list of member attributes to copy from the source to the IEightBittr. */ constants?: string[]; /** * Where the constants should be copied from, if not the IEightBittr itself. */ constantsSource?: any; } /** * A summary of times for reset Functions and the overall operation. */ export interface IResetTime { /** * The name of the reset Function called. */ name: string; /** * The performance.now timestamp of when the reset started. */ timeStart: number; /** * The performance.now timestamp of when the reset finished. */ timeEnd: number; /** * Exactly how long it took between timeStart and timeEnd. */ timeTaken: number; } /** * A summary of times for reset Functions and the overall operation. */ export interface IResetTimes { /** * What order the reset Functions were called. */ order: string[]; /** * The total start, end, and taken times for the reset operation. */ total: IResetTime; /** * Start, end, and taken times for individual reset Functions, in order. */ times: IResetTime[]; } /** * An interface used exclusively as the parent of IGameStartr. IEightBittr * contains useful functions for manipulating IThings that are independent of * the required GameStartr modules. */ export interface IEightBittr { /** * How much to expand each pixel from raw sizing measurements to in-game. */ unitsize: number; /** * Timed result summaries of resetTimed, if it was called. */ resetTimes: EightBittr.IResetTimes; /** * Resets the EightBittr by calling all of the named reset member Functions * on itself. * * @param EightBitter * @param resets The ordered Array of reset Functions to be called. * @param customs Additional arguments to pass to all reset Functions. */ reset(EightBitter: IEightBittr, resets: string[], customs?: any): void; /** * Resets the EightBittr in a timed manner by calling all the named reset * member Functions on itself and adding the time (in milliseconds) along * along with the total process time to an Array, which is then returned. * * @param EightBitter * @param resets The ordered Array of reset Functions to be called. * @param customs Additional arguments to pass to all reset Functions. */ resetTimed(EightBitter: IEightBittr, resets: string[], customs?: any): void; /** * Creates and returns a new HTML element with no image smoothing. * * @param width How wide the canvas should be. * @param height How tall the canvas should be. * @returns A canvas of the given width and height. */ createCanvas(width: number, height: number): HTMLCanvasElement; /** * Shifts a Thing vertically by changing its top and bottom attributes. * * @param thing The Thing to shift vertically. * @param dy How far to shift the Thing. */ shiftVert(thing: IThing, dy: number): void; /** * Shifts a Thing horizontally by changing its top and bottom attributes. * * @param thing The Thing to shift horizontally. * @param dy How far to shift the Thing. */ shiftHoriz(thing: IThing, dx: number): void; /** * Sets the top of a Thing to a set number, changing the bottom based on its * height and the EightBittr's unisize. * * @param thing The Thing to shift vertically. * @param top Where the Thing's top should be. */ setTop(thing: IThing, top: number): void; /** * Sets the right of a Thing to a set number, changing the left based on its * width and the EightBittr's unisize. * * @param thing The Thing to shift horizontally. * @param top Where the Thing's right should be. */ setRight(thing: IThing, right: number): void; /** * Sets the bottom of a Thing to a set number, changing the top based on its * height and the EightBittr's unisize. * * @param thing The Thing to shift vertically. * @param top Where the Thing's bottom should be. */ setBottom(thing: IThing, bottom: number): void; /** * Sets the left of a Thing to a set number, changing the right based on its * width and the EightBittr's unisize. * * @param thing The Thing to shift horizontally. * @param top Where the Thing's left should be. */ setLeft(thing: IThing, left: number): void; /** * Shifts a Thing so that it is horizontally centered on the given x. * * @param thing The Thing to shift horizontally. * @param x Where the Thing's horizontal midpoint should be. */ setMidX(thing: IThing, x: number): void; /** * Shifts a Thing so that it is vertically centered on the given y. * * @param thing The Thing to shift vertically. * @param y Where the Thing's vertical midpoint should be. */ setMidY(thing: IThing, y: number): void; /** * Shifts a Thing so that it is centered on the given x and y. * * @param thing The Thing to shift vertically and horizontally. * @param x Where the Thing's horizontal midpoint should be. * @param y Where the Thing's vertical midpoint should be. */ setMid(thing: IThing, x: number, y: number): void; /** * @param thing * @returns The horizontal midpoint of the Thing. */ getMidX(thing: IThing): number; /** * @param thing * @returns The vertical midpoint of the Thing. */ getMidY(thing: IThing): number; /** * Shifts a Thing so that its midpoint is centered on the midpoint of the * other Thing. * * @param thing The Thing to be shifted. * @param other The Thing whose midpoint is referenced. */ setMidObj(thing: IThing, other: IThing): void; /** * Shifts a Thing so that its horizontal midpoint is centered on the * midpoint of the other Thing. * * @param thing The Thing to be shifted horizontally. * @param other The Thing whose horizontal midpoint is referenced. */ setMidXObj(thing: IThing, other: IThing): void; /** * Shifts a Thing so that its vertical midpoint is centered on the * midpoint of the other Thing. * * @param thing The Thing to be shifted vertically. * @param other The Thing whose vertical midpoint is referenced. */ setMidYObj(thing: IThing, other: IThing): void; /** * @param thing * @param other * @returns Whether the first Thing's midpoint is to the left of the other's. */ objectToLeft(thing: IThing, other: IThing): boolean; /** * Shifts a Thing's top up, then sets the bottom (similar to a shiftVert and * a setTop combined). * * @param thing The Thing to be shifted vertically. * @param dy How far to shift the Thing vertically. */ updateTop(thing: IThing, dy: number): void; /** * Shifts a Thing's right, then sets the left (similar to a shiftHoriz and a * setRight combined). * * @param thing The Thing to be shifted horizontally. * @param dx How far to shift the Thing horizontally. */ updateRight(thing: IThing, dx: number): void; /** * Shifts a Thing's bottom down, then sets the bottom (similar to a * shiftVert and a setBottom combined). * * @param thing The Thing to be shifted vertically. * @param dy How far to shift the Thing vertically. */ updateBottom(thing: IThing, dy: number): void; /** * Shifts a Thing's left, then sets the right (similar to a shiftHoriz and a * setLeft combined). * * @param thing The Thing to be shifted horizontally. * @param dx How far to shift the Thing horizontally. */ updateLeft(thing: IThing, dx: number): void; /** * Shifts a Thing toward a target x, but limits the total distance allowed. * Distance is computed as from the Thing's horizontal midpoint. * * @param thing The Thing to be shifted horizontally. * @param x How far to shift the Thing horizontally. * @param maxDistance The maximum distance the Thing can be shifted. */ slideToX(thing: IThing, x: number, maxDistance: number): void; /** * Shifts a Thing toward a target y, but limits the total distance allowed. * Distance is computed as from the Thing's vertical midpoint. * * @param thing The Thing to be shifted vertically. * @param x How far to shift the Thing vertically. * @param maxDistance The maximum distance the Thing can be shifted. */ slideToY(thing: IThing, y: number, maxDistance: number): void; /** * Ensures the current object is an EightBittr by throwing an error if it * is not. This should be used for functions in any EightBittr descendants * that have to call 'this' to ensure their caller is what the programmer * expected it to be. * * @param current The scope that should be an EightBittr. */ ensureCorrectCaller(current: any): IEightBittr; /** * "Proliferates" all properties of a donor onto a recipient by copying each * of them and recursing onto child Objects. This is a deep copy. * * @param recipient An object to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferate(recipient: any, donor: any, noOverride?: boolean): any; /** * Identical to proliferate, but instead of checking whether the recipient * hasOwnProperty on properties, it just checks if they're truthy. * * @param recipient An object to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferateHard(recipient: any, donor: any, noOverride?: boolean): any; /** * Identical to proliferate, but tailored for HTML elements because many * element attributes don't play nicely with JavaScript Array standards. * Looking at you, HTMLCollection! * * @param recipient An HTMLElement to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferateElement(recipient: HTMLElement, donor: any, noOverride?: boolean): HTMLElement; /** * Creates and returns an HTMLElement of the specified type. Any additional * settings Objects may be given to be proliferated onto the Element via * proliferateElement. * * @param type The tag of the Element to be created. * @param settings Additional settings to proliferated onto the Element. * @returns {HTMLElement} */ createElement(tag?: string, ...args: any[]): HTMLElement; /** * Follows a path inside an Object recursively, based on a given path. * * @param object A container to follow a path inside. * @param path The ordered names of attributes to descend into. * @param num The starting index in path (by default, 0). * @returns The discovered property within object, or undefined if the * full path doesn't exist. */ followPathHard(object: any, path: string[], index?: number): any; /** * Switches an object from one Array to another using splice and push. * * @param object The object to move between Arrays. * @param arrayOld The Array to take the object out of. * @param arrayNew The Array to move the object into. */ arraySwitch(object: any, arrayOld: any[], arrayNew: any[]): void; /** * Sets a Thing's position within an Array to the front by splicing and then * unshifting it. * * @param object The object to move within the Array. * @param array An Array currently containing the object. */ arrayToBeginning(object: any, array: any[]): void; /** * Sets a Thing's position within an Array to the front by splicing and then * pushing it. * * @param object The object to move within the Array. * @param array An Array currently containing the object. */ arrayToEnd(object: any, array: any[]): void; /** * Sets a Thing's position within an Array to a specific index by splicing * it out, then back in. * * @param object The object to move within the Array. * @param array An Array currently containing the object. * @param index Where the object should be moved to in the Array. */ arrayToIndex(object: any, array: any[], index: number): void; } } module EightBittr { "use strict"; /** * An abstract class used exclusively as the parent of GameStartr. EightBittr * contains useful functions for manipulating Things that are independent of * the required GameStartr modules. */ export class EightBittr implements IEightBittr { /** * How much to expand each pixel from raw sizing measurements to in-game. */ public unitsize: number; /** * Timed result summaries of resetTimed, if it was called. */ public resetTimes: EightBittr.IResetTimes; /** * Any custom settings passed in during construction to be passed to * reset Functions. */ protected customs: any; /** * A listing of the names of all member variables of this EightBittr * that should be pulled in from the setting's constantsSource. */ protected constants: string[]; /** * Initializes a new instance of the EightBittr class. Constants are copied * onto the EightBittr from the designated source. * * @param settings Any optional custom settings. */ constructor(settings: IEightBittrSettings = {}) { var EightBitter: EightBittr = EightBittr.prototype.ensureCorrectCaller(this), constants: any = settings.constants, constantsSource: any = settings.constantsSource || EightBitter, i: number; EightBitter.unitsize = settings.unitsize || 1; EightBitter.constants = constants; if (constants) { for (i = 0; i < constants.length; i += 1) { EightBitter[constants[i]] = constantsSource[constants[i]]; } } } /* Resets */ /** * Resets the EightBittr by calling all of the named reset member Functions * on itself. * * @param EightBitter * @param resets The ordered Array of reset Functions to be called. * @param customs Additional arguments to pass to all reset Functions. */ reset(EightBitter: EightBittr, resets: string[], customs?: any): void { var reset: string, i: number; EightBitter.customs = customs; for (i = 0; i < resets.length; i += 1) { reset = resets[i]; if (!EightBitter[reset]) { throw new Error(reset + " is missing on a resetting EightBittr."); } EightBitter[reset](EightBitter, customs); } } /** * Resets the EightBittr in a timed manner by calling all the named reset * member Functions on itself and adding the time (in milliseconds) along * along with the total process time to an Array, which is then returned. * * @param EightBitter * @param resets The ordered Array of reset Functions to be called. * @param customs Additional arguments to pass to all reset Functions. */ resetTimed(EightBitter: EightBittr, resets: string[], customs?: any): void { var timeStartTotal: number = performance.now(), timeEndTotal: number, timeStart: number, timeEnd: number, i: number; this.resetTimes = { order: resets, times: [] }; for (i = 0; i < resets.length; i += 1) { timeStart = performance.now(); EightBitter[resets[i]](EightBitter, customs); timeEnd = performance.now(); this.resetTimes.times.push({ "name": resets[i], "timeStart": timeStart, "timeEnd": timeEnd, "timeTaken": timeEnd - timeStart }); } timeEndTotal = performance.now(); this.resetTimes.total = { "name": "resetTimed", "timeStart": timeStartTotal, "timeEnd": timeEndTotal, "timeTaken": timeEndTotal - timeStartTotal }; } /* HTML Functions */ /** * Creates and returns a new HTML element with no image smoothing. * * @param width How wide the canvas should be. * @param height How tall the canvas should be. * @returns A canvas of the given width and height height. */ createCanvas(width: number, height: number): HTMLCanvasElement { var canvas: HTMLCanvasElement = document.createElement("canvas"), // context: CanvasRenderingContext2D = canvas.getContext("2d"); context: any = canvas.getContext("2d"); canvas.width = width; canvas.height = height; // For speed's sake, disable image smoothing in the first supported browsers if (typeof context.imageSmoothingEnabled !== "undefined") { context.imageSmoothingEnabled = false; } else if (typeof context.webkitImageSmoothingEnabled !== "undefined") { context.webkitImageSmoothingEnabled = false; } else if (typeof context.mozImageSmoothingEnabled !== "undefined") { context.mozImageSmoothingEnabled = false; } else if (typeof context.msImageSmoothingEnabled !== "undefined") { context.msImageSmoothingEnabled = false; } else if (typeof context.oImageSmoothingEnabled !== "undefined") { context.oImageSmoothingEnabled = false; } return canvas; } /* Physics functions */ /** * Shifts a Thing vertically by changing its top and bottom attributes. * * @param thing The Thing to shift vertically. * @param dy How far to shift the Thing. */ shiftVert(thing: IThing, dy: number): void { thing.top += dy; thing.bottom += dy; } /** * Shifts a Thing horizontally by changing its top and bottom attributes. * * @param thing The Thing to shift horizontally. * @param dy How far to shift the Thing. */ shiftHoriz(thing: IThing, dx: number): void { thing.left += dx; thing.right += dx; } /** * Sets the top of a Thing to a set number, changing the bottom based on its * height and the EightBittr's unisize. * * @param thing The Thing to shift vertically. * @param top Where the Thing's top should be. */ setTop(thing: IThing, top: number): void { thing.top = top; thing.bottom = thing.top + thing.height * thing.EightBitter.unitsize; } /** * Sets the right of a Thing to a set number, changing the left based on its * width and the EightBittr's unisize. * * @param thing The Thing to shift horizontally. * @param top Where the Thing's right should be. */ setRight(thing: IThing, right: number): void { thing.right = right; thing.left = thing.right - thing.width * thing.EightBitter.unitsize; } /** * Sets the bottom of a Thing to a set number, changing the top based on its * height and the EightBittr's unisize. * * @param thing The Thing to shift vertically. * @param top Where the Thing's bottom should be. */ setBottom(thing: IThing, bottom: number): void { thing.bottom = bottom; thing.top = thing.bottom - thing.height * thing.EightBitter.unitsize; } /** * Sets the left of a Thing to a set number, changing the right based on its * width and the EightBittr's unisize. * * @param thing The Thing to shift horizontally. * @param top Where the Thing's left should be. */ setLeft(thing: IThing, left: number): void { thing.left = left; thing.right = thing.left + thing.width * thing.EightBitter.unitsize; } /** * Shifts a Thing so that it is horizontally centered on the given x. * * @param thing The Thing to shift horizontally. * @param x Where the Thing's horizontal midpoint should be. */ setMidX(thing: IThing, x: number): void { thing.EightBitter.setLeft( thing, x - thing.width * thing.EightBitter.unitsize / 2); } /** * Shifts a Thing so that it is vertically centered on the given y. * * @param thing The Thing to shift vertically. * @param y Where the Thing's vertical midpoint should be. */ setMidY(thing: IThing, y: number): void { thing.EightBitter.setTop( thing, y - thing.height * thing.EightBitter.unitsize / 2); } /** * Shifts a Thing so that it is centered on the given x and y. * * @param thing The Thing to shift vertically and horizontally. * @param x Where the Thing's horizontal midpoint should be. * @param y Where the Thing's vertical midpoint should be. */ setMid(thing: IThing, x: number, y: number): void { thing.EightBitter.setMidX(thing, x); thing.EightBitter.setMidY(thing, y); } /** * @param thing * @returns The horizontal midpoint of the Thing. */ getMidX(thing: IThing): number { return thing.left + thing.width * thing.EightBitter.unitsize / 2; } /** * @param thing * @returns The vertical midpoint of the Thing. */ getMidY(thing: IThing): number { return thing.top + thing.height * thing.EightBitter.unitsize / 2; } /** * Shifts a Thing so that its midpoint is centered on the midpoint of the * other Thing. * * @param thing The Thing to be shifted. * @param other The Thing whose midpoint is referenced. */ setMidObj(thing: IThing, other: IThing): void { thing.EightBitter.setMidXObj(thing, other); thing.EightBitter.setMidYObj(thing, other); } /** * Shifts a Thing so that its horizontal midpoint is centered on the * midpoint of the other Thing. * * @param thing The Thing to be shifted horizontally. * @param other The Thing whose horizontal midpoint is referenced. */ setMidXObj(thing: IThing, other: IThing): void { thing.EightBitter.setLeft( thing, thing.EightBitter.getMidX(other) - (thing.width * thing.EightBitter.unitsize / 2) ); } /** * Shifts a Thing so that its vertical midpoint is centered on the * midpoint of the other Thing. * * @param thing The Thing to be shifted vertically. * @param other The Thing whose vertical midpoint is referenced. */ setMidYObj(thing: IThing, other: IThing): void { thing.EightBitter.setTop( thing, thing.EightBitter.getMidY(other) - (thing.height * thing.EightBitter.unitsize / 2) ); } /** * @param thing * @param other * @returns Whether the first Thing's midpoint is to the left of the other's. */ objectToLeft(thing: IThing, other: IThing): boolean { return ( thing.EightBitter.getMidX(thing) < thing.EightBitter.getMidX(other) ); } /** * Shifts a Thing's top up, then sets the bottom (similar to a shiftVert and * a setTop combined). * * @param thing The Thing to be shifted vertically. * @param dy How far to shift the Thing vertically. */ updateTop(thing: IThing, dy: number): void { // If a dy is provided, move the thing's top that much thing.top += dy || 0; // Make the thing's bottom dependent on the top thing.bottom = thing.top + thing.height * thing.EightBitter.unitsize; } /** * Shifts a Thing's right, then sets the left (similar to a shiftHoriz and a * setRight combined). * * @param thing The Thing to be shifted horizontally. * @param dx How far to shift the Thing horizontally. */ updateRight(thing: IThing, dx: number): void { // If a dx is provided, move the thing's right that much thing.right += dx || 0; // Make the thing's left dependent on the right thing.left = thing.right - thing.width * thing.EightBitter.unitsize; } /** * Shifts a Thing's bottom down, then sets the bottom (similar to a * shiftVert and a setBottom combined). * * @param thing The Thing to be shifted vertically. * @param dy How far to shift the Thing vertically. */ updateBottom(thing: IThing, dy: number): void { // If a dy is provided, move the thing's bottom that much thing.bottom += dy || 0; // Make the thing's top dependent on the top thing.top = thing.bottom - thing.height * thing.EightBitter.unitsize; } /** * Shifts a Thing's left, then sets the right (similar to a shiftHoriz and a * setLeft combined). * * @param thing The Thing to be shifted horizontally. * @param dx How far to shift the Thing horizontally. */ updateLeft(thing: IThing, dx: number): void { // If a dx is provided, move the thing's left that much thing.left += dx || 0; // Make the thing's right dependent on the left thing.right = thing.left + thing.width * thing.EightBitter.unitsize; } /** * Shifts a Thing toward a target x, but limits the total distance allowed. * Distance is computed as from the Thing's horizontal midpoint. * * @param thing The Thing to be shifted horizontally. * @param x How far to shift the Thing horizontally. * @param maxDistance The maximum distance the Thing can be shifted. */ slideToX(thing: IThing, x: number, maxDistance: number): void { var midx: number = thing.EightBitter.getMidX(thing); // If no maxSpeed is provided, assume Infinity (so it doesn't matter) maxDistance = maxDistance || Infinity; // Thing to the left? Slide to the right. if (midx < x) { thing.EightBitter.shiftHoriz(thing, Math.min(maxDistance, x - midx)); } else { // Thing to the right? Slide to the left. thing.EightBitter.shiftHoriz(thing, Math.max(-maxDistance, x - midx)); } } /** * Shifts a Thing toward a target y, but limits the total distance allowed. * Distance is computed as from the Thing's vertical midpoint. * * @param thing The Thing to be shifted vertically. * @param x How far to shift the Thing vertically. * @param maxDistance The maximum distance the Thing can be shifted. */ slideToY(thing: IThing, y: number, maxDistance: number): void { var midy: number = thing.EightBitter.getMidY(thing); // If no maxSpeed is provided, assume Infinity (so it doesn't matter) maxDistance = maxDistance || Infinity; // Thing above? slide down. if (midy < y) { thing.EightBitter.shiftVert(thing, Math.min(maxDistance, y - midy)); } else { // Thing below? Slide up. thing.EightBitter.shiftVert(thing, Math.max(-maxDistance, y - midy)); } } /* EightBittr utilities */ /** * Ensures the current object is an EightBittr by throwing an error if it * is not. This should be used for functions in any EightBittr descendants * that have to call 'this' to ensure their caller is what the programmer * expected it to be. * * @param current The scope that should be an EightBittr. */ ensureCorrectCaller(current: any): EightBittr { if (!(current instanceof EightBittr)) { throw new Error("A function requires the caller ('this') to be the " + "manipulated EightBittr object. Unfortunately, 'this' is a " + typeof (this) + "."); } return current; } /* General utilities */ /** * "Proliferates" all properties of a donor onto a recipient by copying each * of them and recursing onto child Objects. This is a deep copy. * * @param recipient An object to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferate(recipient: any, donor: any, noOverride: boolean = false): any { var setting: any, i: string; // For each attribute of the donor: for (i in donor) { if (donor.hasOwnProperty(i)) { // If noOverride, don't override already existing properties if (noOverride && recipient.hasOwnProperty(i)) { continue; } // If it's an object, recurse on a new version of it setting = donor[i]; if (typeof setting === "object") { if (!recipient.hasOwnProperty(i)) { recipient[i] = new setting.constructor(); } this.proliferate(recipient[i], setting, noOverride); } else { // Regular primitives are easy to copy otherwise recipient[i] = setting; } } } return recipient; } /** * Identical to proliferate, but instead of checking whether the recipient * hasOwnProperty on properties, it just checks if they're truthy. * * @param recipient An object to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferateHard(recipient: any, donor: any, noOverride?: boolean): any { var setting: any, i: string; // For each attribute of the donor: for (i in donor) { if (donor.hasOwnProperty(i)) { // If noOverride, don't override already existing properties if (noOverride && recipient[i]) { continue; } // If it's an object, recurse on a new version of it setting = donor[i]; if (typeof setting === "object") { if (!recipient[i]) { recipient[i] = new setting.constructor(); } this.proliferate(recipient[i], setting, noOverride); } else { // Regular primitives are easy to copy otherwise recipient[i] = setting; } } } return recipient; } /** * Identical to proliferate, but tailored for HTML elements because many * element attributes don't play nicely with JavaScript Array standards. * Looking at you, HTMLCollection! * * @param recipient An HTMLElement to receive properties from the donor. * @param donor An object do donoate properties to the recipient. * @param noOverride Whether pre-existing properties of the recipient should * be skipped (defaults to false). * @returns recipient */ proliferateElement(recipient: HTMLElement, donor: any, noOverride: boolean = false): HTMLElement { var setting: any, i: string, j: number; // For each attribute of the donor: for (i in donor) { if (donor.hasOwnProperty(i)) { // If noOverride, don't override already existing properties if (noOverride && recipient.hasOwnProperty(i)) { continue; } setting = donor[i]; // Special cases for HTML elements switch (i) { // Children and options: just append all of them directly case "children": case "children": if (typeof (setting) !== "undefined") { for (j = 0; j < setting.length; j += 1) { recipient.appendChild(setting[j]); } } break; // Style: proliferate (instead of making a new Object) case "style": this.proliferate(recipient[i], setting); break; // By default, use the normal proliferate logic default: // If it's null, don't do anything (like .textContent) if (setting === null) { recipient[i] = null; } else if (typeof setting === "object") { // If it's an object, recurse on a new version of it if (!recipient.hasOwnProperty(i)) { recipient[i] = new setting.constructor(); } this.proliferate(recipient[i], setting, noOverride); } else { // Regular primitives are easy to copy otherwise recipient[i] = setting; } break; } } } return recipient; } /** * Creates and returns an HTMLElement of the specified type. Any additional * settings Objects may be given to be proliferated onto the Element via * proliferateElement. * * @param type The tag of the Element to be created. * @param settings Additional settings to proliferated onto the Element. * @returns {HTMLElement} */ createElement(tag?: string, ...args: any[]): HTMLElement { var EightBitter: EightBittr = EightBittr.prototype.ensureCorrectCaller(this), element: HTMLElement = document.createElement(tag || "div"), i: number; // For each provided object, add those settings to the element for (i = 0; i < args.length; i += 1) { EightBitter.proliferateElement(element, args[i]); } return element; } /** * Follows a path inside an Object recursively, based on a given path. * * @param object A container to follow a path inside. * @param path The ordered names of attributes to descend into. * @param num The starting index in path (by default, 0). * @returns The discovered property within object, or undefined if the * full path doesn't exist. */ followPathHard(object: any, path: string[], index: number = 0): any { for (var i: number = index; i < path.length; i += 1) { if (typeof object[path[i]] === "undefined") { return undefined; } else { object = object[path[i]]; } } return object; } /** * Switches an object from one Array to another using splice and push. * * @param object The object to move between Arrays. * @param arrayOld The Array to take the object out of. * @param arrayNew The Array to move the object into. */ arraySwitch(object: any, arrayOld: any[], arrayNew: any[]): void { arrayOld.splice(arrayOld.indexOf(object), 1); arrayNew.push(object); } /** * Sets a Thing's position within an Array to the front by splicing and then * unshifting it. * * @param object The object to move within the Array. * @param array An Array currently containing the object. */ arrayToBeginning(object: any, array: any[]): void { array.splice(array.indexOf(object), 1); array.unshift(object); } /** * Sets a Thing's position within an Array to the front by splicing and then * pushing it. * * @param object The object to move within the Array. * @param array An Array currently containing the object. */ arrayToEnd(object: IThing, array: any[]): void { array.splice(array.indexOf(object), 1); array.push(object); } /** * Sets a Thing's position within an Array to a specific index by splicing * it out, then back in. * * @param object The object to move within the Array. * @param array An Array currently containing the object. * @param index Where the object should be moved to in the Array. */ arrayToIndex(object: IThing, array: any[], index: number): void { array.splice(array.indexOf(object), 1); array.splice(index, 0, object); } }; }