配列から重複した要素を取り除く

(2013年6月修正)

jQueryのunique()は、文字列には使えないようだ。

Description: Sorts an array of DOM elements, in place, with the duplicates removed. Note that this only works on arrays of DOM elements, not strings or numbers.

ということで、自前で作成する。
エラーチェックなどを除いたシンプルなコードは以下のイメージ。(xuarray.js)

// JavaScript

// オブジェクトの配列からプロパティ名を指定して重複を除く
function xua_uniqueObjectArray(objArray, propName){
  var storage = {};
  var uniqueObjArray = [];
  var i, value;

  for (i = 0; i < objArray.length; i++) {
    value = objArray[i][propName];
    if (!(value in storage)) {
      storage[value] = true;
      uniqueObjArray.push(objArray[i]);
    }
  }
  return uniqueObjArray;
}

// 単純な配列から重複を除く
function xua_unique(orgArray){
  var storage = {};
  var uniqueArray = [];
  var i, value;

  for (i = 0; i < orgArray.length; i++) {
    value = orgArray[i];
    if (!(value in storage)) {
      storage[value] = true;
      uniqueArray.push(value);
    }
  }
  return uniqueArray;
}

—–
配列から重複した要素を取り除くには、jQueryのunique()を使うと簡単だ。(JavaScriptには同じことをダイレクトに実現するメソッドはないようで、少々コードを書く必要がある。)

// JavaScript
// 元の配列
rideDates_ = ['2013/01/01', '2013/01/01', '2013/01/02', '2013/01/02'];
// 重複を除く
rideDates = $.unique(rideDates_);
// 結果
// rideDate = ['2013/01/01', '2013/01/02'];
カテゴリー: Tips | タグ: , | コメントする

ローカルストレージを利用して端末にデータを保存する(HTML5)

HTML5では端末にデータを保存するためのWeb Storage という仕組が用意されている。Web Storage にはセッション中のみ有効な Session Storage と、永続的に有効な Local Storage がある。

いずれも「キー」と「値」のペアで保存し、取得時に「キー」を指定すると対応する「値」が返ってくるというシンプルな仕組だ。

// localStorage は window.localStorage と等価で、windowは省略可能
// methods
localStorage.getItem(_key); // キーを指定して値を取得
localStorage.setItem(_key, _val); // キーと値を指定して保存
localStorage.removeItem(_key); // キーを指定してデータを削除
localStorage.clear(); // 全てのデータを削除
localStorage.key(number); // インデックス番号を指定してキーを取得

// property
localStorage.length // 保存されているデータ数を取得

値には文字列を指定する。オブジェクトを保存する際は、JSON.stringify() などを使って文字列化する。(復元する場合は JSON.parse() などを使う。)

使用例:

// データ取得
// ローカルストレージに保存した際のキー(JSONファイル名 例:T9999999.json)
var ls_key = line.ln_file;
// キーを指定してローカルストレージから路線データをロードする
var ls_val = localStorage.getItem(ls_key);
// 取得した結果をオブジェクトに復元する
gCurRail = JSON.parse(ls_val);

// データ保存
loadJsonAsync(uri, function(d){
  // サーバからロードした内容を文字列に変換してローカルストレージに保存
  localStorage.setItem(line.ln_file, JSON.stringify(d));
});

実際にどういうデータが保存されているかは、ブラウザのツールでも確認可能。例えば、chromeでは、JavaScriptコンソールの「Resources」で確認できる。

カテゴリー: Tips | タグ: , | コメントする

列車時刻表(8) – 路線ファイルを端末に保存する

アプリケーションのオフライン動作対応の第一弾として、列車時刻を格納した「路線ファイル(JSON形式)」をローカルストレージに保存するように修正する。(路線ファイルをサーバからロードする頻度が減少する分、動作の高速化も期待できる。)

[現在のフロー]
(1)「路線一覧ファイル」をサーバからロード
– 路線一覧ファイルは、路線名や最新版の生成日付などを保持している。
(2)路線を選択したタイミングで「路線ファイル」をサーバからロード
– 路線ファイルは、駅名や列車時刻表を保持している。(路線ごとに別ファイル)

[修正後のフロー]
(1)「路線一覧ファイル」をサーバからロード (不変)
(2)路線を選択したタイミングで「路線ファイル」をローカルストレージからロード
(3)「路線一覧ファイル」をチェックして、最新版があればサーバから路線ファイルをロード

