Geogebra, Google Sheets, and JavaScript

I found a demo of sending data from GeoGebra to Google Sheets. Here is the link: https://www.geogebra.org/m/uhsmpcpe.

I modified the demo and added several steps that were not explicit.

Google Sheet

Create a new Google Sheet. The cells in the first row will name the data collected from GeoGebra. For this demo enter the following in the cells of the first row:

  • Timestamp
  • Class
  • x-coordinate
  • y-coordinate

Click the “Tools” menu, then select “Script Editor.” Replace the existing code with the following:

//  1. Enter sheet name where data is to be written below
        var SHEET_NAME = "Sheet1";

//  2. Run > setup
//
//  3. Publish > Deploy as web app
//    - enter Project Version name and click 'Save New Version'
//    - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
//
//  4. Copy the 'Current web app URL' and post this in your form/script action
//
//  5. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)

var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service

// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doGet(e){
  return handleResponse(e);
}

function doPost(e){
  return handleResponse(e);
}

function handleResponse(e) {
  Logger.log(e.parameter);
  // Google announced the LockService[1]
  // this prevents concurrent access overwritting data
  // [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
  // we want a public lock, one that locks for all invocations
  var lock = LockService.getPublicLock();
  lock.waitLock(30000);  // wait 30 seconds before conceding defeat.

  try {
    // next set where we write the data - you could write to multiple/alternate destinations
    var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
    var sheet = doc.getSheetByName(SHEET_NAME);

    // we'll assume header is in row 1 but you can override with header_row in GET/POST data
    var headRow = e.parameter.header_row || 1;
    var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    var nextRow = sheet.getLastRow()+1; // get next row
    var row = [];
    var output = "";
    // loop through the header columns
    for (i in headers){
      if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
        row.push(new Date());
      } else { // else use header name to get data
        row.push(e.parameter[headers[i]]);
      }
    }
    // more efficient to set values as [][] array than individually
    sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
    output = JSON.stringify({"result":"success", "row": nextRow});
    if (e.parameter.callback){
      // return jsonp success results
      return ContentService
          .createTextOutput(e.parameter.callback+"("+ output + ");")
          .setMimeType(ContentService.MimeType.JAVASCRIPT);
    }
    else{
      // return jsonp success results
      return ContentService
          .createTextOutput(output)
          .setMimeType(ContentService.MimeType.JSON);
    }
  } catch(e){
    output = JSON.stringify({"result":"error", "error": e});
    if (e.parameter.callback){
      // if error return this, again, in jsonp
      return ContentService
          .createTextOutput(e.parameter.callback+"("+ output + ");")
          .setMimeType(ContentService.MimeType.JAVASCRIPT);
    }
    else{
      return ContentService
          .createTextOutput(output)
          .setMimeType(ContentService.MimeType.JSON);
    }
  } finally { //release lock
    lock.releaseLock();
  }
}

function setup() {
    var doc = SpreadsheetApp.getActiveSpreadsheet();
    SCRIPT_PROP.setProperty("key", doc.getId());
}

Save the Google Script. Back in Google Sheets, click on the “Tools” menu, select “Macros,” then “Import.” Add all listed functions. Click on the “Tools” menu again, select “Macros”, then run the “setup” function by clicking on it.

Click on “Continue” on the “Authorization Required” pop-up box. Continue until you see the option to “allow” the script to “See, edit, create, and delete your spreadsheets in Google Drive.” Click on the “Allow” button.

Back in Google Script, click on the “Publish” menu, then select “Deploy as web app…”. Set the deployment options, then click on the “Deploy” button. Copy (to the clipboard) the macro link that you’ll need in your GeoGebra app.

GeoGebra

Start calculator. Assign x- and y-coordinates of a point “A” to variables “ax” and “ay” in the calculator table. Create a text (e.g., “Statistics”) and link to it when you create your “Class Name” input box.

GeoGebra screenshot

View the settings for the “Submit” button by right-clicking on the button. Click on the “Script” tab. In the “On Click” box, copy and paste the following JavaScript:

var class0 = ggbApplet.getValueString("text1");
// cannot name a variable as 'class'
var ax = ggbApplet.getValue("ax");
var ay = ggbApplet.getValue("ay");

if (confirm("Confirm to submit?\nClass: " + class0 + ", point A = ("+ax+", "+ay+")" )) {
    sendData(class0, ax, ay);
}

In the “Global JavaScript” box, copy and paste the following JavaScript, but replace the assigned value of variable scriptURL with the URL you copied earlier:

function get(url) {
    var xhttp = new XMLHttpRequest();

    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            var response = xhttp.responseText;

            if (response) {
                response = JSON.parse(response);
                if (response.result == "success") {
                    console.log("added to row " + response.row);
                    alert("Successfully submitted");
                } else {
                    alert("Error: " + response.result);
                }

            }
        }
    };

    xhttp.open("GET", url, true);
    xhttp.send();

}

function sendData(class0, x, y) {
    var scriptURL = "https://script.google.com/macros/s/AKfycbw0iQbrj2KFJn67y_L0LHm_dct7HykgI-BqpHyFqgRYy5jcdYUd/exec";
    var app_name = "demo";
    if (class0 !== "") {

        // eg https://script.google.com/macros/s/AKfycbw0iQbrj2KFJn67y_L0LHm_dct7HykgI-BqpHyFqgRYy5jcdYUd/exec?Name=test&Class=1A&x-coordinate=1&y-coordinate=2

        // the name "Class", "x-coordinate", "y-coordinate" must match the header row in the spreadsheet
        var url = scriptURL + "?Name=test";
        url += "&Class=" + class0;
        url += "&x-coordinate=" + x;
        url += "&y-coordinate=" + y;

        get(url);

    } else {
        alert("Please input the class and class number");
    }
}

MathQuill

MathQuill is a formula editor for web applications. I started using MathQuill in my math-problem generator to dynamically generate math problems . In order to use MathQuill, add the following between the <head> tags of your HTML:

<link rel="stylesheet" href="css/mathquill.css"/>
<script src="js/jquery-3.2.1.min.js"></script>
<script src="js/mathquill.js"></script>

At the end of the <html> document, add the following:

<script>
    var MQ = MathQuill.getInterface(2); // for backward compatibility
</script> 

Now you can use the following Javascript code to dynamically generate math statements for <span id=”id”></span> with the corresponding ID:

function name() {
    var span1 = document.getElementById("id");
    span1.innerHTML = "";   
    MQ.StaticMath(span1);
}

cODeLHÁ Programming Club

cODeLHA

I am so excited about the new programming club at ODLHÁ.

First, I’m going to have the club create static pages on GitHub using HTML and CSS. Then I’m going to have the club create web applications using JavaScript. After that, I plan on introducing Python and SQL.

High School Stats Helper

I published my first Android app (“High School Stats Helper“) on the Google Play Store today. The app will have at least 30 stats functions when it is completed; this version has four.

The app serves two purposes:

  • Give my statistics students a free tool for checking their work.
  • Give my Math Support students a model of what I expect them to produce for their end-of-semester project.

Web SQL – Append Options to a Select Menu

This is a continuation of the example from the previous post in which we append options to a select menu then populate a text area based on changes in the selected option.

Suppose your page contains the following select menu and text area:

<label for="selectFirst">Select the first name:</label>
<select name="selectFirst" id="selectFirst">
</select>
                        
<p>The last name will automatically populate the text area below when you select a first name above:</p>
<textarea name="textareaLast" id="textareaLast"></textarea>

Open the database and select all records from the names table:

$(document).ready(function(){
    mydb = window.openDatabase("names_db", "0.1", "A Database of Names", 1024 * 1024);
            
    loadData();  

    $('#selectFirst').change(function () {
    var $this = $(this);
    var listVal = $this.val();
    $('#textareaLast').val(listVal);
    });
});
            
function loadData() 
{
    if(mydb)
    {
        mydb.transaction(function(t) 
        {
            t.executeSql("SELECT * FROM names", [], updateSelectListsView);               
        });
    }
}

In the excerpt above, the following code updates the text area whenever the selected option changes:

$('#selectFirst').change(function () {
var $this = $(this);
var listVal = $this.val();
$('#textareaLast').val(listVal);
});

Append the options to the select menu:

function updateSelectListsView(tx, result)
{
    var options = "";

    var rows = result.rows;
    for(var x=0; x< rows.length; x++){
        var id = result.rows[x].id;
        var fname = result.rows[x].first;
        var lname = result.rows[x].last;
        options += '<option value ="' + lname + '">'+ fname +'</option>';
    }

    $('#selectFirst').append(options);    
}

Web SQL – Open Database, Create Table, Insert/Select/Delete Records

WebSQL is an application programming interface (code that allows an asynchronous, transcational interface to SQLite) for storing data in client-side databases that can be queried using a variant of SQL. WebSQL is only supported in Chrome and Safari (and Android and iOS by extension). Since 2010, it has been deprecated in favor of IndexedDB.

Suppose you want to create a client-side database containing first and last names that the user enters in the following text input fields:

<div data-role="fieldcontain">
    <label for="firstName">First Name:</label>
    <input type="text" id="firstName" />
    <br/>
</div>
                    
<div data-role="fieldcontain">
    <label for="lastName">Last Name:</label>
    <input type="text" id="lastName" /><br />
</div>

Open the database and create a table in the database:

 //Test for browser compatibility
 if (window.openDatabase) {
     //Create the database the parameters are 1. the database name 2.version number 3. a description 4. the size of the database (in bytes) 1024 x 1024 = 1MB
    var mydb = openDatabase("names_db", "0.1", "A Database of Names", 1024 * 1024);

    //create the names table using SQL for the database using a transaction
    mydb.transaction(function(t) {
        t.executeSql("CREATE TABLE IF NOT EXISTS names (id INTEGER PRIMARY KEY ASC, first TEXT, last TEXT)");
            });
    } else {
        alert("WebSQL is not supported by your browser!");
    }

Take the first and last names in the text input fields and insert them into the names table:

//function to add the name to the database
function addName() {
    //check to ensure the mydb object has been created
    if (mydb) {
        //get the values of the first and last text inputs
        var first = document.getElementById("firstName").value;
        var last = document.getElementById("lastName").value;

        //Test to ensure that the user has entered both a first and last
        if (first !== "" && last !== "") {
            //Insert the user entered details into the names table, note the use of the ? placeholder, these will replaced by the data passed in as an array as the second parameter
            mydb.transaction(function(t) {
                t.executeSql("INSERT INTO names (first, last) VALUES (?, ?)", [first, last]);
                outputNames();
            });
        } else {
            alert("You must enter a first and last!");
        }
     } else {
        alert("db not found, your browser does not support web sql!");
     }
}

Populate an empty unordered list with first and last names from the names table:

 //function to get the list of names from the database
function outputNames() {
    //check to ensure the mydb object has been created
    if (mydb) {
        //Get all the names from the database with a select statement, set outputNameList as the callback function for the executeSql command
        mydb.transaction(function(t) {
            t.executeSql("SELECT * FROM names", [], updateNameList);
        });
    } else {
        alert("db not found, your browser does not support web sql!");
    }
}

//function to output the list of names in the database
function updateNameList(transaction, results) {
    //initialise the listitems variable
    var listitems = "";
    //get the name list holder ul
    var listholder = document.getElementById("namelist");

    //clear names list ul
    listholder.innerHTML = "";

    var i;
    //Iterate through the results
    for (i = 0; i < results.rows.length; i++) {
        //Get the current row
        var row = results.rows.item(i);

        listholder.innerHTML += "<li>" + row.last + ", " + row.first + " (<a href='javascript:void(0);' onclick='deleteName(" + row.id + ");'>Delete Name</a>)";
    }
}

Delete records from the names table by clicking on a list item in the unordered list:

//function to remove a name from the database, passed the row id as it's only parameter
function deleteName(id) {
    //check to ensure the mydb object has been created
    if (mydb) {
        //Get all the names from the database with a select statement, set outputNameList as the callback function for the executeSql command
        mydb.transaction(function(t) {
            t.executeSql("DELETE FROM names WHERE id=?", [id], outputNames);
        });
    } else {
       alert("db not found, your browser does not support web sql!");
    }
}

jQuery Mobile – The User Interface

The jQuery Mobile frameworks enables the construction of touch-friendly user interfaces using the data- attributes.

The following is the HTML for UI content “listview”:

<ul data-role="listview">
   <li><a href="#"><h3>Item 1</h3><p>Description 1.</p></a></li>
   <li><a href="#">Item 2</a></li>
   <li data-role="list-divider">Divider 1</li>
   <li><a href="#">Item 3</a></li>
</ul>

The following is the HTML for UI content “collapsible”:

<div data-role="collapsible-set">
   <div data-role="collapsible" data-collapsed="false">
      <h3>Collapsible One</h3>
      <p>This is a collapsible data area.  Put your content here.</p>   
   </div> <!--collapsible -->
</div> <!-- collapsible set -->

The following is the HTML for UI content “flipswitch”:

<label for="myFlip">Flip Switch:</label>
<input type="checkbox" id="myFlip" data-role="flipswitch" />

The following is the HTML for UI content “controlgroup” of checkboxes:

 <fieldset data-role="controlgroup" data-mini="true">
    <legend>Items:</legend>
    <input type="checkbox" name="item1" id="item1" />
    <label for="item1">Item 1</label>
 </fieldset> <!-- controlgroup -->

For a complete list of form elements, check out the jQuery Mobile docs.