←前 |  ↑↑Blog |  ↑Category |  ↓Comment |  ↓Trackback |  次→

Dashboard Widgetの作成方法 その2


サンプルWidgetの作成を通じて基本的な内容を説明します。
その2です。


DashboardMacOS X 10.4(Tiger)にて導入された機能です。
Dashboard用のソフトウェアはWidget(ウィジェットと)呼ばれ、比較的簡単に作成できそうです。

一連のエントリは簡単なWidgetを作成する模様を記述してみます。
最終的に作るものはTrackBack Pingを実行するWidgetです。
作成手順としては、最初から完成形を想定して作成せず、ベースとなるものを作成し徐々に機能を追加する形をとります。

今回のエントリでは、front画面にTrackBackを飛ばす機能をつけます。


2.0. TrackBack用のインタフェースのためにhtmlを変更する

本節の修正後のファイル←1.10TOC2.1→

TrackBack用のインタフェースを作成するためにhtmlを変更します。

TrackBackの仕様(1.21.1(日本語訳))によると、pingを送信する際に必要な情報は、以下の4点です。

  • title: optional
  • excerpt: optional
  • url: required
  • blog_name: optional

これらの他に送信する先のURLが必要なので、入力欄としては全部で5つ必要となります。

さらに、
  • 送信する際に押すボタンと状況を表示する欄を用意する
  • XHTML1.1として整形し直す
  • JavaScriptをWidgetで共通に使えるものとこのWidgetに固有のものと分割する
  • ボタンを独自作成ではなくて、Apple提供のものを利用する

などを考慮して、PiyoTrackBack.htmlを以下のように変更します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>PiyoTrackBack</title>
  <style type="text/css">@import "PiyoTrackBack.css";</style>
  <script type="text/javascript"
          src="file:///System/Library/WidgetResources/button/genericButton.js"
          charset="utf-8" />
  <script type="text/javascript" src="WidgetCommon.js"  />
  <script type="text/javascript" src="PiyoTrackBack.js"  />
 </head>
 <body onload="setup();">
  
   <div id="front"
            onmousemove="mousemove(event)"
            onmouseout="mouseexit(event)">
    <img class="backgroundImage" src="Default.png" />
    <div class="trackBackInfo">
      Blog name:<br />
      <input type="text"
                   size="30"
                   maxlength="50"
                   name="blogName"
                   id="blogName" /><br />
      Entry title:<br />
      <input type="text"
                   size="30"
                   maxlength="50"
                   name="entryTitle"
                   id="entryTitle" /><br />
      Entry excerpt:<br />
      <textarea name="entryExcerpt"
                          rows="3"
                          cols="30"
                          id="entryExcerpt" ></textarea><br />
      Entry Permalink URL:<br />
      <input type="text"
                   size="30"
                   maxlength="255"
                   name="entryUrl"
                   id="entryUrl" /><br />
      URL to ping:<br />
      <input type="text"
                   size="30"
                   maxlength="255"
                   name="urlToPing"
                   id="urlToPing" />
    </div>
    <div id="pingButton"></div>
    <div id="msg">press "Send ping"</div>
    <div class="flip" id="fliprollie"></div>
    <div class="flip"
             id="flip"
             onclick="showPrefs()"
             onmouseover="enterflip(event)"
             onmouseout="exitflip(event)"></div>
   </div>
  
   <div id="back">
    <img class="backgroundImage" src="Default_reverse.png" />
    <div id="doneButton"></div>
   </div>
 </body>
</html>