修正後は、最新版をロードする前にローカルデータもロードしている点が冗長になっているが、今後、路線一覧ファイルのオフライン対応を行うタイミングで効率化する予定。[TODO]

路線一覧ファイルは以下のように、1つの路線が配列の1要素(オブジェクト)になっている。

// JSONファイルに手動でコメントを追記したもの
{
  "lines": [
    {
      "ln_key": "9999999",          // 路線キー
      "ln_upd": "2013/05/09",       // 路線データの生成日
      "ln_name": "[JR]路線名",    // 路線名          
      "ln_file": "T9999999.json"    // JSONファイル名
    },
    {
     /* 2番目の路線, ... */
    }
  ]
}

JSON形式の路線ファイルは、路線ごとに用意しているので、ファイル名をローカルストレージに保存する際のキーとして使用する。

実際のコードは以下のとおり。

// JavaScript
function loadRail(line, callback){
  // ローカルストレージに保存する際のキー(JSONファイル名 例:T9999999.json)
  var ls_key = line.ln_file;
  // キーを指定してローカルストレージから路線データをロードする
  var ls_val = localStorage.getItem(ls_key);
  
  if (ls_val == null) {
  // ローカルストレージに路線データが保存されていないとき(初回)はサーバからロードする
    loadRailRemote(line, callback); 
  } else {
    gCurRail = JSON.parse(ls_val);
    if ( gCurRail.upd < gCurLine.ln_upd ){
      // サーバに最新の路線ファイルがある場合:サーバからロードする
      loadRailRemote(line, callback); 
    } else {
      // ローカルが最新版の場合:ローカルストレージのデータをそのまま使用する	
      callback();
    }
  }
}

function loadRailRemote(line, callback){
  // サーバにあるJSONファイルのフルパス名
  var uri = '/json/tt/' + line.ln_file;
  // サーバから非同期的にファイルロード  
  loadJsonAsync(uri, function(d){
    gCurRail = d;
    // ロードした内容を文字列に変換して、ローカルストレージに保存
    localStorage.setItem(line.ln_file, JSON.stringify(d));
    callback();
  });
}
カテゴリー: Development | タグ: , | 1件のコメント

JavaScriptでブラウザのオンライン/オフライン状態を確認する

作成中のWebアプリを、オフライン状態でも動作するように改良していくにあたり、ブラウザの今の状態を知る必要があるが、これはブラウザに組み込みのオブジェクトである navigator のonLine プロパティを使えば簡単にできる。

// JavaScript
$(document).on('click', '#btnTestOnOff', function(){
   console.debug('#btnTestOnOff:' + navigator.onLine);
});

ちなみに、navigatorオブジェクトに関しては以下もよく使う。

  • navigator.appName:ブラウザの名前
  • navigator.appVersion:ブラウザの名前
  • navigator.userAgent:ブラウザの名前とバージョン

firefoxには「オフラインモード」があるので、テストするにはこちらを使用する。(chromeはオフライン化できないようだ。)

カテゴリー: Tips | タグ: | コメントする

列車時刻表(7) – 乗車予定画面への遷移(共通フッタの利用)

