2009年5月

最新8件を表示します。このリソース群の時系列順リストタイトルリスト、またこのリソースのAtom表現RSS1.0表現も参照できます。

Yahoo Widgetで発車時刻表ウィジェットを作ってみた

最近まわりでYahoo!ウィジェットで何か作るのがブームなので、私もそれに乗って作ってみた。id:n_shuyoid:BigFatCatid:ksmemoの各氏によるメモを見ればだいたい分かる感じ。関係ないけどはてなダイアリーってこういう検索結果を日時の昇順で見ることはできないのだろうか。

XMLでUI仕様を書いて、動作をJavaScriptで書いていく。デバッガがついているので、まあそれで試行錯誤していけばいいのかも。JavaScriptは仕様と違う動作が行われるとかはないけど、WindowsオブジェクトがなくてsetTimeoutとかができないので、自動に表示が変わるみたいなのをするには一手間必要。あとリファレンスが大量すぎて読み通す気力が起きなかったりチュートリアルと違うことが書いてあったりするけど、そこはがんばる。

で、せっかくのWidgetで、やっぱWidgetといえば画面常駐がキモで、ブラウザを開いてみればいいようなものはなるべく避けたい。あとインターネットとやりとりできるのを活かしたい。たとえば、書いた文書が(明示的にEnterなどを押さなくても)そのままインターネットに大公開されるようなテキストエディタだかWikiクライアントだかわからないようなものなどはぜひこのYahoo Widgetで作るべきだろう。しかし、今回は諸般の事情でPOSTできるようなものは歓迎されないので、GETに徹したものを作ることにする。

ということで、goo時刻表をスクレイピングして現在の発車時刻表をそれっぽく表示するウィジェット (拡張子を.widgetに変えてウィジェットエンジンにつっこめば動くはず) を作った。画面イメージは以下のような感じ:

駅名を入力して路線と方面を選ぶと、右側に現時点での発車時刻が表示される。表示は1分毎に自動的にリロードされる。できれば運行情報が下のほうでスクロールしていたりパタパタみたいなエフェクトをかけたかったけどそこまでできなかった。あと、左ペインがマウスホイールでスクロールしてほしかったんだけど、なぜかそれもできなかった。将来的にこれのiPhone版も作りたい。

ソース

traindeps.kon

<?xml version="1.0" encoding="UTF-8"?>
<widget minimumVersion="4.0">
    <settings>
        <setting name="debug" value="off" />
    </settings>
    <window name="main" width="180" height="30" style="background:#333;">
        <title>時刻表表示機</title>
        <text name="label" color="#fff" alignment="left" style="font-size:12px">
            <hOffset>10</hOffset>
            <vOffset>20</vOffset>
            <data>駅名</data>
        </text>
        <textarea name="data" columns="14" lines="1" style="background:#fff;color:#999">
            <hOffset>40</hOffset>
            <vOffset>7</vOffset>
            <data>駅名を入力してください</data>
        </textarea>
        <frame name="mframe" width="150" height="115" vLineSize="10" vScrollBar="mainv">
            <hOffset>7</hOffset>
            <vOffset>30</vOffset>
            <vScrollBar>mainv</vScrollBar>
        </frame>
        <scrollbar name="mainv" height="115" width="15" autoHide="true" thumbcolor="#f00">
            <hOffset>160</hOffset>
            <vOffset>30</vOffset>
        </scrollbar>
        <text name="disptime" color="#fff">
            <hOffset>200</hOffset>
            <vOffset>20</vOffset>
            <data></data>
        </text>
        <text name="time" color="#fff" alignment="left" style="font-size:12px">
            <hOffset>200</hOffset>
            <vOffset>45</vOffset>
            <data>発車</data>
        </text>
        <text name="type" color="#fff" alignment="left" style="font-size:12px">
            <hOffset>240</hOffset>
            <vOffset>45</vOffset>
            <data>種別</data>
        </text>
        <text name="deps" color="#fff" alignment="left" style="font-size:12px">
            <hOffset>300</hOffset>
            <vOffset>45</vOffset>
            <data>行先</data>
        </text>
        <frame name="tframe" width="300" height="120">
            <hOffset>200</hOffset>
            <vOffset>55</vOffset>
            <text name="status" color="#fc0">
                <hOffset>0</hOffset>
                <vOffset>15</vOffset>
                <data></data>
            </text>
        </frame>
        
        <script src="traindeps.js" charset="UTF-8" />
    </window>
