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

Dashboard Widgetの作成方法 その3


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


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

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

今回のエントリで、back画面を中心に作成し、アプリケーションとして完成させます。今回のエントリは対象の修正量が多いので重要な点に絞って説明します。


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

本節の修正後のファイル←2.3TOC3.1→

”PiyoTrackBack”という名前で進めてきましたが、”Trackback”にはサーバ側の処理も含まれており、あまりふさわしい名前ではありません。よって "PiyoTrackbackPing” に変更します。そのためには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>PiyoTrackbackPing</string>
	<key>CFBundleIdentifier</key>
	<string>com.piyosystems.widget.piyotrackbackping</string>
	<key>CFBundleName</key>
	<string>PiyoTrackbackPing</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>PiyoTrackbackPing.html</string>
	<key>AllowNetworkAccess</key>
	<true/>
</dict>
</plist>

"PiyoTrackBack" から "PiyoTrackbackPing" に変更しているだけです。関連作業として、htmlファイル名も"PiyoTrackBack.html" から "PiyoTrackbackPing.html" に変更します。

この時点で作成したWidgetはこれまでのものと名前以外特に変わりません。


3.1. アプリケーションとして完成させるためにhtmlを変更する

本節の修正後のファイル←3.0TOC3.2→

アプリケーションとして完成させるためにhtmlを変更します。

これまで作ってきたものに対して、今回のアプリケーションとして不足している機能は

  • back画面にてRSSのURLを設定値として保存し、再度起動されたときに読み込むようにする
  • back画面にてRSSからTrackbackの際に利用できるようにitem情報を抽出、表示する
  • back画面にて抽出したアイテムをfront画面にセットする
  • front画面にて保存したRSSの最新itemを読み込めるようにする
  • 状態に応じて表示するメッセージを変更する
  • ローカライズする

としました。これらを考慮してPiyoTrackbackPing.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>PiyoTrackbackPing</title>
  <style type="text/css">@import "PiyoTrackbackPing.css";</style>
  <script type="text/javascript"
          src="file:///System/Library/WidgetResources/button/genericButton.js"
          charset="utf-8" />
  <script type="text/javascript" src="localizedStrings.js"  />
  <script type="text/javascript" src="WidgetCommon.js"  />
  <script type="text/javascript" src="node.js"  />
  <script type="text/javascript" src="PiyoTrackbackPing.js"  />
 </head>
 <body onload="setup();">
  
   <div id="front"
        onmousemove="mousemove(event)"
        onmouseout="mouseexit(event)">
    <img class="backgroundImage"
         alt="background image"
         src="Default.png" />
    <div class="trackBackInfo">
      <span id="blogNameLabel"></span>:<br />
      <input type="text"
         size="30"
         maxlength="255"
         name="blogName"
         id="blogName"
         value=""
         onchange="checkStatus()"/><br />
      <span id="entryTitleLabel"></span>:<br />
      <input type="text"
             size="30"
             maxlength="50"
             name="entryTitle"
             id="entryTitle"
             value=""
             onchange="checkStatus()" /><br />
      <span id="entryExcerptLabel"></span>:<br />
      <textarea name="entryExcerpt"
                rows="3"
                cols="30"
                id="entryExcerpt"
                onchange="checkStatus()" ></textarea><br />
      <span id="entryUrlLabel"></span>:<br />
      <input type="text"
             size="30"
             maxlength="255"
             name="entryUrl"
             id="entryUrl"
             value=""
             onchange="checkStatus()"/><br />
      <span id="urlToPingLabel"></span>:<br />
      <input type="text"
             size="30"
             maxlength="255"
             name="urlToPing"
             id="urlToPing"
             value=""
             onchange="checkStatus()"/><br />
    </div>
    <div id="getRecentEntryButton"></div>
    <div id="pingButton"></div>
    <div id="msg"></div>
    <div class="flip" id="fliprollie"></div>
    <div class="flip"
         id="flip"
         onclick="showPrefs()"
         onkeypress="showPrefs()"
         onmouseover="enterflip(event)"
         onmouseout="exitflip(event)"></div>
   </div>
  
   <div id="back">
    <img class="backgroundImage"
               alt="background image"
               src="Default_reverse.png" />
    <div class="rssInfo">
     <div class="rssChannel">
      <span id="rssUrlLabel"></span>:<br />
      <input type="text"
             size="28"
             maxlength="255"
             name="rssUrl"
             id="rssUrl"
             value=""
             onchange="changeRssUrl()"   /><br />
      <input type="text"
             size="30"
             maxlength="255"
             name="channelTitle"
             id="channelTitle"
             value="" /><br />
      <div id="loadButton"></div>
     </div>
     <div class="rssItem">
      <select name="itemTitles"
              id="itemTitles"
              onchange="selectItem()" ></select>
      <div id="itemTitle" ></div>
      <input type="text"
             size="28"
             maxlength="255"
             name="itemLink"
             id="itemLink"
             value=""  /><br />
      <textarea name="itemDesc"
                rows="5"
                cols="30"
                id="itemDesc" ></textarea><br />
     </div>
     <div id="rssMsg"></div>
    </div>
    <div id="reflectButton"></div>
    <div id="doneButton"></div>
   </div>
 </body>
