Tuesday, October 19, 2010

jQuery & JSON to draw single-elimination tournament bracket

Often I see sites that present tournament brackets as an image (even on fairly technical sites, eg http://us.battle.net/sc2/en/blog/936927#blog). Purely out of curiosity, I decided to see what would be involved in merely providing the data from the server and letting an HTML UI build up on the fly using jQuery. To avoid needing to produce a server for this I simply hard-coded in JSON that might have been returned from a server with (very) basic information about the tournament:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
<head>
  <title>MyTournamentName</title>
 <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' type='text/javascript'>
 </script>
 <script src='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js' type='text/javascript'>
 </script>  
  <script type="text/javascript">  
    var matchInfo = {
      "rounds" : [
        { "name": "Round1",
          "matches" : [
            { "id" : 1, "p1" : "mTwDeMuslim", "p2" : "Luffy" },
            { "id" : 2, "p1" : "SeleCT", "p2" : "NEXGenius" },
            { "id" : 3, "p1" : "Fenix", "p2" : "SoftBall" },
            { "id" : 4, "p1" : "White-Ra", "p2" : "Ice" },
            { "id" : 5, "p1" : "HuK", "p2" : "RedArchon" },
            { "id" : 6, "p1" : "Capoch", "p2" : "Loner" },
            { "id" : 7, "p1" : "mTwDIMAGA", "p2" : "MakaPrime" },
            { "id" : 8, "p1" : "TLAF-Liquid`TLO", "p2" : "SEN" }
          ]
        },
        { "name": "Round2",
          "matches" : [
            { "id" : 9, "p1" : null, "p2" : null },
            { "id" : 10, "p1" : null, "p2" : null },
            { "id" : 11, "p1" : null, "p2" : null },
            { "id" : 12, "p1" : null, "p2" : null }
          ]
        },
        { "name": "Round3",
          "matches" : [
            { "id" : 13, "p1" : null, "p2" : null },
            { "id" : 14, "p1" : null, "p2" : null },
          ]
        },
        { "name": "Round4",
          "matches" : [
            { "id" : 15, "p1" : null, "p2" : null },
          ]
        }                
      ]
    };
  </script>
</head>
<body>
  <div>blah blah blah</div>
  <div id="writeHere" class="tournament"></div>
  <div>blah blah blah</div>
</body>
</html>
Next we need to write some jQuery code to fill in the div with id="writeHere" with our purely html-based tournament bracket. Easy enough to do (note that some rudimentary css has been slapped in to show us where which bits are):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
<head>
  <title>MyTournamentName</title>
 <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' type='text/javascript'>
 </script>
 <script src='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js' type='text/javascript'>
 </script>  
  <style type="text/css">
  .tournament {    
    background-color: #F0F0F0;
    border: dashed 1px solid;
    overflow: auto;
  }
  .tournament .bracket {
    background-color: #DFDFDF;
    min-width: 100px;    
    vertical-align: top;
    float: left;
  }
  
  .tournament .bracket .match {
    background-color: #D0D0D0;
    border-top: 1px solid;
    border-right: 1px solid;
    border-bottom: 1px solid;  
  }
  .tournament .bracket .match .p1 {    
    height: 20px;
  }
  .tournament .bracket .match .p2 {
    height: 20px;
  }    
  .tournament .bracket .match .spacer {
    background-color: #DFDFDF;
    height: 38px;
  }
  .tournament .bracket .spacer {
    height: 80px;
  }
  .tournament .bracket .half-spacer {
    height: 40px;
  }
  .tournament .bracket .small-spacer {
    height: 10px;
    background-color: #F1F1F1;
  }
  
  .left-line {
    border-left: 1px solid;
  }
  
  .tournament .cell {
    min-width: 100px;
    height: 20px;
    float: left;
    background-color: #DFDFDF;    
  }   
  .tournament .l2 {
    background-color: #D0D0D0;
  }     
  .tournament .lmax {
    width: 0px;
    clear: both;
  }    
  </style>
  <script type="text/javascript">
  
    var matchInfo = {
      "rounds" : [
        { "name": "Round1",
          "matches" : [
            { "id" : 1, "p1" : "mTwDeMuslim", "p2" : "Luffy" },
            { "id" : 2, "p1" : "SeleCT", "p2" : "NEXGenius" },
            { "id" : 3, "p1" : "Fenix", "p2" : "SoftBall" },
            { "id" : 4, "p1" : "White-Ra", "p2" : "Ice" },
            { "id" : 5, "p1" : "HuK", "p2" : "RedArchon" },
            { "id" : 6, "p1" : "Capoch", "p2" : "Loner" },
            { "id" : 7, "p1" : "mTwDIMAGA", "p2" : "MakaPrime" },
            { "id" : 8, "p1" : "TLAF-Liquid`TLO", "p2" : "SEN" }
          ]
        },
        { "name": "Round2",
          "matches" : [
            { "id" : 9, "p1" : null, "p2" : null },
            { "id" : 10, "p1" : null, "p2" : null },
            { "id" : 11, "p1" : null, "p2" : null },
            { "id" : 12, "p1" : null, "p2" : null }
          ]
        },
        { "name": "Round3",
          "matches" : [
            { "id" : 13, "p1" : null, "p2" : null },
            { "id" : 14, "p1" : null, "p2" : null },
          ]
        },
        { "name": "Round4",
          "matches" : [
            { "id" : 15, "p1" : null, "p2" : null },
          ]
        }                
      ]
    };
  
    $(document).ready(function($) {       
      var base = $('#writeHere');
      var numTeams = 16;
      var matchesByRound = setupMatchboxes(numTeams);
      
      for (var lvl=0; lvl<matchesByRound.length; lvl++) {                
        var matchBoxes = matchesByRound[lvl];        
        var bracket = checkedAppend('<div class="bracket"></div>', base);
        
        for (var i=0; i<matchBoxes.length; i++) {                     
          var match = matchInfo.rounds[lvl].matches[i];
          var matchHtml = '<div class="match" id="match' + match.id + '">'
            + '<div class="p1">' + fmtName(match.p1) + '</div>'
            + '<div class="spacer"></div>'
            + '<div class="p2">' + fmtName(match.p2) + '</div>';
          checkedAppend(matchHtml, bracket);  
        }
      }      
    });
    
    function fmtName(name) {
      return null != name ? name : '?';
    }
    
    function setupMatchboxes(numTeams) {
      var numLevels = Math.log(numTeams)/Math.LN2;
      var numMatchesForLevel = numTeams / 2;
      var matchBoxes = [];
      
      do {
        var matchesForLevel = [];        
        matchBoxes.push(matchesForLevel);
        
        for (var match=0; match<numMatchesForLevel; match++) {
          matchesForLevel.push(match);
        }
        
        numMatchesForLevel = numMatchesForLevel / 2;
      } while(numMatchesForLevel >= 1);
      return matchBoxes;
    }
    
    function checkedAppend(rawHtml, appendTo) {
      var html = $(rawHtml);
      if (0 == html.length) {
        throw "Built ourselves bad html : " + rawHtml;
      }
      html.appendTo(appendTo);      
      return html;
    }
  </script>
</head>
<body>
  <div>blah blah blah</div>
  <div id="writeHere" class="tournament"></div>
  <div>blah blah blah</div>
</body>
</html>
However, this doesn't line things up quite as nicely as one might hope (to say the least):

We have a couple of clear problems:

  1. We probably want a small vertical space between the first row of matches.
  2. For rows 2..N, a match needs to line up such that its top is at the center of one of the matches on the previous row and its bottom is at the center of another. The specific offset helpfully changes from row to row. It turns out to be a bit of a pain to write css for this so instead we'll just write jQuery code to manually size elements for our first pass. Eg we want something like this (note inconsistent sizing and positioning row to row):
Luckily jQuery provides convenient accessors for height and position so we can write code that literally says "make a vertical spacing div that is half the size of that div and make my div tall enough to stretch from there to there". The main thing that will need an update is that we'll need to keep references to the divs as we go along row by row. This will let us easily set things relative to other things similar to:
var newH = stretchTo.position().top + stretchTo.height()/2 - matchDiv.position().top;
This will ultimately yield the following javascript gibberish:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
<head>
  <title>MyTournamentName</title>
 <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' type='text/javascript'>
 </script>
 <script src='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js' type='text/javascript'>
 </script>  
  <style type="text/css">
  .tournament {    
    background-color: #F0F0F0;
    border: dashed 1px solid;
    overflow: auto;
  }
  .tournament .bracket {
    background-color: #DFDFDF;
    min-width: 100px;    
    vertical-align: top;
    float: left;
  }
  
  .tournament .bracket .match {
    background-color: #D0D0D0;
    border-top: 1px solid;
    border-right: 1px solid;
    border-bottom: 1px solid;  
  }
  .tournament .bracket .match .p1 {    
    height: 20px;
  }
  .tournament .bracket .match .p2 {
    height: 20px;
  }    
  .tournament .bracket .match .spacer {
    background-color: #DFDFDF;
    height: 38px;
  }
  .tournament .bracket .spacer {
    height: 80px;
  }
  .tournament .bracket .half-spacer {
    height: 40px;
  }
  .tournament .bracket .small-spacer {
    height: 10px;
    background-color: #F1F1F1;
  }
  
  .left-line {
    border-left: 1px solid;
  }
  
  .tournament .cell {
    min-width: 100px;
    height: 20px;
    float: left;
    background-color: #DFDFDF;    
  }   
  .tournament .l2 {
    background-color: #D0D0D0;
  }     
  .tournament .lmax {
    width: 0px;
    clear: both;
  }    
  </style>
  <script type="text/javascript">
  
    var matchInfo = {
      "rounds" : [
        { "name": "Round1",
          "matches" : [
            { "id" : 1, "p1" : "mTwDeMuslim", "p2" : "Luffy" },
            { "id" : 2, "p1" : "SeleCT", "p2" : "NEXGenius" },
            { "id" : 3, "p1" : "Fenix", "p2" : "SoftBall" },
            { "id" : 4, "p1" : "White-Ra", "p2" : "Ice" },
            { "id" : 5, "p1" : "HuK", "p2" : "RedArchon" },
            { "id" : 6, "p1" : "Capoch", "p2" : "Loner" },
            { "id" : 7, "p1" : "mTwDIMAGA", "p2" : "MakaPrime" },
            { "id" : 8, "p1" : "TLAF-Liquid`TLO", "p2" : "SEN" }
          ]
        },
        { "name": "Round2",
          "matches" : [
            { "id" : 9, "p1" : null, "p2" : null },
            { "id" : 10, "p1" : null, "p2" : null },
            { "id" : 11, "p1" : null, "p2" : null },
            { "id" : 12, "p1" : null, "p2" : null }
          ]
        },
        { "name": "Round3",
          "matches" : [
            { "id" : 13, "p1" : null, "p2" : null },
            { "id" : 14, "p1" : null, "p2" : null },
          ]
        },
        { "name": "Round4",
          "matches" : [
            { "id" : 15, "p1" : null, "p2" : null },
          ]
        }                
      ]
    };
  
    $(document).ready(function($) {       
      var base = $('#writeHere');
      var numTeams = 16;
      var matchesByRound = setupMatchboxes(numTeams);
      var matchDivsByRound = [];
      
      for (var lvl=0; lvl<matchesByRound.length; lvl++) {                
        var matchBoxes = matchesByRound[lvl];        
        var bracket = checkedAppend('<div class="bracket"></div>', base);
        var matchDivs = [];
        matchDivsByRound.push(matchDivs);
        
        for (var i=0; i<matchBoxes.length; i++) {                     
          var vOffset = checkedAppend('<div></div>', bracket);
        
          var match = matchInfo.rounds[lvl].matches[i];
          var matchHtml = '<div class="match" id="match' + match.id + '">'
            + '<div class="p1">' + fmtName(match.p1) + '</div>'
            + '<div class="spacer"></div>'
            + '<div class="p2">' + fmtName(match.p2) + '</div>';
          matchDiv = checkedAppend(matchHtml, bracket);
          matchDivs.push(matchDiv);
          
          if (lvl > 0) {
            //row 2+; line up with previous row
            var alignTo = matchDivsByRound[lvl-1][i*2];
            //offset to line up tops
            var desiredOffset = alignTo.position().top - matchDiv.position().top;
            
            //offset by half the previous match-height
            desiredOffset += alignTo.height() / 2;
            vOffset.height(desiredOffset);            
          } else {
            checkedAppend('<div class="small-spacer"></div>', bracket);
          }
          
          if (lvl > 0) {
            //tweak our size so we stretch to the middle of the appropriate element
            var stretchTo = matchDivsByRound[lvl-1][i*2+1];
            var newH = stretchTo.position().top + stretchTo.height()/2 - matchDiv.position().top;            
            var deltaH = newH - matchDiv.height();
            matchDiv.height(newH);
            var spacer = matchDiv.find('.spacer');
            spacer.height(spacer.height() + deltaH);
          }          
        }
      }      
    });
    
    function fmtName(name) {
      return null != name ? name : '?';
    }
    
    function setupMatchboxes(numTeams) {
      var numLevels = Math.log(numTeams)/Math.LN2;
      var numMatchesForLevel = numTeams / 2;
      var matchBoxes = [];
      
      do {
        var matchesForLevel = [];        
        matchBoxes.push(matchesForLevel);
        
        for (var match=0; match<numMatchesForLevel; match++) {
          matchesForLevel.push(match);
        }
        
        numMatchesForLevel = numMatchesForLevel / 2;
      } while(numMatchesForLevel >= 1);
      return matchBoxes;
    }
    
    function checkedAppend(rawHtml, appendTo) {
      var html = $(rawHtml);
      if (0 == html.length) {
        throw "Built ourselves bad html : " + rawHtml;
      }
      html.appendTo(appendTo);      
      return html;
    }
  </script>
</head>
<body>
  <div>blah blah blah</div>
  <div id="writeHere" class="tournament"></div>
  <div>blah blah blah</div>
</body>
</html>
On nice modern browsers this yields something like this:

Last of all lets clean up our javascript slightly, in particular making our code a little more directly based on the JSON and a little less on hard-coded test variables like numTeams. And lets add a spot for the final victor:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
<head>
  <title>MyTournamentName</title>
 <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' type='text/javascript'>
 </script>
 <script src='http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js' type='text/javascript'>
 </script>  
  <style type="text/css">
  .tournament {    
    background-color: #F0F0F0;
    border: dashed 1px solid;
    overflow: auto;
  }
  .tournament .bracket {
    background-color: #DFDFDF;
    min-width: 100px;    
    vertical-align: top;
    float: left;
  }
  
  .tournament .bracket .match {
    background-color: #D0D0D0;
    border-top: 1px solid;
    border-right: 1px solid;
    border-bottom: 1px solid;  
  }
  .tournament .bracket .match .p1 {    
    height: 20px;
  }
  .tournament .bracket .match .p2 {
    height: 20px;
  }    
  .tournament .bracket .match .spacer {
    background-color: #DFDFDF;
    height: 38px;
  }
  .tournament .bracket .spacer {
    height: 80px;
  }
  .tournament .bracket .half-spacer {
    height: 40px;
  }
  .tournament .bracket .small-spacer {
    height: 10px;
    background-color: #F1F1F1;
  }
  .tournament .bracket .winner {
    border-bottom: 1px solid;
  }
  
  .left-line {
    border-left: 1px solid;
  }
  
  .tournament .cell {
    min-width: 100px;
    height: 20px;
    float: left;
    background-color: #DFDFDF;    
  }   
  .tournament .l2 {
    background-color: #D0D0D0;
  }     
  .tournament .lmax {
    width: 0px;
    clear: both;
  }    
  </style>
  <script type="text/javascript">
  
    var matchInfo = {
      "rounds" : [
        { "name": "Round1",
          "matches" : [
            { "id" : 1, "p1" : "mTwDeMuslim", "p2" : "Luffy" },
            { "id" : 2, "p1" : "SeleCT", "p2" : "NEXGenius" },
            { "id" : 3, "p1" : "Fenix", "p2" : "SoftBall" },
            { "id" : 4, "p1" : "White-Ra", "p2" : "Ice" },
            { "id" : 5, "p1" : "HuK", "p2" : "RedArchon" },
            { "id" : 6, "p1" : "Capoch", "p2" : "Loner" },
            { "id" : 7, "p1" : "mTwDIMAGA", "p2" : "MakaPrime" },
            { "id" : 8, "p1" : "TLAF-Liquid`TLO", "p2" : "SEN" }
          ]
        },
        { "name": "Round2",
          "matches" : [
            { "id" : 9, "p1" : null, "p2" : null },
            { "id" : 10, "p1" : null, "p2" : null },
            { "id" : 11, "p1" : null, "p2" : null },
            { "id" : 12, "p1" : null, "p2" : null }
          ]
        },
        { "name": "Round3",
          "matches" : [
            { "id" : 13, "p1" : null, "p2" : null },
            { "id" : 14, "p1" : null, "p2" : null },
          ]
        },
        { "name": "Round4",
          "matches" : [
            { "id" : 15, "p1" : null, "p2" : null },
          ]
        } 
      ]
    };
  
    $(document).ready(function($) {       
      var base = $('#writeHere');
      var matchDivsByRound = [];
      
      for (var roundIndex=0; roundIndex<matchInfo.rounds.length; roundIndex++) {    
        var round = matchInfo.rounds[roundIndex];
        var bracket = checkedAppend('<div class="bracket"></div>', base);
        var matchDivs = [];
        matchDivsByRound.push(matchDivs);
        
        //setup the match boxes round by round
        for (var i=0; i<round.matches.length; i++) {                     
          var vOffset = checkedAppend('<div></div>', bracket);
        
          var match = round.matches[i];
          var matchHtml = '<div class="match" id="match' + match.id + '">'
            + '<div class="p1">' + fmtName(match.p1) + '</div>'
            + '<div class="spacer"></div>'
            + '<div class="p2">' + fmtName(match.p2) + '</div>';
          matchDiv = checkedAppend(matchHtml, bracket);
          matchDivs.push(matchDiv);
          
          if (roundIndex > 0) {
            //row 2+; line up with previous row
            var alignTo = matchDivsByRound[roundIndex-1][i*2];
            //offset to line up tops
            var desiredOffset = alignTo.position().top - matchDiv.position().top;
            
            //offset by half the previous match-height
            desiredOffset += alignTo.height() / 2;
            vOffset.height(desiredOffset);            
          } else {
            checkedAppend('<div class="small-spacer"></div>', bracket);
          }
          
          if (roundIndex > 0) {
            //tweak our size so we stretch to the middle of the appropriate element
            var stretchTo = matchDivsByRound[roundIndex-1][i*2+1];
            var newH = stretchTo.position().top + stretchTo.height()/2 - matchDiv.position().top;            
            var deltaH = newH - matchDiv.height();
            matchDiv.height(newH);
            var spacer = matchDiv.find('.spacer');
            spacer.height(spacer.height() + deltaH);
          }          
        }                
      }
      //setup the final winners box; just a space for a name whose bottom is centrally aligned with the last match
      bracket = checkedAppend('<div class="bracket"></div>', base);
      var vOffset = checkedAppend('<div></div>', bracket);
      var alignTo = matchDivsByRound[matchInfo.rounds.length - 1][0]; //only 1 match in the last round
      var html = '<div class="winner">?</div>';
      var winnerDiv = checkedAppend(html, bracket);      
      vOffset.height(alignTo.position().top - winnerDiv.position().top + alignTo.height() / 2 - winnerDiv.height());
    });
    
    function fmtName(name) {
      return null != name ? name : '?';
    }
    
    function checkedAppend(rawHtml, appendTo) {
      var html = $(rawHtml);
      if (0 == html.length) {
        throw "Built ourselves bad html : " + rawHtml;
      }
      html.appendTo(appendTo);      
      return html;
    }
  </script>
</head>
<body>
  <div>blah blah blah</div>
  <div id="writeHere" class="tournament"></div>
  <div>blah blah blah</div>
</body>
</html>

Ugly, but sized and positioned the way we want, ready to actually talk to a server and/or get some dynamic elements (eg the ability to designate a winner and have them promote through the tournament).

Ultimately this will hopefully get rolled up into a practicum project involving tournament management.

47 comments:

Isaac Earl said...

This is awesome. mad props. I might try and work this into a project...

lawrab said...

Awesome, I am going to have a play with this...!

Jamie said...

I'm also using this, thanks for the code assist I had rough drafted out pretty much the same idea as your blog outlines and then I wondered.. I bet someone else has thought of this! Lo and behold, you save me from coding it :)

You can see it here, http://www.sjscomputing.com/dailies/dailies/brackets/?game=Starcraft2

Your JSON array was a pain in the ass to re-create serverside but I managed :P. As well, I promise to pretty the site up so don't worry about your work being used in a poor environment.

Thanks again.

Edward Rustan said...

This is a great approach and I have integrated it into a .Net 4.0 web site with good results. However, I'd like to expand it to support double elimination brackets -- does anyone have an idea on how to do that?

vertu said...

Hi, thanks for the codes.

How can I use this with php and mysql.

for example I have mysql tables like users, tournaments etc.. is there any idea?

Sean Steinhoff said...

Fantastic! Flexible and scalable!

Anonymous said...

Awesome start to something great! Was able to modify it to fit my needs. Thanks again for sharing.

Thanh Dang said...

Great !

How I play a tournament with 13 teams ?

Thanks

ahmad fauzi said...

Hooraay.. thanks for sharing.. :D

Naviya Nair said...

I have read your blog its very attractive and impressive. I like it your blog.

Java Online Training Java EE Online Training Java EE Online Training Java 8 online training Core Java 8 online training

Java Online Training from India Java Online Training from India Core Java Training Online Core Java Training Online Java Training InstitutesJava Training Institutes

Karthika Shree said...

Excellent and very cool idea and the subject at the top of magnificence and I am happy to this post..Interesting post! Thanks for writing it.What's wrong with this kind of post exactly? It follows your previous guideline for post length as well as clarity.
Java Training in Chennai

sunitha vishnu said...

It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me...
Android Training in Chennai
Ios Training in Chennai

KarlDeville said...

Thank you for taking the time and sharing this information with us. It was indeed very helpful and insightful while being straight forward and to the point.
mcdonaldsgutscheine | startlr | saludlimpia

OC News said...

Excellent and very cool idea and the subject at the top of magnificence and I am happy to this post..Interesting post! Thanks for writing it.What's wrong with this kind of post exactly? Optocrypto

Steve M said...

Really Great effort. Thanks for your great explanation.

Java Training in Chennai | Java Course in Chennai

vimal kumar said...

I am really happy with your blog because your article is very unique and powerful for new reader.
Click here:
Selenium Training in Chennai | Selenium Training in Bangalore | Selenium Training in Pune | Selenium online Training

johnsy sai said...

Really you have done great job,There are may person searching about that now they will find enough resources by your post
Best Devops Training in pune
advanced excel training in bangalore

SRI said...


This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.. 

best rpa training in chennai |
rpa training in chennai | rpa online training |
rpa training in chennai |
rpa training in bangalore
rpa training in pune
rpa training in marathahalli
rpa training in btm

john brito said...

This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me
best rpa training in chennai
rpa training in chennai |
rpa online training
rpa course in bangalore
rpa training in pune
rpa training in marathahalli
rpa training in btm

kevin john said...

Inspiring writings and I greatly admired what you have to say , I hope you continue to provide new ideas for us all and greetings success always for you..Keep update more information.


rpa training in chennai |
best rpa training in chennai
rpa online training
rpa course in bangalore
rpa training in pune
rpa training in marathahalli
rpa training in btm

Mouni yoga said...

After seeing your article I want to say that the presentation is very good and also a well-written article with some very good information which is very useful for the readers....thanks for sharing it and do share more posts like this.
python course in pune | python course in chennai | python course in Bangalore

sakthi said...


This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.

best rpa training in chennai
rpa training in chennai |
rpa online training
rpa course in bangalore
rpa training in pune
rpa training in marathahalli
rpa training in btm

simbu said...

This is most informative and also this post most user friendly and super navigation to all posts... Thank you so much for giving this information to me.. 
Java training in Chennai | Java training in Tambaram | Java training in Chennai | Java training in Velachery

Java training in Chennai | Java training in Omr | Oracle training in Chennai

Ezhil K S said...

I think you have a long story to share and i am glad after long time finally you cam and shared your experience.
Data Science Training in Chennai | Data Science Training institute in Chennai
Data Science course in anna nagar
Data Science course in chennai | Data Science Training institute in Chennai | Best Data Science Training in Chennai
Data science course in Bangalore | Data Science Training institute in Bangalore | Best Data Science Training in Bangalore
Data Science course in marathahalli | Data Science training in Bangalore

keith kristoper said...

I am really happy with your blog because your article is very unique and powerful for new reader.
Click here:
selenium training in chennai
selenium training in bangalore
selenium training in Pune
selenium training in pune
Selenium Online Training

Dwarakesh babu said...

Howdy, would you mind letting me know which web host you’re utilizing? I’ve loaded your blog in 3 completely different web browsers, and I must say this blog loads a lot quicker than most. Can you suggest a good internet hosting provider at a reasonable price?


Best AWS Training Institute in BTM Layout Bangalore ,AWS Coursesin BTM


Best AWS Training in Marathahalli | AWS Training in Marathahalli

Amazon Web Services Training in Jaya Nagar | Best AWS Training in Jaya Nagar


AWS Training in BTM Layout |Best AWS Training in BTM Layout

AWS Training in Marathahalli | Best AWS Training in Marathahalli

Sri Nithya said...

Wonderful post. Thanks for taking time to share this information with us.
Python Training in Chennai
Python Training Institute in Chennai
RPA Training in Chennai
RPA courses in Chennai
Blue Prism Training in Chennai
UiPath Training in Chennai

Gautam krish said...

The blog which you have posted is more useful for us. Thanks for your information.
IELTS Coaching in Coimbatore
IELTS Coaching Center in Coimbatore
IELTS Center in Coimbatore
Best IELTS Coaching in Coimbatore
Best IELTS Coaching Center in Coimbatore

Vinoth Kumar said...

PLC Training in Chennai | PLC Training Institute in Chennai | PLC Training Center in Chennai | PLC SCADA Training in Chennai | PLC SCADA DCS Training in Chennai | Best PLC Training in Chennai | Best PLC Training Institute in Chennai | PLC Training Centre in Chennai | PLC SCADA Training in Chennai | Automation Training Institute in Chennai | PLC Training in Kerala

Vinoth Kumar said...

PLC Training in Chennai | PLC Training Institute in Chennai | PLC Training Center in Chennai | PLC SCADA Training in Chennai | PLC SCADA DCS Training in Chennai | Best PLC Training in Chennai | Best PLC Training Institute in Chennai | PLC Training Centre in Chennai | Embedded System Training in Chennai | Embedded Training in Chennai | VLSI Training in Chennai | VLSI Training Institute in Chennai

Vinoth Kumar said...

Embedded Training in Chennai | Best Embedded Training in Chennai | Embedded System Training in Chennai | Embedded System Training Institute in Chennai | Best Embedded System Training Institute in Chennai | Embedded Course in Chennai | Embedded System Training Institutes in Chennai | Embedded System Training Center in Chennai | Best Embedded System Training in Chennai | Embedded Systems Training in Chennai | VLSI Training in Chennai | VLSI Training Institute in Chennai

Vinoth Kumar said...

VLSI Training in Chennai | Best VLSI Training in Chennai | VLSI Training Centres in Chennai | VLSI Courses in Chennai | VLSI Training Courses in Chennai | VLSI Training Institute in Chennai | VLSI Training Institutes in Chennai | Best VLSI Training Institute in Chennai

Vinoth Kumar said...

Mechanical Projects | Mechanical Projects in Chennai | Mechanical Project Centers in Chennai | Mechanical Project Centre in Chennai | Mechanical Final Year Project in Chennai | Final Year Projects Mechanical | Final Year Projects Mechanical in Chennai

dhivya said...

This information is impressive. I am inspired with your post writing style & how continuously you describe this topic. Eagerly waiting for your new blog keep doing more.
Ethical Hacking Training in Bangalore
Ethical Hacking Course in Bangalore
Java Certification in Bangalore
Java J2ee Training in Bangalore
Advanced Java Course in Bangalore

sathya shri said...

I really like your blog. You make it interesting to read and entertaining at the same time. I cant wait to read more from you.

angularjs Training in bangalore

angularjs Training in bangalore

angularjs online Training

angularjs Training in marathahalli

angularjs interview questions and answers

Ananya Krishnan said...

Good job in presenting the correct content with the clear explanation. The content looks real with valid information. Good Work

DevOps is currently a popular model currently organizations all over the world moving towards to it. Your post gave a clear idea about knowing the DevOps model and its importance.

Good to learn about DevOps at this time.


devops training in chennai | devops training in chennai with placement | devops training in chennai omr | devops training in velachery | devops training in chennai tambaram | devops institutes in chennai | devops certification in chennai | trending technologies list 2018

Riya Raj said...

Thanks for your blog. The information which you have shared is really useful for us.
Cloud Certification
Cloud Courses
Cloud Security Training
Cloud Training Courses
Cloud Computing Certification Courses

Vicky Ram said...

Useful Information, your blog is sharing unique information...Thanks for sharing!!!

Guest posting sites
Education

mercyroy said...

Nice Article,Great experience for me by reading this info.
thanks for sharing the information with us.keep updating your ideas.
Selenium training near me
Selenium Training in Chennai
Selenium training near me
Selenium Training in Chennai

sathyaramesh said...

thanks for sharing such a nice info.I hope you will share more information like this. please keep on sharing!
Java Training in Chennai
German Training Institute in Chennai
german language coaching centres in chennai
Java Training Institute in Chennai
Best Java Training Institute in Chennai
German Training Institutes in Chennai

Nadeem Malik said...

hi, nice information is given in this blog. Thanks for sharing this type of information, it is so useful for me. nice work keep it up. best digital marketing company in delhi

Nadeem Malik said...

Powerful Video Content Marketing Ideas for Your Business

Akash Thakur said...

Epoxy Grout manufacturer in delhi

Vikash Kumar said...

Laminated Doors manufacturer in hubli
Thanks for giving great kind of information. So useful and practical for me. Thanks for your excellent blog, nice work keep it up thanks for sharing the knowledge.

Amase LED Lighting said...

led lawn lights in delhi
Thanks for giving great kind of information. So useful and practical for me. Thanks for your excellent blog, nice work keep it up thanks for sharing the knowledge.

LindaJasmine said...

I love your way of writing. The content shows your in-depth knowledge on the subject. Thanks for Sharing.
Node JS Training in Chennai
Node JS Course in Chennai
Node JS Advanced Training
Node JS Training Institute in chennai
Node JS Course
IELTS coaching in Chennai
IELTS Training in Chennai
SAS Training in Chennai
SAS Course in Chennai

Ananya Krishnan said...

Good job in presenting the correct content with the clear explanation. The content looks real with valid information. Good Work

DevOps is currently a popular model currently organizations all over the world moving towards to it. Your post gave a clear idea about knowing the DevOps model and its importance.

Good to learn about DevOps at this time.


devops training in chennai | devops training in chennai with placement | devops training in chennai omr | devops training in velachery | devops training in chennai tambaram | devops institutes in chennai | devops certification in chennai | trending technologies list 2018

Post a Comment