前記事に引き続いて、乗車予定一覧画面への遷移を追加する。これまでのコーディングで、「列車時刻表画面」「駅時刻表画面」から乗車・下車アクションを実行したタイミングで乗車予定一覧画面に遷移していたが、それ以外の画面からも一覧画面に遷移できるようにする。

  • 全画面共通のフッタを用意しリンクボタンを配置する
    コードで共通のフッタを作成し、リンクボタンを配置するために、以下のように全画面共通のイベントハンドラ(ページ生成時にフッタを追加する)を定義する。

    <!-- HTML -->
    <!-- 全ページに空のフッタを定義しておく-->
      <div data-role="footer" data-position="fixed" data-id="fixedFooter">
      </div>
    
    // JavaScript
    $('div[data-role="page"]').live("pagebeforecreate", function(evt){
      var footer = '<button data-role="button" data-icon="grid"
        id="footerTTcommon">Ride</a>';
      $(this).find('div[data-role="footer"]').append(footer);
    }
    

    フッタはあらかじめには同一のidを振っておき、data-position=”fixed” とすることで、全画面共通のフッタになる(ようだ)。リンクボタンも同じidとすることで、どの画面でボタンをクリックしても同じイベントが発生すると思われる。[TODO] イベント要確認
    mttview04_c05

  • ボタンクリック時のイベントハンドラを定義する
    // JavaScript
    $('#footerTTcommon').live('click', function(){
      if (gRides.length == 0){
      // 乗車予定データが空の場合はダイアログを表示するだけで画面遷移しない。
        mu_openDialogOK('Information', 'No riding data saved.', function(){
          return;
        });
      //乗車データが存在する場合は、予定一覧画面に遷移する。
      } else {
        $.mobile.changePage('#pgTTRide');
      }
    });
    
  • 「列車時刻表画面」「駅時刻表画面」からは一旦ダミーページに遷移する
    「列車時刻表画面」「駅時刻表画面」のリンクボタンをクリックした際は、一旦ダミーページ(#pgTTRide0)に遷移して、ダイアログを表示する。乗車・下車アクションの実行が確認できたら、一覧画面(#pgTTRide)に遷移する。
カテゴリー: Development, Tips | タグ: , , | コメントする

列車時刻表(6) – 乗車予定登録後の画面遷移

前記事に引き続いて、乗(下)車予定登録機能を実現する。

  • 乗車指定時
    乗車を指定したタイミングでは、まさに列車に「乗っている」状態なので、次のアクションとしては、その列車が通るいずれかの駅で降りることが想定される。そこで、画面にリンクボタンを用意して、”(現在乗車中の)列車時刻表画面”に遷移できるようにする。
    mttview04_c01
    この例では、宮古駅発5:54(681D)の列車に乗車している状態なので、上のリンクボタンで列車時刻表画面に遷移する。
    mttview04_c02
  • 下車指定時
    下車を指定したタイミングでは、現在は「駅にいる」状態ので、次のアクションとしては、
    ・「同じ路線の列車に乗車する(乗り継ぎ)」
    ・「別の路線の列車に乗車する(乗換)」
    のいずれかのアクションが想定される。そこで、画面に2つのリンクボタンを用意して、以下の画面に遷移するようにする。
    ・”駅時刻表画面”(現在いる駅)
    ・”駅検索画面”(現在いる駅を検索キーワードにする)

    先の例で乗車していた列車(681D)を、茂市駅で下車すると以下の画面になる。
    mttview04_c03
    ここで、上のリンクボタンをクリックすると、同じ路線(山田線)で茂市駅から乗車することになる。(乗車時にクリックした列車へのリンクボタンは無効状態にしている。)
    mttview04_c04
    下のリンクボタンをクリックすると、「茂市」で検索した駅一覧を表示した画面に遷移する。
    mttview04_c05
    山田線と岩泉線が候補として表示される。

カテゴリー: Development | タグ: , | コメントする

jQueryで手動(コード)でイベントを発生させる

jQueryのアプリで手動でイベントを発生させたい場合は、trigger() を使う。対象となるイベントにはコードで追加したカスタムイベントも含まれる。

  • 使用法:
    $(セレクタ).trigger(イベント)
    
  • 使用例:
    // JavaScript
    $(this).on('click', '#btnTTRideSrcStation', function(){
      // ボタンが押されたら検索を実行する
      $('#srcTTSrcStations').val( gCurRail.stations[gCurStationIdx].st_name );
      $('#srcTTSrcStations').trigger('change');
    }
    
    // イベントハンドラ
    $(document).on('change', '#srcTTSrcStations', function(){
      // #btnTTRideSrcStationをクリックするとこのイベントハンドラが実行される
    }
    
カテゴリー: Tips | タグ: | コメントする

列車時刻表(5) – 乗車/下車予定を登録する

時刻表で調べた乗車・下車時刻と区間を登録できるようにする。実行サンプルはこちら

  • 列車を選択して「乗車」する
    「駅時刻表」で右側のグリッドボタンをクリックして列車を選択して「乗車」する。
    mttview04_b01
  • 乗車内容の確認
    列車を選択すると、ダイアログを出して予定を表示する画面に遷移する。(この例では「6:02発(1221M)」の列車を選択。)
    mttview04_b02
    mttview04_b03
  • 列車画面に遷移する
    乗車している列車(1221M)の画面に移動する。
    mttview04_b04
  • 駅を選択して「下車」する
    列車画面で右のグリッドをクリックして、下車駅を選択する。ダイアログを表示して、乗車内容を確認する画面に遷移する。(この例では喜久田駅を選択。) [TODO] 現在は下車駅の妥当性チェック(乗車中の列車と同じ列車であること、乗車駅より後ろの駅であること)をしていない。
    mttview04_b05
    mttview04_b06
  • 駅時刻表画面に遷移する
    この例では喜久田駅の時刻表画面に移動する。
    mttview04_b07
  • ここまでの例は、「駅時刻表から列車選択して乗車」→「列車時刻表から駅を選択して下車」という流れだったが、「列車時刻表から駅を選択して乗車」→「列車時刻表から駅を選択して下車」という流れも実装する。
カテゴリー: Development | タグ: , | 1件のコメント

列車時刻表(4) – 駅・路線検索ページの作成

作成中の時刻表に、「駅名・路線名」検索ページを追加する。
追加後の実行サンプルはこちら。画面は以下の感じ。
mttview04_a01
検索文字を入力するテキストボックスの作成は、input要素で type=”search” を指定する。入力内容はchangeイベントを処理して取得する。

  • 路線検索
    以下のような検索ボックスを用意して、イベントハンドラを定義する。

    <!-- HTML -->
    <input type="search" id="srcTTSrcStations" value="..."/>
    
    // JavaScript
    $(this).on('change', '#srcTTSrcLines', function(){
      var keyword = $(this).val();
      // 検索キーワードを含む「路線オブジェクト」を抽出
      var srcLines = gLines.filter(function(item, index){
        if ((item.ln_name).indexOf(keyword) >= 0) return true;
      });
      // 路線一覧を表示する
      showLineList($('#lvTTSrcLines'), srcLines); 
    });
    

    路線一覧を表示する関数: showLineList() は、もともと作成していた路線表示関数と機能的にほぼ同じなので、表示対象のjQueryオブジェクトをパラメータとして追加して共通化した。検索にはindexOf()関数を使っていて、対象にキーワードが含まれていれば全てマッチする。例えば、検索キーワード「山」に対し、「郡山」「中山宿」などがヒットする。

  • 駅検索
    これまで、アプリを起動したときは「路線一覧」を読み込み、路線を選択したタイミングでその路線の駅一覧やダイヤを読み込んでいた。検索ページの追加に伴い、起動時に全ての駅名をデータストアから読み込む機能を追加する。なお(駅一覧はあらかじめデータストアに格納しておく。
    ([TODO] 検索実行時にデータストアから条件検索で取得するように変更する。)

    // JavaScript
    // 起動時に実行
    // 駅一覧をデータストアから読み込み
    gDSTTStation = new xuDatastore('TTStation', 'st_key', gcPropsTTStation);
    gDSTTStation.dbRev('', '', function(d){ // sort順、queryを指定せずに全件取得する
      gStationsSrc = d;
    });
    
  • 実行例
    mttview04_a02
カテゴリー: Development | タグ: , | コメントする

JavaScriptでオブジェクトの配列をフィルタリングする

JavaScriptでオブジェクトの配列(普通の配列でもOK)に対し、あるプロパティの値をキーとして、条件を満たす要素のみを抽出したいときは、array.filter() メソッドを使う。(ソートしたい場合は、ここを参照。)

例えば、以下のような配列があるとする。

// JavaScript
gLines = [
{
  ln_file: "T1301451.json"
  ln_key: "1301451"
  ln_name: "[JR]岩泉線 (茂市~岩泉) "
},
{
  ln_file: "T1301541.json"
  ln_key: "1301541"
  ln_name: "[JR]北上線 (北上~横手) "
},
{
  ln_file: "T1301671.json"
  ln_key: "1301671"
  ln_name: "[JR]磐越東線(ゆうゆうあぶくまライン) (いわき~郡山) "
}];

この配列から、
ln_key が ’1301541′に等しい要素を抽出するには、次のようにする。

// JavaScript
var newLine = gLines.filter(function(item, index){
  if (item.ln_key == '1301541') return true;
});

また、ln_name に「いわき」という文字を含む要素を取り出すには、

// JavaScript
var newLines = gLines.filter(function(item, index){
  if ((item.ln_name).indexOf('いわき') >= 0) return true;
});

とすればOK。

書式:array.filter(callbackfn)
array内の各要素に対し、関数:callbackfn()が1回適用され、「この関数がtrueを返す要素」を集めて新しい配列を作成する。上記の例では、gLines[0], gLines[1], …が順にテストされ、条件(ln_key == ‘…’など)を満たす要素 gLines[1] が抽出される。
callbackfn()のパラメータは最大3つで、最初が(元の)配列の要素(item)、次が要素のインデックス(index)になっている。

カテゴリー: Tips | タグ: | コメントする