Thursday, December 4, 2014

Flash Cards!

Check here for a live demo.

Here at Handle the Bucket, we handle your buckets. We equip needs with know-how, wants with what-for, hows to how-tos. Usually that mission is expressed by connecting you with a variety of early 20th century Spanish poetry, but today our handle is -- flash cards!

Other flash card programs disappointed me. Why were my specific preferences not considered by any of the free programs I tried? How am I supposed to read Arabic in their tiny font? Why can I not access it from any web browser? Why can the answer not be an image?

All good questions, I told myself. Important considerations were being ignored by these magnanimous ne'erdowells, these free flashcard phonies. So I made my own.

Below are the setup steps and all the code. Just remember: this is the premier free flash card program of Southern New England, and our business model anticipates penetration of the entire free Google sheets flash card market by or after the termination of the Google drive program. So tell your friends!



Product Spec



Not surprisingly, the nearly achromatic luster has sucked you in. Follow the steps below to make your own set of flash cards. As you progress, you will be rewarded.

To get things started, here is earned your first gray sticker!



We'll start with the database...

  1. Do you have a gmail account? Good! No...you'll need one. Then go to drive.google.com
  2. Create a new spreadsheet. 
  3. Name the first tab (by clicking at tab image at the bottom) the class of cards you are creating. For this example, I used "Spanish." 
  4. Add the following headers: Image; Facing; Reverse; Category; Difficulty; ID. 
  5. Add data appropriate to each column. If you're reading this you probably have an idea for what flash cards you want to learn. Just FYI, do NOT use quotation marks.

    • Image contains an image URL. This may be blank.
    • Facing is one side of your flash card
    • Reverse is the same side of that flash card. Obviously not.
    • Category contains an identifier for similar cards.
    • Difficulty contains the text "normal." It's the code's prerogative to change it to "star" or "ex."
    • ID is a unique, numbered increment. Don't mess this up: 1, 2, 3, 4...
  6. If you'd like, create additional spreadsheet tabs for different groups of cards, for example, medical terms or Arabic.


Woohoo! You've earned your second gray sticker!



With the database done, we can move onto the code...

  1. Create a new "Google Script" by going to the "Tools" menu in your spreadsheet, clicking "Script Editor," and starting a new blank project.
  2. A file called "Code.gs" will automatically be created. Add the code below. The only modification needed is in the first line, where it says, SPREADSHEET ID. Replace that text with the spreadsheet id of your database which is located in its URL: https://docs.google.com/spreadsheets/d/SPREADSHEET ID/edit#gid=0.
     var spreadsheetID = 'SPREADSHEET ID'  
     function doGet() {  
     return HtmlService.createHtmlOutputFromFile('index');  
     }  
     function setType(row, value, sheetNumber) {  
     var ss = SpreadsheetApp.openById(spreadsheetID);  
     var sheet = ss.getSheets()[sheetNumber];  
     var cF = sheet.getRange("F1:F" + sheet.getLastRow()).getValues()  
     for (var i = 0 ; i < cF.length; i++) {  
     if (row == cF[i][0]) {  
     sheet.getRange(i + 1, 5).setValue(value)  
     }  
     }  
     }  
     function resetRank(sheetNumber) {  
     var ss = SpreadsheetApp.openById(spreadsheetID);  
     var sheet = ss.getSheets()[sheetNumber];  
     var cF = sheet.getRange("F1:F" + sheet.getLastRow()).getValues()  
     for (var i = 0 ; i < cF.length; i++) {  
     sheet.getRange(i + 1, 5).setValue("normal");  
     }  
     }  
     function makeJson(sheetNumber) {  
     var ss = SpreadsheetApp.openById(spreadsheetID);  
     var sheet = ss.getSheets()[sheetNumber];  
     var sValues = sheet.getRange(2,1,sheet.getLastRow(),6).getValues()  
     var jsonString = "["  
     for (var i = 1 ; i < sValues.length; i++) {  
     jsonString += '{'  
     jsonString += '"image":"' + sValues[i][0] + '",'  
     jsonString += '"front":"' + sValues[i][1] + '",'  
     jsonString += '"back":"' + sValues[i][2] + '",'  
     jsonString += '"category":"' + sValues[i][3] + '",'  
     jsonString += '"rank":"' + sValues[i][4] + '",'  
     jsonString += '"id":"' + sValues[i][5] + '"'  
     jsonString += '},'  
     }  
     jsonString += ']'  
     jsonString = jsonString.replace(/\n/g, "");  
     return JSON.parse(jsonString)  
     //return jsonString  
     }  
     function getName(sheetNumber) {  
     var ss = SpreadsheetApp.openById(spreadsheetID);  
     var sheetName = ss.getSheets();  
     var sheets = []  
     for (var i = 0 ; i <sheetName.length ; i ++) {  
     sheets.push(sheetName[i].getName())  
     }  
     return sheets  
     }  