</html>

このhtmlファイルでは、以前のものと比較して以下のように異なります。以下の説明では、以降のCSSやJavaScriptで変更内容をふまえて記述しています。

  • back画面にてRSSのURLを設定値として保存し、再度起動されたときに読み込むようにする:
          <input type="text"
                 size="28"
                 maxlength="255"
                 name="rssUrl"
                 id="rssUrl"
                 value=""
                 onchange="changeRssUrl()"/><br />
      :
        <div id="doneButton"></div>
    

    doneButtonのdiv要素に割り当てたボタンを押すことで、rssUrlのinput要素のvalue属性がwidgetの情報として保存されます。再起動された際にはRSSのURLはwidgetの情報から読み込まれ、rssUrlのinput要素のvalue属性に設定されます。

  • back画面にてRSSからTrackbackの際に利用できるようにitem情報を抽出、表示する:
        <div class="rssInfo">
         <div class="rssChannel">
          <span id="rssUrlLabel"></span>:<br />
          <input type="text"
                 size="28"
                 maxlength="255"
                 name="rssUrl"
                 id="rssUrl"
                 value=""
                 onchange="changeRssUrl()"/><br />
          <input type="text"
                 size="30"
                 maxlength="255"
                 name="channelTitle"
                 id="channelTitle"
                 value="" /><br />
          <div id="loadButton"></div>
         </div>
         <div class="rssItem">
          <select name="itemTitles"
                  id="itemTitles"
                  onchange="selectItem()" ></select>
          <div id="itemTitle" ></div>
          <input type="text"
                 size="28"
                 maxlength="255"
                 name="itemLink"
                 id="itemLink"
                 value=""  /><br />
          <textarea name="itemDesc"
                    rows="5"
                    cols="30"
                    id="itemDesc" ></textarea><br />
         </div>
         <div id="rssMsg"></div>
        </div>

    backのdiv要素内にrssInfoのdiv要素を作成し、その中にRSS関連の表示を行うためのdiv要素やinput要素などを配置しています。各要素の大まかな説明は以下の通りです。

    • rssUrlのinput要素に入力された値は、loadButtonのdiv要素に割り当てたボタンを押した際に処理されます。
    • itemTitlesのselect要素は、読み込まれたRSSから取得された各itemのtitleがoption要素として挿入されます。
    • itemTitleのdiv要素は、itemTitlesのselect要素より選択されたitemのtitleがテキストとして挿入されます。
    • itemLinkのinput要素は、itemTitlesのselect要素より選択されたitemのlinkがvalue属性として設定されます。
    • itemDescのtextarea要素は、itemTitlesのselect要素より選択されたitemのdescriptionがvalue属性として設定されます。
    • rssMsgのdiv要素は、状況に応じてシステムからのメッセージがテキストとして挿入されます。

  • back画面にて抽出したアイテムをfront画面にセットする:
        <div id="reflectButton"></div>
    

    reflectButtonのdiv要素に割り当てたボタンを押すことで、rssInfoのdiv要素内の情報がfront画面に渡されます。

  • front画面にて保存したRSSの最新itemを読み込めるようにする:
        <div id="getRecentEntryButton"></div>
    

    getRecentEntryButtonのdiv要素に割り当てたボタンを押すことで、rssUrlにて指定されたURLからRSSを取得して最新のitemをfront画面に渡します。

  • 状態に応じて表示するメッセージを変更する:
                      onchange="checkStatus()" /><br />
        :
        <div id="msg"></div>
        :
        <div id="rssMsg"></div>
    

    msgやrssMsgを状態に応じたメッセージを表示するために使用します。例えば、input要素の値が変わったとき(onchange)などです。

  • ローカライズする:
    <span id="blogNameLabel"></span>

    など。画面に表示されるものはhtmlに直接記載せずにspan要素やdiv要素とし、JavaScriptにより値をローカライズ対応して表示します。

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


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


