ホーム > タグ > JavaScript

JavaScript

iPhone 向けのタスク管理アプリを作ってみる

iPhone を買って約 1 ヶ月が立ちました。
AppStore には毎日のように新しいアプリが登場していて、うずうずしてきたのでアプリを作り始めました。
とはいってもネイティブアプリではなく、Safari 上で動く Web アプリです。
今回は iPhone 用の Safari にも組み込まれている SQLite を JavaScript から利用できる SQL API を使って、タスク管理(ToDo 管理とも呼びますね)アプリを作ってみようと思います。

せっかく iPhone 向けのアプリなので、インタフェースはやっぱりこだわりたいところ。
その iPhone 向けインタフェースのテンプレートが提供されているので、今回はそれを使いました。

iphone-universal - Google Code

まだ作成途中ですが、下記のリンクから試すことができます。Safari でアクセスしてください。

>>作成したアプリ

注:データベースを削除したい時は、
・通常の Safari の場合:「環境設定」→「セキュリティ」→「データベースを表示」→データベースを選択して「取り除く」
・iPhone 版 Safari の場合:「設定」→「Safari」→「Databases」→編集ボタン→削除
でできます。

今までできたところまで、ホーム画面はこんな感じです。

iphone_tasks_sample.png

テンプレートのおかげで、あまり工数をかけずに iPhone 向けインタフェースができました。

そして SQLite を操作する JavaScript ソースが以下。
こちらのサイトを参考にしました。

// DB オブジェクト
var db;

// DB 初期化(テーブルが無ければ作成する)
function init() {
  if(window.openDatabase) {
    db = openDatabase('test_db', '1.0', 'Tasks');
    db.transaction(
      function(tx) {
        tx.executeSql('SELECT COUNT(*) FROM tasks', [],
          function(tx, rs) {},
          function(tx, error) {
            tx.executeSql('CREATE TABLE tasks (id INTEGER PRIMARY KEY, body TEXT, cmp_f BOOLEAN, priority INTEGER)', [],
              function() {},
              function(error) {
                alert('Database does not created. : ' + error.message);
              }
            );
          }
        );
      }
    );
  } else {
    alert('Sorry, this browser does not supported.');
  }
}

// タスク一覧を表示する
function show() {
  var incmp = document.getElementById('incomplete_list');
  var cmp = document.getElementById('completed_list');
  if(!(incmp && cmp)) return;

  // 現在表示しているタスクをクリアする
  while(incmp.hasChildNodes()) {
    incmp.removeChild(incmp.firstChild);
  }
  while(cmp.hasChildNodes()) {
    cmp.removeChild(cmp.firstChild);
  }
  var ul = document.createElement('ul');
  incmp.appendChild(ul);
  var ul = document.createElement('ul');
  cmp.appendChild(ul);

  // タスク一覧を取得して表示する
  db.transaction(
    function(tx) {
      tx.executeSql('SELECT * FROM tasks ORDER BY priority', [], function(tx, rs) {
        for(var i = 0; i < rs.rows.length; i++) {
          var row = rs.rows.item(i);
          var list = (row.cmp_f == 't') ? cmp : incmp;
          appendItem(list.firstChild, 'edit.html?id=' + row.id, 'arrow', row.body, row.priority);
        }

        // タスク件数が 0 件の場合
        if(incmp.firstChild.childNodes.length == 0) {
          appendItem(uncmp.firstChild, '#', null, '(No Item)', null);
        }
        if(cmp.firstChild.childNodes.length == 0) {
          appendItem(cmp.firstChild, '#', null, '(No Item)', null);
        }
      });
    },
    function(error) {
      alert('Task data does not get. : ' + error.message);
    },
    function() {}
  );
}

// リストを追加する
function appendItem(prt, hrf, cl, val, pri) {
  if(!prt) return;
  var li = document.createElement('li');
  li.className = 'pri' + pri;
  prt.appendChild(li);
  var a = document.createElement('a');
  a.href = hrf;
  if(cl) a.className = cl;
  li.appendChild(a);
  var t = document.createTextNode(val);
  a.appendChild(t);
}

// タスクを新規作成する
function add() {
  var b = document.getElementById('body');
  if(!b) return;
  var body = b.value;
  var p = document.getElementById('priority');
  if(!p) return;
  var idx = p.selectedIndex;
  var priority = p.options[idx].value;
  db.transaction(
    function(tx) {
      tx.executeSql('INSERT INTO tasks VALUES(NULL, ?, ?, ?)', [body, 'f', priority]);
    },
    function(error) {
      alert('Task does not create. : ' + error.message);
    },
    function() {
      location.href = 'index.html';
    }
  );
}

// タスクを削除する
function del() {
  var id = getIdValue();
  if(!id) return;
  if(window.confirm('Delete this task ?')) {
    db.transaction(
      function(tx) {
        tx.executeSql('DELETE FROM tasks WHERE id = ?', [id]);
      },
      function(error) {
        alert(error.message);
      },
      function() {
        location.href = 'index.html';
      }
    );
  }
}