</widget>

traindeps.js

hogeという名前の関数オブジェクトがあったりしてひどい。

(function() {
items = {};
deps = {};
types = {};
prev = "";
done = false;

var reset = function() {
    items = {};
    deps = {};
    types = {};
    prev = "";
    done = false;
    main.width = 180;
}
var display = function(count) {
    if(done) {
        main.width = 450;
        
        var now = new Date();
        var hour = now.getHours() < 3 ? now.getHours() + 24 : now.getHours();
        var minute = now.getMinutes();
        var current = (hour < 10 ? "0"+hour : hour) + "" + (minute < 10 ? "0"+minute : minute);
        if(current == prev) return;
        
        disptime.data = hour+"時"+minute+"分現在";
        if(tframe.hasChildNodes()) {
            var children = tframe.subviews;
            for(i=0; i<children.length; i++) {
                tframe.removeChild(children[i]);
            }
        }
        var i = 0;
        var v = 15;
        var tomorrow = false;
        for(var j in items) {
            
            if(j < current && !tomorrow){
                delete items[j];
            } else {
                tomorrow = true;
                var line_time = new Text();
                line_time.hOffset = 0;
                line_time.vOffset = v;
                line_time.color = "#ffcc00";
                
                line_time.data = j.substr(0,2)+":"+j.substr(2,2);
                tframe.addSubview(line_time);
                
                var type = "普通";
                if(items[j].type in types) type = types[items[j].type];
                var line_type = new Text();
                line_type.hOffset = 40;
                line_type.vOffset = v;
                line_type.data = type;
                
                if(type.indexOf('特') > -1 || type.indexOf("ラ") > -1)
                    line_type.color = "#ff9999";
                else if(type.indexOf("準") > -1)
                    line_type.color = "#ccff99";
                else if(type.indexOf("区") > -1)
                    line_type.color = "#99ff99";
                else if(type.indexOf("勤") > -1)
                    line_type.color = "#99ffff";
                else if(type.indexOf("新") > -1)
                    line_type.color = "#ffcccc";
                else if(type.indexOf("快") > -1)
                    line_type.color = "#cc99ff";
                else if(type.indexOf("急") > -1)
                    line_type.color = "#9999ff";
                else line_type.color = "#ffcc00";
                
                tframe.addSubview(line_type);
                
                var line_deps = new Text();
                line_deps.hOffset = 100;
                line_deps.vOffset = v;
                line_deps.data = (deps[items[j].dep] || items[j].dep);
                line_deps.color = "#ffcc00";
                tframe.addSubview(line_deps);
                
                var line = j + " " + (deps[items[j].dep] || items[j].dep);
                if(items[j].type in types)
                    line += (" " + types[items[j].type]);
                //print(line);
                i++;
                v += 15;
            }
            if(i == count) break;
        }
        if(v == 15) {
            var status = new Text();
            status.color = "#ffcc00";
            status.data = "本日の運行は終了しました。";
            status.hOffset = 0;
            status.vOffset = v;
            tframe.addSubview(status);
        }
        prev = current;
    }
}
var setTimeout = function(func, delay) {
    var timer = new Timer();
    timer.interval = delay / 1000;
    timer.onTimerFired = function() {
        if((typeof func) == "function")
            func();
    else eval(func);
    }
    timer.ticking = true;
    return timer;
}

var timer3 = setTimeout("display(5)", 1000);

data.onGainFocus = function() {
    this.data = "";
    this.style.color = "#000";
};

var clearInterval = function(timer) {
    timer.ticking = false;
}
var getTimetable = function(path) {
    reset();
    var url = "http://transit.goo.ne.jp" + path;
    
    if(!done) {
        var req = new XMLHttpRequest();
        main.width = 450;
        
        if(tframe.hasChildNodes()) {
            var children = tframe.subviews;
            for(i=0; i<children.length; i++) {
                tframe.removeChild(children[i]);
            }
        }
        
        status.data = "データ取得中";
        status.color = "#ffcc00";
        tframe.addSubview(status);
        disptime.data = "  時  分現在";
        
        req.onreadystatechange = function() {
            if(this.readyState == 4) {
                var text = req.responseText;
                var hour;
                var tags = text.match(/<li><font[\s\S]+?<\/li>|<th>\s*\d+\s*<\/th>|<div class="fs12">[\s\S]+?<\/div>/g);
                if(tags) {
                    for(var i in tags) {
                        var each = tags[i].replace(/[ \t\n\r]+/g, "");
                        switch(each[1]) {
                        case 't' :
                            hour = each.match(/<th>\s*(\d+)\s*<\/th>/)[1];
                            hour = hour < 3 ? parseInt(hour) + 24 : hour;
                            //print(hour);
                            if(hour.length == 1) hour = "0"+hour;
                            break;
                        case 'l' :
                            var m = each.match(/<li><font(color="(.+?)")?><span>(.+?)<\/span><\/font><br><font.*>(.+?)<\/font><\/li>/);
                            if(m[4].length == 1) m[4] = "0"+m[4];
                            items[hour+m[4]] = {dep: m[3].replace(/<.+?>/,''), type: m[2]};
                            break;
                        case 'd' :
                            var pair;
                            while((pair = (/<fontcolor="(.+?)">(.+?)<\/font>/g).exec(each)) != null) {
                                types[pair[1]] = pair[2];
                            }
                            while((pair = (/(\S+?)=(.+?)行( |[\r\n]|<)/g).exec(each)) != null) {
                                deps[pair[1]] = pair[2];
                            }
                        }
                    }
                    done = true;
                    display(5);
                } else {
                    status.data = "指定した時刻表データがありませんでした。";
                }
            }
        }
        req.open("GET", url, true);
        req.send();
    }
}
var hoge = function(e, path, i) {
    e.onClick = function() {
        getTimetable(path);
        if(mframe.hasChildNodes()) {
            var children = mframe.subviews;
            for(i=0; i<children.length; i++) {
                if(children[i].hOffset == 15)
                    children[i].color = "#CCCCFF";
            }
            e.color = "#FFCCCC";
        }
    }
}
var searchStation = function() {
    reset();
    main.height = 30;
    if(mframe.hasChildNodes()) {
        var children = mframe.subviews;
        for(i=0; i<children.length; i++) {
            mframe.removeChild(children[i]);
        }
    }
    var url = "http://transit.goo.ne.jp/tconfirm.php?an=7&st_name=" + data.data;
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if(this.readyState == 4) {
            var text = req.responseText;
            
            var stations = text.match(/<dt><a href="\/timetable\/.+?">.+?<\/a><\/dt>|<span><a href="\/timetable\/.+?">\[&nbsp;.+方面&nbsp;\]<\/a><\/span>/g);
            
            var v = 0;
            for(i = 0; i < stations.length; i++) {
                station = stations[i];
                var line = new Text();
                line.id = "cand"+i;
                
                if(/^<dt>/.test(station)) {
                    line.hOffset = 2;
                    line.color = "#CCCCCC";
                } else {
                    line.hOffset = 15;
                    line.color = "#CCCCFF";
                    var anchor = station.match(/^.+?"(.+?)".+$/)[1];
                    
                    hoge(line, anchor, i);
                }
                line.vOffset = v = (v + 15);
                line.data = stations[i].replace(/<.+?>|[\[\]&nbsp;]/g,'');
                mframe.addSubview(line);
            }
            main.height = 150;
        }
    };
    req.open("GET", url, true);
    req.send();
};

label.onClick = searchStation;
data.onKeyPress = function() {
    if(system.event.keyString == "Return" || system.event.keyString == "Enter") {
        searchStation();
        //getTimetable("");
    }
}
})();
タグ