3.2. アプリケーションとして完成させるためにCSSファイルを変更する

本節の修正後のファイル←3.1TOC3.3→

以下の2点を考慮してCSSファイルを変更します。

  • 更新したhtmlファイルを表示させる
  • 共通のスタイル部分を整理する

整理したため更新部分が多く、を赤くすることにあまり意味がありません。よって、ここでのソースの提示については更新部分を特に赤くしません。

@charset "UTF-8";

/* common */
.trackBackInfo,
#msg,
.rssInfo,
#rssMsg {
 font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
  font-size: 12px;
  font-weight: bold;
  color: white;
}

#back,
#fliprollie,
#getRecentEntryButton,
#channelTitle,
#itemTitle,
#itemLink,
#itemDesc {
  display: none;
}


/* general */
#front {
}

#back {
} 

.backgroundImage {
	position: absolute;
	top: 0px;
	left: 0px;
}


/* front */
.trackBackInfo {
  position: absolute;
  top: 15px;
  left: 30px;
}

.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 {
  opacity:0.25;
  background:
   url(file:///System/Library/WidgetResources/ibutton/white_rollie.png)
   no-repeat top left;
  z-index:7999;
}

#getRecentEntryButton {
  position: absolute;
  bottom: 80px;
  left: 30px;
}

#pingButton {
  position: absolute;
  bottom: 80px;
  right: 30px;
}

#msg {
   position: absolute;
  bottom: 40px;
  left: 30px;
  width: 240px;
  border-top: 1px dashed  #fff;
  padding: 5px;
}


/* back */
.rssInfo {
  position: absolute;
  top: 30px;
  left: 30px;
  width: 270px;
}

#rssUrl {
}

#itemTitles {
}

.rssChannel {
  padding: 5px 5px 10px 5px;
  border-bottom: 1px dashed #fff;
}

#channelTitle {
  clear: both;
}

#loadButton {
  position: absolute;
  top: 18px;
  right: 0px;
}

.rssItem {
  padding: 10px 5px 10px 5px;
  border-bottom: 1px dashed #fff;
  width: 240px;
  height: 120px;
}

#itemTitle {
}

#itemLink {
}

#itemDesc{
  display: none;
}

#reflectButton {
  position: absolute;
  bottom: 40px;
  left: 30px;
}

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

#rssMsg {
  padding-top: 10px
}

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

  • 更新したhtmlファイルを表示させる:

    追加した要素についてスタイルを指定しています。状況によって表示/非表示を切り替えるため初期状態ではdisplayがnoneのものもいくつかあります。説明範囲が全体にわたるので特に該当ソースは明示しません。

  • 共通のスタイル部分を整理する:
    /* common */
    .trackBackInfo,
    #msg,
    .rssInfo,
    #rssMsg {
     font-family: "ヒラギノ角ゴ Pro W3", Osaka, "MS ゴシック", sans-serif;
      font-size: 12px;
      font-weight: bold;
      color: white;
    }
    
    #back,
    #fliprollie,
    #getRecentEntryButton,
    #channelTitle,
    #itemTitle,
    #itemLink,
    #itemDesc {
      display: none;
    }

    表示するフォントと初期状態で非表示のものについて、とりまとめて最初に記述しています。こうすることは後々の更新作業の軽減化と間違えの抑止になります。

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


のように表示されます。back画面の一部の要素が表示されるようになりましたが、JaScriptファイルによるローカライズ対応が実施されていないため文字列がまともに表示されません。