// タスクを完了/未完了にする
function cmp(f) {
  var id = getIdValue();
  if(!id) return;
  db.transaction(
    function(tx) {
      tx.executeSql('UPDATE tasks SET cmp_f = ? where id = ?', [(f) ? 't' : 'f', id]);
    },
    function(error) {
      alert(error.message);
    },
    function() {
      location.href = 'index.html';
    }
  );
}

// 1 つのタスクを表示する
function showOne() {
  var id = getIdValue();
  if(!id) return;
  db.transaction(
    function(tx) {
      tx.executeSql('SELECT * FROM tasks WHERE id = ?', [id], function(tx, rs) {
        if(rs.rows.length > 0) {
          var row = rs.rows.item(0);
          var body = document.getElementById('body');
          if(!body) return;
          body.value = row.body;
          var p = document.getElementById('priority');
          if(!p) return;
          p.value = row.priority;
          if(row.cmp_f == 't') {
            var elm = document.getElementById('cmp');
          } else {
            var elm = document.getElementById('uncmp');
          }
          elm.style.display = 'none';
        } else {
          alert('Task does not exist.');
        }
      });
    },
    function(error) {
      alert(error.message);
    },
    function() {}
  );
}

// タスクを更新する
function update() {
  var b = document.getElementById('body');
  if(!b) return;
  var body = b.value;
  if(body.length > 0) {
    var id = getIdValue();
    if(!id) return;
    var p = document.getElementById('priority');
    if(!p) return;
    var priority = p.value;
    db.transaction(
      function(tx) {
        tx.executeSql('UPDATE tasks SET body = ?, priority = ? WHERE id = ?', [body, priority, id]);
      },
      function(error) {
        alert('Task update failed. : ' + error.message);
      },
      function() {
        location.href = 'index.html';
      }
    );
  } else {
    alert('Please input body.');
  }
}

// QueryString から ID 値を取得する
function getIdValue() {
  if(location.search.length > 0) {
    var arr = location.search.substr(1).split('&');
    for(idx in arr) {
      var k = arr[idx].split('=');
      if(k[0] == 'id') return k[1];
    }
  } else {
    return null;
  }
}

Twitter の投稿日時のフォーマットを変更するグリモン

Twitter Time Converter

これは何?

Twitter に表示される投稿日時(3 hours ago など)を yyyy/mm/dd hh:mi 形式に変更する Greasemonkey です。

きっかけは自分のログを振り返っている時に、「17 時間前」などと表示されても直感的にいつだったのかが分かりにくかったからです。

対応しているページは、Friends Timeline, Replies, Archive, Public Timeline, Favorites です。Permalink, Direct Messages には対応していません。

使い方

下記リンクからインストールしてください。 Firefox/Greasemonkey または Safari/Greasekit が必要です。

Twitter Time Converter – Userscripts.org

技術的なところ

Twitter のページの HTML に、ISO 8601 形式の日付が abbr タグの title 属性に入っているのですが、その ISO 8601 形式の日付が JavaScript の Date.parse で解釈できないようなので、仕方なく手動で parse しました。

parse については、以下のライブラリを参考にさせていただきました。感謝。
[JSAN] Date.W3CDTF - ISO-8601日時フォーマット対応JavaScriptクラス

関連リンク

ソース

// ==UserScript==
// @name           Twitter Time Converter
// @namespace      http://www.sukechan.net/
// @description    The format of posted date is change.
// @include        http://twitter.com/*
// @version        1.0
// ==/UserScript==

(function() {
  var f = function() {
    var x = document.evaluate('//a[@class="entry-date"]/abbr', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0; i < x.snapshotLength; i++) {
      var item = x.snapshotItem(i);
      var ds = parseDate(item.getAttribute("title"));
      if (ds) item.parentNode.innerHTML = ds;
    }
  }
  function parseDate(s) {
    var arr = s.split(/[^0-9]/);
    if (arr.length == 8 ) {
      for (var j = 0; j < arr.length; j++) arr[j] = arr[j] - 0;
      var msec = Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
      if (s.indexOf("+") < 0) arr[6] *= -1;
      msec -= (arr[6] * 60 + arr[7]) * 60 * 1000;
      var dt = new Date(msec);
      return dt.getFullYear() + "/" + padZero(dt.getMonth() + 1) + "/" + padZero(dt.getDate()) + " " + padZero(dt.getHours()) + ":" + padZero(dt.getMinutes());
    } else {
      return;
    }
  }
  function padZero(s) {
    return ("0" + s).slice(-2);
  }
  f();
  addFilter(f);
  function addFilter(filter, i) {
    i = i || 4;
    if (window.AutoPagerize && window.AutoPagerize.addFilter) {
      window.AutoPagerize.addFilter(filter);
    }
    else if (i > 1) {
      setTimeout(arguments.callee, 1000, filter, i -1);
    }
  }
})();

Safari 3.1 の Web インスペクタがすごい

先日リリースされた Safari 3.1 の新機能の 1 つに、「開発」メニューがあります。その中の Web インスペクタがとても便利なので紹介します。