フォーカスを当てるとデフォルト文字列が消えるinput text (jQuery編)

いろんな文献を気軽に参照しにくい環境になり、いちいち検索しなくてもいいようにここにまとめておきたい感じになったのでしばらくそんな普段書かないようなPOSTばかりします。

まず、最近良くある、デフォルトの状態では入力例を表示して、フォーカスを合わせると消えるというインプットボックスの実装例について。このブログのコメント入力フォームがまさにそんな感じ。

HTML:
<form action="hoge.php" method="get">
<p><input type="text" name="q" id="q" size="30" value="検索語を入力してください"><input type="submit" value="検索"></p>
</form>
JavaScript(jQuery):
$(function(){
    $("#q").focus(function() {
        if($(this).val() == $(this).attr('defaultValue'))
            $(this).val('');
    }).blur(function() {
        if(jQuery.trim($(this).val()) == "") {
            $(this).val($(this).attr('defaultValue'));
        }
    });
});

$(this) を3度実行するのは勿体無いので最初に var $this = $(this) して $this を使い回すとか。/ もしくは if ( this.value == this.defaultValue ) this.value = '' でもよさそう。

確かに、このthisはわざわざjQueryオブジェクトにしなくても、そのままDOMのプロパティを参照すれば用が足りますね。このネタそのものが、単体だとわざわざjQueryを使うまでもないレベルですけど。