3.3. アプリケーションとして完成させるためにJavaScriptファイルを変更する

本節の修正後のファイル←3.2TOC

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

具体的にはhtmlの考慮ポイント
  • back画面にてRSSのURLを設定値として保存し、再度起動されたときに読み込むようにする
  • back画面にてRSSからTrackbackの際に利用できるようにitem情報を抽出、表示する
  • back画面にて抽出したアイテムをfront画面にセットする
  • front画面にて保存したRSSの最新itemを読み込めるようにする
  • 状態に応じて表示するメッセージを変更する
  • ローカライズする
についての対応となります。これらに対応するために、まずJavaScriptファイルを以下のように整理します。
  • WidgetCommon.js: widget関連で共通に使える操作を定義する
  • PiyoTrackbackPing.js: PiyoTrackbackPing固有の操作を定義する
  • ja.lproj/localizedStrings.js: 日本語へのローカライズのための配列を定義する
  • node.js: html(xhtml)内のdvi要素に対する操作を定義する

back画面にてRSSのURLを設定値として保存し、再度起動されたときに読み込むようにする

アップルより提供されているメソッドを利用してWidget個別の設定を保存することができます。

  • 保存:widget.setPreferenceForKey(value, key)
  • 読み込み:widget.preferenceForKey(key)
実際には以下のように利用しています。
保存

@PiyoTrackbackPing.js

function donePrefs() {
  savePrefs();
  hidePrefs();
}
:
function savePrefs() {
  if (window.widget) {
    var rssUrl = getRssUrlNode().value;
    widget.setPreferenceForKey(rssUrl, "rssUrl");	
  }
}

保存は

  1. doneButton/reflectButtonが押された際に呼ばれるdonePrefs()からsavePrefs()が実行される
  2. savePrefs()内にて、widget.setPreferenceForKey(rssUrl, "rssUrl");が実行される。

という流れで実施されます。

読み込み

@PiyoTrackbackPing.js

function setup() {
  setupLabels();
  setupButtons();
  setupRss();
  checkStatus();
}
:
function setupRss() {
  if (window.widget) {
    var savedRssUrl = widget.preferenceForKey("rssUrl");
    if (savedRssUrl && savedRssUrl.length > 0) {
      setRssUrl(savedRssUrl);
      loadRss();
    }
  }
}

読み込みは

  1. body要素の読み込みの際に呼ばれるsetup()からsetupRss()が実行される
  2. setupRss()内にて、widget.preferenceForKey("rssUrl");が実行される。
  3. 既に保存されていればsetRssUrl()により、その値がrssUrlのinput要素のvalue属性にセットされる。

という流れで実施されます。

back画面にてRSSからTrackbackの際に利用できるようにitem情報を抽出、表示する

上記処理は

  • RSSの取得
  • RSSのchannelの表示
  • RSSのitemの表示

に分けることができます。これらの処理については、Widget作成に関する説明という本筋からは外れるので詳しく説明しません。

RSSの取得

@PiyoTrackbackPing.js

var rss;

function loadRss() {
  var url = getRssUrlNode().value;
  if (!isNullString(url)) {
    if (req.readyState > 1) {
      req.abort();
    }
    req.onreadystatechange = readyStateChangeHandlerForRss;
    req.open("GET", url, false);
    req.send(null);
  } else {
    setRssMsg("Please input your RSS URL.");
  }
}

function readyStateChangeHandlerForRss() {
  if (req.readyState == 4) {
    if((req.status == 200) || (req.status == undefined)) {
      debug(req.responseText);
	  var res = req.responseXML;
	  if ((res == null ) || (isParseError(res))) {
	    setRssMsg("Response from the corresponding site is not appropriate.");
	  } else  {
         if (req.status == undefined) {
           setRssMsg("req.status is undefined.");
         }
         putItemTitles(res);
		 selectItem();
	  }
    } else {
      setInnerTextById("rssMsg",
                       getLocalizedString("Loading failed.") 
                         + "(" + req.status + ")",
                       false);
    }
  } else if (req.readyState == 0) {
    setRssMsg("Uninitialized");
  } else if (req.readyState == 1) {
    setRssMsg("Loading");
  } else if (req.readyState == 2) {
    setRssMsg("Loaded");
  } else if (req.readyState == 3) {
    setRssMsg("Interactive ...");
  } else {
    setRssMsg("Unknown status");
  }
}