Congratulations, you've earned your third gray sticker!



And finally, we add the client-side code!

  1. Within the same Google Script session, create a new HTML file in the script editor (File > New > Html.file), and call it index.html. Add the code below. Note this was designed for an android phone, so if you want to use it across a few different devices, you may need to modify the height a bit. The height you want to change is in red.
  2.  <html>  
       <head>  
       <style>  
        body {font:100px arial black;display:inline-block;min-width:975px;width:100%;}  
        html {width:100%;text-align:center;}    
       .number {display:inline-block;text-align:center;border:1px solid white;padding:15px; width:15%;cursor:pointer;}  
       .card {width:100%;text-align:center;height:1100px;display:table-cell;vertical-align:middle;margin:auto;cursor:pointer;}  
       .selectBox {cursor:pointer;text-align:center; padding-bottom:50px;height:1000px;overflow-y:scroll;}  
       .selectCat {cursor:pointer;font-size:50px;display:inline-block;background-color:#EEE;padding:10px;margin-left:5px;margin-right:5px}  
       .selectCat:hover {color:#B2DFEE;}  
       .selectCat:active {color:#50A6C2;}  
       .selectCatBox {font-size:40px;cursor:pointer;}  
       .selectCatBox:hover {color:#B2DFEE;}  
       .selectCatBox:active {color:#50A6C2;}  
       .etos {cursor:pointer;font-size:50px;display:inline-block;width:50px;border:5px solid black; padding:10px;margin-left:5px;margin-right:5px}  
       .etos:active {color:#50A6C2;}  
       #next:active {color:#50A6C2;}  
       .header {text-align:center;font-size:50px;border-bottom:2px solid black;cursor:pointer;font-size:50px;display:inline-block;background-color:#EEE;padding:10px;margin-left:5px;margin-right:5px;}  
       .header:hover {color:#B2DFEE;}  
       .header:active {color:#50A6C2;}  
       </style>  
       <script>  
         function runScript(value, color2, div) {  
          if (catsOpen == 0) {  
           if (div.style.color == color2) {  
            value = "normal";  
            div.style.color = "";  
           }  
           else {  
            document.getElementById("remove").style.color = "";  
            document.getElementById("star").style.color = "";  
            document.getElementById(value).style.color = color2  
           }  
           google.script.run.setType(x[number].id,value, sheetNum);  
           x[number].rank = value  
          }  
          text[3] = "rank: " + value  
         }  
         function resetRank() {  
          for (var g = 0 ; g < max; g++) {  
           x[g].rank = "normal"  
          }  
          google.script.run.resetRank(sheetNum);  
         }  
      </script>  
      </head>  
      <body>  
        <div style="text-align:center;padding-top:50px;">  
         <div class="number" id="remove" onclick="runScript('remove', 'rgb(255, 0, 0)', this)">&#10008;</div>  
         <div class="number" id="star" onclick="runScript('star', 'rgb(255, 215, 0)', this)">&#10031;</div>  
         <div class="number" id="showCats" onclick="showCats()">&#9660;</div>  
         <div class="number" id="next" onclick="changeCard()">&#10151;</div>  
        </div>  
        <div id="cardC">  
         <div style="text-align:center;display:table;width:100%;">   
          <div id="card" class="card" onclick="drawCard()"><img src="http://stezhkamu.com/style/images/icon-loading.gif" style="width:100px;height:100px;"></div>  
        </div>  
        <div id="catAndNum" style="font-size:50px;padding-bottom:100px">  
        </div>  
        </div>    
       <div id="selectBox" style="display:none" >  
       <div style="margin-bottom:-25px;overflow:hidden;">  
       <div style="overflow-x:scroll;overflow-y:hidden;width:100%;height:175px;text-align:center;margin-bottom:-25px;">  
       <span id="header" style="white-space:nowrap;height:200px;overflow:hidden;"></span>  
       </div></div>  
        <div id="selectBox2" class="selectBox">  
        </div>  
        <div style="border-top:2px solid black;padding-bottom:100px;">  
         <span id="EtoS" class="etos" onclick="EtoS()">F</span>  
         <span class="selectCat" onclick="selectCat('all','category', this)">random</span>  
         <div style="height:90px;width:2px;background-color:black;display:inline-block;margin-bottom:-25px;"></div>  
         <span class="selectCat" onclick="selectCat('remove','rank', this)">removed</span>  
         <span class="selectCat" onclick="selectCat('star','rank', this)">starred</span>  
         <span class="etos" onclick="resetRank()">&#8634;</span>  
        </div>  
       </div>  
       <script>  
        var max=0, number = 0, side=0, EorS = 0, select = 0, catsOpen = 0, jsonNum = [], exactNum, text= [], counter = 0, tracker = "", random=0,x = "";  
        function importGSS(json) {  
         max=0; number = 0; side=0; EorS = 0; select = 0; jsonNum = []; exactNum; text= []; counter = 0; tracker = ""; random=0;x = ""  
         x = json;  
         var key  
         var entries = [], cats = [], spans = []  
         for (key in json) { 
          max += 1   
           //build Cats  
         // entries = x.feed.entry[key].content.$t.split(",")  
          if (cats.length == 0 || (cats.indexOf(x[key].category) == -1 && x[key].category != "")) {  
           cats.push(x[key].category)  
           spans.push(document.createElement("span"))  
           spans[(spans.length-1)].className = "selectCat"  
           spans[(spans.length-1)].setAttribute("onclick", "selectCat('" + x[key].category + "','category', this)")  
           spans[(spans.length-1)].innerHTML = x[key].category  
          }  
         }    
         document.getElementById("selectBox2").innerHTML = ""  
         for (var m = 0; m < spans.length; m++) {  
           document.getElementById("selectBox2").appendChild(spans[m]);  
         }     
         changeCard()  
        }  
        function getRandom() {  
         random = Math.floor(Math.random() * jsonNum.length)  
         console.log(random);  
         if (tracker.indexOf("/" + random + ",") != -1) {getRandom()}  
        }  
        function changeCard() {  
         if (catsOpen == 0) {  
          side = EorS  
          document.getElementById("remove").style.color = "";  
          document.getElementById("star").style.color = "";  
          //get cat number  
          if (select == 1) {  
           getRandom()  
           number = jsonNum[random]  
           tracker += "/" + random + ","  
           console.log(jsonNum.length);  
           //text = x.feed.entry[number].content.$t.split(",")  
           document.getElementById("catAndNum").innerHTML = x[number].category + "<br>" + (counter + 1) + "/" + jsonNum.length  
           if (counter == (jsonNum.length - 1)) {counter = 0;tracker=""}   
           else {counter += 1;}  
          }  
          //get non-cat random number  
          else {  
           number = Math.floor(Math.random() * max)  
           //text = x.feed.entry[number].content.$t.split(",")  
           document.getElementById("catAndNum").innerHTML = x[number].category + "<br>" + (number + 1) + "/" + max   
          }  
          //check if exed or starred, light it up  
          if (x[number].rank == "remove") {   
           document.getElementById("remove").style.color = "rgb(255, 0, 0)";  
          }  
          else if (x[number].rank == "star") {  
           document.getElementById("star").style.color = "gold";  
          }  
          drawCard()  
         }  
         console.log("all set")  
        }  
        function drawCard() {  
         //check if facings or reverse, draw value  
         if (side == 0) {document.getElementById("card").innerHTML = x[number].front; side=1; }  
         else {  
         side=0;  
         if (x[number].image != "") {  
         document.getElementById("card").innerHTML = x[number].back + "<img style='max-width:500px; max-height:500px; ' src='" + x[number].image + "' />";      
        }  
        else {  
        document.getElementById("card").innerHTML = x[number].back  
        }  
        }  
        }  
        function selectCat(name, type, span) {  
         var jsonTemp = []  
         if (name == "all") {select = 0}  
         else {  
          for (var j = 0; j < max; j++) {  
           if (name == "remove") {  
            if (x[j][type] == name) {  
             jsonTemp.push(j)  
             console.log(j + "h")  
            }  
           }  
           else if (x[j][type] == name && x[j].rank != "remove") {  
            jsonTemp.push(j)  
            console.log(j + " 2")  
           }  
          }  
          if (jsonTemp.length == 0) {span.style.textDecoration = "text-decoration: line-through;"; return}     
          select = 1  
          jsonNum = []  
          jsonNum = jsonTemp  
         }  
         catsOpen = 0  
         counter = 0  
         tracker=""  
         changeCard()  
         document.getElementById('cardC').style.display = "block"  
         document.getElementById('selectBox').style.display = "none"  
         document.getElementById('showCats').style.color = ""  
         var spans = document.getElementsByTagName("span");  
         //uncross empty cats  
         for (var j = 0; j < spans.length;j++) {  
          spans[j].style.textDecoration = ""  
         }  
        }  
        function showCats() {    
         if (catsOpen == 0) {  
          document.getElementById('showCats').style.color = "#50A6C2"  
          document.getElementById('cardC').style.display = "none"  
          document.getElementById('selectBox').style.display = "block"  
          document.getElementById('star').style.color = ""  
          document.getElementById('remove').style.color = ""  
          catsOpen = 1  
         }     
         else {  
          document.getElementById('cardC').style.display = "block"  
          document.getElementById('selectBox').style.display = "none"  
          document.getElementById('showCats').style.color = ""  
          console.log(jsonNum.length + " hey")  
          catsOpen = 0  
          //because the highlight may be off:  
          changeCard()  
          var spans = document.getElementsByTagName("span");  
           for (var j = 0; j < spans.length;j++) {  
            spans[j].style.textDecoration = ""  
           }  
         }    
        }  
        function EtoS() {  
         if (EorS == 0) {  
          document.getElementById("EtoS").innerHTML = "B"  
          EorS = 1  
         }  
         else {  
          document.getElementById("EtoS").innerHTML = "F"  
          EorS = 0  
         }  
        }  
        function changeSheet() {  
        google.script.run.withSuccessHandler(importGSS).makeJson(1);  
        }  
        function changeEverything(span,numSheet) {  
        console.log(span)  
        if (sheetNum < sheetsNum - 1) {  
        sheetNum += 1  
        }  
        else {sheetNum = 0}  
        var spans = document.getElementsByTagName("span");  
         //uncross empty cats  
         for (var j = 0; j < spans.length;j++) {  
          spans[j].style.textDecoration = ""  
          spans[j].style.color = ""  
         }  
         span.style.color = "#50A6C2"  
        document.getElementById("selectBox2").innerHTML = "<img src='http://stezhkamu.com/style/images/icon-loading.gif' style='width:100px;height:100px;margin-top:150px;'>"  
        google.script.run.withSuccessHandler(importGSS).makeJson(numSheet - 1);  
        }  
        function setName(name) {  
        document.getElementById("header").innerHTML = name  
        }  
        function getSheetNum(sheets) {  
        var headSpans =[]  
        console.log(sheets);  
         for (var n = 0 ; n< sheets.length ; n ++) {  
          sheetsNum += 1   
           headSpans.push(document.createElement("span"))  
           headSpans[(headSpans.length-1)].className = "header"  
           headSpans[(headSpans.length-1)].setAttribute("onclick", "changeEverything(this," + sheetsNum + ")")  
           headSpans[(headSpans.length-1)].innerHTML = sheets[n]  
          }  
         document.getElementById("header").innerHTML = ""  
         for (var m = 0; m < headSpans.length; m++) {  
           document.getElementById("header").appendChild(headSpans[m]);  
         }     
         headSpans[0].style.color="#50A6C2"  
        }  
         var sheetNum = 0, sheetsNum = 0    
         google.script.run.withSuccessHandler(importGSS).makeJson(0);  
         google.script.run.withSuccessHandler(getSheetNum).getName(0);  
      </script>   
      </body>  
     </html>  
    
  3. Now we just need to push the code to a server. Google makes this easy. Go to the the "Publish" menu and hit "Deploy as web app" and walk through the prompts. Make sure you let everybody see it so that you can access it from anywhere without signing in. 
    • Note if you need to republish (because you modified the code) you can't just hit "Deploy as Web App" again. You have to go to the "File" menu and select "manage versions." Once you save a new version, go to "Deploy as Web App" again, and select the appropriate version in the drop down.
    • You may also have to ensure that the script has permission to access your spreadsheet. To do this, from the "Code.gs" page, click "Run" then "doGet" and click through the prompts. 
  4. Once you've deployed, you will get an access link. Click it and...

Now you are the coolest son-of-a-gun to have ever read this blog! 

Head on back to your spreadsheet and add some more tabs and cards....and though, you don't get another now, marvel at your mighty mass of gray stickers!