Posts

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.

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.

1 comment:

GlenMonday, December 22, 2014 at 2:33:00 AM AST

You can store a value for a user using:
PropertiesService.getUserProperties().setProperty(‘last’, last);

and get it again using:
var last = PropertiesService.getUserProperties().getProperty(‘last’);

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.

1 comment:

UnknownThursday, November 16, 2017 at 3:35:00 AM AST

Lazy method, it’s funny sounds. But about script, yes, its good. I was searched this information, thank you. Yesterday I found article about split method java https://explainjava.com/split-string-java/, pretty good describing this method. Im sure it will help you some day.