import dojo = require("dojo"); import declare = require("dojo/_base/declare"); /** * A simple control that allow to set/get numeric value from inner html of div/span, and provides an ease-out animation on from/to value. * @see {@link https://en.boardgamearena.com/doc/Counter|Documentation} * @example * // Create the template for counter * var jstpl_player_board = '\
\ *
0\ *
'; * * // Setting up player boards * this.stone_counters={}; * for( var player_id in gamedatas.players ) { * var player = gamedatas.players[player_id]; * * // Setting up players boards if needed * var player_board_div = $('player_board_'+player_id); * dojo.place( this.format_block('jstpl_player_board', player ), player_board_div ); * // create counter per player * this.stone_counters[player_id]=new ebg.counter(); * this.stone_counters[player_id].create('stonecount_p'+player_id); * } */ class Counter_Template { span: Element | null = null; current_value: BGA.ID | typeof NaN = 0; target_value: BGA.ID | null | typeof NaN = 0; /** Duration of the animation, default is 100ms. */ speed: number = 100; /** * Associates this counter with an existing target DOM element. * * @throws {TypeError} if the target is not found. */ create(elementOrSelectors: string | Element | null): void | throws { this.span = (typeof elementOrSelectors === 'string') ? document.querySelector(elementOrSelectors) : elementOrSelectors; this.span!.innerHTML = this.current_value as string; } /** * Getter for the property {@link Counter_Template.target_value}. */ getValue(): BGA.ID | null | typeof NaN { return this.target_value; } /** * Sets the value of the counter to the specified amount by immediately updating {@link current_value}, {@link target_value}, and the html of {@link span}. Use {@link toValue} to animate the change over time. * @throws {TypeError} if {@link create} has not been called with a valid element/id and {@link span} has not been manually set. */ setValue(value: BGA.ID): void | throws { this.current_value = value; this.target_value = value; this.span!.innerHTML = this.current_value as string; } /** * Sets the value of the counter to the specified amount by setting {@link target_value} and dynamically updating {@link current_value} over time. The animation ticks every {@link speed} milliseconds, moving 20% closer to the target value each time (minimum 1, resulting in an ease-out like update). * @throws {TypeError} if {@link create} has not been called with a valid element/id and {@link span} has not been manually set. */ toValue(value: BGA.ID): void | throws { this.target_value = toint(value); if (this.current_value != this.target_value) dojo.addClass(this.span!, "counter_in_progress"); setTimeout(dojo.hitch(this, this.makeCounterProgress), this.speed); } /** * Wrapper for {@link toValue} that increments the target value by the specified amount through an ease-out animation. * @throws {TypeError} if {@link create} has not been called with a valid element/id and {@link span} has not been manually set. * @returns The new target value of the counter. */ incValue(by: number | string): number | typeof NaN | throws { this.toValue( parseInt(this.target_value as string, 10) + parseInt(String(toint(by)), 10) ); return this.target_value as number | typeof NaN; } /** * Sets the display of the {@link span} to '-' to indicate that the counter is disabled. The internal values of the counter are not changed and can be re-enabled by using {@link setValue}, {@link toValue}, or {@link incValue}. * @throws {TypeError} if {@link create} has not been called with a valid element/id and {@link span} has not been manually set. */ disable(): void | throws { // @ts-ignore - Throws null TypeError if span is invalid. this.span.innerHTML = "-"; } /** Moves the counter towards the target value by 20% of the difference. */ protected makeCounterProgress(): void | throws { if (this.current_value != this.target_value) { var increment = Math.ceil( // @ts-ignore - Results in NaN for current value. Math.abs(this.target_value - this.current_value) / 5 ); // @ts-ignore - Results in NaN for current value. this.current_value < this.target_value // @ts-ignore - Implicit cast to number ? (this.current_value += increment) // @ts-ignore - Implicit cast to number : (this.current_value -= increment); this.span!.innerHTML = this.current_value as string; setTimeout(dojo.hitch(this, this.makeCounterProgress), this.speed); } else setTimeout(dojo.hitch(this, this.finishCounterMove), 1000); } /** Removes the "counter_in_progress" class from the {@link span} to indicate that the counter has finished animating. */ protected finishCounterMove(): void | throws { this.current_value == this.target_value && dojo.removeClass(this.span!, "counter_in_progress"); } } let Counter = declare("ebg.counter", Counter_Template); export = Counter; declare global { namespace BGA { type Counter = typeof Counter; interface EBG { counter: Counter; } } var ebg: BGA.EBG; }