UPM for Android 1.2 Released

Universal Password Manager for Android 1.2 has been released. The easiest way to install it is from the Android Market. Alternatively the apk can be downloaded from SourceForge. The source is also available there.

From the release notes…

  • Long-clicking on an account now brings up a context menu allowing you to copy username, copy password, launch URL or edit account
  • Added the ability to trust self signed certificates and certificates that have a different Common Name to the website hostname
  • Increased the font size on the Account Details activity
  • Fixed a bug on the main Accounts page so that it recovers gracefully when the database is closed unexpectedly

Calling RESTful Recess Services using Javascript and JSON

As part of a proof of concept I was carrying recently I wanted to deploy and call a simple REST service from Javascript. Ultimately I’ll probably use a Java based framework like Restlet (I’ll be deploying to Google App Engine) but in the meantime the Recess framework proved very useful. Using it’s GUI based tools it’s very easy to setup a persistent data model and expose it as a REST service.

On the client side I used the YUI Javascript framework and JSON as the data interchange format. There didn’t seem to be a direct example of doing this on the Recess website here’s what I came up with.

The only Recess specific requirement is that the Json View is registered on your application controller. More info on views here.

!RespondsWith Layouts, Json

Here’s Javascript code to create a object of type Entry. The object has two attributes, “date” and “data”.

Y.io("/recess/myjournal/entry/", {
  method: "POST",
  data: {
    "entry[date][day]": Y.one("#entryDay").get("value"),
    "entry[date][month]": Y.one("#entryMonth").get("value"),
    "entry[date][year]": Y.one("#entryYear").get("value"),
    "entry[data]": Y.one("#entryText").get("value")
  },
  on: {
    success: function(transactionid, response, arguments) {
      alert("entry successfully added");
    },
    failure: function(transactionid, response, arguments) {
      alert("add entry failed: " + response.responseText);
    }
  }
});

For anyone interested in seeing this code in context here’s the page it’s used on. Remember this is just some simple proof of concept stuff so it’s very rough and ready.

<html>
  <head>
    <title>My Journal</title>
    <script type="text/javascript" charset="utf-8" src="http://yui.yahooapis.com/3.1.1/build/yui/yui-min.js"></script>
  </head>
  <body>
    <h2>Entries</h2>
    <ul id="entriesList">
    </ul>

    <form id="newEntryForm" style="display: none">
      <input id="entryDay" type="text" size="2" maxlength="2">
      <select id="entryMonth">
        <option value="1">Jan</option>
        <option value="2">Feb</option>
        <option value="3">Mar</option>
        <option value="4">Apr</option>
        <option value="5">May</option>
        <option value="6">June</option>
        <option value="7">July</option>
        <option value="8">Aug</option>
        <option value="9">Sept</option>
        <option value="10">Oct</option>
        <option value="11">Nov</option>
        <option value="12">Dec</option>
      </select>
      <input id="entryYear" type="text" size="2" maxlength="4">
      <br />
      <textarea id="entryText" rows="10" cols="20"></textarea>
      <br />
      <input id="cancelAddButton" type="button" value="Cancel">
      <input id="saveEntryButton" type="button" value="Save">
    </form>

    <form>
      <input id="refreshButton" type="button" value="Refresh" disabled="true">
      <input id="addButton" type="button" value="Add">
    </form>

    <script>
      YUI().use("node", "io", "json", "datatype-date", "querystring-stringify-simple", function(Y) {
        // Add an entry to list
        var addEntryToList = function(entryDate) {
          var listNode = Y.one("#entriesList");
          var date = Y.DataType.Date.format(entryDate, {format: "%Y-%m-%d"});
          listNode.append("<li>" + date + "</li>");
        };

        // Retrieve entries and add them to the list
        var populateEntriesList = function() {
          Y.one("#refreshButton").set("disabled", true);

          Y.io("/recess/myjournal/entry.json", {
            method: "GET",
            on: {
              success: function(transactionid, response, arguments) {
                var listNode = Y.one("#entriesList");
                listNode.get("children").remove();

                var entries = Y.JSON.parse(response.responseText);

                Y.each(entries.entrySet, function(entry, index, array) {
                  // The date coming from the server will by in seconds. Date() expects milliseconds so * by 1000.
                  addEntryToList(new Date(entry.date * 1000));
                });
              },
              failure: function(transactionid, response, arguments) {
                alert("get entries failed: " + response.responseText);
              },
              end: function(transactionid, arguments) {
                Y.one("#refreshButton").set("disabled", false);
              }
            }
          });
        };

        var showAddEntryControls = function() {
          Y.one("#newEntryForm").setStyle("display", "block");
        };

        var saveEntry = function() {
          Y.io("/recess/myjournal/entry/", {
            method: "POST",
            data: {
              "entry[date][day]": Y.one("#entryDay").get("value"),
              "entry[date][month]": Y.one("#entryMonth").get("value"),
              "entry[date][year]": Y.one("#entryYear").get("value"),
              "entry[data]": Y.one("#entryText").get("value")
            },
            on: {
              success: function(transactionid, response, arguments) {
                var entryDate = new Date(
                  Y.one("#entryYear").get("value"),
                  Y.one("#entryMonth").get("value") - 1,
                  Y.one("#entryDay").get("value")
                );
                addEntryToList(entryDate);
              },
              failure: function(transactionid, response, arguments) {
                alert("add entry failed: " + response.responseText);
              },
              end: function(transactionid, arguments) {
                Y.one("#newEntryForm").setStyle("display", "none");
              }
            }
          });
        };

        var cancelAddEntry = function() {
          Y.one("#newEntryForm").setStyle("display", "none");
        };

        Y.on("available", populateEntriesList, "#entriesList");
        Y.one("#refreshButton").on("click", populateEntriesList);
        Y.one("#addButton").on("click", showAddEntryControls);
        Y.one("#cancelAddButton").on("click", cancelAddEntry);
        Y.one("#saveEntryButton").on("click", saveEntry);
      });
    </script>
  </body>
</html>