Logical Thinking and Programming in 2012 Autumn
Minohara Class


第13回 作品制作

作品制作の構想

企画を考える

今回の作品制作は何でも良いという訳ではなく、以下の制約があります。

分野などは問いませんが、単なるプログラムの作品ではなくて、まず「自分がこれからも継続的に利用したいもの」 であること、およびこれからの「論理思考に役に立つ作品」であること、および「自律的な学習を促すもの」を念頭に企画を 練っておいて下さい。「自律的に学習」という言葉が難しいと思います。 たとえば、ゲームの場合は、「時間制約のないゲーム」になっているほうが良いと思います。 アクションゲームよりもパズル系を考えてみて下さい。 ただし、対戦相手があるようなゲームは、相手側をプログラムで作るのは大変なので 対戦相手は、人間であるという場合でも結構です。 また、なんらかの学習プログラムの場合は、作成されたアプリケーションを使って自律的に学べることを目指してみて 下さい。

「企画書」は、SFSを通じて提出してもらいます。企画書のフォーマットは、 1ページに収まるようにしてください。これは、お互い見れるような形にします(企画が重ならないように するため)。 単なるテキストファイルでも構いませんし、Wordの文書でも構いません。あるいは、印刷用のPDF形式の 文書でも構いません。それを添付してもらいます。

企画書では、企画のタイトルを付けたり、作者(学籍番号を含む)、作成日、版の番号を付けることが 必須ですが、それ以外に、次のような内容を項目して記述して下さい。

  1. タイトル・キャッチコピー:タイトルが長くなるよりも、タイトルは短くして、サブタイトル(あるいは キャッチコピー)を付けて下さい。
  2. 概要:作品の概略を書いて下さい。
  3. ルール:そのプログラムの操作の仕方を書いてください。
  4. 画面イメージ:スキャナなどで取り込んで貼付けてみてください。あるいは、PhotoshopやIllustratorなどで 概念図を描いたものを貼付けしても構いません。

作品制作の実習

作品制作は、基本的には皆さんの空いた時間の中で行ないます。授業時間で作品制作について取れるのは、 1回分ぐらいの授業ぐらいしかありません。

作品制作については、次のようにすると良いかと思います。

  1. 最初は、一部の機能だけが動く簡単なプロトタイプを作ってみる。
  2. このプロトタイプを元に、機能をメソッドに分割していき、そのメソッドを呼び出すようにプログラムを書き換えてみる。
  3. 最終的な作品は、各メソッドを詳細に設計することで行なう。

だいたい、画家でもプロでも、最終的な作品を作るまでは「習作」として、何回も描き直し、書き直しをしています。 今回も、同じプログラムを3回ぐらい書き直すような気持ちで、だんだんブラッシュ・アップしていきましょう。


外部記憶の利用

プログラムの基本的な機能として、入出力があります。これまでにもinput要素をつかった入力や、write()やinnerHTML()といったメソッドを使ったプログラムからの出力を行ってきました。今回はこれに加えて、データをプログラムの外部に保存する方法を説明します。

Key Value Store

最近のプログラムでは、データを保存するためにKey Value Storeと呼ばれるタイプのデータ保存方法が使われることが多くあります。Key Value Storeは、名前のとおり、Keyと呼ばれるデータを表す鍵とその値をペアにしてデータを保存するための方法です。これまで見てきた中では、オブジェクトに近いものだと考えてください。第6回でみてきたように、オブジェクトの場合にもデータはプロパティ名に紐づけられて格納されていました。オブジェクトとKey Value Storeの違いは、オブジェクトはプログラムを実行している計算機のメモリ上にプログラムが実行されている時のみ存在するものであるのに対して、Key Value Storeは、プログラムの外部(例えば、計算機のファイルやクラウド上のデータベース)に格納される点です。

Session StorageとLocal Storage