このhtmlファイルでは、以前のものと比較して以下のように異なります。

  • pingを送信する際に必要な情報:
        <div class="trackBackInfo">
          Blog name:<br />
          <input type="text"
                       size="30"
                       maxlength="50"
                       name="blogName"
                       id="blogName" /><br />
          Entry title:<br />
          <input type="text"
                       size="30"
                       maxlength="50"
                       name="entryTitle"
                       id="entryTitle" /><br />
          Entry excerpt:<br />
          <textarea name="entryExcerpt"
                              rows="3"
                              cols="30"
                              id="entryExcerpt" ></textarea><br />
          Entry Permalink URL:<br />
          <input type="text"
                       size="30"
                       maxlength="255"
                       name="entryUrl"
                       id="entryUrl" /><br />
          URL to ping:<br />
          <input type="text"
                       size="30"
                       maxlength="255"
                       name="urlToPing"
                       id="urlToPing" />
        </div>

    単純にhtmlのinputとtextareaを利用しています。JavaScriptで抽出しやすいようにそれぞれid属性をつけています。

  • 送信する際に押すボタンと状況を表示する欄:
        <div id="pingButton"></div>
        <div id="msg">press "Send ping"</div>
    

    送信する際に押すボタンは単なるdiv要素としています。後述するように、Appleが提供するJavaScriptを利用することでボタンとして機能するようになります。

    表示する欄はmsgをid属性としたdiv要素としています。後述するようにJavaScriptにより、状況が表示されるようになります。

  • XHTML1.1として整形し直す:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
     "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
    

    この時点ではそこそこ適切なXHTML文書を目指し、文書の先頭部分を変更しました。文章全体としては適切なものとなっていません。一覧のエントリの最終エントリにおいて、適切なXHTMLを目指します。

  • JavaScriptをWidgetで共通に使えるものとこのWidgetに固有のものと分割する:
      <script type="text/javascript" src="WidgetCommon.js"  />
      <script type="text/javascript" src="PiyoTrackBack.js"  />
    

    将来的に使い回せるように、Widgetで共通して使えそうなものをWidgetCommon.jsにします。このWidget固有のものはPiyoTrackBack.jsに記述するようにします。

  • ボタンを独自作成ではなくて、Apple提供のものを利用する: <script type="text/javascript" src="file:///System/Library/WidgetResources/button/genericButton.js" charset="utf-8" />
      ...
     <body onload="setup();">
      ...
        <div id="doneButton"></div>
    

    Apple提供のボタンを利用するためには、該当のJavaScriptファイルを読み込み、対象のdiv要素をid属性で識別できるようにする必要があります。これまでのclass属性をやめてid属性に変更しています。ボタンの作成処理は、JavaScriptの変更部分で後述される、setupメソッドにおいて実施されます。

ということをしています。なお、元々用意していた背景画像のサイズが小さいので、大きくしたものを用意し、今後はそちらを利用します(いきあたりばったりですね)。

この時点で作成したWidgetを起動すると

のように表示されます。CSSファイルやJavaScriptファイルを修正しない限りまともに表示されません。


2.1. CSSファイルを変更する

本節の修正後のファイル←2.0TOC2.2→

変更されたhtmlファイルがまともに表示されるようにCSSファイルを変更します。

  • pingを送信する際に必要な情報
  • 送信する際に押すボタン
  • 状況を表示する欄
  • doneButtonのdiv要素はApple提供のボタンに変更

これらの更新点に従い、PiyoTrackBack.cssを以下のように変更します。

@charset "UTF-8";

#back {
	display: none;
} 
.trackBackInfo {
  font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
  font-size: 12px;
  font-weight: bold;
  color: white;
  position: absolute;
  top: 15px;
  left: 30px;
}

#msg {
  font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
  font-size: 12px;
  font-weight: bold;
  color: white;
  position: absolute;
  bottom: 40px;
  left: 30px;
  width: 240px;
  border-top: 1px dashed  #fff;
  padding: 5px;
}
.backgroundImage {
	position: absolute;
	top: 0px;
	left: 0px;
}

.flip {
  position:absolute;
  bottom: 35px;
  right: 25px;
  width:13px;
  height:13px;
}

#flip {
  opacity:0;
  background:
   url(file:///System/Library/WidgetResources/ibutton/white_i.png)
   no-repeat top left;
  z-index: 8000;
}

#fliprollie {
  display:none;
  opacity:0.25;
  background:
   url(file:///System/Library/WidgetResources/ibutton/white_rollie.png)
   no-repeat top left;
  z-index:7999;
}
#pingButton {
  position: absolute;
  bottom: 80px;
  right: 30px;
}

#doneButton {
  position: absolute;
  bottom: 40px;
  right: 30px;
}

