Wednesday, October 28, 2015

Happy's memory card holder

Long ago I made a memory card holder that held three cards.  It traveled with me across the country at least twice, maybe all three times, and worked well to keep my memory cards from getting lost. Recently, after seeing the card holder I made for Ep1c I wanted one that looked like that for myself. So, I took the one I already had, took it apart, cut some more parts, painted it like a rainbow sandwich, and sewed it up into a memory card holder that holds six cards.

This is something I can be commissioned to make, say fifty dollar painted, or twenty unpainted?



Friday, January 10, 2014

Gmail Inbox Notification Widget

I wanted to display my Gmail Inbox unread message count on my personal web page on Google Sites, so I made a tiny Google Apps Script widget to do this.



The code turned out very simple.

code.gs:
function doGet() {
  return HtmlService.createTemplateFromFile('page')
    .evaluate()
    .setSandboxMode(HtmlService.SandboxMode.NATIVE);
}

function getInboxUnreadCount() {
  return GmailApp.getInboxUnreadCount();
}

page.html:
<style type="text/css">
#refresh { color: blue; text-decoration: underline; }
#refresh:hover { cursor: pointer; }
</style>

<div id="inbox">
Inbox (<span id="count">...</span>)
<span id="refresh">refresh</span>
</div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
function check() {
  clearTimeout(check.timer);
  google.script.run
    .withSuccessHandler(function(result) {
      $('#count').text(result);
      $('#inbox').toggleClass('unread', result > 0);
    })
    .getInboxUnreadCount();
  $('#count').text("...");
  check.timer = setTimeout(check, 600000);
}

$(function() {
  check();
  
  $('#refresh').click(check);
});

</script>

The getInboxUnreadCount() function needed to be run from the script editor to grant the needed permissions to the script, after that it worked from my personal home page.

Interactive Javascript Development

When I was traveling to a friend's place to give her a Christmas not alone I got picked up by someone who suggested I learn jQuery... so I did, and in the process wrote a JavaScript Read Eval Print Loop that makes jQuery and JavaScript development as easy as interactive Python or BASH programming.

The result is a single file web application, JavaScript Read, Eval, Print, Loop. View the source to see what was needed to make it work.

Monday, January 6, 2014

Facebook notification widget

I wanted to display my facebook notification information on my personal home screen.

I ended up with a single HTML file which I have hosted on Google Drive and embedded in an iframe in my personal home screen on Google Sites.

The end result is a single line where the numbers change if there are any notifications to report.



<html><head><title>Facebook Quick Check</title></head><body>
<style type="text/css">
body { margin: 0px; }
</style>

<div id="fb-root"></div>

<div>
    <span id="login"><a href="javascript:doLogin()">FB Login</a>: </span>
    Notice (<span id="notice">...</span>)
    Friend (<span id="friend">...</span>)
    Inbox (<span id="inbox">...</span>)
    <a href="javascript:loadStatus()">refresh</a>
</div>

<div id="out"></div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
I wanted a fluent API that felt like Google Apps Script server calls instead of the regular Facebook API, so I made a quick one...
function FBRun(parent) {
  this.successHandler = parent ? parent.successHandler : function(result) { };
  this.errorHandler = parent ? parent.errorHandler : function(result) { };
};
FBRun.prototype.withSuccessHandler = function(handler) {
  var obj = new FBRun(this);
  obj.successHandler = handler;
  return obj;
};
FBRun.prototype.withErrorHandler = function(handler) {
  var obj = new FBRun(this);
  obj.errorHandler = handler;
  return obj;
};
FBRun.prototype.getHandler = function() {
  var obj = this;
  return function(response) {
    if(!response || response.error) {
      return obj.errorHandler(response);
    }
    return obj.successHandler(response);
  }
};
FBRun.prototype.get = function(url, data) {
  FB.api(url, 'get', data, this.getHandler());
  return this;
};
FBRun.prototype.login = function(data) {
  FB.login(this.getHandler(), data);
  return this;
}
FBRun.prototype.loginStatus = function() {
  FB.getLoginStatus(this.getHandler());
  return this;
}
fbrun = new FBRun();
Load the Facebook API and check the login status on success...
$(document).ready(function() {
  $.ajaxSetup({ cache: true });
  $.getScript('//connect.facebook.net/en_UK/all.js', function(){
    FB.init({
      appId: 'APPLICATION_KEY',
    });
    fbrun
      .withSuccessHandler(onLogin)
      .loginStatus();
  });
});
The code for the login link...
function doLogin() {
  fbrun
    .withErrorHandler(display)
    .withSuccessHandler(onLogin)
    .login({scope: 'manage_notifications,read_requests,read_mailbox'});
}

var perms = {};

function onLogin(response) {
  if(response.status === 'connected') {
    fbrun
      .withErrorHandler(display)
      .withSuccessHandler(function(response) {
        perms = response.data[0];
        
        if(perms.manage_notifications && perms.read_requests && perms.read_mailbox) {
          $('#login').hide();
        }
        start();
      })
      .get('/me/permissions');
  }
}