実例:

フォーカスがあたったときにvalue属性の値がdefaultvalueだったら消して、逆にフォーカスが外れたときに値が空文字列だったらdefaultvalueを表示するというだけ。このdefaultvalueという属性、どうやら仕様書のいうinitial valueを取得するためにW3C DOMのHTMLInputElementで定義されているもので、ちゃんとW3C DOMを実装しているブラウザならちゃんと使えそう。

デフォルト文字を薄くして実際に入力させるときは黒にしたいというときは、あらかじめCSSで#q{color: #999;}などとした上で、

$(function(){
    $("#q").focus(function() {
        if($(this).val() == $(this).attr('defaultValue'))
            $(this).css('color', '#000').val('');
    }).blur(function() {
        if(jQuery.trim($(this).val()) == "") {
            $(this).css('color', '#999').val($(this).attr('defaultValue'));
        }
    });
});

みたいにしたらいいと思う。

あと、Eventを指定するときに、clickとかkeypressみたいなデバイスに依存するものはなるべく避けてfocusやblurを使うべきだってばっちゃが言ってた。

タグ

txqzのURI設計

とくにRFC3986とかで明示されているわけではないけど、Apacheとかを使っていると、http://example.com/hoge/のようなスラッシュで終わるURIはexample.comホストの中にあるhogeフォルダを指していると解釈するのが自然だ。もしDirectoryIndexで指定されたファイルが存在せずIndexesオプションが有効になっていれば、hogeフォルダ以下にあるファイルの一覧が表示される。hogeがフォルダである場合にhttp://example.com/hogeとスラッシュを省略すると、http://example.com/hoge/にリダイレクトされる。

現在のtxqzはブログの記事をデータベースから動的生成して一定期間キャッシュするような設計になっていて、たとえばこのエントリのURIはhttp://txqz.net/blog/2009/05/12/2331だけど、実際にドキュメントルートにblogというフォルダがあってその中に2009というフォルダがあって05というフォルダがあって12というフォルダがあって2331というファイルが置いてあるというわけではない。それでも、なるべく実際にそういう構造になっているかのように見せかけている。たとえば:

http://txqz.net/blog/2009/05/12/2331にアクセス
2009年5月12日23時31分の記事(これ)が表示される
http://txqz.net/blog/2009/05/12/にアクセス
2009年5月12日の記事インデックスが表示される(/2009/05/12/フォルダへのアクセスと捉え、Index of的な表示を行う)
http://txqz.net/blog/2009/05/12にアクセス
2009年5月12日の記事が新しい順に表示される
http://txqz.net/blog/2009/05/にアクセス
2009年5月の記事インデックスが表示される
http://txqz.net/blog/2009/05にアクセス
2009年5月の記事が新しい順に表示される
http://txqz.net/blog/2009/にアクセス
2009年の記事インデックスが表示される (実際は、年単位だと記事数が多くなるので月別の代表的な記事を何件か表示する。特にその月に多くタグ付けされたタグがついている記事を代表的な記事としている。)
http://txqz.net/blog/2009にアクセス
2009年の記事が新しい順に表示される
http://txqz.net/blog/にアクセス
記事インデックスが表示される(全記事の一覧を挙げても多すぎるので、年別の代表的な記事を何件か表示する。選び方は月別と同様。)
http://txqz.net/blogにアクセス
記事が新しい順に表示される

また、古い順に読みたいという需要もあるため、http://txqz.net/blog/2009?1などのように後ろに"?ページ番号"をつけてアクセスするとその範囲の記事が古い順に並べられた部分リストが見られるようになっている。これはhttp://txqz.net/blog/2009/05?1でもhttp://txqz.net/blog/2009/05/12?1でも同様の機能となるが、最近は1日はおろか月に15件も記事を書かないのであまり意味がない。たんに降順だけでなく昇順でも読めますというだけ。ただ、特定の内容について何回かに分けて記事にしてたりすると降順だと読みにくくなったりするので、ぜひ他のブログにも似たような機能をつけて欲しいところ。もちろんMicroformatsには可能な限り対応し、Autopagerizeなどの便利なツールの恩恵に最大限預かれるようにしている。

以上の議論は記事データベースの中からどの範囲の記事リストをどの順で表示するかというものだが、そこから選ばれた記事リストをどういう表現であらわすかという問題もあり、txqzの場合は拡張子をつけることで対処している。http://txqz.net/blog/2009/05/12/2331と拡張子をつけずにアクセスした場合、ユーザーエージェントがtext/htmlをくれと言えばHTML4.01でマークアップしたものをtext/htmlとして返すし、application/xhtml+xmlをくれと言えばXHTML1.1でマークアップしたものをapplication/xhtml+xmlで返す。.htmlという拡張子をつければHTML4.01、.xhtmlならXHTML1.1、.rdfならRSS1.0、.atomならAtom、.jsonならJSONを返す。このブログのフィードがhttp://txqz.net/blog.rdfないしhttp://txqz.net/blog.atomで提供されているのは単にこの仕様に従っただけで、最新の記事というリソースがあって、それをHTMLでマークアップすれば人間向きの表現になり、RDFやAtomでマークアップすれば機械向けの表現になる。リソースとしての意味合いはまったく等しく、単に表現が違うだけなのだから拡張子で違いを表すのが自然だ。

ほかのブログサービスがどうなっているかというと、たとえばはてなダイアリーだと、http://d.hatena.ne.jp/denken/のフィードはhttp://d.hatena.ne.jp/denken/rssで提供されている。完全に後出しじゃんけんなんだけど、ファイル名がrssとあってもなんのRSSか分からないわけで、もし最新の記事のRSSなのだったら、人間向けに/latest.html、機械向けに/latest.rssみたいな感じで提供されていればより分かりやすいURIになりますね。そんなのを追求することがビジネス的にどれだけ意味があるか知りませんが。