前回のエントリのtrackback送信とほぼ同じです。XMLHttpRequestオブジェクトを利用しています。

RSSのchannelの表示

@PiyoTrackbackPing.js

function putItemTitles(res) {
  rss = res;

  var channelTitle = getChannelTitleNode();
  var channel = getFirstElementByTagNameOfRss(rss, "channel");
  var channelTitleNode = getFirstElementByTagNameOfRss(channel, "title");
  if (channelTitleNode == null) {
    channelTitle.style.display = "none";
  } else {
    debug(getInnerText(channelTitleNode));
    channelTitle.value = getInnerText(channelTitleNode);
    channelTitle.value = trim(deleteCRLF(channelTitle.value));
    channelTitle.style.display = "inline";
  }

  var itemLink = getItemLinkNode();
  var itemDesc = getItemDescNode();
  itemLink.style.display = "none";
  itemDesc.style.display = "none";

  var root = getItemTitlesNode();
  var result = "";

  var items = getElementsByTagNameOfRss(rss, "item");
  var itemNum = items.length;
  
  for(var i=0; i < itemNum ; i++) {
    var itemTitle = getElementsByTagNameOfRss(items[i], "title");

    if(itemTitle.length==1) {
      var itemTitleText = itemTitle.item(0).childNodes[0].nodeValue;
	  if (itemTitleText.length > 15) {
	    itemTitleText = itemTitleText.substring(0, 15) + "...";
	  }
      result = result + "<option value='" + i + "'>"
      result = result +  itemTitleText + "</option>?n"
    }
  }

  root.innerHTML = result;
  if (result != "") {
    setRssMsg("Loading the RSS file succeeded.");
  }
}

function getElementsByTagNameOfRss(rootNode, tagName) {
  var results = rootNode.getElementsByTagName(tagName);
  if ((results == null) || (results.length == 0)){
    // rss1.0
    results = 
	rootNode.getElementsByTagNameNS("http://purl.org/rss/1.0/", tagName);
  }
  return results;
}

