Pages

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.

427 comments:

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

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

    ReplyDelete
  3. 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.

    ReplyDelete
  4. 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?

    ReplyDelete
  5. 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?

    ReplyDelete
  6. Fantastic! Flexible and scalable!

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

    ReplyDelete
  8. Great !

    How I play a tournament with 13 teams ?

    Thanks

    ReplyDelete
  9. Hooraay.. thanks for sharing.. :D

    ReplyDelete
  10. 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

    ReplyDelete
  11. 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

    ReplyDelete
  12. 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

    ReplyDelete
  13. 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

    ReplyDelete
  14. 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

    ReplyDelete
  15. 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

    ReplyDelete

  16. 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

    ReplyDelete
  17. 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

    ReplyDelete
  18. 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

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

    Guest posting sites
    Education

    ReplyDelete
  20. 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

    ReplyDelete
  21. 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

    ReplyDelete
  22. Very nice post here thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.

    machine learning course fees in chennai
    machine learning training center in chennai
    top institutes for machine learning in chennai
    Android training in chennai
    PMP training in chennai


    ReplyDelete
  23. Good Job, nice work and efforts. Your content is very interesting and i like that. I want a lot of info from your post....
    Ethical Hacking Course in Chennai
    Hacking Course in Chennai
    Certified Ethical Hacking Course in Chennai
    Ethical Hacking Training in Chennai

    ReplyDelete
  24. Very nice post here and thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
    Java training in Chennai

    Java training in Bangalore
    Selenium training in Chennai

    Selenium training in Bangalore

    ReplyDelete
  25. Thanks for the informative article. This is one of the best resources I have found in quite some time. Nicely written and great info. I really cannot thank you enough for sharing.
    python Training institute in Pune
    python Training institute in Chennai
    python Training institute in Bangalore

    ReplyDelete
  26. Pleasant Tips..Thanks for Sharing….We keep up hands on approach at work and in the workplace, keeping our business pragmatic, which recommends we can help you with your tree clearing and pruning in an invaluable and fit way.
    Data Science training in rajaji nagar
    Data Science with Python training in chennai
    Data Science training in electronic city
    Data Science training in USA
    Data science training in pune

    ReplyDelete
  27. Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
    Best Devops Training in pune
    Devops Training in Bangalore
    Microsoft azure training in Bangalore
    Power bi training in Chennai

    ReplyDelete
  28. I have gone through your blog, it was very much useful for me and because of your blog, and also I gained many unknown information, the way you have clearly explained is really fantastic. Kindly post more like this, Thank You.
    mobile service center chennai
    mobile service centre near me
    mobile service centre chennai
    best mobile service center in chennai

    ReplyDelete
  29. Great Article… I love to read your articles because your writing style is too good,
    its is very very helpful for all of us and I never get bored while reading your article because,
    they are becomes a more and more interesting from the starting lines until the end.
    python online training

    ReplyDelete
  30. You are doing a great job. I would like to appreciate your work for good accuracy
    Regards,
    best selenium training institute in chennai | selenium course in chennai

    ReplyDelete
  31. Amazing post. you have provided some really good infojava training in patna

    ReplyDelete
  32. It is really a great work and the way in which u r sharing the knowledge is excellent.Thanks for helping me to understand basic concepts.
    R Training Institute in Chennai | R Programming Training in Chennai

    ReplyDelete
  33. IT's very informative blog and useful article thank you for sharing with us , keep posting learn more

    Tableau online Training

    Android Training

    Data Science Course

    Dot net Course

    iOS development course

    ReplyDelete
  34. And indeed, I’m just always astounded concerning the remarkable things served by you. Some four facts on this page are undeniably the most effective I’ve had.
    Data science Course Training in Chennai | No.1 Data Science Training in Chennai
    RPA Course Training in Chennai | No.1 RPA Training in Chennai

    ReplyDelete
  35. It’s interesting content and Great work. Definitely, it will be helpful for others. I would like to follow your blog. Keep post

    Check out:
    best hadoop training in omr
    hadoop training in sholinganallur
    best institute for big data in chennai
    big data hadoop course in chennai with placement

    ReplyDelete
  36. Very informative blog and useful article thank you for sharing with us, keep posting learn more about aws with cloud computing

    AWS Online Training

    AWS Certification

    Artificial Intelligence Online Course

    AI Online Course

    AI Training

    Google AI Course

    ReplyDelete
  37. This comment has been removed by the author.

    ReplyDelete
  38. Attend The Python Training in Bangalore From ExcelR. Practical Python Training in Bangalore Sessions With Assured Placement Support From Experienced Faculty. ExcelR Offers The Python Training in Bangalore.

    ReplyDelete
  39. This blog seems interesting. very useful content for readers. Thanks for sharing this information.
    https://www.webdesigningcourse.in/web-design-training-in-chennai.html
    https://www.zuaneducation.com/web-designing-training-courses
    https://www.zuaneducation.com/php-training-courses
    https://www.zuaneducation.com/magento-training-courses
    https://www.webdesigningcourse.in/magento-training-in-chennai.html
    https://www.webdesigningcourse.in/php-training-in-chennai.html
    http://www.seojobtraining.in/web-designing-training-in-chennai.php

    ReplyDelete
  40. Thanks for this blog... Good article!!!
    https://www.zuaneducation.com/web-designing-training-courses
    https://www.digitalmarketingcourses.in/web-designing-training-in-chennai.php

    ReplyDelete
  41. Hey, would you mind if I share your blog with my twitter group? There’s a lot of folks that I think would enjoy your content. Please let me know. Thank you. Data Science Training in chennai | Python training in chennai | machine learning Training in Chennai

    ReplyDelete
  42. hanks for the Valuable information.Really useful information. Thank you so much for sharing.It will help everyone.Keep Post. Find Some Indian Memes.

    Entertainment News

    ReplyDelete
  43. Thanks for sharing this useful information
    https://www.zuaneducation.com/python-training-chennai

    ReplyDelete
  44. It's an interesting article..!! Thanks for sharing. For Router Queries, Check in to our site..!!

    Router Support , Connect to my Router , Router Settings , Wired router setup, wireless router setup, wifi router setup

    ReplyDelete
  45. Thanks for sharing this informative blog, It is really useful for me, through this blog I got a clear view about jQuery concept.
    MSBI Online Training

    ReplyDelete
  46. Nice article>
    For data science training in bangalore,visit:
    Data science training in bangalore

    ReplyDelete
  47. The stores additionally contact involve a drugstore and bank. Other than staple, one can look for home enhancement and stylistic layout things, attire and design embellishments customer support check this

    ReplyDelete
  48. Oh, great information. Never forget to admire again.
    꽁머니

    ReplyDelete
  49. Nice Content.
    For Data Science training in Bangalore, Visit:
    Data Science training in Bangalore

    ReplyDelete
  50. I am really enjoyed a lot when reading your well-written posts. It shows like you spend more effort and time to write this blog. I have saved it for my future reference. Keep it up the good work.web design company in velachery

    ReplyDelete
  51. Nice Post
    For Python training in Bangalore, Visit:
    Python training in Bangalore

    ReplyDelete
  52. I like the helpful info you provide in your articles. I’ll bookmark your weblog and check again here regularly. I am quite sure I will learn much new stuff right here! Good luck for the next!

    "web designing classes in chennai | Web Designing courses in Chennai "
    Web Designing Training and Placement | Best Institute for Web Designing
    Web Designing and Development Course | Web Designing Training in Chennai

    ReplyDelete
  53. Visit for Python training in Bangalore:
    Python training in Bangalore

    ReplyDelete
  54. Nice Post
    For Data Science training in Bangalore,
    Visit: Data Science training in Bangalore

    ReplyDelete
  55. amazing post and written in very simple and impressive language. Thanks for sharing
    harry potter wifi names

    ReplyDelete
  56. Nice Post. You have done a great job. Please Keep Posting and Keep Sharing. Emotional Quotes

    ReplyDelete






  57. Thanks for sharing an informative blog keep rocking bring more details.I like the helpful info you provide in your articles. I’ll bookmark your weblog and check again here regularly. I am quite sure I will learn much new stuff right here! Good luck for the next!

    "web designing course in chennai | web designing classes in chennai "
    "web designing training in chennai | web designing institute in chennai "
    "web designing and development course | web designing training and placement "

    ReplyDelete
  58. Thank you for this great article i learn a lot from your article keep it up.

    ReplyDelete
  59. The strncmp() function in C++ is used for comparing two string and checking if they are equal or not. strcmp() function compares two strings character by character from the first character until the end of one of the string occurs then it returns the result.

    ReplyDelete
  60. Very interesting blog Thank you for sharing such a nice and interesting blog and really very helpful article.python training in bangalore

    ReplyDelete
  61. DevOps Training in Chennai .
    I am glad that I have visited this blog. Really helpful, eagerly waiting for more updates.

    ReplyDelete
  62. thank you very much for share this wonderful article 토토사이트

    ReplyDelete
  63. really thanks for posting such an informative stuff...

    oracle training

    ReplyDelete
  64. Thank you so much for the great and very beneficial stuff that you have shared with the world.

    Upgrade your career Learn AWS Training from industry experts get Complete hands-on Training, Interview preparation, and Job Assistance at Bangalore Training Academy Located in BTM Layout.

    ReplyDelete
  65. This post is really nice and informative. The explanation given is really comprehensive and informative . Thanks for sharing such a great information..Its really nice and informative . Hope more artcles from you. I want to share about the best java tutorial videos for beginners with free bundle videos provided and java training .

    ReplyDelete
  66. Such a great information for blogger i am a professional blogger thanks…

    Softgen Infotech is the Best HADOOP Training located in BTM Layout, Bangalore providing quality training with Realtime Trainers and 100% Job Assistance.

    ReplyDelete
  67. Excellent Blog! I would like to thank for the efforts you have made in writing this post. I am hoping the same best work from you in the future as well. I wanted to thank you for this websites! Thanks for sharing. Great websites!
    data analytics courses online

    ReplyDelete

  68. I wish to say that this post is amazing, nice written and include approximately all important infos. I would like to see more posts like this
    Regards,
    Python Training in Chennai | Python Programming Classes | Python Classes in Chennai

    ReplyDelete
  69. Office installation is a well-known name in the pc world. Whether you are a professional working in the corporate world, or even a college student Or anybody who's working on a computer system have to set up Office installation for Creating your task easier. So search Office.com/set up in your browser and receive the Most useful software package on your system.
    Office.com/setup

    ReplyDelete
  70. Dubai video surveillance and surveillance system

    The best provider of CCTV installation services in Dubai
    We offer services at your doorstep. If you need a video surveillance camera, we will help you. We are dubai cctv installation , our engineers are certified by the dubai administration,
    Our security experts can provide all the details, we also offer video surveillance if you need bulk goods. We also offer office and home services.


    ReplyDelete

  71. I have been reading for the past two days about your blogs and topics, still on fetching! Wondering about your words on each line was massively effective. Techno-based information has been fetched in each of your topics. Sure it will enhance and fill the queries of the public needs. Feeling so glad about your article. Thanks…!
    best software testing training in chennai
    best software testing training institute in chennai with placement
    software testing training
    courses

    software testing training and placement
    software testing training online
    software testing class
    software testing classes in chennai
    best software testing courses in chennai
    automation testing courses in chennai

    ReplyDelete
  72. You can learn from the help of our blog on What the Acquisition of Skype May Mean for Microsoft. Click on the following link to read more about "Acquisition of Skype May Mean for Microsoft
    "

    ReplyDelete
  73. I just loved your article on the beginners guide to starting a blog. Thank you for this article. AI Training with highly experienced facutly.

    ReplyDelete
  74. Karena peluang menang pada permainan tertentu sangat penting bagi semua penjudi, Microgaming menyediakan laporan pembayaran reguler di situsnya pokerpelangi
    98toto

    ReplyDelete
  75. Thanks for this blog is unique information step by step. I here by attached my site would you see this blog

    7 tips to start a career in digital marketing

    “Digital marketing is the marketing of product or service using digital technologies, mainly on the Internet, but also including mobile phones, display advertising, and any other digital medium”. This is the definition that you would get when you search for the term “Digital marketing” in google. Let’s give out a simpler explanation by saying, “the form of marketing, using the internet and technologies like phones, computer etc”.

    we have offered to the advanced syllabus course digital marketing for available join now

    more details click the link now

    https://www.webdschool.com/digital-marketing-course-in-chennai.html

    ReplyDelete
  76. Amazing this blog

    Web designing trends in 2020

    When we look into the trends, everything which is ruling today’s world was once a start up and slowly begun getting into. But Now they have literally transformed our lives on a tremendous note. To name a few, Facebook, Whats App, Twitter can be a promising proof for such a transformation and have a true impact on the digital world.


    we have offered to the advanced syllabus course web design and development for available join now

    more details click the link now

    https://www.webdschool.com/web-development-course-in-chennai.html

    ReplyDelete
  77. Thanks for sharing such a great information..Its really nice and informative.. data science training

    ReplyDelete
  78. Pretty article! I found some useful information in your blog, it was awesome to read, thanks for sharing this great content to my vision, keep sharing.... hadoop online training

    ReplyDelete
  79. Good blog post. I like this.
    Watch american rodeo 2020 Live Stream
    If you are a sport lover, then check this out.

    ReplyDelete
  80. You have done a good job in writing a useful content. I am glad to see this post. I just stumble upon your blog and found it very useful for me. Thanks a lot for sharing.
    Esports Tournament App Development

    ReplyDelete
  81. This is so elegant and logical and clearly explained. Brilliantly goes through what could be a complex process and makes it obvious.

    oracle erp training

    ReplyDelete
  82. very nice and great It shows like you spend more effort and time to write this blog. I have saved it for my future reference 구글상위노출.

    ReplyDelete
  83. Having read this I thought it was extremely informative. I appreciate you finding the time and energy to put this article together. I once again find myself personally spending a lot of time both reading and leaving comments. But so what, it was still worth it! KBC Head Office Contact Number

    ReplyDelete
  84. From:Aptoide Apk Download
    Thank you for excellent article.I enjoyed reading your blog!!

    ReplyDelete
  85. Thank you for sharing this good article. visit our website 바카라사이트

    ReplyDelete
  86. This comment has been removed by the author.

    ReplyDelete
  87. This comment has been removed by the author.

    ReplyDelete
  88. Such a very useful articles. i would like to thank you for your efforts you had made for writing this awesome blogs.
    For Machine learning Course in Bangalore visit:
    Machine Learning Online training course in Bangalore

    ReplyDelete
  89. This comment has been removed by the author.

    ReplyDelete
  90. This comment has been removed by the author.

    ReplyDelete
  91. Such a very useful articles. i would like to thank you for your efforts you had made for writing this awesome blogs.
    For Machine learning Course in Bangalore visit:
    Machine Learning Online Training In Bangalore
    Machine Learning Online Training In India
    Machine Learning Online Training course
    Machine Learning Online course

    ReplyDelete
  92. Study ExcelR Data Analytics Course in Bangalore where you get a great experience and better knowledge.
    Data Analytics Course in Bangalore

    ReplyDelete
  93. Effective blog with a lot of information. I just Shared you the link below for Courses .They really provide good level of training and Placement,I just Had Data Science Classes in this institute , Just Check This Link You can get it more information about the Data Science course.


    Java training in chennai | Java training in annanagar | Java training in omr | Java training in porur | Java training in tambaram | Java training in velachery

    ReplyDelete
  94. Thanks For Provide This Code

    Kisi Ko Bhi Java Shikhana Hai To Ye Article Jarur Padhiye
    Java Kya Hai Or Kaise Shikhe Full Janakri

    ReplyDelete