HTML5では、Session StorageとLocal Storageと呼ばれる2つのKey Value Storeが用意されており、JavaScriptからもこれらの記憶領域にアクセスすることができます。ただし、Session StorageやLocal Storageは新しい機能であり、古いブラウザでは使うことができませんので注意が必要です。

Session StorageとLocal Storageは両方ともWeb Storageと呼ばれるもので、ブラウザにデータを蓄積するための機能です。両者は同じインターフェイスを持ち、使い方も同じです。しかし、Session Storageがページを閉じたりしてしまうとデータを破棄してしまうのに対し、Local Storageはブラウザを再起動しても元のデータが残ります。

次のプログラムを見てください。このプログラムは、学籍番号と名前を登録するsave()という関数と、登録された全員に対して「こんにちは」と挨拶をする関数hello()で構成されています。プログラムの4行目に「var storage = sessionStorage;」とありますが、ここで変数storageに対してSession Storageのグローバル変数であるsessionStorageを代入し、以後storageという変数を介してSession Storageにアクセスをしています。save関数では、storage変数に対してsetItem()メソッドを用いて、値をセットしています。setItem()メソッドは、setItem(key, value)の書式を持ち、1つ目の引数に鍵を、2つめの引数に値を指定します。また、hello()関数では、for文を使ってSession Storageに保存されている値を順にアラートで表示しています。Session Storageは配列やオブジェクト同様、lengthプロパティを指定することによって、Storageの中に保存されているKey-Valueのペアの数を知ることができます。これを用いてforループを作り、その中では、key()メソッドを使ってi番目のKey-Valueペアのkeyを取得し、更に次の行でアラートを用いて取得したペアを表示しています。

<script type="text/javascript">
<!--
var storage = sessionStorage;
 
function hello() {
  for ( var  i=0; i<storage.length; i++) {
    key = storage.key(i);
    window.alert("こんにちは、" + storage.getItem(key) + "(学籍番号: " + key + ")さん");
  }
}
 
function save() {
  var num = document.getElementById( "num" ).value;
  var num = document.getElementById( "name" ).value;
  storage.setItem( num, name );
}
//-->
</script>
  
<form>
学籍番号: <input id="num" type="text" size="20"><br />
名前: <input id="name" type="text" size="20"><br />
<input type="button" value="保存" onclick="save()"><br />
<input type="button" value="表示" onclick="hello()">
</form>

前にも述べましたが、Web StorageにはSession Storageの他に、Local Storageと呼ばれるものがあります。Session Storageがページを閉じたりしてしまうとデータを破棄してしまうのに対し、Local Storageはブラウザを再起動しても元のデータが残るところが異なります。ただし、データはあくまでブラウザ側に保存されますので、保存したデータを他の人と共有するような使い方はできません。あくまで同じブラウザを使う際に利用するものと考えてください。例えば、文字の色などをブラウザに保存しておき、次回の起動時にそれを活用して同じ表示をさせるなどの用途で使われます。Local Storageを使う場合は、sessionStorageグローバル変数の代わりにlocalStorageグローバル変数を使います。他のメソッドなどのインターフェイスは全く同じです。

Web Storageを使う場合は、特にLocal Storageを使う場合は注意が必要です。データをブラウザ上で動く多くのプログラムで共有してしまうため、同じKeyを使ってしまい、他のプログラムが保存したデータを上書きしてしまったりすることがあります。プログラムのIDをKeyに埋め込む等、Keyの作り方には工夫をしましょう。

Web Storageは、下記のようなメソッドを持ちます。

メソッド/プロパティ 説明
clear() すべてのKey-Valueのペアを削除する
getItem(key) keyで指定したKey-Valueペアの値を取得する
key(num) numで指定した番号の鍵を取得する
removeItem(key) keyで指定したKey-Valueペアを削除する
setItem(key,value) keyとvalueで指定したKey-Valueペアを登録する
length Web Storageに保存されているデータの数を取得する

