Showing posts with label web. Show all posts
Showing posts with label web. Show all posts

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.

Sunday, October 20, 2013

Facebook API Hello World

After working through the Facebook API Getting Started guide I decided to write an even simpler Hello World application that also displays some debugging information. This is my first Facebook Application.

facebook-hello.html:
<html>
<head><title>Facebook API Hello World</title></head>
<body>
<script type="text/javascript">
  window.fbAsyncInit = function() {
    // https://developers.facebook.com/docs/reference/javascript/FB.init/
    FB.init({
      appId: '222165227950143' /* registered application id */,
      status: true /* check the login status */,
      cookie: true /* set the session cookie */,
      xfbml: true /* enable social plugins */
    });

    // get the initial login status
    // https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus/
    FB.getLoginStatus(onLogin);
  };

  // Load the SDK Asynchronously
  (function(d, s, id){
    if (d.getElementById(id)) {return;}
    var js, fjs = d.getElementsByTagName(s)[0];
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/all.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));

  function onLogin(response) {
    document.getElementById('onlogin-count').innerHTML++;
    document.getElementById('login-status').innerHTML = response.status;

    if(response.status === 'connected') {
      var uid = response.authResponse.userID;
      document.getElementById('user-id').innerHTML = uid;
      
      readName();
    }
  }

  function readName() {
    // https://developers.facebook.com/docs/reference/api/user/
    FB.api(
      'https://graph.facebook.com/me',
      'get',
      function(response) {
        document.getElementById("user-count").innerHTML++;
         
        var e = document.getElementById('user-name');
        if(!response) {
          e.innerHTML = '<i>no object</i>';
        } else if(response.error) {
          e.innerHTML = '<i>Error: ' + response.error.message + '</i>';
        } else {
          e.innerHTML = response.name;
        }
      }
    );
  }
</script>

<p>When we are connected to facebook, read the name of the current user and
display it here.  If you would like to read the name of the current user again
or before being connected to facebook, press the "read name" button.</p>

<p>
<!-- https://developers.facebook.com/docs/reference/plugins/login -->
<div><div 
  class="fb-login-button"
  data-scope=""
  data-onlogin="onLogin">
</div></div>

(Status: <span id="login-status"><i>???</i></span>)
(User ID: <span id="user-id"><i>???</i></span>)
(onLogin calls: <span id="onlogin-count">0</span>)
</p>

<p>
<input type="button" value="read name" onclick="readName()">
<span id="user-name"><i>???</i></span>
(readName calls: <span id="user-count">0</span>)
</p>

</body>
</html>

This is my first time working with the Facebook API. I like how all the API calls execute in the background, and not block the browser.

Friday, October 18, 2013

Escape HTML Text

Often when I am putting code examples up I have been manually escaping all the html tag characters to get it into the post. Now I have a piece of JavaScript to do it for me. I will present this as a short, self contained, correct, example.

escape.html:
<!DOCTYPE html>
<html><head><title>Escape HTML Text</title></head><body><form>

<div>text = <br>
<textarea id="text" rows="10" cols="80"></textarea></div>
<div><button type="button" onclick="setText(escapeHtml(getText()))">text = escapeHtml(text)</button></div>
<div><button type="button" onclick="setText(unescapeHtml(getText()))">text = unescapeHtml(text)</button></div>

<script type="text/javascript">
//<![CDATA[
    function getText() { return document.getElementById("text").value; }
    function setText(text) { document.getElementById("text").value = text; }
    function escapeHtml(text) {
        return text.replace(/&/g,"&amp;").replace(/"/g,"&quot;")
            .replace(/</g,"&lt;").replace(/>/g,"&gt;");
    }
    function unescapeHtml(text) {
        return text.replace(/&lt;/g,"<").replace(/&gt;/g,">")
            .replace(/&quot;/g,"\"").replace(/&amp;/g,"&");
    }
//]]>
</script>
</form></body></html>

Friday, March 26, 2010

setting up blogger to format latex expressions

I want to be able to post mathematical expressions and have them formatted nicely.

After some searching I found yourequations.com which had a nice little script to do the job, and did some digging on how Google Docs was doing the rendering of the expressions.

From there I found Google Chart Tools.

After gathering all that information I wrote up a script block to add to the blog template.

To install this just add it to a Blogger layout block after the Blog Posts layout block.

<script type="text/javascript">
var tags = [ "pre", "code" ];
for(var i = 0; i < tags.length; i++) {
  var eqn = document.getElementsByTagName(tags[i]);
  for (var j = 0; j < eqn.length; j++) {
    var e = eqn[j];
    if (e.getAttribute("lang") != "eq.latex") { 
      continue;
    }
    if (e.innerHTML.match(/<img.*?>/i)) {
      continue;
    }

    var str = e.innerHTML.
      replace(/<br>/gi,"").
      replace(/<br \/>/gi,"").
      replace(/&lt;/gi,"<").
      replace(/&gt;/gi,">").
      replace(/&amp;/gi,"&");

    var url_str = escape(str).
      replace(/\+/g, "%2B");

    e.innerHTML = "<img " +
      "src=\"http://chart.apis.google.com/chart" +
      "?cht=tx&chf=bg,s,ffffff00" +
      "&chl=" + url_str + "\" " +
      "title=\"" + str + "\" alt=\"" + str + "\" " +
      "class=\"eq_latex\" align=\"middle\" " +
      "border=\"0\" />";
  }
}
</script>
After installing the layout block just enclose the expressions in <pre lang="eq.latex"> and </pre>, or <code lang="eq.latex"> and </code>. For example, <code lang="eq.latex">\int_{0}^{1}xdx</code> renders as \int_{0}^{1}xdx.

setting up blogger to format source code

I want to be able to post source code with basic syntax highlighting.

After some searching I found a JavaScript module that would do it using markup. After more searching I found a Google hosted copy of the script to use on blogger, in use on JQuery HowTo.

The software used to do this is google-code-prettify: syntax highlighting of code snippets in a web page. It keeps it simple and does what it says it does well.

To install the functionality I added the following block of code to a Blogger layout block after the Blog Posts layout block.

<!--
from: http://jquery-howto.blogspot.com/2009/02/new-code-highlighter-for-bloggercom.html
-->
<script src="http://www.gstatic.com/codesite/ph/2429932258724909799/js/prettify/prettify.js">
</script>
<script type="text/javascript">
prettyPrint();
</script>

<style type="text/css">

/* Pretty printing styles. Used with prettify.js. */

.str { color: #080; }
.kwd { color: #008; }
.com { color: #800; }
.typ { color: #606; }
.lit { color: #066; }
.pun { color: #660; }
.pln { color: #000; }
.tag { color: #008; }
.atn { color: #606; }
.atv { color: #080; }
.dec { color: #606; }
pre.prettyprint { padding: 2px; border: 1px solid #888; overflow:auto; }

@media print {
  .str { color: #060; }
  .kwd { color: #006; font-weight: bold; }
  .com { color: #600; font-style: italic; }
  .typ { color: #404; font-weight: bold; }
  .lit { color: #044; }
  .pun { color: #440; }
  .pln { color: #000; }
  .tag { color: #006; font-weight: bold; }
  .atn { color: #404; }
  .atv { color: #060; }
}
</style>

To use it in a post just involves wrapping the code in <pre class="prettyprint"> and </pre>, or <code class="prettyprint"> and </code>, and ensuring that the ampersands and angle brackets are properly escaped using &amp;, &lt;, and &gt;.

Update: Added automatic scroll bars for wide lines.