このCSSファイルでは、以前のものと比較して以下のように異なります。

  • pingを送信する際に必要な情報:
    .trackBackInfo {
      font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
      font-size: 12px;
      font-weight: bold;
      color: white;
      position: absolute;
      top: 15px;
      left: 30px;
    }

    以前のpiyoTrackBackTextに代わって、trackBackInfo のdiv要素を表示するように変更しています。

  • 送信する際に押すボタン:
    #pingButton {
      position: absolute;
      bottom: 80px;
      right: 30px;
    }

    Apple提供のボタンを利用するpingButtonのdiv要素に対して追加しています。表示位置の指定のみしています。

  • 状況を表示する欄:
    #msg {
      font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
      font-size: 12px;
      font-weight: bold;
      color: white;
      position: absolute;
      bottom: 40px;
      left: 30px;
      width: 240px;
      border-top: 1px dashed  #fff;
      padding: 5px;
    }

    状況を表示する欄のdiv要素に対して追加しています。フォントと表示位置、ボーダーラインの設定をしています。

  • doneButtonのdiv要素はApple提供のボタンに変更:
    #doneButton {
      position: absolute;
      bottom: 40px;
      right: 30px;
    }

    Apple提供のボタンを利用するdoneButtonのdiv要素に対して追加しています。表示位置の指定のみしています。以前のclassがdoneButtonのものについては削除しています。

この時点で作成したWidgetを起動すると

のように表示されます。ほぼ表示されていますが、JavaScriptファイルを修正しないため、ボタンが表示されません。


2.2. JavaScriptファイルを変更する

本節の修正後のファイル←2.1TOC2.3→

変更されたhtmlファイルがまともに表示されることと、trackback pingが機能することを中心にJavaScriptファイルを変更します。

具体的には
  • Apple提供のボタンの利用
  • trackbackの実行
  • Widgetに共通で使えるものを別ファイル化
  • debug用関数の作成
これらを考慮します。
  • Widgetに共通で使えるものを別ファイル化

    については、これまで作成したものが実はWidgetに固有のものが一切ないので、これまでのPiyoTrackBack.jsをWidgetCommon.jsに名前を変えるだけで実現できます。

それ以外に対応するために、以下のものをPiyoTrackBack.jsとして作成します。

// PiyoTrackBack.js
//  2005.5
//  by kuki@piyosystems.com

// <
// < general section start
// <

var debugMode = false;

function debug(text) {
  if (debugMode) {
    alert(text);
  }	
}

function setup() {
  createGenericButton(document.getElementById("pingButton"), 
                                            "Send ping",
                                            ping);
  createGenericButton(document.getElementById("doneButton"), 
                                            "Done",
                                            hidePrefs);
}

// >
// > general section end
// >


// <
// < trackback section start
// <

var req = new XMLHttpRequest() ;

function ping() {
  if (req.readyState > 1) {
    req.abort();
  }
  var url = document.getElementById("urlToPing").value;
  req.onreadystatechange = readyStateChangeHandler;
  req.open("POST", url, true);
  req.setRequestHeader("Content-Type",
                       "application/x-www-form-urlencoded; charset=utf-8");
  req.send(createPostData());
}

  
function readyStateChangeHandler() {
  var msg = document.getElementById("msg");
  if (req.readyState == 4) {
    if(req.status == 200) {
      debug(req.responseText);
	  var res = req.responseXML;
	  if ((res == null ) || (isParseError(res))) {
	    msg.innerHTML = "該当サイトからの応答が不正です";
	  } else {
	    var elements = res.getElementsByTagName("error");
		if ((elements == null) || (elements.length != 1)) {
		  msg.innerHTML = "該当サイトからの応答が不正です";
		} else {
          var errorVal = parseInt(elements.item(0).childNodes[0].nodeValue );
          if (errorVal == 0) {
            msg.innerHTML = "OK";
          } else {
		    msg.innerHTML = "NG";
		  }
		}
	  }
    } else {
      msg.innerHTML = "読み込みに失敗しました";
    }
  } else if (req.readyState == 0) {
    msg.innerHTML = "準備中・・(open前)";
  } else if (req.readyState == 1) {
    msg.innerHTML = "準備中・・(send前)";
  } else if (req.readyState == 2) {
    msg.innerHTML = "発行済み・・";
  } else if (req.readyState == 3) {
    msg.innerHTML = "取得中・・";
  } else {
    msg.innerHTML = "状態不明・・";
  }
}