練習問題13-1

  1. 上記のプログラムを実行し、動作を確認しなさい。その際、一度タブを閉じたらどうなるか、新しいタブで同じページを開いたらどうなるか、ブラウザを再起動したらどうなるか等、挙動を確認しなさい。
  2. 上記のプログラムの「sessionStorage」を「localStorage」に書き換え、動作を確認しなさい。その際、一度タブを閉じたらどうなるか、新しいタブで同じページを開いたらどうなるか、ブラウザを再起動したらどうなるか等、挙動を確認しなさい。
  3. 今回ページにアクセスした時刻と、前回ページにアクセスした時刻を表示するプログラムを作成しなさい。
  4. アクセス時刻の表示

    アクセス時刻の表示

  5. 上のプログラムで、初回のアクセス時には「このページへアクセスしたのは、初めてです。」と表示するようにしなさい。また、アクセス履歴を消去するためのリセットボタンを追加しなさい。ただし、getItem()メソッドを使ってWeb Storageにアクセスした際に、Key-Valueペアが存在しない時は、getItem()メソッドはnullを返します。
  6. アクセス時刻の表示(改)

    アクセス時刻の表示(改)

File API

プログラムから、ローカルファイル、つまりブラウザを起動しているコンピュータ上のファイルからデータを読み出したくなるときがあります。この様な要望に応えるため、W3CではHTML5の中でFile APIを策定しています。ここでは、File APIの使い方について説明します。

ただし、File APIもWeb Storage同様に新しいインターフェイスであり、まだまだFile APIをサポートしていないブラウザも多く使われていることに気をつけてください。

Fileの指定

ローカルファイルにアクセスできるということは、危険を伴います。たとえば、悪意のあるサイトにアクセスしてしまい、そこからダウンロードされたJavaScriptのプログラムがローカルに保存されているデータを勝手に別のサイトに送ってしまい、情報が漏れるなどの可能性があります。このため、File APIではアクセスできるファイルを、input要素によって指定されたファイルのみに制限しています。ここでは、input要素によるファイルの指定の方法を説明しましょう。

次のHTMLの例を見てください。これがファイルを指定するためのinput要素です。typeプロパティで「file」を指定しています。

<input type="file" />

実際に上のようなinput要素をHTMLに埋め込むと次のようなファイル選択インターフェイスが表示されます。

また、複数のファイルを一度に選択するようなインターフェイスを作ることもできます。このためにはmultipleプロパティを追加して、次のように記述します。

<input type="file" multiple="true" />

ファイルの属性読み込み

それでは準備ができましたので、ローカルファイルにアクセスしてみましょう。ファイルにアクセスするためには、まずファイルオブジェクトを取得します。次のプログラムを見てください。

<script type="text/javascript">
<!--
function viewFileInfo() {
  var files = document.getElementById("fileSelector").files;
  var fileinfo = document.getElementById( "fileInfo" );

  fileinfo.innerHTML += "<ul>";
  for (var i=0; i<files.length; i++) {
    fileinfo.innerHTML += "<li>" + files[i].name + " (size=" + files[i].size + "bytes)</li>"; 
  }
  fileinfo.innerHTML += "</ul>";
}
//-->
</script>

<input id="fileSelector" type="file" multiple="true" />
<button onclick="viewFileInfo()">情報表示</button>
<div id="fileInfo"></div>

このプログラムの5行目では、input要素のfilesプロパティを取得してきて、変数であるfilesに代入しています。filesプロパティは配列になっており、したがって変数filesも配列となります。これは、fileタイプのinput要素が複数のファイルを同時に選択できるインターフェイスとなっているため、配列1つ1つにファイルオブジェクトを格納できるようにするためです。また、9行目からのfor文では、そのファイルオブジェクト1つ1つについて、ファイル名(nameプロパティ)、ファイルサイズ(sizeプロパティ)を取得して箇条書きで表示しています。

ファイルの属性については次のようなものを使うことができます。

プロパティ 説明
name ファイル名
type “text/xml”などのファイルのタイプ
size ファイルサイズ
urn ファイルのURN