function getFirstElementByTagNameOfRss(rootNode, tagName) {
  var nodes = getElementsByTagNameOfRss(rootNode, tagName);
  if ((nodes == null) || (nodes.length == 0)) {
    return null;
  } else {
    return nodes.item(0);
  }

RSSのchannelの表示は取得したRSSから、channel要素を取得し表示(select要素内にoption要素を作成)することになります。注意することは、RSS1.0はnamespaceを利用しているため、単純にtitle要素を取得できないことくらいです。今回は

  • getElementsByTagNameOfRss(): RSSからタグ名による要素の取得をします。複数の要素を返します。RSS1.0の場合はnamespaceを利用した取得となります。
  • getFirstElementByTagNameOfRss(): RSSからタグ名による要素の取得をします。最初の要素のみを返します。RSS1.0の場合はnamespaceを利用した取得となります。

というメソッドを作成して利用しています。

RSSのitemの表示

@PiyoTrackbackPing.js

function selectItem() {
  var itemTitles = getItemTitlesNode();
  var item = getElementsByTagNameOfRss(rss, "item").item(itemTitles.selectedIndex);
  var itemTitleNode = getFirstElementByTagNameOfRss(item, "title");
  var itemLinkNode = getFirstElementByTagNameOfRss(item, "link");
  var itemDescNode = getFirstElementByTagNameOfRss(item, "description");

  setItemTitle(trim(deleteCRLF(getInnerText(itemTitleNode))));
  setItemLink(trim(deleteCRLF(getInnerText(itemLinkNode))));
  setInnerTextWithoutTag(document, "itemDesc",  itemDescNode);
  setItemDesc(trim(deleteCRLF(getItemDescNode().value)));
  debug(getItemDescNode().value);

  var itemLink = getItemLinkNode();
  var itemDesc = getItemDescNode();  
  if (itemLinkNode == null) {
    itemLink.style.display = "none";
  } else {
    itemLink.style.display = "inline";
  }
  if (itemDescNode == null) {
    itemDesc.style.display = "none";
  } else {
    itemDesc.style.display = "inline";
  }
  checkStatus();
}

RSSのitemの表示は、itemTitlesのselect要素の値が変更になった場合に呼び出されるselectItem()内で実現されます。ここでの値の取得の際もRSS1.0に注意する必要があります。

back画面にて抽出したアイテムをfront画面にセットする

@node.js

function setBlogName(val) {
  setValueById("blogName", val, false);
}

:
;

function getChannelTitleNode() {
  return document.getElementById("channelTitle");
}

@PiyoTrackbackPing.js

function setupButtons() {
  createGenericButton(document.getElementById("getRecentEntryButton"), 
                                            getLocalizedString("Recent entry"),
                                            getRecentEntry);
  createGenericButton(document.getElementById("pingButton"), 
                                            getLocalizedString("Send ping"),
                                            ping);
  createGenericButton(document.getElementById("loadButton"), 
                                            getLocalizedString("Load"),
                                            loadRss);
  createGenericButton(document.getElementById("reflectButton"), 
                                            getLocalizedString("Reflect to front"),
                                            reflect);
  createGenericButton(document.getElementById("doneButton"), 
                                            getLocalizedString("Done"),
                                            donePrefs);
}

function reflect() {
  savePrefs();
  setFrontValue();
  hidePrefs();
}


function setFrontValue() {
  setBlogName(getChannelTitleNode().value);
  setEntryTitle(getItemTitleNode().value);
  setEntryUrl(getItemLinkNode().value);
  setEntryExcerpt(getItemDescNode().value);
}

主要なdiv要素やinput要素を処理するためにnode.jsを作成します。node.jsには該当要素を処理するメソッドを定義します。ここのようなものを作成しておくと、back画面にて抽出したアイテムをfront画面にセットする処理は、上記のsetFrontValue()のようになり、非常に簡単に記述できます。

front画面にて保存したRSSの最新itemを読み込めるようにする

@PiyoTrackbackPing.js

function setupButtons() {
  createGenericButton(document.getElementById("getRecentEntryButton"), 
                                            getLocalizedString("Recent entry"),
                                            getRecentEntry);
  createGenericButton(document.getElementById("pingButton"), 
                                            getLocalizedString("Send ping"),
                                            ping);
  createGenericButton(document.getElementById("loadButton"), 
                                            getLocalizedString("Load"),
                                            loadRss);
  createGenericButton(document.getElementById("reflectButton"), 
                                            getLocalizedString("Reflect to front"),
                                            reflect);
  createGenericButton(document.getElementById("doneButton"), 
                                            getLocalizedString("Done"),
                                            donePrefs);
}

function getRecentEntry() {
  setupRss();
  if (!isNullString(getItemTitleNode().value)) {
    setFrontValue();
  } else if (!isNullString(getRssUrlNode().value)) {
    setMsg("Loading failed.");
  } else {
    setMsg("The configuration of RSS is necessary.");
  }
}

function setupRss() {
  if (window.widget) {
    var savedRssUrl = widget.preferenceForKey("rssUrl");
	if (savedRssUrl && savedRssUrl.length > 0) {
      setRssUrl(savedRssUrl);
      loadRss();
	}
  }
}

function setFrontValue() {
  setBlogName(getChannelTitleNode().value);
  setEntryTitle(getItemTitleNode().value);
  setEntryUrl(getItemLinkNode().value);
  setEntryExcerpt(getItemDescNode().value);
}

ボタンが押されると、RSSファイルを取得し直してから、front画面へ情報を渡す処理が実行されます。

状態に応じて表示するメッセージを変更する

@node.js

function setMsg(val) {
  setInnerTextById("msg", val, true);
}

@WidgetCommon.js

function isNullString(target) {
  return ((target == null) || (target.length == 0));
}

@PiyoTrackbackPing.js

function checkStatus() {
  didSend = false;

  // front
  if ((isNullString(getEntryUrlNode().value)) ||
       (isNullString(getUrlToPingNode().value))) {
    setMsg("Please input necessary item.");
  } else {
    setMsg("Sending trackback ping is possible.");
  }
  
  // back
  var recentButton = document.getElementById("getRecentEntryButton");
  
  if (isNullString(getRssUrlNode().value)) {
    setRssMsg("Please input your RSS URL.");
    recentButton.style.display = "none";
  } else if (isNullString(getItemTitleNode().value)) {
    setRssMsg("Please push  the 'Load' button.");
    recentButton.style.display = "none";
  } else {
    setRssMsg("Value can be reflected to the front panel.");
    recentButton.style.display = "block";
  }
}

各要素の状態を確認して、msgのdiv要素にテキストを挿入します。状態によっては、ボタンの表示/非表示も設定されます。

ローカライズする

Widgetにおけるローカライズ

  • language project directory(.lprojの拡張子のフォルダ。以降LPDと呼ぶ)を作成する
  • LPDはそのWidgetが対応する言語ごとに用意する。
  • LPDは対応する言語により名前が確定する。例えば以下の通り。
    • 日本語: ja.lproj
    • 英語: en.lproj
    • フランス語: fr.lproj
  • LPD内には、
    • InfoPlist.stringsファイル
    • CSSファイル
    • JavaScriptファイル
    • 画像ファイル
    などが配置できる。
  • Dashboardは、htmlファイルにおいて@importやsrc属性によるファイル検索の際に、Widget用フォルダの直下よりも先にLPD内を調べるので、ローカライズしたいファイルをLPD内に配置する。
  • 表示する文字列をローカライズする一般的な方法は次の通りです。
    • 英語の表示文字列をkeyとし、ローカライズされた表示文字列を値として、連想配列を作成し、LPD内に配置する。
    • 上記連想配列を利用し、ローカライズ化された文字列を返すメソッドを作成する。その際、連想配列に存在しない場合は、そのkeyをそのまま返すような実装とする。
    • 直接文字列を表示しないで、作成したメソッドを利用して文字列を表示するようにする。

具体的には以下のようになります。

@WidgetCommon.js

function getLocalizedString(key) {
  try {
    var result = localizedStrings[key];
    if (result == undefined) {
      result = key;
    }
    return result;	
  } catch(err) {
    // nothing to do
    return "err";
  }
  return key;
}

function setInnerTextById(id, val, isLocalized) {
  var node = document.getElementById(id);
  setInnerText(node, val, isLocalized);
}

function setInnerText(node, val, isLocalized) {
  if (isLocalized) {
    node.innerText = getLocalizedString(val);
  } else {
    node.innerText = val;
  }
}

@node.js

function setMsg(val) {
  setInnerTextById("msg", val, true);
}

@PiyoTrackbackPing.js

function getRecentEntry() {
  setupRss();
  if (!isNullString(getItemTitleNode().value)) {
    setFrontValue();
  } else if (!isNullString(getRssUrlNode().value)) {
    setMsg("Loading failed.");
  } else {
    setMsg("The configuration of RSS is necessary.");
  }
}

@ja.lproj/localizedStrings.js.js

var localizedStrings = new Array;
:
localizedStrings["Please input necessary item."] = "必要事項を入力してください";
:
localizedStrings["Loading failed."] = "読み込みに失敗しました";

連想配列は、ja.lproj/localizedStrings.js.js において作成しています。WidgetCommon.js にて定義された getLocalizedString() がローカライズされた文字列を返すメソッドです。連想配列に存在しない場合は、与えられたkeyを返り値としています。

InfoPlist.stringsファイル

このファイルはInfo.plistのローカライズ版です。例えばWidgetの名前などに適用できます。

/* Localized versions of Info.plist keys */

CFBundleDisplayName = "PiyoTrackbackPing";

ここで、

CFBundleDisplayName = "ぴよトラックバックピン";
とすればアプリケーション名は"ぴよトラックバックピン"と日本語化されます。

以上でJavaScriptの説明は終了です。

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


のように表示されます。


以上、最後の方は駆け足になってしまいましたが、一通りの説明は完了しましたので、一区切りとします。すでにデザイン面や機能面で改良したい点が出てきていますので改良後は追加のエントリを作成します。Dashboard作成の参考になれば幸いです。

TOC

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

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