diff --git a/public/js/bracket.js b/public/js/bracket.js
new file mode 100644
index 0000000..0cc624c
--- /dev/null
+++ b/public/js/bracket.js
@@ -0,0 +1,1310 @@
+/**
+ * jQuery Bracket
+ *
+ * Copyright (c) 2011-2016, Teijo Laine,
+ * http://aropupu.fi/bracket/
+ *
+ * Licenced under the MIT licence
+ */
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+///
+(function ($) {
+ var Option = (function () {
+ function Option(val) {
+ this.val = val;
+ if (val instanceof Option) {
+ throw new Error('Trying to wrap Option into an Option');
+ }
+ if (this.val === undefined) {
+ throw new Error('Option cannot contain undefined');
+ }
+ }
+ Option.of = function (value) {
+ return new Option(value);
+ };
+ Option.empty = function () {
+ return new Option(null);
+ };
+ Option.prototype.get = function () {
+ if (this.val === null) {
+ throw new Error('Trying to get() empty Option');
+ }
+ return this.val;
+ };
+ Option.prototype.orElse = function (defaultValue) {
+ return (this.val === null) ? defaultValue : this.val;
+ };
+ Option.prototype.orElseGet = function (defaultProvider) {
+ return (this.val === null) ? defaultProvider() : this.val;
+ };
+ Option.prototype.map = function (f) {
+ return (this.val === null) ? Option.empty() : new Option(f(this.val));
+ };
+ Option.prototype.forEach = function (f) {
+ if (this.val !== null) {
+ f(this.val);
+ }
+ return this;
+ };
+ Option.prototype.toNull = function () {
+ return (this.val === null) ? null : this.val;
+ };
+ Option.prototype.isEmpty = function () {
+ return this.val === null;
+ };
+ return Option;
+ }());
+ var Score = (function (_super) {
+ __extends(Score, _super);
+ function Score() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ Score.of = function (val) {
+ var type = typeof (val);
+ var expected = 'number';
+ if (val !== null && type !== expected) {
+ throw new Error("Invalid score format, expected " + expected + ", got " + type);
+ }
+ return Option.of(val);
+ };
+ Score.empty = function () {
+ return Option.empty();
+ };
+ return Score;
+ }(Option));
+ var ResultObject = (function () {
+ function ResultObject(first, second, userData) {
+ this.first = first;
+ this.second = second;
+ this.userData = userData;
+ if (!first || !second) {
+ throw new Error('Cannot create ResultObject with undefined scores');
+ }
+ return;
+ }
+ return ResultObject;
+ }());
+ var BranchType;
+ (function (BranchType) {
+ BranchType[BranchType["TBD"] = 0] = "TBD";
+ BranchType[BranchType["BYE"] = 1] = "BYE";
+ })(BranchType || (BranchType = {}));
+ var Order = (function () {
+ function Order(isFirst) {
+ this.isFirst = isFirst;
+ }
+ Order.first = function () {
+ return new Order(true);
+ };
+ Order.second = function () {
+ return new Order(false);
+ };
+ Order.prototype.map = function (first, second) {
+ return this.isFirst ? first : second;
+ };
+ return Order;
+ }());
+ var TeamBlock = (function () {
+ function TeamBlock(source, // Where base of the information propagated from
+ name, order, seed, score) {
+ this.source = source;
+ this.name = name;
+ this.order = order;
+ this.seed = seed;
+ this.score = score;
+ }
+ // Recursively check if branch ends into a BYE
+ TeamBlock.prototype.emptyBranch = function () {
+ if (!this.name.isEmpty()) {
+ return BranchType.TBD;
+ }
+ else {
+ try {
+ return this.source().emptyBranch();
+ }
+ catch (e) {
+ if (e instanceof EndOfBranchException) {
+ return BranchType.BYE;
+ }
+ else {
+ throw new Error('Unexpected exception type');
+ }
+ }
+ }
+ };
+ return TeamBlock;
+ }());
+ // http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
+ function isNumber(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ }
+ function EndOfBranchException() {
+ this.message = 'Root of information for this team';
+ this.name = 'EndOfBranchException';
+ }
+ var MatchResult = (function () {
+ function MatchResult(a, b) {
+ this.a = a;
+ this.b = b;
+ return;
+ }
+ MatchResult.teamsInResultOrder = function (match) {
+ var aBye = match.a.name.isEmpty();
+ var bBye = match.b.name.isEmpty();
+ if (bBye && !aBye) {
+ if (match.b.emptyBranch() === BranchType.BYE) {
+ return [match.a, match.b];
+ }
+ else {
+ return [];
+ }
+ }
+ else if (aBye && !bBye) {
+ if (match.a.emptyBranch() === BranchType.BYE) {
+ return [match.b, match.a];
+ }
+ else {
+ return [];
+ }
+ }
+ else if (!match.a.score.isEmpty() && !match.b.score.isEmpty()) {
+ if (match.a.score.get() > match.b.score.get()) {
+ return [match.a, match.b];
+ }
+ else if (match.a.score.get() < match.b.score.get()) {
+ return [match.b, match.a];
+ }
+ }
+ return [];
+ };
+ // Arbitrary (either parent) source is required so that branch emptiness
+ // can be determined by traversing to the beginning.
+ MatchResult.emptyTeam = function (source) {
+ return new TeamBlock(source, Option.empty(), Option.empty(), Option.empty(), Score.empty());
+ };
+ MatchResult.prototype.winner = function () {
+ return MatchResult.teamsInResultOrder(this)[0] || MatchResult.emptyTeam(this.a.source);
+ };
+ MatchResult.prototype.loser = function () {
+ return MatchResult.teamsInResultOrder(this)[1] || MatchResult.emptyTeam(this.a.source);
+ };
+ return MatchResult;
+ }());
+ function depth(a) {
+ function df(a, d) {
+ if (a instanceof Array) {
+ return df(a[0], d + 1);
+ }
+ return d;
+ }
+ return df(a, 0);
+ }
+ function wrap(a, d) {
+ if (d > 0) {
+ a = wrap([a], d - 1);
+ }
+ return a;
+ }
+ function trackHighlighter(teamIndex, cssClass, container) {
+ var elements = container.find('.team[data-teamid=' + teamIndex + ']');
+ var addedClass = !cssClass ? 'highlight' : cssClass;
+ return {
+ highlight: function () {
+ elements.each(function () {
+ $(this).addClass(addedClass);
+ if ($(this).hasClass('win')) {
+ $(this).parent().find('.connector').addClass(addedClass);
+ }
+ });
+ },
+ deHighlight: function () {
+ elements.each(function () {
+ $(this).removeClass(addedClass);
+ $(this).parent().find('.connector').removeClass(addedClass);
+ });
+ }
+ };
+ }
+ function postProcess(container, w, f) {
+ var source = f || w;
+ var winner = source.winner();
+ var loser = source.loser();
+ if (winner && loser) {
+ if (!winner.name.isEmpty()) {
+ trackHighlighter(winner.seed.get(), 'highlightWinner', container).highlight();
+ }
+ if (!loser.name.isEmpty()) {
+ trackHighlighter(loser.seed.get(), 'highlightLoser', container).highlight();
+ }
+ }
+ container.find('.team').mouseover(function () {
+ var teamId = $(this).attr('data-teamid');
+ // Don't highlight BYEs
+ if (teamId === undefined) {
+ return;
+ }
+ var track = trackHighlighter(parseInt(teamId, 10), null, container);
+ track.highlight();
+ $(this).mouseout(function () {
+ track.deHighlight();
+ $(this).unbind('mouseout');
+ });
+ });
+ }
+ function defaultEdit(span, data, done) {
+ var input = $('');
+ input.val(data);
+ span.empty().append(input);
+ input.focus();
+ input.blur(function () {
+ done(input.val());
+ });
+ input.keydown(function (e) {
+ var key = (e.keyCode || e.which);
+ if (key === 9 /*tab*/ || key === 13 /*return*/ || key === 27 /*esc*/) {
+ e.preventDefault();
+ done(input.val(), (key !== 27));
+ }
+ });
+ }
+ function defaultRender(container, team, score, state) {
+ switch (state) {
+ case 'empty-bye':
+ container.append('BYE');
+ return;
+ case 'empty-tbd':
+ container.append('TBD');
+ return;
+ case 'entry-no-score':
+ case 'entry-default-win':
+ case 'entry-complete':
+ container.append(team);
+ return;
+ }
+ }
+ function winnerBubbles(match) {
+ var el = match.el;
+ var winner = el.find('.team.win');
+ winner.append('
1st
');
+ var loser = el.find('.team.lose');
+ loser.append('2nd
');
+ return true;
+ }
+ function consolationBubbles(match) {
+ var el = match.el;
+ var winner = el.find('.team.win');
+ winner.append('3rd
');
+ var loser = el.find('.team.lose');
+ loser.append('4th
');
+ return true;
+ }
+ var winnerMatchSources = function (teams, m) { return function () { return [
+ { source: function () { return new TeamBlock(function () { throw new EndOfBranchException(); }, teams[m][0], Option.of(Order.first()), Option.of(m * 2), Score.empty()); } },
+ { source: function () { return new TeamBlock(function () { throw new EndOfBranchException(); }, teams[m][1], Option.of(Order.second()), Option.of(m * 2 + 1), Score.empty()); } }
+ ]; }; };
+ var winnerAlignment = function (match, skipConsolationRound) { return function (tC) {
+ tC.css('top', '');
+ tC.css('position', 'absolute');
+ if (skipConsolationRound) {
+ tC.css('top', (match.el.height() / 2 - tC.height() / 2) + 'px');
+ }
+ else {
+ tC.css('bottom', (-tC.height() / 2) + 'px');
+ }
+ }; };
+ function prepareWinners(winners, teams, isSingleElimination, opts, skipGrandFinalComeback) {
+ var roundCount = Math.log(teams.length * 2) / Math.log(2);
+ var matchCount = teams.length;
+ var round;
+ for (var r = 0; r < roundCount; r += 1) {
+ round = winners.addRound(Option.empty());
+ for (var m = 0; m < matchCount; m += 1) {
+ var teamCb = (r === 0) ? winnerMatchSources(teams, m) : null;
+ if (!(r === roundCount - 1 && isSingleElimination) && !(r === roundCount - 1 && skipGrandFinalComeback)) {
+ round.addMatch(teamCb, Option.empty());
+ }
+ else {
+ var match = round.addMatch(teamCb, Option.of(winnerBubbles));
+ if (!skipGrandFinalComeback) {
+ match.setAlignCb(winnerAlignment(match, opts.skipConsolationRound));
+ }
+ }
+ }
+ matchCount /= 2;
+ }
+ if (isSingleElimination) {
+ winners.final().setConnectorCb(Option.empty());
+ if (teams.length > 1 && !opts.skipConsolationRound) {
+ var prev = winners.final().getRound().prev();
+ var third_1 = prev.map(function (p) { return function () { return p.match(0).loser(); }; }).toNull();
+ var fourth_1 = prev.map(function (p) { return function () { return p.match(1).loser(); }; }).toNull();
+ var consol_1 = round.addMatch(function () {
+ return [
+ { source: third_1 },
+ { source: fourth_1 }
+ ];
+ }, Option.of(consolationBubbles));
+ consol_1.setAlignCb(function (tC) {
+ var height = (winners.el.height()) / 2;
+ consol_1.el.css('height', (height) + 'px');
+ var topShift = tC.height() / 2 + opts.matchMargin;
+ tC.css('top', (topShift) + 'px');
+ });
+ consol_1.setConnectorCb(Option.empty());
+ }
+ }
+ }
+ var loserMatchSources = function (winners, losers, matchCount, m, n, r) { return function () {
+ /* first round comes from winner bracket */
+ if (n % 2 === 0 && r === 0) {
+ return [
+ { source: function () { return winners.round(0).match(m * 2).loser(); } },
+ { source: function () { return winners.round(0).match(m * 2 + 1).loser(); } }
+ ];
+ }
+ else {
+ /* To maximize the time it takes for two teams to play against
+ * eachother twice, WB losers are assigned in reverse order
+ * every second round of LB */
+ var winnerMatch_1 = (r % 2 === 0) ? (matchCount - m - 1) : m;
+ return [
+ { source: function () { return losers.round(r * 2).match(m).winner(); } },
+ { source: function () { return winners.round(r + 1).match(winnerMatch_1).loser(); } }
+ ];
+ }
+ }; };
+ var loserAlignment = function (teamCon, match) { return function () { return teamCon.css('top', (match.el.height() / 2 - teamCon.height() / 2) + 'px'); }; };
+ function prepareLosers(winners, losers, teamCount, skipGrandFinalComeback, centerConnectors) {
+ var roundCount = Math.log(teamCount * 2) / Math.log(2) - 1;
+ var matchCount = teamCount / 2;
+ for (var r = 0; r < roundCount; r += 1) {
+ /* if player cannot rise back to grand final, last round of loser
+ * bracket will be player between two LB players, eliminating match
+ * between last WB loser and current LB winner */
+ var subRounds = (skipGrandFinalComeback && r === (roundCount - 1) ? 1 : 2);
+ for (var n = 0; n < subRounds; n += 1) {
+ var round = losers.addRound(Option.empty());
+ for (var m = 0; m < matchCount; m += 1) {
+ var teamCb = (!(n % 2 === 0 && r !== 0)) ? loserMatchSources(winners, losers, matchCount, m, n, r) : null;
+ var isLastMatch = r === roundCount - 1 && skipGrandFinalComeback;
+ var match = round.addMatch(teamCb, Option.of(isLastMatch ? consolationBubbles : null));
+ match.setAlignCb(loserAlignment(match.el.find('.teamContainer'), match));
+ if (isLastMatch) {
+ // Override default connector
+ match.setConnectorCb(Option.empty());
+ }
+ else if (r < roundCount - 1 || n < 1) {
+ var cb = (n % 2 === 0) ? function (tC, match) {
+ // inside lower bracket
+ var connectorOffset = tC.height() / 4;
+ var center = { height: 0, shift: connectorOffset * 2 };
+ return match.winner().order
+ .map(function (order) { return order.map(centerConnectors ? center : { height: 0, shift: connectorOffset }, centerConnectors ? center : { height: -connectorOffset * 2, shift: connectorOffset }); })
+ .orElse(center);
+ } : null;
+ match.setConnectorCb(Option.of(cb));
+ }
+ }
+ }
+ matchCount /= 2;
+ }
+ }
+ function prepareFinals(finals, winners, losers, opts, topCon, resizeContainer) {
+ var round = finals.addRound(Option.empty());
+ var match = round.addMatch(function () {
+ return [
+ { source: function () { return winners.winner(); } },
+ { source: function () { return losers.winner(); } }
+ ];
+ }, Option.of(function (match) {
+ /* Track if container has been resized for final rematch */
+ var _isResized = false;
+ /* LB winner won first final match, need a new one */
+ if (!opts.skipSecondaryFinal && (!match.winner().name.isEmpty() && match.winner().name === losers.winner().name)) {
+ if (finals.size() === 2) {
+ return false;
+ }
+ /* This callback is ugly, would be nice to make more sensible solution */
+ var doRenderCb = function () {
+ var rematch = ((!match.winner().name.isEmpty() && match.winner().name === losers.winner().name));
+ if (_isResized === false) {
+ if (rematch) {
+ _isResized = true;
+ resizeContainer();
+ }
+ }
+ if (!rematch && _isResized) {
+ _isResized = false;
+ finals.dropRound();
+ resizeContainer();
+ }
+ return rematch;
+ };
+ var round_1 = finals.addRound(Option.of(doRenderCb));
+ /* keep order the same, WB winner top, LB winner below */
+ var match2_1 = round_1.addMatch(function () {
+ return [
+ { source: function () { return match.first(); } },
+ { source: function () { return match.second(); } }
+ ];
+ }, Option.of(winnerBubbles));
+ match.setConnectorCb(Option.of(function (tC) {
+ return { height: 0, shift: tC.height() / 2 };
+ }));
+ match2_1.setConnectorCb(Option.empty());
+ match2_1.setAlignCb(function (tC) {
+ var height = (winners.el.height() + losers.el.height());
+ match2_1.el.css('height', (height) + 'px');
+ var topShift = (winners.el.height() / 2 + winners.el.height() + losers.el.height() / 2) / 2 - tC.height();
+ tC.css('top', (topShift) + 'px');
+ });
+ return false;
+ }
+ else {
+ if (finals.size() === 2) {
+ finals.dropRound();
+ }
+ else if (finals.size() > 2) {
+ throw new Error('Unexpected number of final rounds');
+ }
+ return winnerBubbles(match);
+ }
+ }));
+ match.setAlignCb(function (tC) {
+ var height = (winners.el.height() + losers.el.height());
+ if (!opts.skipConsolationRound) {
+ height /= 2;
+ }
+ match.el.css('height', (height) + 'px');
+ var topShift = (winners.el.height() / 2 + winners.el.height() + losers.el.height() / 2) / 2 - tC.height();
+ tC.css('top', (topShift) + 'px');
+ });
+ if (!opts.skipConsolationRound) {
+ var prev_1 = losers.final().getRound().prev();
+ var consol_2 = round.addMatch(function () {
+ return [
+ { source: function () { return prev_1.get().match(0).loser(); } },
+ { source: function () { return losers.loser(); } }
+ ];
+ }, Option.of(consolationBubbles));
+ consol_2.setAlignCb(function (tC) {
+ var height = (winners.el.height() + losers.el.height()) / 2;
+ consol_2.el.css('height', (height) + 'px');
+ var topShift = (winners.el.height() / 2 + winners.el.height() + losers.el.height() / 2) / 2 + tC.height() / 2 - height;
+ tC.css('top', (topShift) + 'px');
+ });
+ match.setConnectorCb(Option.empty());
+ consol_2.setConnectorCb(Option.empty());
+ }
+ winners.final().setConnectorCb(Option.of(function (tC) {
+ var connectorOffset = tC.height() / 4;
+ var topShift = (winners.el.height() / 2 + winners.el.height() + losers.el.height() / 2) / 2 - tC.height() / 2;
+ var matchupOffset = topShift - winners.el.height() / 2;
+ var _a = winners.winner().order
+ .map(function (order) { return order.map({
+ height: matchupOffset + connectorOffset * (opts.centerConnectors ? 2 : 1),
+ shift: connectorOffset * (opts.centerConnectors ? 2 : 1)
+ }, {
+ height: matchupOffset + connectorOffset * (opts.centerConnectors ? 2 : 0),
+ shift: connectorOffset * (opts.centerConnectors ? 2 : 3)
+ }); })
+ .orElse({
+ height: matchupOffset + connectorOffset * (opts.centerConnectors ? 2 : 1),
+ shift: connectorOffset * 2
+ }), height = _a.height, shift = _a.shift;
+ height -= tC.height() / 2;
+ return { height: height, shift: shift };
+ }));
+ losers.final().setConnectorCb(Option.of(function (tC) {
+ var connectorOffset = tC.height() / 4;
+ var topShift = (winners.el.height() / 2 + winners.el.height() + losers.el.height() / 2) / 2 - tC.height() / 2;
+ var matchupOffset = topShift - winners.el.height() / 2;
+ var _a = losers.winner().order
+ .map(function (order) { return order.map({
+ height: matchupOffset + connectorOffset * (opts.centerConnectors ? 2 : 0),
+ shift: connectorOffset * (opts.centerConnectors ? 2 : 3)
+ }, {
+ height: matchupOffset + connectorOffset * 2,
+ shift: connectorOffset * (opts.centerConnectors ? 2 : 1)
+ }); })
+ .orElse({
+ height: matchupOffset + connectorOffset * (opts.centerConnectors ? 2 : 1),
+ shift: connectorOffset * 2
+ }), height = _a.height, shift = _a.shift;
+ height += tC.height() / 2;
+ return { height: -height, shift: -shift };
+ }));
+ }
+ var Round = (function () {
+ function Round(bracket, previousRound, roundNumber,
+ // TODO: results should be enforced to be correct by now
+ _results, doRenderCb, mkMatch, isFirstBracket, opts) {
+ this.bracket = bracket;
+ this.previousRound = previousRound;
+ this.roundNumber = roundNumber;
+ this._results = _results;
+ this.doRenderCb = doRenderCb;
+ this.mkMatch = mkMatch;
+ this.isFirstBracket = isFirstBracket;
+ this.opts = opts;
+ this.containerWidth = this.opts.teamWidth + this.opts.scoreWidth;
+ this.roundCon = $("");
+ this.matches = [];
+ }
+ Object.defineProperty(Round.prototype, "el", {
+ get: function () {
+ return this.roundCon;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Round.prototype.addMatch = function (teamCb, renderCb) {
+ var _this = this;
+ var matchIdx = this.matches.length;
+ var teams = (teamCb !== null) ? teamCb() : [
+ { source: function () { return _this.bracket.round(_this.roundNumber - 1).match(matchIdx * 2).winner(); } },
+ { source: function () { return _this.bracket.round(_this.roundNumber - 1).match(matchIdx * 2 + 1).winner(); } }
+ ];
+ var teamA = function () { return teams[0].source(); };
+ var teamB = function () { return teams[1].source(); };
+ var matchResult = new MatchResult(new TeamBlock(teamA, teamA().name, Option.of(Order.first()), teamA().seed, Score.empty()), new TeamBlock(teamB, teamB().name, Option.of(Order.second()), teamB().seed, Score.empty()));
+ var match = this.mkMatch(this, matchResult, matchIdx, this._results.map(function (r) {
+ return r[matchIdx] === undefined ? null : r[matchIdx];
+ }), renderCb, this.isFirstBracket, this.opts);
+ this.matches.push(match);
+ return match;
+ };
+ Round.prototype.match = function (id) {
+ return this.matches[id];
+ };
+ Round.prototype.prev = function () {
+ return this.previousRound;
+ };
+ Round.prototype.size = function () {
+ return this.matches.length;
+ };
+ Round.prototype.render = function () {
+ this.roundCon.empty();
+ if (!this.doRenderCb.isEmpty() && !this.doRenderCb.get()()) {
+ return;
+ }
+ this.roundCon.appendTo(this.bracket.el);
+ this.matches.forEach(function (m) { return m.render(); });
+ };
+ Round.prototype.results = function () {
+ return this.matches.reduce(function (agg, m) { return agg.concat([m.results()]); }, []);
+ };
+ return Round;
+ }());
+ var Bracket = (function () {
+ function Bracket(bracketCon, initResults, mkMatch, isFirstBracket, opts) {
+ this.bracketCon = bracketCon;
+ this.initResults = initResults;
+ this.mkMatch = mkMatch;
+ this.isFirstBracket = isFirstBracket;
+ this.opts = opts;
+ this.rounds = [];
+ }
+ Object.defineProperty(Bracket.prototype, "el", {
+ get: function () {
+ return this.bracketCon;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Bracket.prototype.addRound = function (doRenderCb) {
+ var id = this.rounds.length;
+ var previous = (id > 0) ? Option.of(this.rounds[id - 1]) : Option.empty();
+ // Rounds may be undefined if init score array does not match number of teams
+ var roundResults = this.initResults
+ .map(function (r) { return (r[id] === undefined)
+ ? new ResultObject(Score.empty(), Score.empty(), undefined)
+ : r[id]; });
+ var round = new Round(this, previous, id, roundResults, doRenderCb, this.mkMatch, this.isFirstBracket, this.opts);
+ this.rounds.push(round);
+ return round;
+ };
+ Bracket.prototype.dropRound = function () {
+ this.rounds.pop();
+ };
+ Bracket.prototype.round = function (id) {
+ return this.rounds[id];
+ };
+ Bracket.prototype.size = function () {
+ return this.rounds.length;
+ };
+ Bracket.prototype.final = function () {
+ return this.rounds[this.rounds.length - 1].match(0);
+ };
+ Bracket.prototype.winner = function () {
+ return this.rounds[this.rounds.length - 1].match(0).winner();
+ };
+ Bracket.prototype.loser = function () {
+ return this.rounds[this.rounds.length - 1].match(0).loser();
+ };
+ Bracket.prototype.render = function () {
+ this.bracketCon.empty();
+ /* Length of 'rounds' can increase during render in special case when
+ LB win in finals adds new final round in match render callback.
+ Therefore length must be read on each iteration. */
+ for (var i = 0; i < this.rounds.length; i += 1) {
+ this.rounds[i].render();
+ }
+ };
+ Bracket.prototype.results = function () {
+ return this.rounds.reduce(function (agg, r) { return agg.concat([r.results()]); }, []);
+ };
+ return Bracket;
+ }());
+ function connector(roundMargin, connector, teamCon, align) {
+ var height = connector.height, shift = connector.shift;
+ var width = roundMargin / 2;
+ var drop = true;
+ // drop:
+ // [team]'\
+ // \_[team]
+ // !drop:
+ // /'[team]
+ // [team]_/
+ if (height < 0) {
+ drop = false;
+ height = -height;
+ }
+ /* straight lines are prettier */
+ if (height < 2) {
+ height = 0;
+ }
+ var src = $('').appendTo(teamCon);
+ src.css('height', height);
+ src.css('width', width + 'px');
+ src.css(align, (-width - 2) + 'px');
+ // Subtract 1 due to line thickness and alignment mismatch caused by
+ // combining top and bottom alignment
+ if (shift >= 0) {
+ src.css('top', (shift - 1) + 'px');
+ }
+ else {
+ src.css('bottom', (-shift - 1) + 'px');
+ }
+ if (drop) {
+ src.css('border-bottom', 'none');
+ }
+ else {
+ src.css('border-top', 'none');
+ }
+ var dst = $('').appendTo(src);
+ dst.css('width', width + 'px');
+ dst.css(align, -width + 'px');
+ if (drop) {
+ dst.css('bottom', '0px');
+ }
+ else {
+ dst.css('top', '0px');
+ }
+ return src;
+ }
+ function countRounds(teamCount, isSingleElimination, skipGrandFinalComeback, skipSecondaryFinal, results) {
+ if (isSingleElimination) {
+ return Math.log(teamCount * 2) / Math.log(2);
+ }
+ else if (skipGrandFinalComeback) {
+ return Math.max(2, (Math.log(teamCount * 2) / Math.log(2) - 1) * 2 - 1); // DE - grand finals
+ }
+ else {
+ // Loser bracket winner has won first match in grand finals,
+ // this requires a new match unless explicitely skipped
+ var hasGrandFinalRematch = (!skipSecondaryFinal && (results.length === 3 && results[2].length === 2));
+ return (Math.log(teamCount * 2) / Math.log(2) - 1) * 2 + 1 + (hasGrandFinalRematch ? 1 : 0); // DE + grand finals
+ }
+ }
+ function exportData(data) {
+ var output = $.extend(true, {}, data);
+ output.teams = output.teams.map(function (ts) { return ts.map(function (t) { return t.toNull(); }); });
+ output.results = output.results
+ .map(function (brackets) { return brackets
+ .map(function (rounds) { return rounds
+ .map(function (matches) {
+ var matchData = [matches.first.toNull(), matches.second.toNull()];
+ if (matches.userData !== undefined) {
+ matchData.push(matches.userData);
+ }
+ return matchData;
+ }); }); });
+ return output;
+ }
+ var ResultId = (function () {
+ function ResultId() {
+ this.counter = 0;
+ }
+ ResultId.prototype.get = function () {
+ return this.counter;
+ };
+ ResultId.prototype.getNext = function () {
+ return ++this.counter;
+ };
+ ResultId.prototype.reset = function () {
+ this.counter = 0;
+ };
+ return ResultId;
+ }());
+ function teamElement(roundNumber, match, team, opponent, isReady, isFirstBracket, opts, resultId, topCon, renderAll) {
+ var resultIdAttribute = team.name.isEmpty() || opponent.name.isEmpty() ? '' : "data-resultid=\"result-" + resultId.getNext() + "\"";
+ var sEl = $("");
+ var score = (team.name.isEmpty() || opponent.name.isEmpty() || !isReady)
+ ? Option.empty()
+ : team.score.map(function (s) { return "" + s; });
+ var scoreString = score.orElse('--');
+ sEl.text(scoreString);
+ var entryState = team.name
+ .map(function () { return score
+ .map(function () { return 'entry-complete'; })
+ .orElseGet(function () { return (opponent.emptyBranch() === BranchType.BYE)
+ ? 'entry-default-win'
+ : 'entry-no-score'; }); })
+ .orElseGet(function () {
+ var type = team.emptyBranch();
+ switch (type) {
+ case BranchType.BYE:
+ return 'empty-bye';
+ case BranchType.TBD:
+ return 'empty-tbd';
+ default:
+ throw new Error("Unexpected branch type " + type);
+ }
+ });
+ var tEl = $("");
+ var nEl = $("").appendTo(tEl);
+ opts.decorator.render(nEl, team.name.toNull(), scoreString, entryState);
+ team.seed.forEach(function (seed) { tEl.attr('data-teamid', seed); });
+ if (team.name.isEmpty()) {
+ tEl.addClass('na');
+ }
+ else if (match.winner().name === team.name) {
+ tEl.addClass('win');
+ }
+ else if (match.loser().name === team.name) {
+ tEl.addClass('lose');
+ }
+ tEl.append(sEl);
+ // Only first round of BYEs can be edited
+ if ((!team.name.isEmpty() || (team.name.isEmpty() && roundNumber === 0 && isFirstBracket)) && typeof (opts.save) === 'function') {
+ if (!opts.disableTeamEdit) {
+ nEl.addClass('editable');
+ nEl.click(function () {
+ var span = $(this);
+ function editor() {
+ function done_fn(val, next) {
+ // Needs to be taken before possible null is assigned below
+ var teamId = team.seed.get();
+ opts.init.teams[~~(teamId / 2)][teamId % 2] = Option.of(val || null);
+ renderAll(true);
+ span.click(editor);
+ var labels = opts.el.find('.team[data-teamid=' + (teamId + 1) + '] div.label:first');
+ if (labels.length && next === true && roundNumber === 0) {
+ $(labels).click();
+ }
+ }
+ span.unbind();
+ opts.decorator.edit(span, team.name.toNull(), done_fn);
+ }
+ editor();
+ });
+ }
+ if (!team.name.isEmpty() && !opponent.name.isEmpty() && isReady) {
+ var rId_1 = resultId.get();
+ sEl.addClass('editable');
+ sEl.click(function () {
+ var span = $(this);
+ function editor() {
+ span.unbind();
+ var score = !isNumber(team.score) ? '0' : span.text();
+ var input = $('');
+ input.val(score);
+ span.empty().append(input);
+ input.focus().select();
+ input.keydown(function (e) {
+ if (!isNumber($(this).val())) {
+ $(this).addClass('error');
+ }
+ else {
+ $(this).removeClass('error');
+ }
+ var key = (e.keyCode || e.which);
+ if (key === 9 || key === 13 || key === 27) {
+ e.preventDefault();
+ $(this).blur();
+ if (key === 27) {
+ return;
+ }
+ var next = topCon.find('div.score[data-resultid=result-' + (rId_1 + 1) + ']');
+ if (next) {
+ next.click();
+ }
+ }
+ });
+ input.blur(function () {
+ var val = input.val();
+ if ((!val || !isNumber(val)) && !isNumber(team.score)) {
+ val = '0';
+ }
+ else if ((!val || !isNumber(val)) && isNumber(team.score)) {
+ val = team.score;
+ }
+ span.html(val);
+ if (isNumber(val)) {
+ team.score = Score.of(parseInt(val, 10));
+ renderAll(true);
+ }
+ span.click(editor);
+ });
+ }
+ editor();
+ });
+ }
+ }
+ return tEl;
+ }
+ var Match = (function () {
+ function Match(round, match, seed, results, renderCb, isFirstBracket, opts, resultId, topCon, renderAll) {
+ this.round = round;
+ this.match = match;
+ this.seed = seed;
+ this.renderCb = renderCb;
+ this.isFirstBracket = isFirstBracket;
+ this.opts = opts;
+ this.resultId = resultId;
+ this.topCon = topCon;
+ this.renderAll = renderAll;
+ this.connectorCb = Option.empty();
+ this.matchCon = $('');
+ this.teamCon = $('');
+ this.alignCb = null;
+ this.matchUserData = !results.isEmpty() ? results.get().userData : undefined;
+ if (!opts.save) {
+ // The hover and click callbacks are bound by jQuery to the element
+ var userData_1 = this.matchUserData;
+ if (opts.onMatchHover) {
+ this.teamCon.hover(function () {
+ opts.onMatchHover(userData_1, true);
+ }, function () {
+ opts.onMatchHover(userData_1, false);
+ });
+ }
+ if (opts.onMatchClick) {
+ this.teamCon.click(function () {
+ opts.onMatchClick(userData_1);
+ });
+ }
+ }
+ match.a.name = match.a.source().name;
+ match.b.name = match.b.source().name;
+ match.a.score = results.map(function (r) { return r.first.toNull(); });
+ match.b.score = results.map(function (r) { return r.second.toNull(); });
+ /* match has score even though teams haven't yet been decided */
+ /* todo: would be nice to have in preload check, maybe too much work */
+ if ((!match.a.name || !match.b.name) && (isNumber(match.a.score) || isNumber(match.b.score))) {
+ console.log('ERROR IN SCORE DATA: ' + match.a.source().name + ': ' +
+ match.a.score + ', ' + match.b.source().name + ': ' + match.b.score);
+ match.a.score = match.b.score = Score.empty();
+ }
+ }
+ Object.defineProperty(Match.prototype, "el", {
+ get: function () {
+ return this.matchCon;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ Match.prototype.getRound = function () {
+ return this.round;
+ };
+ Match.prototype.setConnectorCb = function (cb) {
+ this.connectorCb = cb;
+ };
+ Match.prototype.connect = function (cb) {
+ var _this = this;
+ var align = this.opts.dir === 'lr' ? 'right' : 'left';
+ var connectorOffset = this.teamCon.height() / 4;
+ var matchupOffset = this.matchCon.height() / 2;
+ var result = cb
+ .map(function (connectorCb) { return connectorCb(_this.teamCon, _this); })
+ .orElseGet(function () {
+ if (_this.seed % 2 === 0) {
+ return _this.winner().order
+ .map(function (order) { return order.map({
+ shift: connectorOffset * (_this.opts.centerConnectors ? 2 : 1),
+ height: matchupOffset
+ }, {
+ shift: connectorOffset * (_this.opts.centerConnectors ? 2 : 3),
+ height: matchupOffset - connectorOffset * (_this.opts.centerConnectors ? 0 : 2)
+ }); })
+ .orElse({
+ shift: connectorOffset * 2,
+ height: matchupOffset - connectorOffset * (_this.opts.centerConnectors ? 0 : 1)
+ });
+ }
+ else {
+ return _this.winner().order
+ .map(function (order) { return order.map({
+ shift: -connectorOffset * (_this.opts.centerConnectors ? 2 : 3),
+ height: -matchupOffset + connectorOffset * (_this.opts.centerConnectors ? 0 : 2)
+ }, {
+ shift: -connectorOffset * (_this.opts.centerConnectors ? 2 : 1),
+ height: -matchupOffset
+ }); })
+ .orElse({
+ shift: -connectorOffset * 2,
+ height: -matchupOffset + connectorOffset * (_this.opts.centerConnectors ? 0 : 1)
+ });
+ }
+ });
+ this.teamCon.append(connector(this.opts.roundMargin, result, this.teamCon, align));
+ };
+ Match.prototype.winner = function () { return this.match.winner(); };
+ Match.prototype.loser = function () { return this.match.loser(); };
+ Match.prototype.first = function () {
+ return this.match.a;
+ };
+ Match.prototype.second = function () {
+ return this.match.b;
+ };
+ Match.prototype.setAlignCb = function (cb) {
+ this.alignCb = cb;
+ };
+ Match.prototype.render = function () {
+ var _this = this;
+ this.matchCon.empty();
+ this.teamCon.empty();
+ // This shouldn't be done at render-time
+ this.match.a.name = this.match.a.source().name;
+ this.match.b.name = this.match.b.source().name;
+ this.match.a.seed = this.match.a.source().seed;
+ this.match.b.seed = this.match.b.source().seed;
+ var isDoubleBye = this.match.a.name.isEmpty() && this.match.b.name.isEmpty();
+ if (isDoubleBye) {
+ this.teamCon.addClass('np');
+ }
+ else if (!this.match.winner().name) {
+ this.teamCon.addClass('np');
+ }
+ else {
+ this.teamCon.removeClass('np');
+ }
+ // Coerce truthy/falsy "isset()" for Typescript
+ var isReady = !this.match.a.name.isEmpty() && !this.match.b.name.isEmpty();
+ this.teamCon.append(teamElement(this.round.roundNumber, this.match, this.match.a, this.match.b, isReady, this.isFirstBracket, this.opts, this.resultId, this.topCon, this.renderAll));
+ this.teamCon.append(teamElement(this.round.roundNumber, this.match, this.match.b, this.match.a, isReady, this.isFirstBracket, this.opts, this.resultId, this.topCon, this.renderAll));
+ this.matchCon.appendTo(this.round.el);
+ this.matchCon.append(this.teamCon);
+ this.el.css('height', (this.round.bracket.el.height() / this.round.size()) + 'px');
+ this.teamCon.css('top', (this.el.height() / 2 - this.teamCon.height() / 2) + 'px');
+ /* todo: move to class */
+ if (this.alignCb !== null) {
+ this.alignCb(this.teamCon);
+ }
+ var isLast = this.renderCb.map(function (cb) { return cb(_this); }).orElse(false);
+ if (!isLast) {
+ this.connect(this.connectorCb);
+ }
+ };
+ Match.prototype.results = function () {
+ // Either team is bye -> reset (mutate) scores from that match
+ var hasBye = this.match.a.name.isEmpty() || this.match.b.name.isEmpty();
+ if (hasBye) {
+ this.match.a.score = this.match.b.score = Score.empty();
+ }
+ return new ResultObject(this.match.a.score, this.match.b.score, this.matchUserData);
+ };
+ return Match;
+ }());
+ var undefinedToNull = function (value) { return value === undefined ? null : value; };
+ var wrapResults = function (initResults) { return initResults
+ .map(function (brackets) { return brackets
+ .map(function (rounds) { return rounds
+ .map(function (matches) { return new ResultObject(Score.of(undefinedToNull(matches[0])), Score.of(undefinedToNull(matches[1])), matches[2]); }); }); }); };
+ var JqueryBracket = function (opts) {
+ var resultId = new ResultId();
+ var data = opts.init;
+ var isSingleElimination = (data.results.length <= 1);
+ // 45 === team height x2 + 1px margin
+ var height = data.teams.length * 45 + data.teams.length * opts.matchMargin;
+ var topCon = $('').appendTo(opts.el.empty());
+ function resizeContainer() {
+ var roundCount = countRounds(data.teams.length, isSingleElimination, opts.skipGrandFinalComeback, opts.skipSecondaryFinal, data.results);
+ if (!opts.disableToolbar) {
+ topCon.css('width', roundCount * (opts.teamWidth + opts.scoreWidth + opts.roundMargin) + 40);
+ }
+ else {
+ topCon.css('width', roundCount * (opts.teamWidth + opts.scoreWidth + opts.roundMargin) + 10);
+ }
+ // reserve space for consolation round
+ if (isSingleElimination && data.teams.length <= 2 && !opts.skipConsolationRound) {
+ topCon.css('height', height + 40);
+ }
+ }
+ var w, l, f;
+ function renderAll(save) {
+ resultId.reset();
+ w.render();
+ if (l) {
+ l.render();
+ }
+ if (f && !opts.skipGrandFinalComeback) {
+ f.render();
+ }
+ if (!opts.disableHighlight) {
+ postProcess(topCon, w, f);
+ }
+ if (save) {
+ data.results[0] = w.results();
+ if (l) {
+ data.results[1] = l.results();
+ }
+ if (f && !opts.skipGrandFinalComeback) {
+ data.results[2] = f.results();
+ }
+ // Loser bracket comeback in finals might require a new round
+ resizeContainer();
+ if (opts.save) {
+ opts.save(exportData(data), opts.userData);
+ }
+ }
+ }
+ if (opts.skipSecondaryFinal && isSingleElimination) {
+ $.error('skipSecondaryFinal setting is viable only in double elimination mode');
+ }
+ if (!opts.disableToolbar) {
+ embedEditButtons(topCon, data, opts);
+ }
+ var fEl, wEl, lEl;
+ if (isSingleElimination) {
+ wEl = $('').appendTo(topCon);
+ }
+ else {
+ if (!opts.skipGrandFinalComeback) {
+ fEl = $('').appendTo(topCon);
+ }
+ wEl = $('').appendTo(topCon);
+ lEl = $('').appendTo(topCon);
+ }
+ wEl.css('height', height);
+ if (lEl) {
+ lEl.css('height', wEl.height() / 2);
+ }
+ resizeContainer();
+ var mkMatch = function (round, match, seed, results, renderCb, isFirstBracket, opts) {
+ return new Match(round, match, seed, results, renderCb, isFirstBracket, opts, resultId, topCon, renderAll);
+ };
+ w = new Bracket(wEl, Option.of(data.results[0] || null), mkMatch, true, opts);
+ if (!isSingleElimination) {
+ l = new Bracket(lEl, Option.of(data.results[1] || null), mkMatch, false, opts);
+ if (!opts.skipGrandFinalComeback) {
+ f = new Bracket(fEl, Option.of(data.results[2] || null), mkMatch, false, opts);
+ }
+ }
+ prepareWinners(w, data.teams, isSingleElimination, opts, opts.skipGrandFinalComeback && !isSingleElimination);
+ if (!isSingleElimination) {
+ prepareLosers(w, l, data.teams.length, opts.skipGrandFinalComeback, opts.centerConnectors);
+ if (!opts.skipGrandFinalComeback) {
+ prepareFinals(f, w, l, opts, topCon, resizeContainer);
+ }
+ }
+ renderAll(false); // LBEdit: I NEEED this
+ return {
+ data: function () {
+ return exportData(opts.init);
+ }
+ };
+ };
+ function embedEditButtons(topCon, data, opts) {
+ var tools = $('').appendTo(topCon);
+ var inc = $('+').appendTo(tools);
+ inc.click(function () {
+ var len = data.teams.length;
+ for (var i = 0; i < len; i += 1) {
+ data.teams.push([Option.empty(), Option.empty()]);
+ }
+ return JqueryBracket(opts);
+ });
+ if (data.teams.length > 1 && data.results.length === 1 ||
+ data.teams.length > 2 && data.results.length === 3) {
+ var dec = $('-').appendTo(tools);
+ dec.click(function () {
+ if (data.teams.length > 1) {
+ data.teams = data.teams.slice(0, data.teams.length / 2);
+ return JqueryBracket(opts);
+ }
+ });
+ }
+ if (data.results.length === 1 && data.teams.length > 1) {
+ var type = $('de').appendTo(tools);
+ type.click(function () {
+ if (data.teams.length > 1 && data.results.length < 3) {
+ data.results.push([], []);
+ return JqueryBracket(opts);
+ }
+ });
+ }
+ else if (data.results.length === 3 && data.teams.length > 1) {
+ var type = $('se').appendTo(tools);
+ type.click(function () {
+ if (data.results.length === 3) {
+ data.results = data.results.slice(0, 1);
+ return JqueryBracket(opts);
+ }
+ });
+ }
+ }
+ var assertNumber = function (opts, field) {
+ if (opts.hasOwnProperty(field)) {
+ var expectedType = 'number';
+ var type = typeof (opts[field]);
+ if (type !== expectedType) {
+ throw new Error("Option \"" + field + "\" is " + type + " instead of " + expectedType);
+ }
+ }
+ };
+ var assertBoolean = function (opts, field) {
+ var value = opts[field];
+ var expectedType = 'boolean';
+ var type = typeof (value);
+ if (type !== expectedType) {
+ throw new Error("Value of " + field + " must be boolean, got " + expectedType + ", got " + type);
+ }
+ };
+ var assertGt = function (expected, opts, field) {
+ var value = opts[field];
+ if (value < expected) {
+ throw new Error("Value of " + field + " must be greater than " + expected + ", got " + value);
+ }
+ };
+ var isPow2 = function (x) { return (x & (x - 1)); };
+ var methods = {
+ init: function (originalOpts) {
+ var opts = $.extend(true, {}, originalOpts); // Do not mutate inputs
+ if (!opts) {
+ throw Error('Options not set');
+ }
+ if (!opts.init && !opts.save) {
+ throw Error('No bracket data or save callback given');
+ }
+ if (opts.userData === undefined) {
+ opts.userData = null;
+ }
+ if (opts.decorator && (!opts.decorator.edit || !opts.decorator.render)) {
+ throw Error('Invalid decorator input');
+ }
+ else if (!opts.decorator) {
+ opts.decorator = { edit: defaultEdit, render: defaultRender };
+ }
+ if (!opts.init) {
+ opts.init = {
+ teams: [
+ [Option.empty(), Option.empty()]
+ ],
+ results: []
+ };
+ }
+ var that = this;
+ opts.el = this;
+ if (opts.save && (opts.onMatchClick || opts.onMatchHover)) {
+ $.error('Match callbacks may not be passed in edit mode (in conjunction with save callback)');
+ }
+ var disableToolbarType = typeof (opts.disableToolbar);
+ var disableToolbarGiven = opts.hasOwnProperty('disableToolbar');
+ if (disableToolbarGiven && disableToolbarType !== 'boolean') {
+ $.error("disableToolbar must be a boolean, got " + disableToolbarType);
+ }
+ if (!opts.save && disableToolbarGiven) {
+ $.error('disableToolbar can be used only if the bracket is editable, i.e. "save" callback given');
+ }
+ if (!disableToolbarGiven) {
+ opts.disableToolbar = (opts.save === undefined);
+ }
+ var disableTeamEditType = typeof (opts.disableTeamEdit);
+ var disableTeamEditGiven = opts.hasOwnProperty('disableTeamEdit');
+ if (disableTeamEditGiven && disableTeamEditType !== 'boolean') {
+ $.error("disableTeamEdit must be a boolean, got " + disableTeamEditType);
+ }
+ if (!opts.save && disableTeamEditGiven) {
+ $.error('disableTeamEdit can be used only if the bracket is editable, i.e. "save" callback given');
+ }
+ if (!disableTeamEditGiven) {
+ opts.disableTeamEdit = false;
+ }
+ if (!opts.disableToolbar && opts.disableTeamEdit) {
+ $.error('disableTeamEdit requires also resizing to be disabled, initialize with "disableToolbar: true"');
+ }
+ /* wrap data to into necessary arrays */
+ var r = wrap(opts.init.results, 4 - depth(opts.init.results));
+ opts.init.results = wrapResults(r);
+ assertNumber(opts, 'teamWidth');
+ assertNumber(opts, 'scoreWidth');
+ assertNumber(opts, 'roundMargin');
+ assertNumber(opts, 'matchMargin');
+ if (!opts.hasOwnProperty('teamWidth')) {
+ opts.teamWidth = 70;
+ }
+ if (!opts.hasOwnProperty('scoreWidth')) {
+ opts.scoreWidth = 30;
+ }
+ if (!opts.hasOwnProperty('roundMargin')) {
+ opts.roundMargin = 40;
+ }
+ if (!opts.hasOwnProperty('matchMargin')) {
+ opts.matchMargin = 20;
+ }
+ assertGt(0, opts, 'teamWidth');
+ assertGt(0, opts, 'scoreWidth');
+ assertGt(0, opts, 'roundMargin');
+ assertGt(0, opts, 'matchMargin');
+ if (!opts.hasOwnProperty('centerConnectors')) {
+ opts.centerConnectors = false;
+ }
+ assertBoolean(opts, 'centerConnectors');
+ if (!opts.hasOwnProperty('disableHighlight')) {
+ opts.disableHighlight = false;
+ }
+ assertBoolean(opts, 'disableHighlight');
+ var log2Result = isPow2(opts.init.teams.length);
+ if (log2Result !== Math.floor(log2Result)) {
+ $.error("\"teams\" property must have 2^n number of team pairs, i.e. 1, 2, 4, etc. Got " + opts.init.teams.length + " team pairs.");
+ }
+ opts.dir = opts.dir || 'lr';
+ opts.init.teams = !opts.init.teams || opts.init.teams.length === 0 ? [[null, null]] : opts.init.teams;
+ opts.init.teams = opts.init.teams.map(function (ts) { return ts.map(function (t) { return t === null ? Option.empty() : Option.of(t); }); });
+ opts.skipConsolationRound = opts.skipConsolationRound || false;
+ opts.skipSecondaryFinal = opts.skipSecondaryFinal || false;
+ if (opts.dir !== 'lr' && opts.dir !== 'rl') {
+ $.error('Direction must be either: "lr" or "rl"');
+ }
+ var bracket = JqueryBracket(opts);
+ $(this).data('bracket', { target: that, obj: bracket });
+ return bracket;
+ },
+ data: function () {
+ var bracket = $(this).data('bracket');
+ return bracket.obj.data();
+ }
+ };
+ $.fn.bracket = function (method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ }
+ else {
+ $.error('Method ' + method + ' does not exist on jQuery.bracket');
+ }
+ };
+})(jQuery);