練習問題13-2

  1. 上記のプログラムを実行し、動作を確認しなさい。
  2. 他のtypeやurnなどのプロパティも表示するように変更しなさい。

ファイルからの読み込み

一般的なプログラム言語において、ファイルをアクセスする場合には「ファイルのオープン」「ファイルの読み書き」「ファイルのクローズ」という流れをとります。「ファイルのオープン」によってファイルを使用することを宣言し、実際にファイルにアクセスし、最後に「ファイルのクローズ」によって使用し終わったことを宣言するわけです。これは、二つのプログラムが同時に1つのファイルに書き込んだ際にファイルが壊れてしまうのを避けるなどの目的があります。

しかし、File APIではこのような流れを取らず、イベントハンドラとしてファイルにアクセスをします。ファイルの読み込み準備が終わった段階で呼び出される関数を予め用意しておき、ファイルの読み込みを行うのです。これによって、ブラウザにファイルが読み込まれた時点でイベントハンドラが起動されます。

下記のプログラムを見てください。9行目においてFileReader()を使って新たなオブジェクトを生成しています。生成したオブジェクトは変数readerに代入されています。これがファイルを読み込む際に利用するオブジェクトです。このオブジェクトにハンドラを設定し、ファイルの読み込みを指示することによってファイルの内容にアクセスすることができます。4行目から6行目はファイルが読み込まれた際に呼び出されるハンドラ関数です。ファイルにアクセスするハンドラは関数の引数としてイベントをとります。このイベントの中のtargetプロパティの中の、resultプロパティがファイルの中身そのものになります。このため、プログラムの5行目の意味はidプロパティとして「fileContent」を持つdiv要素の中身をファイルの中身に置き換える、ということになります。10行目では、FileReaderオブジェクトにonloadイベントを設定しています。このイベントはファイルが読み込まれた時点で発生します。つまり、ファイルの読み込みが終わったら、ここで指定されている関数が実行されるわけです。12行目はinput要素からファイルの要素をとりだしています。13行目は取り出したファイルのリストの0番目を実際にテキストファイルとして読み込むことを指示しています。

<div  id="result" ></div>
<script type="text/javascript">
<!--
function printFile(evt) {
    var result = document.getElementById( "result" );
    result.innerHTML += evt.target.result;
}
 
function readFile() {
  var reader = new FileReader();
  reader.onload = printFile;
 
  var files = document.getElementById("fileSelector").files;
  reader.readAsText(files[0], "utf-8");
}
//-->
</script>
 
<input id="fileSelector" type="file" multiple="true" />
<button onclick="readFile()">内容表示</button>
<div id="fileContent"></div>

File APIのイベントハンドラには次のようなものがあります。

イベント 説明
onloadstart 読み込みの開始時
onprogress 読み込みの進捗状況を確認する際等に使われるもので、読み込みの途中で呼び出される
onabort ファイルの読み込みの中止時
onerror エラー発生時
onload ファイルの読み込みに成功した時
onloadend ファイルの読み込みが終了した時(成功/不成功は問わない)

CSVの読み込み

計算機上でデータを扱う場合、CSVと呼ばれる形式のファイルが用いられることが多くあります。CSVとはComma Separated Valuesの略で、その名の通りデータが「,(カンマ)」で区切られて格納されたファイルです。表の桁に当たる部分が「,」で区切られて並べられており、行に当たる部分は改行されています。

メソッド/プロパティ 説明
clear() すべてのKey-Valueのペアを削除する
getItem(key) keyで指定したKey-Valueペアの値を取得する
key(num) numで指定した番号の鍵を取得する
removeItem(key) keyで指定したKey-Valueペアを削除する
setItem(key,value) keyとvalueで指定したKey-Valueペアを登録する
length Web Storageに保存されているデータの数を取得する