ただまあ、こういう内側の仕様や外側のマークアップやUIを考えることばかりに興味がいって、肝心のブログ記事が全然書かれてなかったりするのでナニでアレなわけです。


拡張子で表現内容を変える例としてMitterがある。これは人間向け表現をhttp://mitter.jp/youzaka.html (ただし、拡張子なしでのアクセスが一般的)、機械向け表現をhttp://mitter.jp/youzaka.atomで提供している。もしかしたらapplication/atom+xmlをほしがるユーザーエージェントがアクセスしたら拡張子なしでもAtom版が返ってくるかもしれないけどそこまでは検証してない。

タグ

研修テキストのマークアップのしかた

ということで、某SIerが作成したテキストで全体研修を受けている。Cが終わってPHPの話になり、HTMLは研修で扱わないということだったけどPHPを扱う関係上HTMLはテキストに登場せざるを得ない。フォームのサンプルとして、こんなのが載っていた:

<p>
  <form>
     サンプルです<input type="text"><input type="submit">
  </form>
</p>

さて、HTML4.01 Strict DTDを読んでみると:

<!ELEMENT P - O (%inline;)*            -- paragraph -->
<!ENTITY % block
     "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
      BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
<!ELEMENT FORM - - (%block;|SCRIPT)+ -(FORM) -- interactive form -->
<!ATTLIST FORM
  %attrs;                              -- %coreattrs, %i18n, %events --
  action      %URI;          #REQUIRED -- server-side form handler --
  method      (GET|POST)     GET       -- HTTP method used to submit the form--
  enctype     %ContentType;  "application/x-www-form-urlencoded"
  accept      %ContentTypes; #IMPLIED  -- list of MIME types for file upload --
  name        CDATA          #IMPLIED  -- name of form for scripting --
  onsubmit    %Script;       #IMPLIED  -- the form was submitted --
  onreset     %Script;       #IMPLIED  -- the form was reset --
  accept-charset %Charsets;  #IMPLIED  -- list of supported charsets --
  >
<!ENTITY % formctrl "INPUT | SELECT | TEXTAREA | LABEL | BUTTON">

<!-- %inline; covers inline or "text-level" elements -->
<!ENTITY % inline "#PCDATA | %fontstyle; | %phrase; | %special; | %formctrl;">

これの意味するところは、P要素の子にはインライン要素しか来ず、FORM要素はブロック要素でありFORM要素の子にはFORM要素以外のブロック要素とSCRIPT要素しか来ず、INPUTやSELECTなどのフォームコントロール系要素はインライン要素であるということだ。つまり、件のHTMLはPの子にFORMが来ていることと、FORMの子にINPUTが来ていることが間違っている。さらに、FORM要素のaction属性は#REQUIREDなので必ず明示しなけばならない。もっともloose.dtdではFORM要素の子に%flow;が来れることになっているので、HTML 4.01 Transitionalにおいては2番目の指摘は間違いではない。しかし、Transitionalはその名の通り過渡的なものであって、これから作成するHTMLをわざわざTransitionalにするのは意図にそぐわない。まして、研修で扱うべきものではない。まあ、そのそもDoctype宣言とかもない謎HTMLなので、どのバージョンのHTMLに準拠した文書かわからないのだが。

もっとも、実装上、ほとんどの環境ではPの子にFORMが来ようがFORMのaction属性を省略しようが問題はない。しかしですね、さきほどまで学習していたCでは明示的にstdio.hをインクルードしてましたよね。おまじないとかで。GCCではstdio.hを明示的にインクルードしなくてもprintfなどのstdio.hで定義された関数を使うことができます。だからといってCの研修で#include <stdio.h>は省略してもいいよなんて言いませんよね。さっきまで学習していたCにもこういうレベルの誤りが含まれているのではないかと気が気でなくなります。テキストを通じて一定の品質を確保してほしいものですし、こういうところから、プログラマのマークアップ言語軽視が垣間見えるようで嫌な気分になります。

