Bracket prototyping - 2

by goranobradovic

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-debug.js"></script>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<br/>
<div class='container-fluid'>
  <div data-bind="with:parameters">
    <fieldset class='parameters table-bordered'>
      <legend>
        Bracket parameters
      </legend>
      <label for="numberOfParticipants">Number of participants</label>
      <select id="numberOfParticipants" data-bind="options: availableNumberOfParticipants,
                         value: numberOfParticipants">
      </select>
    </fieldset>
    <fieldset class='parameters table-bordered' data-bind="with:display">
      <legend>
        Display parameters
      </legend>
      <span class='float' style="display:none;">
            <label for="useAlternateTemplate">Use alternate template</label>
            <input id="useAlternateTemplate" type='checkbox' data-bind="checked:alternateTemplate" />
      </span>
      <label for='canvasWidth'>Width: <span id='canvasWidth' data-bind='text:width'></span></label>
    </fieldset>
  </div>
  <br/>
  <div class='container-fluid' data-bind="with:bracket">
    <fieldset data-bind="if:numberOfParticipants">
      <legend>
        Bracket output
      </legend>
      <div data-bind="ifnot:$root.parameters.display.alternateTemplate()" style="display:none;">
        <div data-bind="template: { name: 'match-template', data: final }">
        </div>
      </div>
      <div class='clear'>
      </div>
      <div class='container container-fluid' data-bind="if:$root.parameters.display.alternateTemplate()" style="display:none;">
        Second template
        <div data-bind="template: {name: 'winner-match-template', data: final}">
        </div>
      </div>
      <br/>
      <div id='canvasBrackets'>

      </div>
    </fieldset>
  </div>
</div>


<script type="text/html" id="match-template">
  <table class='table table-hover table-bordered table-match'>
 ...

CSS

@import url('//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css');
table.table-match>tbody>tr>td {
  padding: 0px;
  border-top: none;
}

table.table-match>tbody>tr>td>table.table-match>tbody>tr>td {
  border-width: 0px;
  border-left-width: 1px
}

table.table-match {
  margin-bottom: 0px;
  border-top: none;
}

.match-container {
  display: -webkit-box;
  /* OLD - iOS 6-, Safari 3.1-6 */
  display: -moz-box;
  /* OLD - Firefox 19- (buggy but mostly works) */
  display: -ms-flexbox;
  /* TWEENER - IE 10 */
  display: -webkit-flex;
  /* NEW - Chrome */
  display: flex;
  list-style: none;
  border: 1px gray solid;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
}

.match-item {
  padding: 0px;
  width: 50%;
  height: 100%;
  margin: 0px;
  line-height: 15px;
  color: black;
  font-weight: bold;
  font-size: 14px;
  text-align: center;
  vertical-align: middle;
  /*flex-flow: column;*/
  align-items: stretch;
}

.match-team {
  width: 100%;
  height: 50%;
  display: block;
}

svg {
  border: 1px solid silver;
}

.node {
  cursor: pointer;
}

.node circle {
  stroke: steelblue;
  stroke-width: 1.5px;
}

.link {
  fill: none;
  stroke: lightgray;
  stroke-width: 1.5px;
}

JavaScript

function DisplayParameters() {
  var self = this;
  self.updateForNumberOfParticipants = function(numberOfParticipants) {
    var depth = Math.ceil(Math.log2(numberOfParticipants)) + 1;
    self.width(50 + self.maxLabel() * depth);
    self.height(numberOfParticipants * 40);
  };
  self.width = ko.observable(1200);
  self.height = ko.observable(800);
  self.maxLabel = ko.observable(150);
};

function Parameters() {
  var self = this;
  self.display = new DisplayParameters();
  self.display.alternateTemplate = ko.observable(false);
  self.numberOfParticipants = ko.observable(0);
  var optionsForDropdown = Array.apply(null, {
    length: 127
  }).map(Number.call, function(i) {
    return i + 2;
  });
  self.availableNumberOfParticipants = ko.observableArray(optionsForDropdown);

  self.numberOfParticipants.subscribe(self.display.updateForNumberOfParticipants);
};

function Team(name) {
  var self = this;
  self.className = 'team';
  self.name = name;
  self.children = [];
  self.toJson = function() {
    return {
      name: self.name,
      children: null
    };
  };
}

function Match(id, name, left, right) {
  var self = this;
  self.id = id;
  self.className = 'match';
  self.children = [left, right];
  self.name = name;
  self.toJson = function() {
    return {
      name: self.name,
      children: self.children.map(function(item) {
        return item.toJson();
      })
    };
  };
};

function Bracket(parameters) {
  var self = this;
  self.parameters = parameters;
  self.final = ko.observable(null);
  var currentMatchNumber;

  self.parameters.numberOfParticipants.subscribe(generateBracketMatches);

  function generateBracketMatches(numberOfParticipants) {
    currentMatchNumber = 1;
    var teams = Array.apply(null, {
        length: numberOfParticipants
      }).map(Number.call, function(i) {
        return i + 1;
      })
      .reverse();
    console.log('//////////////////////////////////////////////////');
    var matches = [];
    var teamMatches =...