アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

CodeIgniterでSPA実装してみる

SPA is 何

参考:SPA(Single Page Application)の学習、そこに高度なスキルなどいらない - Qiita

Single Page Applicationの略。
一枚のHTMLの中でJS使ってがちゃがちゃ書き換えようぜっていう思想です。

FacebookとかTwitterみたいなやつね。

CodeIgniterで実装してみた

参考:ui-routerとcodeigniterを組み合わせてSPAをつくるときのメモ - よっこらせとプログラム

JSのプラグインとかもそのへんに落ちてるみたいですが、今回はCodeIgniter+jQuery環境で自分で作ってみました。
(別にjQueryである必要はないです)

とりあえずサンプル完成品を貼っていく。
適当に抜粋してるので適当に解釈してください。

仕様

「/sample/test_a/」「/sample/test_b/」の二つのページを行き来するだけのもの。
それぞれに互いへのリンクが張ってあります。
で、もちろんSPAなので中身のコンテンツ部分だけを書き換える感じ。
もちろん、各URLに直接アクセスしたときもそれぞれのページが表示されるようにするし、ブラウザのURLや履歴もちゃんと更新する。

作ったのは

  • core/MY_Controller.php
  • controllers/Sample.php
  • views/top.html
  • views/test_a.html
  • views/test_b.html

core/MY_Controller.php

やってること

  • viewに渡すパラメータ変数はここで定義している
  • __construct():spaっていうパラメータに「on」が渡されたらspaフラグを立てる
  • view():spaフラグが立ってたらコンテンツ部分だけを返し、立ってなければ普通にview()する
  • spa_view():viewをちょっと拡張して、必ず結果文字列を返すようにしたやつ

controllerをシンプルにしたいあまりここがごちゃごちゃに。
実際に使うとなったらもうちょっと切り分けなきゃいけないケースが出てくるかもしれない。

<?php 
class MY_Controller extends CI_Controller {

	var $spa = FALSE;

	var $data_list = array('content' => NULL);

	public function __construct()
	{
		parent::__construct();
		if ($this->input->get("spa") == "on")
		{
			$this->spa = TRUE;
		}
	}

	public function view($template_name = "top")
	{
		if ($this->spa == TRUE)
		{
			echo $this->data_list['content'];
			return;
		}
		$this->load->view($template_name . ".html", $this->data_list);
	}

	public function spa_view($template_name)
	{
		return $this->load->view($template_name . ".html", $this->data_list, TRUE);
	}
}

controllers/Sample.php

やってること

  • test_a():test_aというviewの中身を取ってview()するだけのやつ
  • test_b():test_aというviewの中身を取ってview()するだけのやつ
<?php
class Sample extends MY_Controller {

	public function test_a()
	{
		$this->data_list['content'] = $this->spa_view("test_a");
		$this->view();
	}

	public function test_b()
	{
		$this->data_list['content'] = $this->spa_view("test_b");
		$this->view();
	}
}

views/top.html

やってること

  • id="spa_content" なdivタグを用意して、この中身を書き換えるようにする
  • JSで、aタグがクリックされたときに以下処理を行うようにしている
    • aタグからhref要素を取得
    • 「#」が含まれていれば以降の処理は無視、1文字目が「/」だったらサイト内リンク、そうでなければ外部リンクと判断(自分の場合はこれで事足りるので雑な実装)
    • 外部リンクだったら別タブで開いて処理終了
    • サイト内リンクだったら「?spa=on」とパラメータをくっつけてgetする(自分の場合は他でクエリストリング使わないので雑な実装)
    • getに成功したらdivタグの中身を書き換えて、ブラウザの履歴も書き換えて、終了
    • getに失敗したらエラーメッセージをポップアップして終了
<!DOCTYPE html>
<html>
<head><title>test</title></head>
<body>

<div>test-header</div>
<div id="spa_content"><?= $content ?></div>
<div><a href="http://google.com">test</a></div>
<div>test-footer</div>

<script src="/jquery.min.js"></script>
<script>
$(document).on('click', 'a', function(){
  href = $(this).attr('href');
  if (href.indexOf('#') != -1){
      return;
  }
  if (href.substr(0, 1) != '/'){
    $(this).attr('target', '_blank');
    return;
  }
  $.ajax(href+'?spa=on',{ type: 'get', }
  )
  .done(function(data) {
    $('#spa_content').html(data);
    history.pushState(null, null, href);
  })
  .fail(function() {
    alert('データの取得に失敗しました。時間をおいて再度お試しください。');
  });
  return false;
});
</script>
</body>
</html>

test_a.html / test_b.html

ただリンク張ってるだけのやつ

<a href="/top/test_b/">test a</a>
<a href="/top/test_a/">test b</a>

…でオッケーかと思ったら足りなかった

ちゃんとしたサンプルはないけど以下のような感じで解決しました。

  • postしたいとき
    • やり方はgetとほぼ同じ。単にpost用の関数を作ってそれをコールするようにした
  • つーかブラウザの「戻る」「進む」が効かない
    • ↓のような感じで解決
// 戻ったり進んだりすると発火するやつ
window.addEventListener("popstate", function($e){
    // aタグクリックで発火するやつの中身をほぼそのまま移植(もちろんpushStateを省く)
}, false);

あとはどうしても「onclick="location.href=..."」したいときとかももちろんSPAしてくれない。