環境設定の「詳細」にある「メニューバーに”開発”メニューを表示」にチェックを入れると、開発メニューが表示されます。以前のバージョンでは隠し機能であった「Debug」メニューになります。

Safari 3.1 開発メニュー

開発メニューから「Web インスペクタを表示」を選択します。HTML や JavaScript のソースを閲覧できます。
要素を選択すると、その要素がページ上でハイライト表示され、非常に分かりやすいです。
Safari 3.1 Web インスペクタ

この Web インスペクタ以外にも Safari 3.1 にはさまざまな機能が追加されています。みなさんもぜひ使ってみてください。

Twitter に Google Maps へのリンクを追加するグリモン

Twitter Google Maps Link

これは何?

Twitter の発言で、L:住所 と表示されているロケーション情報に、Google Maps へのリンクを追加する Greasemonkey です。

使い方

下記リンクからインストールしてください。例によって Greasemonkey が必要です。(Safari の場合は GreaseKit)

インストール後、Twitter の L:住所 という部分が Google Maps へのリンクになります。

twittergooglemapslink.user.js (version 1.0)
Userscripts.org で管理することにしました。
Twitter Google Maps Link – Userscripts.org

その他、技術的なこと

既に同じグリモンがあるかもしれませんが、見つからなかったので作りました。

今回、初めて XPath を使いました。最初 DOM でやろうと思ったんですが、処理が煩雑になるし XPath の方が早いらしいので。以下のリンクを参考にしました。

// ==UserScript==
// @name           Twitter Google Maps Link
// @namespace      http://www.sukechan.net/
// @description    Location is convert into the link to Google Maps.
// @include        http://twitter.com/*
// @version        1.0
// ==/UserScript==

(function() {
  var x = document.evaluate('//*[@class="entry-title entry-content"] | //div[@class="desc"]/descendant::p[1]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  for (var i = 0; i < x.snapshotLength; i++) {
    var idx = x.snapshotItem(i).textContent.indexOf("L:")
    if (idx >= 0) {
      loc = x.snapshotItem(i).textContent.substr(idx + 2).split(/\s+/)[0].replace(/[\n\r\s]/g, "");
      x.snapshotItem(i).innerHTML = x.snapshotItem(i).innerHTML.replace(loc, "" + loc + "");
    }
  }
})();

相手に Follow されてるかどうかを表示するグリモン

Twitter Follower Check

これは何?

Twitter で、指定したユーザに Follow されてるかどうかを表示する Greasemonkey スクリプトです。

以前作ったこれを Greasemonkey にしました。

使い方

下記リンクからインストールしてください。要 Greasemonkey

インストール後、Twitter のユーザページにアクセスすると、「Does follow do you?」というボタンが追加されます。そのボタンをクリックすると、あなたがそのユーザから follow されてるかどうかを表示します。

twitterfollowercheck.user.js (version 1.0)

その他

グリモンは初めて作りました。
Firefox, Safari(GreaseKit) で動くと思います。
コードも晒しておきます。つっこみ大歓迎。

// ==UserScript==
// @name           Twitter Follower Check
// @namespace      http://www.sukechan.net/
// @include        http://twitter.com/*
// @version        1.0
// ==/UserScript==

(function(){
  var num = 1;
  var screenName = location.href.substr(19);

  // Followers Request
  function getFollowers() {
    addElm.disabled = true;
    addElm.value = "Loading...";
    var httpObj = createRequest();
    if (httpObj) {
      httpObj.open("GET", "http://twitter.com/statuses/followers.json?page=" + num, true);
      httpObj.send(null);
      httpObj.onreadystatechange = function() {
        if (httpObj.readyState == 4) {
          if (httpObj.status == 200) {
            var jsonData = eval(httpObj.responseText);
            if (jsonData.length > 0) {
              for (var i = 0; i < jsonData.length; i++) {
                if (screenName == jsonData[i].screen_name) {
                  addElm.value = "You are followed.";
                  return;
                }
              }
              getFollowers(num++);
            } else {
              addElm.value = "You are not followed.";
            }
          } else {
            addElm.value = "error. status: " + httpObj.status;
          }
        }
      }
    }
  }

  // XMLHttpRequest
  function createRequest() {
    var xmlHttp = null;
    if (window.XMLHttpRequest) {
      xmlHttp = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
      try {
        xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
      } catch (e) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
      }
    }
    return xmlHttp;
  }

  // add element
  var elm = document.getElementById("follow-control");
  if (elm) {
    var addElm = document.createElement("input");
    addElm.setAttribute("class", "follow-button");
    addElm.setAttribute("type", "button");
    addElm.setAttribute("value", "Does follow do you?");
    addElm.addEventListener("click", function() {getFollowers()}, false);
    addElm.style.width = "150px";
    addElm.style.marginTop = "3px";
    elm.appendChild(addElm);
   }
})();
 Page 4 of 5 « 1  2  3  4  5 »

Home > Tags > JavaScript

Search
Feeds
Meta
あわせて読みたい
あわせて読みたいブログパーツ
Others...
フィードメーター - sukechan.net
track feed

Return to page top