function display(response) {
  $('#out').text(JSON.stringify(response));
}
Load the information and start the refresh timer...
function start() {
  loadStatus();
  
  if(start.active) {
    return;
  }
  start.active = true;
  
  function check() {
    loadStatus();
    setTimeout(check, 600000);
  }
  setTimeout(check, 600000);
}

function loadStatus() {
  if(perms.manage_notifications) {
    $('#notice').text('...');
    fbrun
      .withErrorHandler(display)
      .withSuccessHandler(function(response) {
        if(response.summary.unseen_count != undefined) {
          $('#notice').text(response.summary.unseen_count);
        } else {
          $('#notice').text("0");
        }
      })
      .get('/me/notifications', { limit: 0 });
  } else {
    $('#notice').text('-');
  }
  if(perms.read_requests) {
    $('#friend').text('...');
    fbrun
      .withErrorHandler(display)
      .withSuccessHandler(function(response) {
        $('#friend').text(response.summary.unread_count);
      })
      .get('/me/friendrequests', { limit: 0 });
  } else {
    $('#friend').text('-');
  }
  if(perms.read_mailbox) {
    $('#inbox').text('...');
    fbrun
      .withErrorHandler(display)
      .withSuccessHandler(function(response) {
        $('#inbox').text(response.summary.unseen_count);
      })
      .get('/me/inbox', { limit: 0 });
  } else {
    $('#inbox').text('-');
  }
}
</script>
</body></html>

Update: limit the data returned to just the summaries by setting the item limit to zero.

Saturday, January 4, 2014

Google Apps Script execution model

I was wondering if Google Apps Script web applications retained state between invocations, so I did a small experiment.

Using a Script created in Google Drive:

Code.gs:
function doGet() {
  return HtmlService.createTemplateFromFile('page')
    .evaluate()
    .setSandboxMode(HtmlService.SandboxMode.NATIVE);
}

var date = new Date();
var last = -1;

function func(value) {
  Utilities.sleep(100);
  var out = "date = " + date + ", last = " + last;
  last = value;
  return out;
}

page.html:
<style type="text/css">
pre {
  border: 1px solid green;
}
</style>

<div id="out">
<pre>loading...</pre>
</div>

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function() {
  for(var i = 0; i < 2; i++) {
    launch(i);
  }
});

setTimeout(function() {
  for(var i = 2; i < 4; i++) {
    launch(i);
  }
}, 2000);

function launch(id) {
  $("<pre />").text("started " + id).appendTo('#out');
  google.script.run
    .withSuccessHandler(function(result) {
      $("<pre />").text("finished " + id + ": " + result).appendTo('#out');
    })
    .func(id);
}
</script>

This output was produced:
loading...
started 0
started 1
finished 0: date = Sat Jan 04 2014 12:03:06 GMT-0400 (AST), last = -1
finished 1: date = Sat Jan 04 2014 12:03:06 GMT-0400 (AST), last = -1
started 2
started 3
finished 2: date = Sat Jan 04 2014 12:03:08 GMT-0400 (AST), last = -1
finished 3: date = Sat Jan 04 2014 12:03:08 GMT-0400 (AST), last = -1

Which leads me to the conclusion that the server is stateless, at least from the point of view of the scripts running on it.

Friday, January 3, 2014

lazy initialization in JavaScript

I have a function that is not often used and has a long initialization step, when it is used it is used often, making it a very good candidate for lazy initialization. This function is in a Google Apps Script web application, so minimizing processing time is desirable.

Functions in JavaScript are assignable closures, so it is easy to write a lazy initializing function without any if statements.

var getImage = function(name) {
  var images = { };

  var files = MESSAGES_FOLDER.getFiles();
  while(files.hasNext()) {
    var file = files.next();
    images[file.getName()] = file.getDownloadUrl();
  }

  return (getImage = function(name) {
    return images[name];
  })(name);
}

After the initialization part of the function (making a map of file names to download urls) it is replaced by a new function that does not include the initialization, and that new function is called to get the result for the first use.

This is also an implementation of the State pattern.

Thursday, December 5, 2013

Simple and super practical note book

All the notebooks I have kept in my bag so far seemed lacking in little ways: I couldn't easily tuck things into them; sometimes finding the next blank page was a challenge; they consume a lot more space than I feel they need to; taking pages out of them quickly became unsightly.

So I started pondering how to make one where I could freely add and remove pages without having an effect on the feel of the note book.

I made the notebook from plastic canvas, and sewed it with strips of shopping bags.  The shell does not care about getting wet, but the pages inside do.


It is just larger than a quarter sheet of letter sized paper, so it is very easy to make new pages as I need them.


The pages are folded over a strip of plastic, so they can be completely rearranged, and anything thin that can be folded can be tucked between any of the pages.  I did not realize in advance that I can always be writing on the top few pages by just moving blank pages up to the top of the stack.  I had printed driving directions on the left side earlier in the week, and now they have been flipped over to be reused as note pages.

In the future I am going to try some graph paper or lined paper in here, but this is also working very well.