例えば上の表は、下のように表されます。(「setItem(key,value)」の部分はコンテンツに「,」を含んでいるので、「”"(ダブルクォーテーション)」で囲まれています。本来のCSVではこのように「,」を含む場合には「”"」で囲むのですが、この授業では「”"」の処理については割愛しています。)

メソッド/プロパティ,説明
clear(),すべてのKey-Valueのペアを削除する
getItem(key),keyで指定したKey-Valueペアの値を取得する
key(num),numで指定した番号の鍵を取得する
removeItem(key),keyで指定したKey-Valueペアを削除する
"setItem(key,value)",keyとvalueで指定したKey-Valueペアを登録する
length,Web Storageに保存されているデータの数を取得する

多くのプログラミング言語では、CSVを扱うライブラリが用意されているのですが、JavaScriptでは現段階ではあまりメジャーなライブラリは無いようです。そこで、自分でCSVを処理する方法を考えてみましょう。

次のプログラムを見てください。このプログラムはCSVを表として表示するプログラムです。readFile()関数の部分は文字コードを選べるようになっている以外は既出のものと同じです。printFile()関数の中身が異なっています。この関数では、まず、7行目で変数csvにファイルの中身を代入し、8行目で改行コードを変換しています。テキストファイルでは、文字コードが複数存在しているように、改行を表すコードが3種類あります。「CR(“\r”)」「LF(“\n”)」「CF+LF(“\r\n”)」です。8行目ではこれらの改行コードを「LF」に統一しています。その上で、9行目で文字列に対するsplit()メソッドを使って、行を分割しています。ここまで、linesという名前の配列に1行ずつ格納されました。これを1行ずつ処理しているのが11行目のfor文です。12行目では今度は桁を分割しています。これでcells配列には、処理している行の値が1つずつ格納されます。14行目では、表に対してtr要素、つまり行を挿入しています。15行目から17行目はセルを挿入しています。

<script type="text/javascript">
<!--
function printFile(evt) {
 
  var csv = evt.target.result;
  csv.replace(/\r\n?/g,"\n");  // 改行コードの統一
  var lines = csv.split("\n"); // 行を分割
 
  var tablearea = document.getElementById( "tableArea1" );
  
  for (var i=0; i<lines.length; i++) {
    var cells = lines[i].split(",");
 
    tablearea.innerHTML += "<tr id=\"line" + i + "\">"
    for (j=0; j<cells.length; j++) {
      tablearea.innerHTML += "<td>" + cells[j] + "</td>";
    }
    tablearea.innerHTML += "</tr>"
  }
}
 
function readFile() {
  var reader = new FileReader();
  reader.onload = printFile;
 
  var files = document.getElementById("fileSelector").files;
  var encoding = document.getElementById( "encode" ).value;
  reader.readAsText(files[0], encoding );
}
//-->
</script>
 
<input id="fileSelector" type="file" multiple="true" />
<select id="encode">
<option value="utf-8">UTF-8</option>
<option value="Shift_JIS">SJIS</option>
</select>
<button onclick="readFile()">内容表示</button>
<table id="tableArea1"></table>

このプログラムは、CSVにおけるエスケープや「”"」の処理を無視しているため、CSVの完全な処理はできません。完全なCSVの処理を行うためには、Webでライブラリを探したりした方が良いでしょう。

練習問題13-3

  1. 日本郵便のページに郵便番号と住所が入ったCSVファイルがあります。これを1つダウンロードしてきて表を表示しなさい。ただし、全てを表示させようとすると時間がかかるため、10行程度を抜き出して動作確認をすること。
  2. CSVファイルのそれぞれの桁の意味は郵便番号データの説明のページで説明されています。郵便番号と住所のみが表示されるようにしなさい。また、その際、不要な「”(ダブルクォーテーション)」を削除しなさい。
  3. 「緯度,経度,施設の名前」の順番でデータが入っているファイルがあります。Google Mapsを使って施設の場所にピンをたてるプログラムを作成しなさい。Google Mapsのマーカーの説明を参考にすると良いでしょう。