javadocが生成するHTMLも、PRE要素の子にIMGを置いている。階層構造を表現するのに手っ取り早いからとはいえ、これも同じような感じ。

タグ

電車が遅延してると乗換検索がうまく使えない

ジョルダンのiPhoneアプリは駅探のと違って無料だしジョルダンライブが交通情報板の簡潔版みたいで便利なのでよく使っている。今日は恵比寿で飲み会があってフラフラになりながら大混雑の山手線に乗って乗換案内を見てみたらやたら不自然な乗換時刻が案内されて困った。よくよくいろいろ見直してみたら、今乗っている電車がやや遅れていたからだった。さすがにそんな数分の遅れまで反映させて乗換案内をするなんて難しいだろうけど、酔ってる頭にやさしい乗換案内がほしいなあと思った。

あと、乗換情報サイトは、定期券の情報を登録しておくことで定見併用での最安ルートが提案されるようにすると、リピート率向上に資すると思うのでこれはぜひ取り入れてほしい。

タグ

ホワキャンのポイントで東方おみくじ

なんか友人についていってアキバのホワイトキャンバスへ行った。なんかポイント制度が変わるとかで、友人はそれまで貯めていたポイントをいったん精算することにした。それでポイントと引き換えに得たのが「東方おみくじ'09」。しかも3つも引いて、一つ私にくれた。橙が「それってオイシイ?」というフリップを掲げている大吉のおみくじなんだけど、せっかく貯めたポイントで交換されるのがそんなんでいいの? いや、失言だと思うけど。いただいた橙のおみくじは大事に取ってあります。

タグ

お釣りの出る商品券を家計簿でどう扱うべきか分からない

クレカのポイントが貯まったのでジェフグルメカード5000円分と交換した。吉野家やモスバーガーなどのファストフード店でも使え、券面500円に満たない買い物にはお釣りが出る。ほかの商品券はクレジットカードが使える店でも有効なものが多く、なるべく現金を使いたくないブームの渦中にいる私としては、最適解はJCBギフトカードや図書カードではなくこのジェフグルメカードだと思う。

で、この商品券、前述のとおりお釣りが出る。吉野家で380円の牛丼を食べることにより、現金120円を得ることができる。この場合、家計簿にはどう付けるべきなのだろうか。商品券をもらった時点で5000円の収入とみなすのか、現金化した時点で収入扱いにした方がいいのか。いちおう現金になった時点で収入扱いだろうということで、家計簿には-120円の支出と書いたけど、ベストプラクティスがあれば知りたい。

タグ

草食系男子というか惣菜を食べる惣食系男子

同じクラスの人たちが男性を中心に6人ほど弁当を持参しており、その流れに沿おうと思って無印でランチボックスを買った。あとお茶はペットボトルに入れて前日凍らせたものを持参するようにしたので、平日にお金を使わないキャンペーンをはじめた。まあキャンペーンといっても単に平日にお金を使わないだけだ。

だいたい朝に作ったものの半分を弁当箱に詰める感じにしていて、件のランチボックスはご飯を入れる容器のほかにおかずを入れる容器が2つ付いているので、シューマイやウィンナー、あと卵焼きにマヨネーズやらニラやらタマネギやらショウガやらを入れてラーユやコショウで味付けしたものなどを入れたりしている。いちどニンニクも下ろして混ぜてみたが、あまりにもにおいがばら撒かれる結果になってしまったのでもうしない。種類が貧弱なのでおいおい増やしていきたい。

あとメディアがいう草食系男子には弁当を持参する者が含まれるそうで、そんな消費を刺激しない事象を取り上げても儲からないだろうに何を狙っているのだろうか。あと、草食と言われますが我々が食べるのは惣菜であってむしろ惣食です。これが言いたかっただけです。

タグ
© 2001-2010 Chisa YOUZAKA. Some rights reserved.