function createPostData() {
  var entryTitle = document.getElementById("entryTitle").value;
  var entryExcerpt = document.getElementById("entryExcerpt").value;
  var entryUrl = document.getElementById("entryUrl").value;
  var blogName = document.getElementById("blogName").value;

  var result = "title=" + entryTitle 
                         + "&excerpt=" + entryExcerpt
                         + "&url=" + entryUrl
                         + "&blog_name=" + blogName;
  debug(result);
  return encodeURI(result);
}

function isParseError (doc) {
  return (doc.parseError!=null && doc.parseError.errorCode!=0) ||
               (doc.documentElement.tagName=="parsererror"
                 && doc.documentElement.namespaceURI==
                        "http://www.mozilla.org/newlayout/xml/parsererror.xml") ||
               (doc.documentElement == null);
}

// >
// > trackback section end
// >

このJavaScriptファイルでは、以前のものと比較して以下のように異なります。

  • Apple提供のボタンの利用:
    function setup() {
      createGenericButton(document.getElementById("pingButton"), 
                                                  "Send ping",
                                                  ping);
      createGenericButton(document.getElementById("doneButton"), 
                                                  "Done",
                                                  hidePrefs);
    }

    Apple提供のcreateGenericButtonメソッドを利用します。引数は順番に

    • ボタンとして取り扱うdiv要素(DOMのElementオブジェクト)
    • ボタンに表示する文字列
    • ボタンが押されたときに呼び出されるメソッド

    という意味です。このsetupメソッドは、PiyoTrackBack.htmlのbodyタグ内にのonloadイベントに対応しているので、PiyoTrackBack.htmlが最初に表示された際に呼び出されます。

  • trackbackの実行:
    // <
    // < trackback section start
    // <
    
    var req = new XMLHttpRequest() ;
    
    function ping() {
      if (req.readyState > 1) {
        req.abort();
      }
      var url = document.getElementById("urlToPing").value;
      req.onreadystatechange = readyStateChangeHandler;
      req.open("POST", url, true);
      req.setRequestHeader("Content-Type",
                           "application/x-www-form-urlencoded; charset=utf-8");
      req.send(createPostData());
    }
    
      
    function readyStateChangeHandler() {
      var msg = document.getElementById("msg");
      if (req.readyState == 4) {
        if(req.status == 200) {
          debug(req.responseText);
    	  var res = req.responseXML;
    	  if ((res == null ) || (isParseError(res))) {
    	    msg.innerHTML = "該当サイトからの応答が不正です";
    	  } else {
    	    var elements = res.getElementsByTagName("error");
    		if ((elements == null) || (elements.length != 1)) {
    		  msg.innerHTML = "該当サイトからの応答が不正です";
    		} else {
              var errorVal = parseInt(elements.item(0).childNodes[0].nodeValue );
              if (errorVal == 0) {
                msg.innerHTML = "OK";
              } else {
    		    msg.innerHTML = "NG";
    		  }
    		}
    	  }
        } else {
          msg.innerHTML = "読み込みに失敗しました";
        }
      } else if (req.readyState == 0) {
        msg.innerHTML = "準備中・・(open前)";
      } else if (req.readyState == 1) {
        msg.innerHTML = "準備中・・(send前)";
      } else if (req.readyState == 2) {
        msg.innerHTML = "発行済み・・";
      } else if (req.readyState == 3) {
        msg.innerHTML = "取得中・・";
      } else {
        msg.innerHTML = "状態不明・・";
      }
    }
    
    function createPostData() {
      var entryTitle = document.getElementById("entryTitle").value;
      var entryExcerpt = document.getElementById("entryExcerpt").value;
      var entryUrl = document.getElementById("entryUrl").value;
      var blogName = document.getElementById("blogName").value;
    
      var result = "title=" + entryTitle 
                             + "&excerpt=" + entryExcerpt
                             + "&url=" + entryUrl
                             + "&blog_name=" + blogName;
      debug(result);
      return encodeURI(result);
    }
    
    function isParseError (doc) {
      return (doc.parseError!=null && doc.parseError.errorCode!=0) ||
                   (doc.documentElement.tagName=="parsererror"
                     && doc.documentElement.namespaceURI==
                            "http://www.mozilla.org/newlayout/xml/parsererror.xml") ||
                   (doc.documentElement == null);
    }
    
    // >
    // > trackback section end
    // >

    trackbackの実行は、XMLHttpRequestオブジェクトにより非同期に行われます。いわゆるAjaxです。A(async)である必要性はありません。(^^;。このオブジェクトについてはいろいろなサイトで説明されています。Appleによる解説も有名です。XMLHttpRequestオブジェクトやAjaxについては、大きな話題であるので、ここでは詳しく説明しません。

    trackback pingの送信はについては、大まかに以下のように実行されます。

    1. XMLHttpRequestオブジェクト(以下XHRと呼ぶ)を作成する。
    2. 「Send ping」ボタンが押されると、pingメソッドが呼び出され、XHRにtrackbackの情報と返答xmlに対する処理メソッド(readyStateChangeHandler)を設定される。
    3. XHRによりHttpRequestが送信される。
    4. trackback先からの返答xmlをreadyStateChangeHandlerメソッドが処理する。
    5. 処理結果はmsgのdiv要素に反映される。

    これより詳細のの解説は特に書きませんので、気になる方はソースをお読みください。たいしたことはやっておりません。

  • debug用関数の作成:
    var debugMode = false;
    
    function debug(text) {
      if (debugMode) {
        alert(text);
      }	
    }

    変数debugModeがtrueのとき、debugメソッドに渡された文字列がalertにより表示されます。デバッグ時にSafari上で実行する際に利用しました。

この時点で作成したWidgetを起動すると

のように表示されます。「Send ping」ボタンを押すと

のように表示され、もうまく処理されません。これはInfo.plistにおいてセキュリティの設定が足りていないためです。


2.3. Info.plistファイルを変更する

本節の修正後のファイル←2.2TOC3.0→

ネットワークのアクセスを許可するようにInfo.plistファイルを変更します。

Info.plistを以下のように変更します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC
 "-//Apple Computer//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDisplayName</key>
	<string>PiyoTrackBack</string>
	<key>CFBundleIdentifier</key>
	<string>com.piyosystems.widget.piyotrackback</string>
	<key>CFBundleName</key>
	<string>PiyoTrackBack</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1.0</string>
	<key>CloseBoxInsetX</key>
	<integer>20</integer>
	<key>CloseBoxInsetY</key>
	<integer>15</integer>
	<key>MainHTML</key>
	<string>PiyoTrackBack.html</string>
	<key>AllowNetworkAccess</key>
	<true/>
</dict>
</plist>

このInfo.plistファイルでは、以前のものと比較して以下のように異なります。

  • ネットワークへのアクセスを許可:
    	<key>AllowNetworkAccess</key>
    	<true/>
    

    AllowNetworkAccessをtrueにすると、ネットワークリソースへのアクセスが可能となります。ユーザには初回実行時に実行してよいか確認するようになります。

この時点で作成したWidgetを起動すると

のように表示されます。「Send ping」ボタンを押すと

のように表示され、正しく動作します。実際、

のようにtrackbackされています。


本エントリは以上です。
次のエントリは設定画面に、登録されたRSSからtrackbackするエントリを選択する機能をつけて、アプリケーションを完成させます。

TOC3.0→

( ・∀・)つ〃∩ ヘェーヘェーヘェー">
投稿時間: 2005年05月15日 (日) at 01:36       
 

←前  |  ↑↑Blog |  ↑Category |  ↑Entry top |  ↑Comment |  次→
←前  |  ↑↑Blog |  ↑Category |  ↑Entry top |  ↑Comment |  次→