MySQLでレコードに改行が入っているとWHERE句でリザルトが得られない件

ハマりにハマるデータベース

PHPでMySQLを触っているときに起きた事例で、ミスがミスだけに多少恥ずかしいが、問題解決の何らかの参考になればいいかと思い、記事にしてみた。

テキストファイルから、fgetsで文字列行を取得して、データベースにINSERTするという作業を行っていた。

しかし、そうしてINSERTしたレコードは

SELECTのWHERE句で参照できない

という奇妙な自体が起こった。

問題を解決するためにいろいろ試行錯誤したが、以下の状態であることがわかった。

  • 1.Adminerなどの管理ツールで手動でレコードを入れた場合には参照できる。
  • 2.LIKE句を使うとリザルトが得られる

改行の存在

WHERE句ではダメで、なんでLIKE句ならリザルトを得られるのか。

そこで突き止めたのが改行いわゆる「制御文字」の存在である。

じつはfgetsを使うのはほぼ初めてで、いままでファイルから行単位で文字列を取り出してデータベースに入れるということはやったことがなかった。

テキストファイルの行には改行など制御文字が入っていることもうっかり忘れていた。

制御文字は普通目に見えない。エディターなどの設定で改行などを視認できるようすることは可能だが、今回の作業で全く念頭になく、制御文字の有無など調べていなかった。

今回の事例で初めて知ったのだが、MySQLのINSERTでは制御文字もそのままINSERTされるようだ。

当たり前といえば当たり前だ。文書構造を勝手に変えられては堪った物じゃない。MySQLは正しい仕事をしているのだ。

悪いのは俺。

今回の事例の要点

例えば

transfer
という行をファイルからfgetsで取得して、無加工でデータベースにINSERTをしたとする。

ファイルの行に改行が含まれている場合、目には見えないが

transfer<LF>
という状態で取得されているはずだ。(<LF>というのが改行の制御文字だとおもってくだちい)

これをそのままインサートすればデータベースのレコードは

transfer<LF>
となり、WHERE句でtransferを指定してもリザルトは空になってしまう。

厄介なのが、これに対してエラーが出ないということだ。何らかのエラーが出てくれれば対処の仕方も考えられるが、正常には動いてリザルトは空という状態になる。

だが、あいまい検索を行うLIKE句を使えばパターンマッチになるので、リザルトを得ることができるので、なんだか不思議な状態になってしまうのだ。

改行制御文字を削除してINSERT

そこで俺は改行を取り去ってINSERTすることにした。下手くそで恥ずかしいが、今回の事例に対処したINSERT用のコードだ。

処理上、1行ごとに処理を分けたかったので分岐しているが、実際に改行を削除しているのはstr_replace関数だ。

str_replaceにありえる改行のエスケープシークエンスを設定して、空白(”)に入れ替えている。関数の名前もそうであるように、削除というよりリプレースと言ったほうが正しいか。

class InsertDictionary 
{ 
private $db;
/**
 * 偶数?
 *
 * @param integer $number 
 * @return bool
 */
private function inspectEven($number)
{
    $mod = $number % 2;
    if($mod === 0){
        return true;
    }elseif($mod !== 0){
        return false;
    }
}
/**
 * データベースに突っ込む
 */
public function __construct()
{
    $this->db = new ClassOperatingDB();
    $sqlpre = 
    $this->db->getDbhandle()->prepare
    (
       " 
        insert ignore into
        transwords(id,enword,transword) 
        values(:id, :enword, :transword)
       " 
    );
    $sqlpre->bindParam(':id',$id);
    $sqlpre->bindParam(':enword',$enword);
    $sqlpre->bindParam(':transword',$transword);

    $handle = fopen("hoge.txt", "r");
    if ($handle){
        while (($buffer = fgets($handle)) !== false) {
            static $i = 1;
            static $j = 0;
            if ($this->inspectEven($i)) {
                $transword = str_replace
                    (
                        array("\r\n", "\r", "\n"),
                        '',
                        $buffer
                    ); 
                $sqlpre->execute(); 
                $j++;
                echo "{$j}行目インサート<br>";
            }else{
                $enword = str_replace
                    (
                        array("\r\n", "\r", "\n"),
                        '',
                        $buffer
                    ); 
            }
            $i++;
        }
        fclose($handle);
        echo "インサート終了 行数".$j."行";
    }
  }
}

まとめ

いやハマったハマった。何回もINSERTしまくってやっと原因を突き止めたような感じだ。今回の作業が納期が差し迫った仕事の件でなくてよかった。

作業では10万行INSERTする必要があり、貧乏ゆえの悲しさか開発用の仮想マシンもしょぼいので試行錯誤にもかなり時間がかかってしまった。

ともかく改行事件に悩まされた数時間を取り戻したい。

9割方無職だから暇はいっぱいあるのだが。

adminerでセッションパスのパーミッションエラーが出る原因と解消方法

adminerで思わぬエラーがでる

パーミッションエラー

adminerはデータベース管理ツールで、phpMyAdminとは違い、1ファイルで設置できることから手軽に扱えるとして人気がある。俺もたいていはadminerを使うようにしている。

さてそんなadminerで以下のようなPHPエラーが表示されることがある。

Warning: session_start(): open(/var/lib/php/session/sess_dm531g7qh2rhbfp5phctj1p9g7, O_RDWR) failed: Permission denied (13) in /var/www/html/test/am422.php on line 68 Warning: session_write_close(): open(/var/lib/php/session/sess_dm531g7qh2rhbfp5phctj1p9g7, O_RDWR) failed: Permission denied (13) in /var/www/html/test/am422.php on line 69 Warning: session_write_close(): Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/var/lib/php/session) in /var/www/html/test/am422.php on line 69

このエラーが大量に出る場合はPHPセッションパスに対して書き込み権限がないユーザがhttpdを動かしている可能性が高いと思われる。

一応このエラーがでてもadminer自体は使えないことはない。

しかしadminerではセッションを使うことで正しく動くので、このままでは気持ちが悪い。

adminerでのパーミッションエラー対処法

対処法を確認しておこう。

PHPセッションパスはphp.iniの以下の行で指定されている(以下はCentOSの場合)。

/etc/php.ini
1268  session.save_path = "/var/lib/php/session"

デフォルトではこのディレクトリは所有者root:グループapacheになっているはずだ。またパーミッションは770になっているはずである。

ここのパスに対してhttpdの実行ユーザの書き込み権限がないと、セッションを扱うことができない。

通常httpdがデフォルトで設定しているユーザ(apache)から、何らかの理由でユーザやグループを変更した場合、各ディレクトリに対しての権限に不都合が起こる場合がある。

今回の例の原因はまさにそれだ。

httpdの実行ユーザを修正するには、httpd.confの244行目と245行目にユーザとグループの設定を確認しよう。

/etc/httpd/conf/httpd.conf
244  User apache                                                                                                                                    
245  Group apache

この設定を確認して、ユーザやグループを変更してしまっていないかを確認しよう。

もし何らかの作業で変更して元に戻していない場合は、デフォルトのユーザ、グループにすることで、adminerのセッションディレクトリパーミッションエラーは消えるはずだ。

まとめ

できるかぎりはデフォルト設定のユーザで動かした方が、トラブルは少ないと思う。

またセッション用のディレクトリを変えてしまっていた場合も、同様に権限エラーが出る場合が多い。

もし、大して理由もないなら、ユーザ、グループの変更やディレクトリ指定の変更はよしておいたほうがよさそうだ。

思わぬ不具合に直面してしまうかもしれない。

ローカルでdebパッケージをインストールするとき依存関係も解決してくれる「gdebi」

ローカルにダウンロードしたパッケージをインストールしたい

依存関係の解決

ローカル保存したdebパッケージをインストールする際にはdpkgコマンドを使うかとおもうが、このコマンドは依存関係の解決をしてくれない。

依存関係とはインストールしようとしているアプリケーションが依存するライブラリなどのことを指す。

依存するライブラリがなければアプリケーションは一部の機能が使えなかったり、正常には動かない。

依存するライブラリ等が少なければいいが、かなり多いアプリケーションもあり、俺のような初心者には依存関係の解消は少し荷が重たいので、できれば自動的に依存ライブラリ等のインストールをしてもらいたいものだ。

たしかにリポジトリからatp等でパッケージインストールすれば、依存関係に対処しながらインストールを進めてくれる。

リポジトリのパッケージではダメな場合もある

しかし開発環境などの兼ね合いで、どうしてもリポジトリのパッケージバージョンが低く、別途ローカルに最新版パッケージをダウンロードしてインストールしなければならない場合がある。

dpkgでも依存関係のインフォメーションは出してくれるのでそれをガイドにして、手動でインストールしていくことはできるが、やはり手間であることは変わらない。

ローカルインストールでも依存関係を解決できる

依存関係の解消を自動で行ってくれる

そこでdebian/ubuntu系を使っている人にはgdebiの出番である。ローカル保存したdebパッケージを依存関係を解決してインストールしてくれる。

リポジトリには最新版パッケージはないが、アプリケーション開発元のサイトに最新版パッケージが置かれていることはよくあることだ。

ローカルパッケージをインストールする際に、gdebiを使うとしあわせになれるだろう。

インストール

使うにはまずインストールが必要だ。

# sudo apt install gdebi -y

これでgdebiが使えるようになる。

使い方

使い方は簡単で、以下のように書けばいい。

># sudo gdebi hoge.deb

manページを見てもらえばわかるが、オプションもほとんどなく、簡素なコマンドだ。ほとんどパッケージを指定するだけと言ってもいい。

まとめ

依存関係のトラブル解消はLINUXを使う上で欠かせないスキルだとおもうので、むしろ手動で依存関係を解決したほうが勉強にはなるかもしれぬ。

しかしあまりこれらのトラブルで時間をとられると、本来の作業ができず本末転倒だ。

まずはgdebiなどの便利ツールを使いながら徐々に慣れるといいかもしれない。

MarkDown変換コマンドラインツール「Pandoc」を使う

Pandoc

Markdown形式で記述したファイルをhtmlに変換してくれるツール。

コマンドラインツールなのでlinuxで作業する人にはうってつけかもしれない。

使用方法

導入方法などは本家ページを参照していただくのが良いと思うが、簡単に導入と使い方の説明をしたい。

本家サイト

Pandoc
http://pandoc.org/index.html

インストール

debian/ubuntu系なら以下でインストールできる。

#apt install pandoc -y

対応フォーマット

Markdown→htmlだけでなく、さまざまなフォーマットへの変換を行ってくれるので、とても便利。

入力形式
markdown
reStructuredText
textile
HTML
DocBook
LaTeX
MediaWiki markup
TWiki markup
OPML
Emacs Org-Mode
Txt2Tags
Microsoft Word docx
EPUB
Haddock markup
出力形式
XHTML
HTML5
HTML slide shows using Slidy
reveal.js
Slideous
S5
DZSlides
Microsoft Word docx
OpenOffice/LibreOffice ODT
OpenDocument XML
EPUB version 2 or 3
FictionBook2
DocBook
GNU TexInfo
Groff man pages
Haddock markup
InDesign ICML
OPML
LaTeX
ConTeXt
LaTeX Beamer slides
PDF
Markdown (including CommonMark)
reStructuredText
AsciiDoc
MediaWiki markup
DokuWiki markup
Emacs Org-Mode
Textile

正直俺はいくつかのフォーマットは知らないし、ほとんどMarkdown→html(かその逆)しか使わないが、これだけ変換対応があれば万人が使えるツールだと思う。

変換方法

操作はとても簡単だ。基本的には変換したいファイルと変換後のファイルフォーマットを指定すればよい。例えばmarkdownからhtmlに変換したい場合は以下だ。

# pandoc -f markdown -t html hoge.md

以下のようにも書ける

# pandoc --from markdown -to html hoge.md

これで変換された文章がコマンドラインに表示されることになる。標準出力なのでパイプ等で他のコマンドに引き渡しが可能だ。

また標準出力ではなく、ファイルに保存したい場合は以下のように書けば良い。

# pandoc -f markdown -to html -o hoge.html hoge.md

-oは–outputオプションの短縮形で、出力先ファイルを指定するものだ。変換したhtmlはhoge.htmlに保存されることになる。

markdownの方言対応

markdownは本家の更新保守が止まっており、さまざまな団体などで方言的な拡張版が作られている。

そのため、単にmarkdownと言ってもひとつには定まっていない現状がある。

もしかしたら意図したとおりの変換が行えない場合もあるかもしれない。

pandocではmarkdownフォーマットを指定に以下のように細かく指定することが可能だ。

指定フォーマット 意味
markdown pandoc拡張版
markdown_strict 本家markdown
markdown_phpextra PHP Markdown拡張版
markdown_github github拡張版

お使いのmarkdownフォーマット方言を指定することで、意図した変換が行えるようになるかと思う。

もちろんpandocの機能はこれだけでなく、さまざまなオプションが存在している。詳しくはmanページを参照することをおすすめする。

また、英語が苦手な人には、日本語翻訳マニュアルサイトを紹介しておきたい。

Japanese Pandoc User’s Association
http://sky-y.github.io/site-pandoc-jp/

まとめ

最近ではメモなどの小さいものから、ブログ記事などの長めのものまで、ほとんどmarkdownで書くようになってしまった。

プレーンテキストとちがって、文章を整理しながら書けるのはとても気持ちがいい。

Linuxで日本語名ディレクトリを英語名に変換する方法

Linuxディレクトリの日本語を英語に変える

本記事での環境はdebian GNU/Linux 8(jessie)を想定して進めたいと思う。

日本語環境でのインストール

Linuxを日本語でインストールすると、/home以下のディレクトリが日本語になる。

デスクトップ用途としては便利で問題はないのだが、コマンドラインで作業する際にはディレクトリを打ちにくくて、日本語名の存在は煩わしい。

かと言ってこれらのディレクトリを手動でリネームしてしまうと、いろいろ不具合が生じてしまう(らしい)。

ディレクトリ名を英語に変えるコマンド

その1

そこでこれはコマンドから修正するのが正しいやり方のようだ。

$ LC_ALL=C xdg-user-dirs-gdk-update

xdg-user-dirs-updateというコマンドもあるが、これはCUI版だ(と思う) xdg-user-dirs-gdk-updateはGUI版なので、実行時にGUIダイアログが出る。

その2

$ LANG=C xdg-user-dirs-gdk-update

と言った方法も紹介されているのだが、どうもうまく行かない場合もあるようだ。

LANG=Cの罠

LANG=Cはデフォルトロケール(国地域ごとの言語や単位などのまとまり)を意味するので、英語がデフォルトロケールだと「英語」でディレクトリ名を変えるということになる。

しかし昨今のlinuxでは日本語環境がデフォルトである場合が増えてきており、LANG=Cが日本語を意味することもあるようだ。

ロケールを確認してみる

現在のロケールの確認

localeコマンドで確かめてみる。このコマンドは現在のロケールを調べるコマンドだ。rootではなく、対象となる作業用ユーザで大丈夫だ。

$ locale

俺の環境では以下のように表示された。

LANG=ja_JP.UTF-8LANGUAGE=
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC=ja_JP.utf8
LC_TIME=ja_JP.utf8
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY=ja_JP.utf8
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER=ja_JP.utf8
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT=ja_JP.utf8
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=

利用可能なロケールの確認

利用可能なロケールを確認するには-aオプションをつけて実行

$ locale -a

俺の環境では以下のように表示された

C
C.UTF-8
POSIX
ja_JP.utf8

Cという怪しいロケールがあるが、LANG=CのCはこのことだ。

各環境変数でのdateコマンドの表示の確認

さらに俺の環境で各環境変数がどのように表示されるか試してみた。

$ LANGUAGE=C date
2015年  6月 15日 月曜日 13:47:02 JST

$ LANG=C date
2015年  6月 15日 月曜日 13:47:09 JST

$ LANG=POSIX date
2015年  6月 15日 月曜日 13:47:48 JST

$ LC_ALL=C date
Mon Jun 15 13:49:21 JST 2015

実際に変更してみる

こういった結果だったので、英語にするにはLC_ALL=Cを使う必要があるのかと思ったが、

$ LANG=C xdg-user-dirs-gtk-update

としても以下のように英語名での変換を行ってくれる旨のダイアログが表示された。

 xdg-user-dirs-gtk-update
Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged.
Moving DESKTOP directory from デスクトップ to Desktop
Moving DOWNLOAD directory from ダウンロード to Downloads
Moving TEMPLATES directory from テンプレート to Templates
Moving PUBLICSHARE directory from 公開 to Public
Moving DOCUMENTS directory from ドキュメント to Documents
Moving MUSIC directory from 音楽 to Music
Moving PICTURES directory from 画像 to Pictures
Moving VIDEOS directory from ビデオ to Videos

(なにやらエラー的なメッセージがあるが、これはwindowの生成に関するメッセージで本件とは直接関わりがないので、無視する。)

もちろん

$ LC_ALL=C xdg-user-dirs-gtk-update

でも実行できた。

xdg-user-dirs-gtk-updateコマンドの実行後は念のためログインしなおす。

するとログイン時に以下のようなダイアログが表示されるかもしれない。

 xdg-user-dirs-gtk-update

ログインし直すと、再度日本語ロケールになるので、日本語名ディレクトリにするか聞いてくるのだ。親切ではあるが、今回の場合は遠慮しておきたい。

というわけでこれは、「次回から表示しない」にチェックをつけた上で、「古い名前のままにする」を選択しよう。

こうすることで英語名ディレクトリとして固定されることになる。

ディレクトリを確認してみる

lsコマンドを実行してみる。

$ ls
Desktop  Downloads  Pictures  Templates  ダウンロード  画像
Documents  Music    Public    Videos   ドキュメント

あれ?日本語のディレクトリが存在している。

どうやら日本語ディレクトリの状態でファイル等が入っていた場合、それらは直接的に変換されず、新たに英語名ディレクトリが作られるようだ。

俺の環境ではダウンロード、画像、ドキュメントにファイルが入っていたので、残ったわけだ。だから残り方は人によって様々だろうと思う。

もし日本語ディレクトリが残ったら、英語名のディレクトリには手動でファイルを移さないといけないようだ。ちょっと面倒だが仕方あるまい。

ファイルを移したあとは日本語ディレクトリはもう必要ないので、消してしまって構わない。

まとめ

俺のようにlinuxを使い始めて間もないユーザには日本語環境はありがたいものだが、コマンドラインを使って作業をすることがあるのなら、日本語環境に拘泥しないほうがいいかもしれない。

コマンドラインで日本語ディレクトリパスを入力するのが本当に面倒だ。

パスのTAB補完も日本語で入れないといけないのでなんとも使いにくい。

いつも思うが、もっと英語に不自由しないようになりたかったな。

生半可にデュアルブートをしてはいけない

ひどい目にあった。

最近、開発環境に不満を覚え始めていたので、思い切ってベースOSをLinuxにしようと思い立った。

昨今のwebフロントエンド開発には数多くのツールが必要になってきている。これらのツールはインストールや設定などをCUIでやることが多い。

必然的にwindowsのコマンドプロンプトでは対応できないので、cygwinなどのUNIXライク環境ソフトを入れざるを得ない。

cygwinは良いソフトだと思うが、疑似環境なのでどうしてもフラストレーションがたまる場面もある。

プログラミング環境はwindowsという人はかなり多いと思うが、webフロントエンド開発よりの人がMacを使うのもうなずける。

9割がた無職なフリーランスの俺としては、オサレMacを買う予算を捻出できるわけもなく、Linuxが選択肢になるわけだ。

俺はASUSのK55VDという廉価なノートを使っている。windows8がプリンストールされており、自分で8.1にバージョンアップしてある。

この記事を書いている頃はwindows10のアップデート予約が始まっていたりして、windows関係のニュースが多い。

最初は硬派にプリンストールされているwindowsをきれいさっぱり消して(もちろんデータはバックアップするが)、Linuxだけにしようかとも思ったのだが、windows10は無償バージョンアップだし、消してしまうのはもったいない気がして、それならデュアルブートすればいいかと思い立ったのである。

かなり昔にデュアルブートをやった記憶があり、「BIOSで切り替えればいいんでしょ」とCentOS7をほとんどなんの予備知識もなしに、インストールしてしまった。

インストールはつつがなく進み、特にエラーも出ずに終わった。

しかし結果ご想像通り、windowsもLinuxも起動しなくなったのである。というかBIOSのBootデバイスの選択肢にHDDが現れないのである。

最近ではBIOSではなく、UEFIファームウェアというものが初期起動をつかさどっているらしく、このUEFIの形式でHDDをパーティション、フォーマットしたりしなくてはいけないらしい。

確かに俺のノートもBIOSかと思っていたらUEFIファームウェア(に対応しているBIOSといったほうがいいのか?)が入っていた。いつもの青い画面だからてっきりBIOSだと思っていた。

windowsはUEFI形式でインストールされていたが、CentOS7は旧来のBios形式でインストールしてしまったという体になるようだ。

それから約4日かけて、HDDの復旧方法を調べたり実践したりしてみたが、どうにも治らず。

馬鹿なことにwindowsの起動ディスクもつくっていなかったから事態は深刻だった。

俺のノートはインストールメディアが付属してないので、本来はリカバリー領域をつかって修復・再インストールをするのだが、起動ディスクがないためにそれもできず。

いろいろ悩んだ挙句、Microsoftから8.1のインストールメディアをダウンロードできることがわかり、windows8.1を入れなおすことにした。

今回のことで昨今のOS起動についてそれなりに詳しくなれたし、Linuxの知識もいろいろ得られたので、無駄な時間だったとは思わないが、見慣れないテクニカルタームとCUIでの作業で知恵熱が出そうな感じだ。

HDDは別の1TBの外付けHDDにクローンとして保存しているので、もし知識がついてきたらまた復旧をやってみたいとは思う。

Gitの最低限必要な基礎知識

Gitを導入したい

本記事はGitを導入しようと思うが、コマンドラインに慣れていない、バージョン管理システムを使うのは初めてだという人向けに書いたつもりだ。

また主に1人でGitを使うことを想定している。あまり範囲が広いと理解が困難になるので、説明は1人で使うにあたって必要最低限の要件に収めているつもりだ。

Gitとは

Gitは分散型バージョン管理システム

バージョン管理システムは開発の履歴を管理することができる仕組みのことだ。

管理対象はどんな種類のプログラミング言語・プロジェクトでも構わない(はず)。だからwebサイト開発(html,css,js,phpなど)だろうが、アプリ開発(swift,Ruby,python等)だろうが、組み込み開発(C/C++,java等)だろうが、会計システム開発(cobol等)だろうが管理可能だ。

バージョン管理システムでプロジェクトを管理すると、どこでどのようにどんな変更・修正が加えられたのかが整理され、前のバージョンに容易に戻ることもできるので効率よく、かつソースコードに混乱をきたさなくて済むようになっている。

Gitは分散バージョン管理システムと呼ばれる。Gitではリポジトリ(後述)からローカルにリポジトリクローンを得ることで作業を進める。開発者ごとにリポジトリが分散するので分散型と呼ばれる所以である。Gitは分散型であるからこその恩恵を受けやすいバージョン管理システムだ。

Gitが作られた経緯

GitはLinuxのカーネル開発を効率的に行う為に生み出された。もともとLinuxのカーネル開発には別なシステムバージョン管理システムが使われていたのだそうだ。

linux git

しかし、既存のバージョン管理システムでは具合が悪かったようで、新たにバージョン管理システムを生み出す必要がでてきた。そこでLinuxの開発者であるリーナストーバル氏が新しいバージョン管理システムを開発するに至る。

この辺の経緯はwikipediaに詳しくあるので参照をお勧めする。wikipedia Git

カーネル開発となるとプロジェクトも巨大で、もちろん1人ではできない。複数の凄腕たちがプロジェクトに参加することになる。 Linuxカーネル開発のようなオープンソース開発では全世界中に開発者がいることになる。

Gitは大人数で巨大なソースコードプロジェクトを効率的に共有する為に開発されたということになる。

カーネル開発というある意味最前線で稼働していること、GitHubなどのホスティングサービスが出現したこと、オープンソースであり個人での扱いやすさも相まって急速にGitの利用が広まったというのが現在の状況のようだ。

Gitの概念

リポジトリの意味

リポジトリには倉庫とか保管所とかという意味がある。具体的にはファイル置場という解釈でよいかと思う。

Gitではさまざまな仕組みで履歴やファイルを格納してバージョンを管理する仕組みを提供するが、それらは具体的にディレクトリ構造と複数の種類のファイルの関連性で実現されている。そのことをGitリポジトリというのである。

分散型の所以リモートリポジトリとローカルリポジトリ

Gitは分散型バージョン管理システムだと説明したが、その分散型について説明しておきたい。

中央集権的な仕組みのバージョン管理システムではリポジトリは一つしか存在しない。開発者全員でそのリポジトリにアクセスすることになる。

普通このような共有するリポジトリはサーバー上などに設置されるので、リモートリポジトリと呼ばれる。リモートリポジトリはマスターのような扱いになる。

Gitの場合でもリモートリポジトリが存在するのは変わらないが、リポジトリのコピーをローカルにもってくることができる。そのコピー作業をクローンといい、ローカルにもってきたリポジトリはローカルリポジトリと呼ばれる。

ローカルリポジトリで各開発者が納得のいくまで作業ができるようになっており、マスターとなるリモートリポジトリを不用意に汚すことを回避できるようになっている。

リモートリポジトリへローカルリポジトリの内容を送る「プッシュ」

ローカルリポジトリで作業を進めることになるので、作業が進めば進むほど、リモートリポジトリとローカルリポジトリの内容には差異がでてくる。

Gitではローカルリポジトリの内容をリモートリポジトリに同期することができる。その操作の事をプッシュという。プッシュを行うとローカルリポジトリとリモートリポジトリは同じ内容になるという訳だ。

リモートリポジトリの内容をローカルリポジトリに持ってくる「プル」

複数の開発者で作業を進めていると誰かがプッシュをしているかもしれない。だからリモートリポジトリの更新された内容をローカルに持ってくることもできる。その操作をプルという。

誰かがプッシュをすれば、リモートリポジトリには自分以外の人の変更点が付け加えられていることになる。その変更点をローカルにも取り込んでおかないと、開発の整合性が保てなくなってしまうかもしれない。

このようにGitではリモートリポジトリとローカルリポジトリの間でやり取りをしながら開発作業を進めていくことになる。

ローカルリポジトリだけも管理は可能だが…

ただ、1人で開発する場合はローカルリポジトリだけでもバージョン管理が可能だ。1人しかいなければ共有するリモートリポジトリが必要ないからだ。

しかしながらバックアップ的観点からしても、1人開発のときでもリモートとローカルの活用は有意義なことだと思う。

リポジトリの中身

リポジトリの実体はディレクトリとファイルだと説明した。

Gitではリポジトリと作るとその場所に.gitというディレクトリが拵えられる。このフォルダの中にいろいろな情報やファイルがGitで定められている方法に従って格納されるようになっている。

Gitディレクトリの各ディレクトリもしくはファイルの大まかな意味を説明したのが以下だ。作業を進めるとさらにファイルや各ディレクトリに子ディレクトリが出来たりする。(※以下はNetBeansIDEでjGitを使用した場合のディレクトリ・ファイル構成となる。Gitそのものとは若干構成が異なるかもしれない)

名前 種類 意味
.git ディレクトリ ルートディレクトリ
branches ディレクトリ ブランチの情報を格納
hooks ディレクトリ フックスクリプトを格納
logs ディレクトリ リポジトリ操作のログを格納
objects ディレクトリ Gitオブジェクトを格納
refs ディレクトリ リファレンスを格納
config ファイル リポジトリの設定要件
index ファイル コミット予定のファイル表
HEAD ファイル 現在の作業対象ブランチを示すhead

 Gitを使うようになれば追々調べたりすることになると思うが、今の段階ではすべてのディレクトリやファイルの意味を理解しなくてもいい。とりあえず大事だと思われる部分を説明していきたい。

履歴やファイルそのものである「Gitオブジェクト」

Gitでは開発履歴やプロジェクトファイルをオブジェクトファイルとして格納する。そのことを総称してGitオブジェクトと言う。

Gitオブジェクトは4種類のみだ

名前 役割
commitオブジェクト 履歴情報をファイル化したもの。コミットという操作をすると作られる。
Blobオブジェクト プロジェクトファイルをBinary Large Objectという方式でファイルにしたもの。ステージングもしくはコミットという操作をすると作られる。
Treeオブジェクト ディレクトリ構造をファイル化したもの。コミットという操作をすると作られる。
Tagオブジェクト 名前付けに使うタグの情報をファイル化したもの。タグを制作すると作られる

4種類しかないが、これらのオブジェクトファイルの絶妙な関連性でうまいこと開発履歴が管理できるようになっているのである。

Gitオブジェクトが作られる操作

履歴を残す「コミット」

履歴を残す操作をコミットという。

開発作業が進むと区切りの必要性が出てくる。一定の作業量や、機能搭載の目途など履歴を残したほうがいいなというタイミングはいろいろな哲学があるが(なるべく整理されたコミットをすることが望まれる)、コミットすることはまさに履歴を残していくことに他ならないので、Gitを使う上でもっとも根幹をなす操作の一つだ。

コミットでcommitオブジェクト(ファイル)が生成格納される。また同時にTreeオブジェクトが生成格納される。

コミットの準備「ステージング」

ステージングを行うとblobオブジェクト(ファイル)が生成格納される(ステージングを行わずコミットした場合はコミット時にblobオブジェクトが生成格納される)。

コミットはステージングを行わなくとも直に行うことができるが、コミットの前にステージングする方が一般的だ。

ステージングはコミットする予定のファイルをblobオブジェクトファイルにして、先にリポジトリへアップロードする。

また.gitディレクトリ直下のindexというファイルに、予定に上がったblobオブジェクトファイルのID(後述)が記述される。

このindexファイルを確認することで、コミットを正確に行うことができるという訳だ。「しまった今回のコミットに必要だったファイルを含めていなかった」などということを回避することができる。

Gitオブジェクトが格納されている場所

objectsディレクトリ

Gitオブジェクトはobjectsディレクトリに格納される。各オブジェクトは基本的に英数字名のフォルダに一つのファイルが一つのオブジェクトとして格納されている。

objects-dir

Gitオブジェクトファイルの名前

各オブジェクトファイルには長い英数字の名前付けられている。この長いファイル名はファイルの内容を元にSHA-1という関数で計算され付けられる。

Gitオブジェクトのファイル名はIDやハッシュ値と呼ばれ、オブジェクトを一意に特定するために使われる。つまりファイル名がユニークになるというわけだ。

object-id

この仕組みにより、内容が異なるオブジェクトファイルは同じIDにならない。逆に同じ内容のオブジェクトファイルはどこでオブジェクトが生成されても必ず同じIDになるようになっている。

履歴を形づくる「Gitオブジェクト」の役割

先ほどの章で簡単に各Gitオブジェクトの意味を紹介したが、もう少し詳しく見ていきたいと思う。

Gitオブジェクトの関連性

実はオブジェクトファイルを見ただけではどの種類のオブジェクトなのか見分けが付かない。ファイルを開いてみてもバイナリだから(バイナリエディタなどで見ないと)よくわからない。

ごちゃまぜに格納されているようなものだ。見分けがつかないのにどのように整理をしているのだろうか?

Gitオブジェクトは各個に与えられた役割により、他のオブジェクトと関連性を構築している。これは別に難しいことではなく、ちょっとした伝言ゲーム的な管理方法になっているのだ。前の章で説明したオブジェクトのIDが重要な要素を握っている。

まず重要なのがcommitオブジェクトだ。前の章でcommitオブジェクトはコミットという操作で作られると説明した。先に説明したとおりコミットとは履歴を残す操作のことだ。

だからcommitオブジェクトがある時点での履歴そのものと言える。commitオブジェクトファイルには以下のような事が書かれている。

commit-object

そうcommitオブジェクトファイルにはコミットに関係しているオブジェクトファイルのIDが記載されているだけなのだ。

頼りない感じがするが、commitオブジェクトファイルの内容から、コミットに関係するTreeオブジェクトとBlobオブジェクトを特定することができるようになっているという寸法だ。

commitオブジェクトと他のオブジェクトとの関連性を図にしてみた。

git-object

commitオブジェクトは自分の親の(前の)commitオブジェクトのIDを持っている。

そしてディレクトリ構造情報が書かれているTreeオブジェクトのIDを持っている。

さらにTreeオブジェクトはディレクトリとディレクトリに属するBlobオブジェクトファイルの情報持っている。


これでcommitオブジェクトは自分と連続性のあるcommitオブジェクトを辿ることができ、かつ自分に関係しているオブジェクトを特定することができるのだ。

つまりcommitオブジェクト→Treeオブジェクト→Blobオブジェクトという参照の流れがGitの履歴管理の基本と言える。IDが一意で定まるので危険なく単純な伝言ゲームで管理が出来てしまう。

イメージとしてはツリー構造と捉えるといいかもしれない。

object-tree

コミットがある時点での履歴、コミットのつながりが開発履歴の流れという事になる。重要な概念なのでしっかり理解できるようにしておきたい。

開発ストリームを意味する「ブランチ」

ブランチの意味

Gitにはもう一つ重要な概念がある。それはブランチだ。遅めの朝食のことではない。綴りはbranchで意味は「分岐」だ。Git上ではブランチを開発ストリームとする。

リポジトリを初期化し、最初のコミットがされるとmasterブランチが作られる。masterは元になる開発ストリームだ。ブランチはコミットをぶら下げておく竿のようなものとイメージしてもらうといいかもしれない。

branch-master

ブランチを作る

最初に説明したとおりブランチは分岐ということで、開発ストリームを分岐することができる。

ブランチは安全にかつスマートに日々の開発を進める為にも使えるので使用頻度の高い機能だ。(ブランチという言葉は分割するという動詞的な意味で使われる場合も、ブランチされたストリーム自体を指すこともあるので注意)

branch-branch

たとえばmasterブランチからnewブランチを分岐させたとしよう。分岐させた後はそれぞれのブランチで作業(コミット等)が進むので、互いのブランチに影響を与えない。

branch-commit

これを活用すればmasterブランチを汚さずに開発を進めることができる。

ブランチをマージする

分岐できるなら統合もできる。Gitではブランチを統合する際にはマージ(merge)という機能をつかう。開発の流れを一本化するのだ。

branch-commit

マージすると、それぞれのブランチでのコミットが共有されるようになる。例ではnewブランチでのコミットがmasterブランチと関連を持つようになる。(マージしてもnewブランチは別途削除の操作をしない限り無くなるわけではない)

マージしたことも履歴として残すべきなので、通常マージコミットというコミットが作られる。このマージコミット以前はmasterブランチもnewブランチも同じコミットを共有しているということになる。

マージ後のブランチの削除

マージした後にマージ元のブランチを削除してもコミットが失われることは無い。

branch-marge-del

現在位置を特定する「リファレンス」と「ヘッド」

いままでGitオブジェクトとブランチを説明してきた。今度は作業対象を指し示すリファレンス(reference)とヘッド(HEAD)についてだ。

Gitでは多数のブランチやコミットが存在することになる。自分が今どこを対象に作業を行っているかわからなくなると大変だ。その為の目印がリファレンスとヘッドなのである。

リファレンス

リファレンスとは各ブランチにひとつづつ存在する、ブランチの先頭のコミットを指し示す情報ファイルだ。このファイルには単純に先頭のcommitオブジェクトのIDが記述されている。(正確に言うとフォルダ名+ID)

ref

コミットがされる度にリファレンスファイルは書き換えられ、常にそのブランチの先頭コミットを指し示すようになっている。

リファレンスファイルは.git→refs→headsの中に、ブランチの名前がファイル名となって格納されている。

ref-commit

HEAD

さらにリファレンスを参照するHEADという特殊なリファレンスがリポジトリに一つ存在している。HEADは現在の作業対象となっているブランチを指し示すリファレンスだ。

head

HEADファイルは.gitディレクトリ直下にHEADというファイル名で格納されている。

具体的には作業対象のブランチのリファレンスを指し示している。HEADファイルには単純にリファレンスファイルのパスが記述されているだけだ。

作業しているブランチを切り替えることを「〇○ブランチにチェックアウトする」という。チェックアウトとするとHEADファイルはチェックアウト先のブランチのリファレンスを指し示すことになる。

head-checkout

リファレンスとHEADのおかげでどこが先頭か、今はどこが作業対象なのかが判るようになっている。

まとめ

振り返りしてみる

今回の記事について振り返ってみる

  • Gitは分散型バージョン管理システムである
  • GitはLiunxのカーネル開発の為に生み出された
  • リポジトリとはファイル置場である。
  • Gitリポジトリの実体は.gitディレクトリをルートとするディレクトリ構造とファイル群
  • 開発履歴やプロジェクトファイルを意味するGitオブジェクトファイルは4種類
  • Gitオブジェクトの名前はハッシュ値でIDとして活用される
  • Gitオブジェクトはコミット・ステージングなどの操作で生成される
  • commitオブジェクトは履歴そのもので、内容を辿ればそのコミットに関係するオブジェクトを特定することができる
  • 開発ストリームをブランチとして分岐させる事が出来る
  • 分岐後、コミットは各ブランチにて行われ関連性を持たない
  • ブランチはマージ(統合)する事が出来る
  • マージされた後のブランチは削除されてもコミットが消えることはない
  • ブランチの先頭を指し示すのがリファレンス
  • 作業対象のブランチを指し示すのがHEAD

ざっとGitの概念を説明してきた。最初にも言及したとおり必要最低限のつもりなのだが、実のところまだ言及したりない部分がある。Gitにはもっと多くの要素があるが、実際に使ってみながらでないと説明しにくい点も多い。

おそらく初めてGitに関するこの手の記事を読まれた方は「なんのこっちゃ」と思うところも多いのではないか?

厳密に言えば間違っている部分があるかもしれないので、鵜呑みにはしないでほしいが、なるべく簡単に理解できるよう図をたくさん作る努力はしたつもりだ。

「なんのこっちゃ」が「なんだこのことか」に変っていただけたら嬉しい。

cssファイル・jsファイルを埋め込む!wordpressサイトのスピードアップ作戦!

速度が大事

Googleがサイト評価の指標としてサイトの速度を採用したのは記憶に新しい。

SEOの面から考えてもサイト速度は無視すべからざる項目となっている。

またSEOを抜きにしても、サイトが重鈍であるというのは、せっかくアクセスしてくれたユーザの離脱を招くものであり、サイトの価値を下げる要因になり兼ねない。

そこで今回はwordpressを使った際に、サイトの速度アップに参考になるであろう方法を紹介したいと思う。

速度改善の項目

今回はgoogleが提供しているPageSpeed Insightsを使った場合に、修正勧告される項目

スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する

に焦点を当てたいと思う。

またブラウザ側ではchromeのデベロッパーツールにて数値の確認を行いたいと思う。

PageSpeed Insights

cssファイル・jsファイル等の読み込みに関して

headタグにjsライブラリや広告関係スクリプトの読み込み、cssの読み込みを行っていると大抵この勧告が出るだろう。

これらの弊害としては、ファイルが読み込まれるまで他の処理がストップするという点にある。結果的にページの表示が遅くなる原因なのだ。

解決方法としては、jsファイルであればasync等のオプションで非同期ローディングにする、footerで読み込ませる等が挙げられるが、ライブラリ等は読み込みのタイミングを制御するのが、難しい場合もある。

またcssファイルは通常非同期では読み込めないので、インライン記述による解決が提案されることが多い。 CSSファイルの場合、全体を遅延読み込みをするとサイトの見た目に対して致命的な影響を与えかねない。

すくなくとも見えている範囲のcssだけは読み込む必要がある。

PageSpeed Insightsでの説明でも「インラインにしたら?」と言っているが、インラインにすると記述箇所が分散して、開発がとっちらかるのが嫌で、いまいち踏み切れない人も多いのではないだろうか?

そこでjsファイルやcssファイルに細工を施し、wordpressなりの方法を提案してみたい。

テンプレートファイル機能の活用

wordpressでサイトを構築する場合、テンプレートファイルを使うことが多いかと思う。この仕組みをcssやjsファイルにも適応する方法だ。

主要なテンプレートファイルにはそれぞれ専用の関数が用意されているが、自作のテンプレートファイルを読み込む為に、get_template_part()を使うことができる。

つまり任意の位置にphpファイルを埋め込むことが可能なのだ。

jsのコードの場合、styleタグで前後を囲うことで、埋め込み用のphpファイルとすることができる。またcssもscriptタグで前後を囲うことで埋め込み用のphpファイルとして扱うことが可能だ。

そうして作ったphpファイルをget_template_part()で必要な場所に埋め込む。

このようなローディングを

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>テストページ</title>
        <link rel="stylesheet" href="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/style.css">
        <script src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/js/jquery-2.1.4.min.js" ></script>
        <script src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/js/jsfile01.js" ></script>
        <script src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/js/jsfile02.js" ></script>
        <script src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/js/jsfile03.js" ></script>
    </head>
    <body>
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img01.jpg">
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img02.jpg">
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img03.jpg">
    </body>
</html>

このように埋め込みにする形(cssとjsファイルはそれぞれphpファイルにしている)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>テストページ</title>
        <?php get_template_part("style"); ?>
        <?php get_template_part("jquery"); ?>
        <?php get_template_part("jsfile01"); ?>
        <?php get_template_part("jsfile02"); ?>
        <?php get_template_part("jsfile03"); ?>
    </head>
    <body>
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img01.jpg">
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img02.jpg">
        <img src="http://pecodrive.heteml.jp/1bit/wp-content/themes/1bit1/img/img03.jpg">
    </body>
</html>

概念としては図のような感じになるかと思う。

temp1

jQueryなどのライブラリもこのようにPHPファイルとしてラッピングしてしまうことで、get_template_part()をつかって埋め込むことができる。

(※jQueryはMITライセンスなので大丈夫だと思うが、ライブラリが採用しているライセンスによっては拡張子の変更やタグの追加だけでも、ライセンス違反になるものがあるかもしれないので、良く確認する必要があること、自己責任でお願いしたい。)

ブラウザ上の計測でどれだけ改善されるか

実際に見てもらえばわかりやすいかと思う。chromeのデベロッパーツールのNetWorkでの計測結果だ。

上の画像がcssファイル1つ、jsファイル3つ、jQueryライブラリ(圧縮)1つをlink、spriptタグで読み込んでいる通常のローディング方法(以下[ロード])で、 下の画像が同じファイルをテンプレートファイルで埋め込みした場合(以下[埋め込み])の結果だ。

普通のローディング

テンプレートファイル方式

[ロード]の場合

最初のリクエストでhtmlがレスポンスされ、そのあとにcssファイル、jsファイルのリクエスト・レスポンスが立て続けに起こっていることが判る。

get01

cssファイルは非同期通信でのリクエストではないため、このことがjsfile01~03のリクエストを遅延させているのも良く分かる。

get02

jQueryファイルは圧縮されているものを読み込ませているが、それでも80kB位あるので、それなりにContent Downloadが発生している。

get03

最終的にすべてのリクエストが得られるまでに887ms掛かっており、ページ読み込み完了までには1.38s掛かっている。

get04

[埋め込み]の場合

一方[埋め込み]は、リクエスト・レスポンスが一本しかない。最初のリクエストでcss、jsファイルを埋め込んでレスポンスしているので、一回のコネクションで終了しているのだ。

get05

jQueryなどを含んだ為、初回レスポンスの容量は80kBを超したが、リクエスト完了までに603msと[ロード]に比べ300ms程度も速度が改善している。

またページ読み込み完了までに701msと、[ロード]に比べ600ms以上の改善がされている。

get06

これは[ロード]の場合、cssファイルの読み込みを待ってからじゃないとレンダリングが開始できないのに対し、[埋め込み]は初回のレスポンス後すぐにレンダリングが開始できることによる差だ。

場合によっては1秒以上の差がつく可能性もある。

実際にPageSpeed Insightsで計測した場合には以下の様に点数が改善した。

速度改善

埋め込み側には「スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する」の修正勧告が無くなっているのが判るかと思う。

デメリットの整理

この方法は速度改善が出来る点においては有用だと思うが、デメリットも存在する。

埋め込む為、キャッシュが効かない。

ブラウザキャッシュは速度改善につながるのだが、この方法を用いると、当たり前だがjsファイルやcssファイルのキャッシュが効かない。

どのくらいの頻度でキャッシュ活用がされるかに関しては、そのサイトのユーザのアクセス状況によるものなので、何とも言えない。

新規アクセスが多いサイトなら、キャッシュよりも埋め込みの方が利点が多いかもしれないが、リピーターが多いサイトではキャッシュの方がいいのかもしれない。

サイトのアクセス状況をかんがみてどちらがいいのかを検討する必要はあるかもしれない。

レスポンスを一つにまとめることになるので、レスポンスボディは重くなる

これは上記のキャッシュの件ともかぶるが、本来キャッシュで済んでいたファイルを埋め込みしていることになる場合もあるので、明らかに無駄な容量になってしまう。

ただ、数十kb~百数十kb程度の世界では、容量増よりもコネクション数の増大の方が速度に対して負荷がかかるのではないかと思っている。

ファイルが増える

デメリットとなるかどうかは、サイトの制作状況に依るだろうが、管理するファイルが増えることになるので、やはり幾分か手間がかかると言えるだろう。

俺は部品分けでのサイト構築の方がメリットがあると思う(部品の再利用なども含め)が、ファイルが分かれることで開発しにくくなる人もいるかもしれない。

まとめ

デメリットも把握してもらった上ではあるが有用な場合もあるのではないかと思う。

この方法では動的に埋め込むファイルを変えることが出来ることや、ajaxとの相性も意外といいことはメリットとして挙げられるだろう。

あまり正規なやり方とは言えないが、実際に速度改善がなされるので、場合においては方法として検討してもいいのではないかと思う。

フレッツスポット(NTT-Spot)に接続するとSoftApが使えなくなる件

SoftApはとても便利

俺は貧乏なので図書館などで作業することが多いのだが、その図書館ではmobilepointとNTT-spot(フレッツスポット)が使える。

ほとんど一か所の図書館で作業するので、wimaxなどを契約するよりアクセスポイントの方がランニングコストが安くて俺にはぴったりだ。

windows7や8などにはSoftApという機能がある。簡単に言えば、パソコンをwifiアクセスポイントにしてしまう機能だ。これは公衆アクセスポイントと相性がいい。

webページなどの動作確認をするために、複数のデバイスを持ち歩いているので(といってもタブレット1台、スマホ2台だが)。これらのデバイスもネットにつなぐ必要がある。

mobilepointはweb認証方式なので、一台一台認証するのが意外と面倒だ。

そんなわけで親機PCにSoftApによってアクセスポイント化してデバイスをネットワークにつないでいた。こうすると認証が面倒ではないのでちょっと楽になっていた。

繋がんないよ

先日NTT-spot(フレッツスポット)を契約し使ってみたところ、速度が非常に良好なのでメインをNTTにするかと思った矢先、問題が起きた。

NTT-spotに接続した場合、SoftApを使った接続ではデバイスたちがネットにつながらなくなるのだ。親機PCしか繋がらない。

あれ?どうしてだろう?

もともとNTT-spotは1アカウントにつき一つの機器しか接続する事が出来ない。同時接続が出来ないのである。それはわかっていた。

しかし、SoftApなのでNTT-spotに接続しているのは親機となるPCであって、デバイスたちはPCに対して接続している訳だから、同時接続にはならないのでは?と思っていたのだが、違うんだろうか?

softap

より正確に言うと、デバイスは親機PCとはしっかり認証できており、つながっているが外に出れないという状態だ。

エラーを見る限り、どうもipの割り当てが上手くいっていないようである。

SoftAp時にはwindows機がDHCPサーバの役割も果たしてくれるはずなのだが…。

静的ipを与えてもダメで外に出れない。

mobilepoint接続時はあっさりと外に出れるので、SoftApの設定の間違いではないだろう。

ニッチな件だなこれは

俺の解釈ではSoftAp時には親機がルータになっているものと思っていた。違うのだろうか?

ip関係で同時接続問題が絡むとするならipマスカレード…だろうか?

まちがいなくNTT-spotの同時接続排除が原因ではあるだろう(NTTには何の恨みもないよ)と思うが、どうにもこの件はニッチなようで、ネットで調べてもなかなか解からない。

ダメならダメでしょうがないのだが、理由がわからないのがとても嫌だ。

どういった原因でできないのかをちゃんと知りたいと思う。

解決できる(もしくは納得できる)日はくるのだろうか?

ネットワークの勉強をもっとしようと思う。

wordpressでajax実践編[ページネーションをajaxで行ってみる]

概要

はじめに

今回の題材として、wordpressのページネーションをajaxで行ってみたいと思う。

スクロールが表示物の無くなる寸前くらいになった時にajax通信を開始して、新たに記事リストを追加表示していくという感じを想定している。

つまりスクロールしていれば勝手にすべての(クエリに合致した)記事が読み込まれていくということである。

どちらかといえば、モバイルの利便性に重点を置いた実装かと思う。

この記事の前に書いたwordpressで使ってみるajaxの基本も合わせて読んでいただけるとより網羅的になるかと思う。

また今回は前回の記事のようにwordpressが用意してくれている経路(admin-ajax.phpにリクエストを送る方法)を使うのではなく、独自ファイルを用意してajax通信を試みようと思う。

さらに前回と同じように、ネイティブjavascriptとjQueryのどちらともでコードを書いてみた。俺のコードは下手くそこの上ないが、一応ネイティブとjQueryを対比することが可能だ。

jQueryで書くにしてもネイティブの書き方を知っておくことでより理解が深まるのではないかと思う。

ネイティブでajaxを書いているサイトがほとんど見つからないので、もしかしたら有意義かもしれない。

概念図

まずは今回の概念を図にまとめてみた。

ajax

あまり見やすい図とは言えないが、今回のajaxがどんな風に成り立っているかがわかってもらえたら幸いだ。

本記事ではコードとその説明で進めていきたいと思うが、この図も合わせて参照してもらうと、より把握しやすいのではないかと思う。

デモ・すべてのコード

今回もデモページを用意してみた。条件分岐の都合上、別ドメインサイトではあるが、動作の確認をしてもらえるかと思う。

デモはネイティブで書かれた方jsファイルを使っている。

デモページ

またコード全体を記事面に載せるには長くうざったらしい感じがするし、ダウンロードもできるのでGitHubに置かせてもらうことにした。下手なコードをさらすのは勇気がいるが、煮るなり焼くなり好きにしてもらいたい。

デモコード(GitHub)

リクエストデータを用意する

まずはリクエストデータ関係に関して説明していきたい。今回の目的として記事リストをajaxによって得たいと言うことなので、その処理を進める為に、いくつかデータが必要になる。

対象となるファイルは[index.php]だ。

ajaxによるページネーションをどのように行うかで用意すべきデータも変わるのだと思うが、とりあえず今回は以下のようなリクエストデータが必要になった。

リクエストデータ一覧

今回用意するデータ 意味
flag ページ読み込みのフラグ
nonce セキュリティチェック用
is_single シングルページの判定
category カテゴリIDの取得とフラグ
tag タグIDの取得とフラグ
search 検索ワードの取得とフラグ
looplength ループの個数
registered 読み込み中画像(くるくる)の表示フラグ

「flag」

ajaxで記事リストをレスポンスしてもらうとき、どうしても残り個数を判定しなくてはならない。表示するものがないのにajax通信を何度も発生させてしまうことになるからだ。

そこでPHP側では常に表示すべき記事数の残りをカウントし、一回に表示する個数以下しか残っていない(今回の通信ですべて表示しつくす)場合は、出し尽くしフラグとしてfalseをレスポンスしている。

このflagはそのflaseを受け取るためのjavascript側のオブジェクトプロパティだ。

これによってjavascript側でajax通信を発生させるかの判断が行えるようになる。

「nonce」

nonceについては、すこし説明しておきたい。nonceはセキュリティだ。サーバから発行したハッシュ値(このハッシュ値をnonceという)をリクエストに含めて返しサーバがチェックすることで、その通信が正規なものかどうかを確認する仕組みとなっている。

nonceは主にCSRFを防ぐための手段として使われる。掲示板に勝手に書き込まれたり、ネットショッピングを勝手にされたりというのはCSRFの典型的な被害だが、こういった攻撃を無効化できる。

wordpressではこのnonceを出力する関数、チェックする関数が準備されている。

nonceは外部から推測、もしくは容易に生成できるようなハッシュ値では意味がないので、実質的に暗号生成と同じだ。

そのため堅牢なハッシュ値が必要なのだが、wordpressは内部に保持している暗号生成用のハッシュ値や時刻など複数の値を用いてnonceを生成しているので、自前で作るより安心かと思う。

PHPでのwordpress関数を使ったnonce生成は以下の通り。

$nonce = wp_create_nonce();

生成も簡単だし、後述するがチェックもとても簡単なので、ぜひ盛り込んでおくことをお勧めする。

「is_single」「category」「tag」「search」

今回の例ではindexページだけでカテゴリーアーカイブもタグアーカイブも検索結果表示も兼ねる形となっているので、これらの条件分けが必須となる。

さらに重要なのが、条件分岐にあわせてタグID、カテゴリID、検索文言を取得するようにしている。

このデータをリクエストに用いることで、PHP側でwp_queryオブジェクトを生成する際のクエリとして使い、条件分岐に合わせたレスポンスを返す事が可能となる。

もちろんアーカイブテンプレートなどを使っても構わないが、ファイルを統合できるので、俺はこの方法が意外と好きだ。

念の為言っておくとwordpressにはほかに日付アーカイブ、投稿者アーカイブ、カスタム投稿アーカイブ、カスタムタクソノミーアーカイブというページ分岐が存在するので、しっかりした対応をするなら、これらも分岐条件に入れる必要はある。

ただ、投稿者が1人でカスタム投稿もカスタムタクソノミーも使っていない場合はそもそもそれらは用意する必要(日付アーカイブは別として)がない。

「looplength」

このデータは「すでに表示されている記事リストの個数」だ。単純ではあるが、ページに表示されているループ要素の外殻、本例のcssセレクタで言えば「.index-loop」の個数をカウントしている。

この個数はPHP側でクエリを発行する際に「オフセット」として使うことになる。

詳しくはPHPの該当処理の箇所で説明したいと思う。

「registered」

registeredもフラグだ。

ajaxにはローディングタイムラグが生じる場合がある。スパッと通信が終わってくれればいいのだが、ユーザの回線状況やサーバの状態などでどうしても通信に時間が掛かってしまうこともある。

その際、「今通信中ですよ」と知らせる為にローディング中画像、通称「くるくる画像」を表示させることが多い。

くるくる画像はユーザに今何が起こっているかを端的に説明できる。

読み込み中であることをわかってもらうことでユーザの離脱率を下げることができるという意見もある。

これも後述になってしまうが、ajaxの発動条件を作る際に気を付けないと、ajaxスタートイベントが何度も起きてしまう場合がある。その際にくるくる画像のON・OFFを適切に行う為、表示条件をフラグ化しておくと楽になるのではないかと思う。

registeredはその為に使用する。

リクエストデータは最終的にjson形式としてまとめて使おうと思う。

PHPからjavascriptへ変数を渡す

図を確認してもらえればわかる通り、リクエストデータの「nonce」「is_single」「category」「tag」「search」「flag」に格納される値はPHP側で出力されている値だ。

そこでPHPからjavascriptに値を渡す必要が出てくる。

やり方としては2つの方法が考えられる。

PHPからjavascriptへ変数を渡す方法-その1

自力でやる

自力の場合、スクリプトタグに挟まれたjavascriptをPHPからecho等で出力することで受け渡しができる。

こんな感じで記述するとjavascriptにデータを受け渡す事が可能。


$nonce = wp_create_nonce();
if(is_single()){
    $is_single = true;
}else{
    $is_single = false;
}
echo <<<AAA
        var nonce = '{$nonce}';
        var is_single = '{$is_single}';
AAA;

自力での変数出力

上記の例では以下のように変数が出力されることになる。

handmead

変数でグローバルに何個も出すと、名前空間が汚れるのが嫌だと言う場合は、オブジェクトでまとめてしまうのがスマートな方法かもしれない。オブジェクトであれば名前汚しは一個で済む。

PHPからjavascriptへ変数を渡す方法-その2

関数をつかう

実はwordpressではPHPからjavascriptへの変数受け渡しを関数として実装している。wordpressを使っているのならこの関数を使うのがもっともスマートだろう。今回の例でもこの方法を用いている。

その関数と言うのはwp_localize_script()だ。この関数はwp_register_script()かwp_enqueue_script()と一緒に使う必要がある。例としては以下だ。


//スクリプトを登録する
wp_enqueue_script( 'ajax', get_template_directory_uri(). '/js/ajaxnaitive.js', array('jquery') , '1.0' , false );
//登録したスクリプトに'AjaxData'というオブジェクトでデータを作成する
//これらはwp_head()の前に記述する必要がある
wp_localize_script( 
        'ajax', 
        'AjaxData',
        array( 
            flag => 'true',
            registered => 'false',
            nonce => $nonce, 
            category => $categoryId, 
            tag => $tag,
            search => $search,
            is_single => $is_single
        ) 
);
wp_head();

wp_enqueue_script()

コメントの通り、wp_enqueue_scriptでスクリプトを登録する。

第一引数はハンドル名で、のちにwp_localize_scriptで指定することになる。

第二引数はパスで「絶対ハードコーディングするなよ」(絶対に押すなよ的なフリではない)とcodexに書かれているので、get_template_directory_uri等をつかって取得するようにしよう。ちなみに空白のスクリプトファイルでも問題ない。

第三引数は先に読み込む必要のあるスクリプトを指定できる。配列なので複数指定が可能だ。jQueryなどのライブラリを先に読み込みたいときに便利だ。

第四引数はwp_footer()でスクリプトを読み込むようにするかどうか。デフォルトはfalseなので、スクリプトはwp_head()で読み込まれるようになる。footerで読み込む必要がある様だったらtrueにしよう。もちろんその場合はwp_footer()の前に関数を書かなければならない。

昨今ではページの見える範囲のレンダリングをブロッキングするスクリプト読み込みなどは嫌厭される傾向があるので、問題がない場合はfooterで読み込んだ方がいいのかもしれない。

ちなみにここで指定したスプリクトファイルは別途スクリプトタグで読み込ませる必要はない。

wp_localize_script()

この関数でPHPからjavascriptにデータを渡す。第一引数はハンドル名。wp_enqueue_scriptで指定したものだ。

第二引数は生成するオブジェクトの名前で任意で構わないが、グローバルにでるので重複してはいけない。

なるべくグローバルを汚さないようオブジェクトで変数を格納するようにしているのだと思う。これであれば汚れる名前は一個だけで済む。

第三引数はjavascriptに渡したいデータだ。連想配列で指定することによって、キーがプロパティ名となってデータが格納されるという寸法だ。

関数での出力

例にあげた記述でヘッダ部分に以下のようにスクリプトが出力されるはずだ。ご覧のとおりグローバルオブジェクト’AjaxData’のプロパティとして、指定したデータが格納されていることが判る。

wplocalizescript02

javascript側からこのオブジェクトにアクセスするには、いつも通りオブジェクトを扱うように以下の記述でOKだ。

AjaxData.nonce

自力でやるにしても関数を使うにしてもどのみち、グローバルにすくなくとも一つはオブジェクトを出すことに変わりはないので、やりやすい方法で構わないと思う。

ネイティブjavascriptでのajax

javascript側は最初にネイティブjavascriptから説明していきたいと思う。

対象となるファイルは[ajaxnaitive.js]だ。

リクエストデータをjsonにする

jsonとはjavascriptのオブジェクトを元に制定されたデータ形式だ。

リクエストを行う際は出来る限り1回ですべてのデータを送れるのが良い。

と言うのも、通信にはあたりまえだが時間が掛かる。リクエスト・レスポンスの回数ごとに時間が増大していくことになる。

だからできるだけ、変数一個一個ではなく、オブジェクトの形などでリクエストするのが望ましい。

ただjavascriptオブジェクトをそのままの形でリクエストには使うことはできない。

そこでデータはjson形式にしてしまうのが便利だ。

少し語弊があるが、jsonは通信用のオブジェクトと捉えてもらうといいかもしれない。

JSON.stringify

json形式データを作るにはjavascriptのJSON.stringifyを使う。以下が例だ。

//------------------------------------
//リクエストデータをjsonにする関数
//------------------------------------
function ajaxRequestsJson(){
    var loopLength = elementObj().indexLoop.length;
    var requestJson = JSON.stringify({
        //グローバルに出したオブジェクトから受け取る
        nonce : AjaxData.nonce,
        category : AjaxData.category,
        tag : AjaxData.tag,
        search : AjaxData.search,
        looplength : loopLength
    });
    return requestJson;
}

例では関数宣言の形にしてるが、もちろん単体で使用しても構わない。使い方は簡単で、引数にオブジェクトを突っ込むだけ。これでrequestJsonがjson形式データになる。

今回はjQueryでもネイティブでもjson変換は同じコードで行っている。

実のところjsonはPHPなどと連携する場合、とても奥が深いのだが、それの説明を含めるととんでもなく長くなるのでよしておきたい(決して知識がないわけじゃないんだからねっ)

オブジェクトの生成とイベントハンドラ(開始地点)のバインド ネイティブ版

XMLHttpRequest

少し説明が前後したが、順番の上ではXMLHttpRequestオブジェクト作成を一番先に行っている。

ajaxではXMLHttpRequestオブジェクトを使うことになる。ブラウザで多少の実装差異があるが、基本的には標準化されているので、古いブラウザでなければ問題なく標準実装されているはずだ。

ネイティブでのオブジェクト生成は以下の通り。

var Request = new XMLHttpRequest();

今回IE6などのレガシーブラウザの対応方法は省かせてもらおうと思う。もうIE6~8の対応意味はほとんど消失しているだろう。もし対応の必要がある人は、IE6においてはActiveXについても調べる必要が出てくる。

一応IE7以降ではXMLHttpRequestが実装されている(IE7の時点ではXMLHttpRequestが標準化されている訳ではないようだ)ので今回の記事でも通信は可能かもしれない(未検証)。

イベントハンドラのバインド

ajaxの発動条件となるイベントにイベントハンドラをバインドしている。つまりここがajax処理の起点になっているという訳だ。

window.addEventListener( "load" ,  function(){ scrollSenser( Request ); } , false );
window.addEventListener( "scroll" , function(){ scrollSenser( Request ); } , false );

ロードが完了した際とスクロールが発生した際に、後述するがajax通信を行う為のscrollSenser()という自作関数が呼ばれるようになっている。

ajax通信の初期化・開始 ネイティブ版

リクエストデータが用意できたら今度は通信の準備だ。

ネイティブでは通信はスリーステップで行う。

今回はsendAjaxという関数にしてみた。この関数はXMLHttpRequestオブジェクトを引数にしている。

//------------------------------------ 
//ajax実行関数
//------------------------------------ 
function sendAjax( request ){
    request.open( "POST" , "http://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/ajaxcontent.php" , true );
    request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );
    request.send( ajaxRequestsJson() );
}

openメソッド

まず通信の初期化を行う。これにはXMLHttpRequestオブジェクトのopenメソッドを使う。

request.open( "POST" , "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php" , true );

第一引数にはHTTPメソッドの指定だ。基本的にPOSTとGETしか使わない。POSTはリクエストにデータが伴う場合、GETはリクエストにデータを伴わない場合に使うことになる。

GETでもクエリの形でURIにデータを忍ばすことは出来るが、jsonなどの便利な形式が使えないし、クエリデータを整形するのは意外と骨が折れるので、データがあるなら無理せずPOSTにした方がよいかと思う。

第二引数は通信先のパスだ。wordpressが用意してくれている経路では、admin-ajax.phpのパスを指定することになるが、任意のファイルで行う際は用意したajax通信用のPHPファイルを指定する。今回はajaxcontent.phpというファイルになる。

第三引数は同期通信を望む場合はflaseにし、非同期通信を望む場合はtrueにする。ajaxは非同期通信である事が重要なので、falseにする機会は滅多にないはずだ。デフォルトではtrueなので省略しても構わない。

setRequestHeaderメソッド

このメソッドでリクエストにヘッダを与えることができる。ヘッダによってサーバに対して「このデータはjsonで、UTF-8がんす」と教えることができる。

request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );

コンテンツタイプと言うのはデータの形式の事で、MIMEタイプと同義だ。

今回はjson形式でリクエストを行うので、application/jsonを指定することになる。

その他にも以下の様なコンテンツタイプが存在している。

コンテンツタイプ 意味
text/plain プレーンテキストを送る場合
text/html HTM文書を送る場合
application/xml Xml形式データを送る場合
application/x-www-form-urlencoded フォームデータを送る場合

chrasetはデフォルトでUTF-8になる。だが念の為、明記しておいた方がいいだろう。文字化けに泣く羽目になるかもしれない。

もちろん用途によっては他のキャラセットを使う必要があるかもしれないが、俺はあまり詳しくないので説明が能わない。

sendメソッド

このメソッドで実際に送信が始まる。先に説明したリクエストデータをjsonにする関数を引数に取っているが、これはjsonデータを指定していることと同義だ。

request.send( ajaxRequestsJson() );

これで拵えたリクエストデータがサーバに届くことになる。

イベントハンドラ ネイティブ版

ajax通信のレスポンスは通常XMLHttpRequestオブジェクトのイベントにてハンドリングすることになる。

ネイティブとjQueryではこのあたりが大分違う印象を受けるかと思うが、jQueryでも内部で処理しているのはこれらのイベントなので、ネイティブで書く予定がなくても、知っておくといいかもしれない。

基本的にこれらのイベントハンドリングがajaxの中心的な部位になる。

イベントの種類

XMLHttpRequestオブジェクトは通信開始、通信中、通信ステータス変化、通信成功、通信失敗、通信中断、通信完了を捕まえることが出来るようになっている。

イベントがどのようなタイミングで発火するかは図も参照してもらうとわかりやすいかもしれない。

イベント種類 発火条件
onreadystatechange readystateが変化
onloadstart 通信開始
onprogress 通信中
onloadend 通信完了(成否無関係)
onload 通信成功
onerror 通信エラー
onabort 通信中断
ontimeout 通信タイムアウト

onloadstartイベント

このイベントはajaxがスタートした場合に発火する。ajaxがスタートした際に行いたい処理はこのイベントにバインドするのがよい。

例ではこのイベントに、くるくる画像の付与と画面を少し白くする処理を与えている。

//------------------------------------
//通信開始イベントハンドラ
//----------------------------------
Request.onloadstart = function(e){
    console.log("スタートしたで");
    var obj = elementObj();
    if( AjaxData.registered !== true){
        //画面を白っぽくして…
        obj.responseArea0.style.opacity = "0.5";
        //くるくるを追加
        var element = document.createElement( "div" ); 
        element.id = "loading"; 
        element.innerHTML = "<img src='http://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif' alt="" />
        obj.body.appendChild( element );
        AjaxData.registered = true;
    }
};

下記のコードで記事挿入親要素のopacityを0.5にして、画面を白っぽい感じにしている。

obj.responseArea0.style.opacity = "0.5";

また下記のコードでbodyにくるくる画像付け加えている。

var element = document.createElement( "div" ); 
element.id = "loading"; 
element.innerHTML = "<img src='http://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif' alt="" />"; 
obj.body.appendChild( element );

ネイティブで要素を加えるのは結構面倒だ。エレメントを登録しなくてはならないからだ。jQueryのようにすぐにappendするわけにはいかないのである。

今回の例ではloadingというidをもつdivを作り、その中にくるくる画像のimgタグを突っ込んでいる。

ちなみにinnerHTMLは標準化されているプロパティではない(いわゆるDOM0というやつ)なので、本来は使うべきではないのだろうが、ほとんどのブラウザに実装されており、現実的には楽に要素を付け加えられるので使われている現状がある。

これらの処理は

if( AjaxData.registered !== true)

によって条件分岐している。registeredは先に説明したとおり、くるくる画像の付与フラグである。付与されていない場合のみくるくる画像が付与されるようにしてある。

今回はスクロールによってajaxが発生するようにしているのだが、スクロールの状態によってはonloadstartが何度も発火してしまう場合があり、

<div id="loading"></div>

が重複して出力されてしまう可能性がある。それを避ける為に、くるくる画像を付与したらtrueにし、外れたらfalseし分岐させている。

onloadendイベント

このイベントはajaxが終了した場合に発火する。成功したか失敗したかは関係なく発火するので、終了をキャッチするならこのイベントを使うのがいいだろう。

//------------------------------------
//通信終了イベントハンドラ
//------------------------------------
Request.onloadend = function(e){
    console.log("終わったで");
    var body = document.querySelector( "body" );
    var responseArea = document.querySelector( "#responseArea" );
    //成功したら画面を戻して…
    responseArea.style.opacity = "1.0";
    //くるくるを外す
    var loading = document.getElementById("loading");
    body.removeChild( loading );
    //くるくるフラグをセット
    AjaxData.registered = false;
};

下記のコードでonloadstartイベントハンドラで0.5にした記事挿入親要素のopacityを1.0戻し復帰させている。

obj.responseArea0.style.opacity = "1.0";

また下記のコードでくるくる画像を取り除いている。

var loading = document.getElementById("loading");
obj.body.removeChild( loading );

最後にフラグをセットして重複出力を防いでいる。

AjaxData.registered = false;

onloadイベント

このイベントはajaxが成功した場合に発火する。その為、通常はレスポンス処理のイベントハンドラをバインドする事になると思う。

//------------------------------------
//通信成功イベントハンドラ
//------------------------------------
Request.onload = function(e){
   //jsonデータをパース
   var responceData = JSON.parse(Request.response);
   //レスポンスされたフラグをグローバルにセット
   AjaxData.flag = responceData.flag;
   //追加エレメントを準備
   var element = document.createElement( "div" ); 
   element.innerHTML = responceData.html; 
   document.querySelector( "#responseArea" ).appendChild( element );
};

レスポンスはXMLHttpRequestオブジェクトのresponseに格納されることになっている。

var responceData = JSON.parse( Request.response );

今回はレスポンスもjsonなので、JSON.parseにてパースを行っている。これによってデータはjavascriptのオブジェクトに変換されることになる。

そしてレスポンスデータにはhtmlとflagというプロパティを持たせているので、記事ループhtmlはhtmlプロパティを参照することで得られる。

ネイティブでは要素追加を登録する必要があるので、div要素を作り、そこにレスポンスhtmlを突っ込んでいる。

element.innerHTML = responceData.html; 
document.querySelector( "#responseArea" ).appendChild( element );

onreadystatechangeイベント

このイベントはXMLHttpRequestオブジェクトのreadyStateプロパティが変化した場合に発火する。readyStateは数字で全部で5つの状態が存在している。

//------------------------------------
//通信中イベントハンドラ
//------------------------------------
Request.onreadystatechange = function(e){
    console.log("今" + Request.readyState + "やで");
};
番号 意味
0 リクエストが初期化されていない
1 サーバとの接続確立
2 (サーバが)リクエストを受信した
3 (サーバの)リクエストの処理中
4 レスポンスの準備完了

onreadystatechangイベントを追うことでajax通信状態を細かく把握でき、また細かいコントロールが可能になる。今回の例ではあまりこのイベントには関係する処理がないので、単にreadyStateを出力するconsole.logだけを置いているが、ajaxを複雑にコントロールしたいならばこのイベントは必須である。

onerrorイベント

このイベントは通信が失敗した場合に発火する。先ほど説明したonloadイベントとは排他的で、どちらかのイベントしか起こらないことが保障されている。

//------------------------------------
//通信失敗イベントハンドラ
//------------------------------------
Request.onerror = function(e){
   console.log("エラーやで");
};

ajax失敗時の処理は通常このイベントにイベントハンドラをアタッチして行われる。

今回の例ではエラー処理を何も行っていないので(!!)これもconsole.logを置いてコメント出力するだけにしてある。

普通であれば通信のやり直しなどの処理をバインドすることになるかと思う。

ontimeoutイベント

このイベントは通信がタイムアウトした場合に発火する。

//------------------------------------
//通信タイムアウトイベントハンドラ
//------------------------------------
Request.ontimeout = function(e){
   console.log("タイムアウトやで");
};

timeout処理も何も行っていないので(!!!!)console.logを置いてコメント出力するだけにしてある。

ちなみにtimeoutのデフォルト値はブラウザの実装に依るのではないかと思う(W3Cに記述が見当たらなかった)。

ネイティブでtimeout値をセットするには以下の様にtimeoutプロパティで設定を行う。

var Request = new XMLHttpRequest();;
request.open( "POST" , "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php" , true );
request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );
Request.timeout = 2000;
request.send( ajaxRequestsJson() );

指定値の単位はミリ秒なので、1000で1秒だ。おそらく0にするとエラーになるはずなので、0以上の値を指定する必要がある。

timeoutの設定はopenメソッドとsendメソッドの間で行う必要がある。

onabortイベント

このイベントは通信を中断した場合に発火する。

//------------------------------------
//通信中断イベント
//------------------------------------
Request.onabort = function(e){
   console.log("中断したで");
};

今回は処理を実装していないが、XMLHttpRequestオブジェクトのabort()メソッドでajaxを中断することが可能だ。

onabortはabort()で中断されたときに発火される。

スクロール位置でajaxを発生させる仕組み

今回はスクロールの状態に対してajaxを発生させるようにしている。 その処理を行っているのが以下の自作関数だ。

  • elementObj()
  • bodyScrollSenser()
  • processPoint()
  • allLoopHeight()
  • scrollsenser()

ちなみにこの箇所もネイティブ、jQueryでほぼ共通だ。(違う箇所についてはjQuery版の際に説明したい)

elementObj() DOMオブジェクトまとめ

DOMオブジェクトを取得する関数として定義。いろいろな場所でエレメントオブジェクトを使うのでまとめた。

//------------------------------------
//エレメントオブジェクトまとめ関数
//------------------------------------
function elementObj(){
    console.log("エレメントオブジェクトまとめ関数");
    var body = document.querySelectorAll( "body" )[0];
    var html = document.querySelectorAll( "html" )[0];
    var indexLoop = document.querySelectorAll( ".index-loop" );
    var indexLoop0 = document.querySelectorAll( ".index-loop" )[0];
    var responseArea = document.querySelectorAll( "#responseArea" );
    var responseArea0 = document.querySelectorAll( "#responseArea" )[0];
    var single = document.querySelectorAll( "#single" );
    var windowHeight = $( window ).height();
    return {
        "body" : body,
        "html" : html,
        "indexLoop" : indexLoop,
        "indexLoop0" : indexLoop0,
        "responseArea" : responseArea,
        "responseArea0" : responseArea0,
        "single" : single,
        "windowHeight" : windowHeight

    };
}

シングルトンにしていないのは、querySelectorAllを使っているからだ。その都度値を取得する必要があるので、関数の形にしてある。

なんだか無駄な感じもしなくもないが、まあいいかと思っている。

ちなみにネイティブの方がjQueryでDOMオブジェクトを得るよりも、相当速く(上手くいくと10倍程度)動作する。

bodyScrollSenser() スクロール取得

windowスクロールを取得する関数。

//------------------------------------
//スクロール取得
//------------------------------------
function bodyScrollSenser(){
    var obj = elementObj();
    var bodyScroll;
    //スクロールの取得(一応クロスブラウザ対応)
    if( obj.body.scrollTop === 0 || obj.body.scrollTop == false ){
        bodyScroll = obj.html.scrollTop;
    }else{
        bodyScroll = obj.body.scrollTop;
    }
    return bodyScroll;
}

特に説明する箇所はないが、しいて言うなら一応クロスブラウザ対応にしてある。

chromはbodyオブジェクトからスクロール値を得ることができるが、FirefoxとIEはhtmlオブジェクトからスクロール値を得る。

この関数はスクロール発生時に実行されるので、bodyかhtmlのどちらかでスクロール値が取れているはずだ。

なので条件分岐することで、bodyで取得するか、htmlで取得するかを分けている。

allLoopHeight() 要素の高さ計算

すでに表示されている記事リストの要素のoffsetHeight(最外殻高さ)の合計を計算する。

//------------------------------------
//要素の高さ計算関数
//------------------------------------
function allLoopHeight(){
    var obj = elementObj();
    var allLoopHeight = 0;
    //.index-loop全部の高さを合計
    for( var i = 0 ; i < obj.indexLoop.length ; i++ ){
        allLoopHeight = allLoopHeight + obj.indexLoop[i].offsetHeight;
    }
    return allLoopHeight;
}

次のprocessPoint()での値として使う。

processPoint() ajax発動位置算出

ajaxを発生させる基準を計算する関数。

//------------------------------------
//ajax発動位置算出関数
//------------------------------------
function processPoint(){
    var obj = elementObj();
    //index-loopは最小1列、最大では何列になるかがわからないので、横幅を計測して列数分endpointを縮小する
    if(!obj.indexLoop0){
        return { "bodyPoint" : 0 , "endPoint" : 0 };
    }else{
        var ratio = Math.floor( obj.responseArea0.offsetWidth  /  obj.indexLoop0.offsetWidth );
        var endPoint = ( allLoopHeight() / ratio );
        var bodyPoint = bodyScrollSenser() +  obj.windowHeight + ( obj.windowHeight * 0.1 );
        return { "bodyPoint" : bodyPoint , "endPoint" : endPoint };
    }
}

今回のデモでは一応レスポンシブなつくりにしている。PC、スマートフォン、タブレットなどで見てもらうと、記事リストの列数がかわるを確認してもらえるはずだ。

そのため記事リストが何列になるかはデバイスに依るので、記事リストがapendされる親要素(#responseArea)のoffsetwidth(最外殻幅)を記事リストの外殻要素(.index-Loop)で割って比(ratio)を出している。

記事リストの外殻要素のoffsetHeight(最外殻高さ)を合計するallLoopHeight()を先ほどのratioで割ってやれば、記事リストが切れる位置が判るので、それをendPointとしている。

またスクロール値にwindowのheightを足すことで、画面下辺のスクロール位置を得ることが出来き、これをbodyPointとしている。

すなわち、endPointよりもbodyPointが同じか大きくなった場合にajaxを発生させるという事だ。

bodyPointの計算に

( obj.windowHeight * 0.1 )

を足しているのは、記事が切れる少し前の位置でajaxが発生するようにするマージンである。

scrollSenser() スクロール検知

実質的なメイン関数。ajaxを発生させるかの条件分岐を行う。

//------------------------------------
//スクロール検知関数
//------------------------------------
function scrollSenser( request ){
    console.log("スクロール検知関数");
    var flag = AjaxData.flag;
    var obj = elementObj();
    var point = processPoint();
    //初回の動作
    if( obj.indexLoop.length === 0 && AjaxData.is_single === "false" ){
        console.log("初回やで");
        sendAjax( request );
    //二回目以降の動作
    }else if( obj.indexLoop.length > 1 ){
        //フラグチェック
        if( flag === false ){
            console.log("全部出し尽くしたで");
        //フラグがtrueだったらajaxスタートのスクロール検知の処理を続行
        }else if( flag === true ){
            //スクロールが基準値に達したらajax発動
            console.log("point.endPoint" + point.endPoint);
            console.log("point.bodyPoint" + point.bodyPoint);
            if( point.endPoint < point.bodyPoint){
                //通信中はajaxをスタートさせないようにする
                if( request.readyState === 0 /*初期化前状態*/ || request.readyState === 4 /*通信終了状態*/){ 
                    sendAjax( request );
                }
            }
        }
    }
}

表示されている記事リストが無い場合(つまり初回の場合)には一度ajaxを発生させるようにしている。

if( obj.indexLoop.length === 0 && AjaxData.is_single === "false" ){
        console.log("初回やで");
        sendAjax( request );

二回目以降は、PHP側からもたらされたflagにて判断を行う。flagがtrueであれば記事はまだ残存しているということになるし、flagがfalseであればもう記事はないので、ajaxを発生させても仕方ないから、なにも処理しない。

if( flag === false ){
            console.log("全部出し尽くしたで");

さらにflagがtrueの場合に、今度はスクロールのチェックを行う。先に説明したprocessPoint()から受け取ったendPointとbodyPointを比べて、bodypointがendpointよりも大きくなったら、処理が進むようになっている。

}else if( flag === true ){
            //スクロールが基準値に達したらajax発動
            console.log("point.endPoint" + point.endPoint);
            console.log("point.bodyPoint" + point.bodyPoint);
            if( point.endPoint < point.bodyPoint){

そして最後に、XMLHttpRequestオブジェクトのreadyStateを確認している。今回の例ではオブジェクトを一つ(現実的には上書きしている)だけ使っているので、前の通信の状態がreadyStateに残っている。

 if( request.readyState === 0 /*初期化前状態*/ || request.readyState === 4 /*通信終了状態*/){ 
     sendAjax( request );
 }

もし通信が始まっていないならreadyStateは0になる。

通信が始まっていればreadyStateは1~3のどれかの値を取ることになる。

通信が終わっていればreadyStateは4になる。

その為、readyStateが4か0の時には通信をスタートさせてもいいということになる。

以上の条件をすべてクリアしている場合に、sendAjax()がよばれ、ajaxが発生することになる。

jQueryでのajax

長くてうんざりかもしれないが、こんどはjQuery版の説明していきたいと思う。

対象となるファイルは[ajax.js]だ。

グローバルイベントハンドラのバインド jQuery版

jQueryではonメソッドをつかってloadとscrollイベントが発生した場合にscrollsenser()が呼ばれるようにしてある。

ネイティブと同じくここが起点となる。

//------------------------------------
//ajax実行
//------------------------------------
$( window ).on( "load scroll" , function(){
    scrollsenser( );
    atachcss(obj1);
});

XMLHttpRequestオブジェクト jQuery版

jQueryでは$.ajax()メソッドがjqXHRというオブジェクトを返すことになる。このオブジェクトはXMLHttpRequestのスーパーセット(拡張版みたいな意味)なのでほぼXMLHttpRequestとして扱うことができる。

//------------------------------------
//jqXHRオブジェクトをつくる関数
//------------------------------------
function jqueryAjax(){
    var jqXHR = $.ajax({
        type: "POST",
        url: "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php",
        data: ajaxRequestsJson()
    }).done( function( responceData ){
        doneProcess( responceData );//成功時
    }).always( function(){
        alwaysProcess();//完了時
    }).fail(function(){
        alwaysProcess();//失敗時
    });

    return jqXHR;
}

この例ではjqXHRにXMLHttpRequestのスーパーセットが入ることになる。

他の部分はそれぞれの項目で後述する。

ajax通信の初期化・開始 jQuery版

jQuery版ではajax初期化・開始に.ajaxメソッドをつかう。

$.ajax({
        type: "POST",
        url: "http://pecodrive.heteml.jp/pe/wp-content/themes/test/ajaxcontent.php",
        data: ajaxRequestsJson()
})

type url data

これらはネイティブ版のopenまたはsendで指定しているデータと同じだ。このように.ajaxメソッドに設定値を与える事で、通信の初期化・開始が行える。

ネイティブ版ではsetRequestHeaderメソッドでヘッダをセットしていたが、jQueryではリクエストデータの形式を勝手に判断してくれて、適切なヘッダを付与してくれる。もちろん自分で付与することも可能でその為のメソッドもあるが、今回は特に必要ないので詳しい説明は省かしてもらった。

先に説明したとおりajaxメソッドはXMLHttpRequestのスーパーセットオブジェクトを吐く。設定値を与えオブジェクトを構築するまでをこの関数で行うことになる。

コールバック関数 jQuery版

jQueryでは、イベントにイベントハンドラをバインドするといった方法ではなく、jQueryが用意しているメソッドを使うことになる。

先に説明したとおりに、$.ajaxはXMLHttpRequestのスーパーセットであるjqXHRを返す。

このjqXHRはPromiseインターフェースが持っているメソッドとプロパティを持っている。

Promiseに関して(さらに言えばDeferredに関して)は本記事の範疇を超してしまうので、詳しくは別記事にしたいと思う。

簡単に説明すると、jqXHRは処理が成功した、処理が失敗した、処理が終わった、処理中などといった状態をこちらからトラッキングせずに、自分から訴え出ることのできる子と言ったらわかりやすいだろうか?

つまるところPromiseやDeferredオブジェクトを使うとブロッキングを避けることができるので、柔軟かつ応答性の良い処理が行えるのである。

その為、従来のsuccess、error、complete等を使った書き方よりも優れていると言える(はず)。

done() 成功時のコールバック

ajaxが成功した場合に実行されるコールバック。

.done( function( responceData ){
        doneProcess( responceData );//成功時

ネイティブでいうとonloadに相当する。

このコールバックにはdoneProcess()という自作関数を当てている。

//------------------------------------
//通信成功時処理関数
//------------------------------------
function doneProcess( responceData ){
    //レスポンスされたフラグをグローバルにセット
    AjaxData.flag = responceData.flag;
    $( "#responseArea" ).append( responceData.html );
}

やっている事はネイティブのonloadイベントハンドラとほとんど変わりはない。

jsonからjvascriptオブジェクトへの変換処理がないのは、jQueryがその辺の処理を勝手にやってくれるからである。

always() 終了時のコールバック

ajaxが終了した場合に実行されるコールバック。

.always( function(){
        alwaysProcess();//完了時

ネイティブでいうとonloadendに相当する。

これも成功時と同じように、alwaysProcess()という自作関数を与えて、ネイティブとほとんど変わらない処理をしている。

//------------------------------------
//通信完了時処理関数
//------------------------------------
function alwaysProcess(){
    console.log( "終わったで" );
    //成功したら画面を戻して…
    $( "#responseArea" ).css( "opacity" , "1" );
    //くるくるを外す
    $( "#loading img" ).remove();
    //くるくるフラグをセット
    AjaxData.registered = false;
}

fail() 失敗時のコールバック

ajaxが失敗した場合に実行されるコールバック。

.fail(function(){
        alwaysProcess();//失敗時

ネイティブでいうとonerrorに相当する。

特に意味のある処理をアタッチしていない(!!!)

//------------------------------------
//通信失敗時処理関数
//------------------------------------
function failProcess(){
    console.log("エラーやで");
}

その他のコールバック

ネイティブの方に存在した、onloadstart、onreadystatechange、ontimeout、onabort、onprogressに相当するものがないじゃないかと思われたかもしれない。

onloadstartに関してはstratProcess()という関数がコールバックにあたるが、これは単にscrollsenser()のjqueryAjax()の実行に後に読んでいるだけだ。

//------------------------------------
//通信開始時処理関数
//------------------------------------
function stratProcess(){
    console.log( "はじまったで" );
    if( AjaxData.registered !== true){
        //画面を白っぽくして…
        $( "#responseArea" ).css( "opacity" , "0.5" );
        //くるくるを追加
        $( "body" ).append( "<div id="loading"><img src="http://pecodrive.heteml.jp/ajaxtest/wp-content/themes/ajaxtest/img/ajax-loader3.gif" alt="" /></div>");
        //くるくるフラグをセット
        AjaxData.registered = true;
    }
}

というのもonloadstartが何に当たるのかがよくわからなかったからだ。勉強不足もいいところだが、特に困らなかったのでこれでいいかという感じである。

その他のイベントに関しても、jQueryではラッピングされているので、何が何に当たるかがあまり判らなかった。

少なくともonprogressに関してはprogress()が、onreadystatechangeに関してはstate()が対応するかと思う。

今回の処理ではそれほど困る事もないのでまあいいかといった感じである。

PHPファイルの処理

さてPHP側である。

何回か説明しているとおり、wordpressにはあらかじめajax経路が存在している。/wp-admin/admin-ajax.phpにリクエストする方法だ。

しかし今回はwordpressがマネージしてくれる経路でajaxを行わない。その為に自作のPHPを拵えることになる。本例ではファイル名をajaxcontent.phpとした。

このajaxcontent.phpはwordpressとは何にも関係ないので、このファイル上でwordpress関数を使うことは能わない。

wordpress関数を使えるようにする

その為、関数を使えるように関数定義ファイルを読み込む必要がある。

//wordpress関数を使えるようにするためにwp-load.phpを読み込む
require_once( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/wp-load.php' );

wordpressは実に便利にできており、公開範囲直下に存在するwp-load.phpを読み込むことで、芋づる式に各種クラス・関数定義ファイルを読み込むことができる。

上記の例ではマジック変数__FILE__とdirname( )でパスを取得している。dirnameを4つも重ねているのは、ajaxcontent.phpをテーマフォルダに入れているからだ。テーマフォルダから見た場合、wp-load.phpは4階層上に存在しているのでdirnameを4つ重ねているのだ。

リクエストデータを受け取る

$requests = json_decode( file_get_contents( "php://input" ) , true );

php://inputはリクエストのbody部分から生データを取得することができる。

file_get_contentsメソッドでphp://inputを指定することで、javascriptによってサーバに送られたPOSTのリクエストデータを得ることができる。

リクエストデータはjsonなので、PHPで使えるようにjson_decodeメソッドでデコードする必要がある。

json_decodeの第二引数はデコード形式の指定で、trueにすると連想配列となる。falseにするとデータ形式に合わせた解析がおこなわれ、適切なPHPの型に変換される。おおよそオブジェクトに変換されることになるかと思う。

nonceチェック

通信のセキュリティを高める為に、nonceの発行を行ったが、ここでnonceが正規なものかをチェックする。

//nonceチェック nonceが不正ならここでdie
if( ! wp_verify_nonce( $requests["nonce"] ) ){
            die( "不正やで?" );
}

チェックは非常に簡単で、wp_verify_nonceで行える。/wp-admin/admin-ajax.phpでのajaxの場合、check_ajax_refererを使うのがもっとも楽だが、この関数はadmin-ajax.phpを経由することを前提としているので、今回のような独自ルートの場合はwp_verify_nonceを使うのが良いと思う。

$requestsにはリクエストデータが入っているので、今回の場合などは$requests[“nonce”]でnonce値を引っ張ることができる。

コードでは、もしnonceが不正であれば、即座にdieで通信を終えることになる。もちろんdieだけでなく、高度な処理を書くこともできるので、各位の必要に応じて実装して構わない。

表示数の定義

ここで表示数の定義を行っている。

//表示数の定義
$posts_per_page = get_option('posts_per_page');

wordpress管理画面の表示設定にある「1ページに表示する最大投稿数」を引っ張て来ている。

こうすることによって、管理画面上で表示数を変えることができるので少し楽だ。

「1ページに表示する最大投稿数」を10にしている場合、ajax1回につき10記事分のリストがレスポンスされることになる。

wp_queryオブジェクト生成用のパラメータ

ここではwp_queryオブジェクトを生成するためのパラメータを拵えている。

//$wp_query用のクエリを拵える
$args = array(  
    'posts_per_page'   => $posts_per_page,
    'offset'           => $requests["looplength"],
    'orderby'          => 'post_date',
    'order'            => 'DESC',
    'post_type'        => 'post',
    'post_status'      => 'publish'
);

//カテゴリーアーカイブの場合
if( $requests["category"] ){
    $args = $args + array(  
        'cat' => $requests["category"]
    );
}

//タグーアーカイブの場合
if( $requests["tag"] ){
    $args = $args + array(  
        'tag_id' => $requests["tag"]
    );
}

//検索の場合
if($requests["s"]){
    $args = $args + array(  
        's' => $requests["s"]
    );
}

記事表示数

先に説明したget_option(‘posts_per_page’)で取得した値を設定している。

'posts_per_page' => $posts_per_page

「1ページに表示する最大投稿数」を10にすれば、10記事づつレスポンスされることになる。

記事参照位置のオフセット

ここで重要なのは以下の箇所だ。

'offset'=> $requests["looplength"]

wp_queryコンストラクタが取るパラメータに「offset」と言うものがある。これは指定した分検索位置をずらすというパラメータだ。

今回はリストはクラス要素の「.index-loop」で構成している。またこの「.index-loop」の個数をカウントしてすでに表示されている個数を判断している。

たとえば30記事分のリストがすでに表示されている場合にajax通信を行うと、次は31記事目からレスポンスしてもらいたいはずだ。

しかし「offset」を設定しておかないと、照会されるのは1記事目からになってしまう。

31記事目からにするためには、既に表示されている個数をoffsetする必要があるのだ。

そのため、リクエストデータに含めたlooplength(すでに表示されている.index-loopの個数)をoffsetに当てているのだ。

これで、毎回の通信で1記事目からのレスポンスにならずに済む。

記事リストのソート

'orderby' => 'post_date'

orderbyが並べ替えをする基準だ。今回は日付の降順で並べているが、他にも多数指定できるパラメータがある。またこれらは複数指定が可能だ。

‘none’, ‘name’, ‘author’, ‘date’, ‘title’, ‘modified’,’menu_order’, ‘parent’, ‘ID’, ‘rand’, ‘comment_count’

orderが並べ替え方法だ。

'order' => 'DESC',

‘DESC’は降順で、昇順の場合は’ASC’を指定する。

条件分岐によるパラメータ追加

ここではis_home、is_category、is_tag、is_search()ではなく、リクエストデータの有無で条件分岐をしている。リクエストデータにカテゴリIDがあればそれをクエリ条件に加える、タグIDがあればそれをクエリ条件に加える、検索キーワードがあればそれをクエリ条件に加える、といった処理を行っている。

//カテゴリーアーカイブの場合
if( $requests["category"] ){
    $args = $args + array(  
        'cat' => $requests["category"]
    );
}
//タグーアーカイブの場合
if( $requests["tag"] ){
    $args = $args + array(  
        'tag_id' => $requests["tag"]
    );
}
//検索の場合
if($requests["s"]){
    $args = $args + array(  
        's' => $requests["s"]
    );
}

基本のパラメータ配列にこれらがプラスされ、状況にあったレスポンスを得られるようになっている。

wp_queryオブジェクトの生成と残り個数の確認

wp_queryオブジェクト

拵えたパラメータ配列をwp_queryコンストラクタに与えてwp_queryオブジェクト生成。

//$wp_queryオブジェクトを作成
$wp_query = new WP_Query( $args );

照会された記事数のカウント

そして、照会された記事数をカウントしている。記事は$wp_queryのpostsプロパティに配列の形で格納されているので、この配列の個数をcountメソッドで数えることで。照会された記事数がわかる。

//クエリに合致した投稿数をカウント
$post_count = count( $wp_query->posts );

残存記事数の確認

その記事数が$posts_per_pageよりも少ない場合、次にリクエストされてもレスポンスする記事が無い(つまり今回で最後)ということなので、falseを$flagに入れている。

もし記事数が$posts_per_pageより多ければ、$flag = trueとなる。

$flagは最終的にレスポンスデータとしてjavascriptに返されるので、javascript側ではこのフラグ値でajax通信を行うかどうか判断している。

//もし残存投稿数が$posts_per_page以下ならfalseを返してajaxを止める
if($post_count < $posts_per_page ){
    $flag = false;
}else{
    $flag = true;
}

htmlデータを作る

htmlデータの生成

//疑似ループにてloop部品を仕立てる
while (have_posts()){
    the_post();
    $permalink = get_the_permalink();
    $time = get_the_time('Y/m/d G:i');
    $title = get_the_title();
    $output = preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches);
    $cat = get_the_category();
    $catname = $cat[0]->cat_name;
    $catid = $cat[0]->cat_ID;
    $caturl = get_category_link($catid);
    $img = $matches [1] [0];
    $tags = get_the_tags();
    $retags = array_values((array)$tags);
    $tagname = $retags[0]->name;
    $tagid = $retags[0]->term_id;
    $tagurl = get_tag_link($tagid);
    if(empty($img)){ 
        $img = get_template_directory_uri()."/img/noimage.png";
    }
    $img_tag = "<img class=\"img\" src=" . $img . ">";
    $loophtml = <<< EOF
        <div class="index-loop">
            {$img_tag}
            <span class="time">
                {$time}
            </span>
            <span class="cat">
                カテゴリー
                <a href="{$caturl}">
                    {$catname}
                </a>
            </span>
            <span class="tag">
                タグ 
                <a href="{$tagurl}">
                    {$tagname}
                </a>
            </span>        
            <span class="title">
                タイトル
                <a href="{$permalink}">
                    {$title}
                </a>
            </span>
        </div>
EOF;
    //どんどん付け足していく
    $tmp = $tmp . $loophtml;
}

テンプレートファイルと同じようにhave_posts()やthe_post()を使うと楽だ。

the_post()を使うと記事はループ毎に$postに格納されるのでハンドリングがたやすくなる。

$postに格納してしまえば、get_the_permalink()、get_the_time()、get_the_title()などを使うことができる。

$permalink = get_the_permalink();
$time = get_the_time('Y/m/d G:i');
$title = get_the_title();

ループ毎に1個のdiv.index-loopができるので、それをどんどん足していってひと塊のhtmlデータしている。

$tmp = $tmp . $loophtml;

以下は記事で使われている画像を取得するためのコード

$output = preg_match_all('/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post->post_content, $matches);
$img = $matches [1] [0];
if(empty($img)){ 
   $img = get_template_directory_uri()."/img/noimage.png";
}
$img_tag = "<img class=""img"" src=" . $img . " alt="" />";

レスポンスデータの整理・送信

レスポンスデータをjson化

作ったhtmlデータと記事残存フラグをjsonデータにする。

今回は配列にしてから、json_encodeにてjson形式データに変換している。これはjvascript側でリクエストデータを生成した時と同じような処理だ。

ちなみに配列じゃなけらばならないわけではなく、オブジェクトでもOKだ。

//レスポンスデータをjson形式に整形
$data = array(
    "html" => $tmp,
    "flag" => $flag,
);
$jsonData = json_encode( $data );

レスポンスヘッダー

javascript側でリクエスト時にリクエストヘッダが必要だったように、ブラウザにレスポンスを返す際もレスポンスヘッダが必要になる。

//レスポンスヘッダーをセット
header( "Content-Type: application/json; X-Content-Type-Options: nosniff; charset=utf-8" );

レスポンスデータはjsonなのでコンテンツタイプにはapplication/jsonを指定する。

X-Content-Type-Options: nosniff;と言うのは、データの自動解析をオフにするパラメータだ。これを付けておくと、データが誤解析される事を意図するXSS攻撃を防ぐことができる。

wordpressが用意してくれる経路でのajaxでは、レスポンス時にこのX-Content-Type-Options: nosniff;を自動的に不可してくれるが、今回は独自経路なので、明示的に書く必要がある。

セキュリティーなので忘れずにつけておきたい。

データ送信

最後にデータをechoして、dieで終了。

//レスポンス送信
echo  $jsonData;
//おわり
die();

補足説明

コードをダウンロードしてもらえばわかるかと思うが、jsファイルには、紹介していないコードが含まれている。

これはレスポンシブを行う為の自作関数なので、チラ見してもらえれば十分だ。

俺はメディアクエリを使って分岐させるのとても嫌いなので、javascript側から分岐させるようにしている。

今回ブレークポイントは三つ、320、1200、9999にしてある。すなわち320px以下、321px以上~1200px、1201px~9999pxでレスポンシブが発動する。

レスポンス追従しながらでもajax発動するので、ぜひ確認してもらえたらと思う。

まとめ

ざっと3万字の記事になってしまった。もっと整理して記事を書ければいいのだが、申し訳ない気持ちでいっぱいである。

今回の記事でajaxの端緒でもお伝えできていれば幸いだ。

PHPとjavascriptを交互に書いていると、いつの間にかjavascriptでvar_dumpで書いてみたり、$を付けて見たりする。PHP側でconsole.logを書いて、なんでエラーなんだと悩むこと度々。

こういうことがあると、サーバ側もクライアント側も言語を統一したくなる気持ちもわかる。

しかし人気は落ちてきているとはいってもjQueryには助けられることが多い。ネイティブで書くのは本当に骨が折れる。

最近ではTypescriptが便利だなと思いつつある。

wi-fiスポットについて「mobilepointからNTT-SPOTにして救われた話」

外での作業でネットにどのようにつなぐか

俺はしがないフリーランス。自宅で作業するとどうも怠けてしまうが、コワーキングプレースやプライベートオフィスを借りるほど、恰好つけることができない。だから大抵、無料で使える図書館で作業を行っている。

外に出て作業するときネットにどうつなぐかの選択肢はいろいろある。俺はもともとwimaxを使っていたが、よく作業する図書館での電波の入りがすこぶる悪く使い物にならないので、やめてしまった。

iphoneのデザリングを使っていたこともあるが、速度制限が入ってしまうのでどうも具合が悪い。それにMVNOに乗り換えてしまった。

そこで最終的に残されたのは公衆wifiスポットの利用である。

公衆wifiスポット

俺が利用している図書館にはmobilepoint(BBモバイルポイント)とNTT-SPOT(フレッツスポット)が使える。

iPhoneがsoftbankだったせいもあり、またNTT-SPOTは初期費用が掛かるのと、光回線を契約していない場合、月額が高かったので、なんとなくmobilepointを使うことにした。

しかしこれがとてつもなく遅い。

場所や通信環境に依ることなので、あくまで俺個人の環境での話だが、ストレスのたまる遅さだったのである。

mobilepointの遅さたるや…

良いときには1Mbpsくらい出ることもあるが、たいてい100Kbps~数百Kbpsになっていることが多い。これだとiPhoneなんかで速度制限を受けているような気分だ。

softbankのFAQにも「接続の際の環境(アクセスポイントからの距離、建物の形状、使用人数等)により異なりますが、実効速度は最大3M~4Mbps程度(ベストエフォート)となります。」とあるので、まあメガ単位出ればいいよねと思っていたのだが、予想を下回る遅さだったと言わざるを得ない。

さすがに最低500kbps位は常時出てくれないと困る。

遅いのもイライラしたが、さらにネットがよく切れるのだ。遅いからタイムアウトしている可能性もあるが、時間帯によってほとんど繋がらず使い物になりはしない。

仕事上webの案件が多く、サーバとのやり取りが頻繁で、アップロード、ダウンロードが出来ないと仕事にならない。どうも正午前後とか、17時前後とかにぶちぶち切れることが多いように思う。

サーバーにファイルがデプロイできずに困ったことは一度や二度ではない。

ただ他に選択肢もないので、いやいやながらも使い続けていた。

転機

ところが先日実家でインターネットをADSLから光にする話があり、老いた両親の代わりに俺が手続をしたのだが、「あ、NTTのフレッツ契約にするならNTT-SPOT安くなるか」と思い出したのである。

親に事情を説明し、フレッツスポットの契約もまとめて行った。フレッツ契約しているとNTT-SPOTは月額200円(税抜き)という安さだ。

mobilepointが1Mbps行かないくらいなので、せめてメガ単位は平均で出てくれたらなと、それほど期待もしていなかった。

しかし繋いでみたところ常時10Mbps位出ている!

良いときには15Mbps~20Mbps近くになることもしばしば。

これだけ出てくれると何も文句はない。重たいファイルもガンガンアップロードできる。十分に快適に作業を行えるレベルだ。

今のところmobilepointで起きていたようなぶちぶち切断をお見舞いされたことはない。

NTTさまさまだ。

単純に比較はできないかもしれないが…

もちろん俺が利用している図書館でNTT-SPOTがたまたまスピードが出ていると言うだけなのかもしれない。

場所によってはmobilepointの方がスピードでる場合もあるのかもしれない。

利用者の多さとか、エリアの問題とか単純に比較してしまえない項目もあるだろう。

ただ、かなり歴然とした差なので、おそらく他所でも同じような状態なのではないかと思う。

もしmobilepointに不満があり、NTT-SPOTが選択肢にあるなら乗換を考えてもいいかもしれない。

俺のように救われることになるかもしれないよ。

[ajax]XMLHttpRequestオブジェクトのプロパティおよびメソッドのおさらい

XMLHttpRequestをおさらい

ネイティブで書く機会はあまりないかもしれないが…

ajaxの基礎であるXMLHttpRequest。ライブラリなどでajaxをいじっていると、ネイティヴのプロパティやメソッドを忘れがちだ。

そこで今回の記事では改めてXMLHttpRequestオブジェクトのプロパティおよびメソッドをまとめてみようと思う。各々がどのような用途に使えるかも含めて解説したいと思う。

もっとも優秀なライブラリが多数あるので、ネイティブでajaxを書く機会はそれほど多くはないのかもしれない。

それでもajaxの仕組みやライブラリのソースコードの理解などに役に立つ事は間違いないかと思う。

XMLHttpRequestオブジェクト標準実装

ブラウザによって実装はまちまちなメソッドやプロパティがあるので、標準実装されているものに絞ってリストにした。

メソッド

メソッド 意味
abort() リクエストがすでに送信されている場合、リクエストを中止する。
getAllResponseHeaders() 文字列にてですべてのレスポンスヘッダを返す。レスポンスを何も受け取らなかった場合はnullを返す。
getResponseHeader() 指定したヘッダ文を含む文字列を返す。指定したヘッダ(レスポンスを受信していない場合も含む)が存在しない場合はnull を返す
open() リクエストを初期化する
send() リクエストを送信する
setRequestHeader() HTTP リクエストヘッダの値を設定する

イベント

イベント 意味
onloadstart 通信開始
onprogress 受信中
onabort 通信中止
onerror 送信失敗
onload 送信成功
ontimeout タイムアウト
onloadend 通信完了(失敗成功に関係なく発生)
onreadystatechange 通信の状態が変化するたびに発生

プロパティ

プロパティ 意味
readyState 通信状態を5つの状態であらわす
status HTTPステータス
statusText HTTPステータス文字列
responseText 文字列型のレスポンス
responseXML Document型のレスポンス
responseType レスポンスボディの型指定
response レスポンスボディ
upload XMLHttpRequestUpload オブジェクトを取得する
timeout タイムアウトを発生させる間隔設定
withCredentials Cookie等の認証情報の送信可否
responseURL リダイレクト先URL

使い方

プロパティにしてもメソッドにしてもイベントにしても意外と少ないなという感覚。これならネイティブでの記述もそれほど難しくないようにも思う。

基本的にajaxはページ遷移なしにページを書き換えする用途に使われる。その為、どんなイベントでajaxを発動させるかは、作り手の考えに依る。

何かをクリックしたら、スクロールが一定量になったら、一定時間ごとになどなど考えられるイベントは豊富だ。

しかし、どんなイベントで通信を開始するにせよ、必要であればデータを用意して、初期化し送信して、レスポンスを受け取るのは変わらない。

基本的には以下の様になるかと思う。



        //XMLHttpRequestオブジェクトを作る
        var request = new XMLHttpRequest();

        //通信の初期化(送信メソッドと送信先URIの設定)
        request.open( "POST" , "http://hoge.jp/content.php" , true );
        //POST送信時のヘッダー設定
        request.setRequestHeader( "content-type" , "application/json; charset=UTF-8" );
        //送信実行
        request.send( /*なんらかのデータ*/ );

        //通信開始時のイベントハンドラ
        request.onloadstart = function(e){
            console.log("ajax start");
        };
        //readyStateが変化した際のイベントハンドラ
        request.onreadystatechange = function(e){
           console.log("ajax readystate change");
        };
        //通信終了時のイベントハンドラ(成功の可否を問わずに発生)
        request.onloadend = function(e){
            console.log("ajax end");
        };
        //成功時のイベントハンドラ
        request.onload = function(e){
            console.log("ajax success");
        };
        //エラー発生時のイベントハンドラ
        request.onerror = function(e){
           console.log("ajax error");
        };
        //タイムアウトのイベントハンドラ
        request.ontimeout = function(e){
           console.log("ajax timeout");
        };

補足

レスポンスをどのように料理するかは作り手の思惑によって違うかと思うので、レスポンスを処理するロジックは書いていない。jsonで受け取りたい場合や、htmlやxmlで受け取りたい場合、もしくはオブジェクトや単なる文字列など目的によってさまざまだろう。

例では全部コンソール出力にしているが、普通であれば成功時のイベントハンドラであるonloadの部分にレスポンスを処理するコードを書くことになるだろう。

データの送受信のヘッダーに関してはajaxとというよりもHTTPの範囲なのでここでは深入りしないが、ほとんどどんなデータでもやり取りできるので、問題になることはないはず。

まとめ

あまり大した内容ではない記事だが、プロパティ、メソッドの備忘録になったら幸いだ。

ajaxはjQueryで書いている人が多いかと思うが、たまにはネイティブで書いてみても勉強になっていいかもしれない。

ライブラリの読み込みはブロッキング要因としてサイトの速度に影響があるので、ネイティブで直にヘッダーに記述するなどして速度チューンをすることも可能かもしれない。

wordpressでいろいろな設定値を取得してみる

wordpressの設定値とは

管理画面で設定している色々な項目

wordpressでは管理画面でさまざまな設定を行うことができる。投稿の表示数とか、サイトの名前、コメントの許可などなどだ。

カスタマイズしていくうちにこれらの「設定値」を取得したくなってくる場合がある。良くあるのは最大表示数を取得して、それに応じた動作を作る場合なんかがあるだろう。

しかし、これらの設定値はどのようにして取得するのか、意外と情報が少ない気がしてならない。

wp_options

管理画面上で行える設定のほとんどはwp_optionsというテーブルに格納されている。このテーブルでは設定項目名がキーとなって格納されているので、コラム数は4つと少ない。

wp_optionsに格納されている設定値はget_option()関数で取得できることは、知っている方も多いかと思うが、設定項目名のキーが判らないという人も多いのではないか。

codexにもOption Referenceというページが存在しているが、あまり翻訳されていないようなので、うまいこと欲しい情報を得られないかもしれない。

wp_optionsに格納されている設定値のリスト

完全ではないが…

そこで焼け石に水ではあるが、奮起してwp_optionsに格納される設定値とそのキーをまとめてみた。基本的にはここに挙げたキーはすべてget_option()にて使用することが可能だ。

いつものごとくわからない項目が結構出てしまった。というのもcodexにも記載がなく、検索しても詳細が分からないキーが結構あるためだ。出来るだけ網羅したつもりだが完全なリストでは全くないのでご容赦いただきたい。

管理画面上の項目と対応するものは出来るだけ管理画面上のでの項目名を併記するようにした。

設定値のリスト

キー 設定の説明(?が付いているのは不明な点があると言う意味) 対応する管理画面の項目 デフォルト値
active_plugins アクティブ(有効)になっているプラグイン プラグイン
admin_email adminのメールアドレス 設定->一般->一般設定->メールアドレス:
auto_core_update_notified ?自動アップデートの許可するか
avatar_default アバターのデフォルト設定 設定->ディスカッション->ディスカッション設定->アバター->デフォルトアバター
avatar_rating アバターの格付け 設定->ディスカッション->ディスカッション設定->アバター->評価による制限 G
blacklist_keys コメントのブラックリストキー 設定->ディスカッション->ディスカッション設定->コメントブラックリスト
blogdescription ブログ(サイト)の詳細説明 設定->一般->一般設定->キャッチフレーズ
blogname ブログ(サイト)の名前 設定->一般->一般設定->サイトのタイトル
blog_charset ブログ(サイト)の文字セット設定 設定->一般->一般設定->サイトの言語)
blog_public 検索エンジンに検索してもらう・してもらわない 設定->表示設定->表示設定->検索エンジンでの表示 1(検索してもらう)
can_compress_scripts スクリプトを圧縮するか 0(しない)
category_base URL構造カスタマイズベース 設定->パーマリンク設定->パーマリンク設定->オプション->カテゴリーベース NULL
category_children ?子カテゴリー
close_comments_days_old x日以上前の投稿のコメントフォームを自動的に閉じる 設定->ディスカッション->ディスカッション設定->他のコメント設定 14
close_comments_for_old_posts 古い投稿でコメントを表示するか 0(しない)
comments_notify コメントされたときにメールで知らせる 設定->ディスカッション->ディスカッション設定->自分宛のメール通知 1(知らせる)
comments_per_page 1ページあたりx件のコメントを含む複数ページに分割し、xのページをデフォルトで表示する 設定->ディスカッション->ディスカッション設定->他のコメント設定 50
comment_max_links x個以上のリンクを含んでいる場合は承認待ちにする 設定->ディスカッション->ディスカッション設定->コメントモデレーション 2
comment_moderation コメントの手動承認を必須にする 設定->ディスカッション->ディスカッション設定->コメント表示条件 0(許可後表示)
comment_order 古いo新しいコメントを書くページのトップに表示する 設定->ディスカッション->ディスカッション設定->他のコメント設定 asc(昇順)
comment_registration ユーザー登録してログインしたユーザーのみコメントをつけられるようにする 設定->ディスカッション->ディスカッション設定->他のコメント設定 0(必要ない)
comment_whitelist すでに承認されたコメントの投稿者のコメントを許可し、それ以外のコメントを承認待ちにする 設定->ディスカッション->ディスカッション設定->コメント表示条件 1(持っている)
cron ?cronの設定
current_theme 現在有効になっているテーマ 外観->テーマ
dashboard_widget_options ?管理画面のウィジェット設定
date_format 日付書式 設定->一般->一般設定->日付のフォーマット
db_upgraded ?
db_version ?データベースのバージョン
default_category 投稿がデフォルトで属するカテゴリのID 設定->投稿設定->投稿設定->投稿用カテゴリーの初期設定 1
default_comments_page ? newest
default_comment_status デフォルトでコメントを許可するか open(許可する)
default_email_category メール経由での投稿がデフォルトで属するカテゴリのID 設定->投稿設定->投稿設定->メールでの投稿 1
default_link_category ?リンクがデフォルトで属するカテゴリのID 2
default_pingback_flag ?
default_ping_status ピンバックとトラックバックを許可するか 設定->ディスカッション->ディスカッション設定->投稿のデフォルト設定 open(許可する)
default_post_format デフォルトの投稿タイプ 設定->投稿設定->投稿設定->デフォルトの投稿フォーマット 0(post)
default_role デフォルトの権限 設定->一般->一般設定->新規ユーザーのデフォルト権限グループ subscriber(購読者)
gmt_offset 国際標準時からのオフセット
gzipcompression ブラウザから要求があった場合gzipでの圧縮を行うか 0(圧縮しない)
hack_file ?
home ホームのURL 設定->一般->一般設定->サイトアドレス (URL)
html_type MIMEタイプ text/html
image_default_align ?画像のデフォルト整列位置 NULL
image_default_link_type ? file
image_default_size ? NULL
initial_db_version データベースのバージョン?
large_size_h ラージサムネイルのデフォルトサイズ(縦) 設定->メディア->メディア設定->画像サイズ->大サイズ 1024
large_size_w ラージサムネイルのデフォルトサイズ(横) 設定->メディア->メディア設定->画像サイズ->大サイズ 1024
links_updated_date_format リンクアップデートの日付書式 F j, Y g:i a
link_manager_enabled ?リンクマネージャを使うか
mailserver_login メールサーバのログインネーム 設定->投稿設定->投稿設定->メールでの投稿
mailserver_pass メールサーバのログインパスワード 設定->投稿設定->投稿設定->メールでの投稿
mailserver_port メールサーバのポート 設定->投稿設定->投稿設定->メールでの投稿
mailserver_url メールサーバアドレス 設定->投稿設定->投稿設定->メールでの投稿
medium_size_h ミディアムサムネイルのデフォルトサイズ(縦) 設定->メディア->メディア設定->画像サイズ->中サイズ
medium_size_w ミディアムサムネイルのデフォルトサイズ(横) 設定->メディア->メディア設定->画像サイズ->中サイズ
moderation_keys コメントの名前やEmail、IP、URLなどにここで設定したキーがヒットすると表示待ちになる 設定->ディスカッション設定->コメントモデレーション NULL
moderation_notify コメントが承認されたときにメールを受け取る 設定->ディスカッション設定->自分宛のメール通知 1(受け取る)
page_comments ?コメントをページ分割するか
page_for_posts 表示するページのID、show_on_frontが有効な場合に有効 設定->表示設定->表示設定->フロントページの表示
page_on_front フロントページに表示する固定ページのID(show_on_front の値が pageある必要あり) 設定->表示設定->表示設定->フロントページの表示
permalink_structure パーマリンクの形式 設定->パーマリンク設定->パーマリンク設定->共通設定
ping_sites pingの送信先 設定->投稿設定->投稿設定->更新情報サービス
posts_per_page 1ページにおける投稿の最大表示数 設定->表示設定->表示設定->1ページに表示する最大投稿数
posts_per_rss RSSにおける投稿の最大表示数 設定->表示設定->表示設定->RSS/Atom フィードで表示する最新の投稿数
recently_activated ?
recently_edited ?最近編集した投稿
require_name_email 名前とメールアドレスの入力を必須にする 設定->ディスカッション->ディスカッション設定->他のコメント設定 1
rss_use_excerpt RSSで抜粋を使用するか 設定->表示設定->表示設定->RSS/Atom フィードでの各投稿の表示 0
show_avatars アバターを表示するか 設定->ディスカッション->ディスカッション設定->アバター->アバターの表示 1
show_on_front フロントページに表示される投稿ID 設定->表示設定->表示設定->フロントページの表示 posts(最新記事)
sidebars_widgets サイドバーに適用されているウィジェット
siteurl サイトのURL
start_of_week 週初めの設定 設定->一般->一般設定->週の始まり 1(月曜日)
sticky_posts 先頭固定の投稿 設定->表示設定->表示設定->フロントページの表示
stylesheet 現在のスタイルシート 外観->テーマ
tag_base URL構造カスタマイズベース 設定->パーマリンク設定->オプション->タグベース NULL
template 現在有効のテーマ 外観->テーマ
theme_mods_xxxxxxxxxx インストールされているテーマの詳細(テーマ分存在する)
theme_switched ? ?
thread_comments コメントスレッドを使用するか 設定->ディスカッション->ディスカッション設定->他のコメント設定 1(する)
thread_comments_depth コメントをx階層までのスレッド (入れ子) 形式にする 設定->ディスカッション->ディスカッション設定->他のコメント設定 5
thumbnail_crop サムネイルを正方形にトリミングするか 1(する)
thumbnail_size_h サムネイルのデフォルトサイズ(縦) 設定->メディア->メディア設定->画像サイズ->サムネイルのサイズ 150
thumbnail_size_w サムネイルのデフォルトサイズ(横) 設定->メディア->メディア設定->画像サイズ->サムネイルのサイズ 150
timezone_string タイムゾーン 設定->一般->一般設定->タイムゾーン
time_format 時間書式 設定->一般->一般設定->時間のフォーマット H:i
uninstall_plugins アンインストールしたプラグイン プラグイン
uploads_use_yearmonth_folders アップロードしたファイルを年月ベースのフォルダに整理 設定->メディア->メディア設定->ファイルアップロード 1(する)
upload_path ? NULL
upload_url_path ? NULL
users_can_register だれもが登録できるか 設定->一般->一般設定->メンバーシップ 0(できない)
use_balanceTags ? 0
use_smilies 顔文字を画像に変換するか 設定->投稿設定->投稿設定->整形 1(する)
use_trackback トラックバックの送受信を許可するか 0(しない)
widget_archives ?ウィジェット関係 ?
widget_categories ?ウィジェット関係 ?
widget_meta ?ウィジェット関係 ?
widget_recent-comments ?ウィジェット関係 ?
widget_recent-posts ?ウィジェット関係 ?
widget_rss ?ウィジェット関係 ?
widget_search ?ウィジェット関係 ?
widget_text ?ウィジェット関係 ?
WPLANG 言語 ja
wp_user_roles ? ?
wp_user_roles ? ?

補足説明

キーの数

キーの数はかなり多く、おそらく100以上にはなるはずだ。プラグインなどの設定値もwp_optionsに格納されるので、それらプラグイン独自のキーを含めると200以上になる場合もあるだろう。

ご自分のデータベースを覗きたい場合は拙著で申し訳ないが画像はどのようにデータベースに記録されているか画像はどのようにデータベースに記録されているかを参考にして頂けると幸いである。

プラグインの設定値

プラグインの設定値に関しては、各位でインストール状況が異なると思われるのでリストには挙げなかったが、おおよそプラグイン名がキーに付いているので見分けやすいとは思う。

もしプラグインを利用したカスタマイズを積極的に行う際は対象のプラグインがどのようなキーを持っているのか確認するのがいいかと思う。

その他の設定値

また、プラグイン以外にも、_site_transientもしくは_transientから始まるキーが記録されている場合があると思う。transientは一時的なというような意味があるので、これらのキーは一時的に格納されている設定値との認識で構わないと思う。

俺が確認した限りではこれらのキーはwordpress本体やテーマ、プラグインなどのアップデート情報の格納などを行っているようだ。いずれにしてもほとんどはシリアライズされて格納されているので、データベースを覗けば確認することができる。

まとめ

wordpressの設定値が必要なカスタマイズになってくると結構複雑になってくるし、日本語での情報もなかなか検索できなくなってくるかもしれない。特殊なことをやればやるほど、情報は少なくなっていくので困ったものだ。

wordpressをカスタマイズする時は有効な情報に当たるのも、技術の内なのかもしれない。

しかし英語しゃべれないまでも、読めるように教育してほしかったな。義務教育。

wordpress関数を外から使うにはwp_load.phpを読み込む

wordpressの仕組みを生かせないものか

wordpressはCMSとしての歴史が長く、便利な仕組み、関数を数多く備えている。

簡単なCMSでも自作すると大変なので、オープンソースのwordpressはとてもありがたいアプリケーションと言えるだろう。

そんな便利なwordpress関数を、外部から使うことはできないだろうか?

wordpressの関数を外部から使う

できる!!

結論から言うとwordpress関数を外部から使うことは可能だ。

たとえばajaxを独自経路で構築する場合、そのphpはwordpressにはマネージメントされないものなので、wordpress関数は使えない。

しかしあるファイルをrequireすると関数定義やクラス、各種設定が芋ずる的に読み込まれるので、関数やオブジェクトが使えるようになるのだ。

これは便利この上ない。

wordpressのファイル構造

wordpressは多段的にファイルを読み込むように設計されている。

まずindex.phpが起点となる。このindex.phpはテーマのindexではなく、publicスペースに置いてあるindexだ。

index.phpには幾つかのコメントと、wp-blog-header.phpを読み込む為のrequireが一行書かれているだけだ。

wp-blog-header.phpも簡素で、wp-load.phpとtemplate-loader.phpを読み込むためのrequireが書かれている。

ここで重要なのはwp-load.phpだ。wp-load.php以降では各関数の定義ファイルやクラスファイルを読み込んでいくことになる。

wp-load.phpではwp-config.php、functions.php、version.php、load.phpなどを読み込む。

wp-config.phpはデータベース情報が記載されている重要なファイルで、これを元にwordpressはデータベースに接続しているのだ。 このファイルではさらにwp-settings.phpを読み込んでいる。

このwp-settings.php以降で各種初期設定や、各種ファイルの読み込みがさらになされることになる。

最終的にはtemplate-loader.phpからクエリに従ってテンプレートファイルが読み込まれページが表示されることになる。

ファイル読み込みの図

すべてを網羅している訳ではないが、ファイルが読み込まれていく順番を図にしてみた。

wordpress

ほとんどの関数定義やクラス定義、または初期化設定(クラスのインスタンス化や各種定数の設定など)はwp_load.php以降で行われているようだ。

つまり最低限wp-load.phpを読み込むことで、wordpress関数を外部から使うことが出来るようになる。

wp-load.phpを読み込む

実際に読み込むのは簡単で単にrequireをしてあげればよい。ただ特に理由がないのならrequire_onceを使うことをお勧めする。

require_once( dirname( __FILE__ ) . '/wp-load.php' );

__FILE__はphpのマジック定数で、ファイル名が設定されるので、これを用いてディレクトリを得るdirnameメソッドでディレクトリの絶対パスを得て、目的のファイル名にスラッシュを付けて、つなげる。これでフルパスになるので、めでたくrequireできる訳だ。

requireする側のファイルと目的のファイルの階層が異なる場合は以下の様に階層を飛び越えるように記述する。

require_once( dirname(dirname( __FILE__ )) . '/wp-load.php' );

wp-load.phpに限って言えば、パブリックスペース直下に存在するファイルなので、wp-load.phpがrequireする側のファイルより階層が深いということは起こりえない。

逆にrequireする側のファイルがテーマフォルダなどにおかれている場合は、dirnameを何個も重複させなくてはいけないが、落ち着いて階層数を数えて必要なだけ使ってやればよい。

まとめ

一つのファイルを読み込むだけで、いろんな関数を使えるようになるのはとても便利。もちろん外部からだと、活用できる関数も限られてしまうかもしれないが、さまざまなアイデアが思いつく。

この仕組みのおかげでwordpressが組み込んでいない経路でのajaxが本当に快適に出来る。ありがたいことだ。

皆さんも、何かの機会にこの仕組みを活用してみてはどうだろうか。

wordpressで画像のAlt属性やキャプションを単体取得する簡単な方法

画像の情報を取得したいのだが…

wordpressの関数

wordpressではさまざまな関数があり、欲しい情報をカバーする関数はたいてい用意されている。

だから関数を知れば知るほどやれることは増えるのだが、いかんせんwordpressの関数の一部は余計な情報を引っ張って来る若干おせっかいな面がある事も否めない。

特に画像関係の関数は扱いが難しいかもしれない。

画像の情報は特に多い

画像にはさまざまな情報が付与されている。ファイル名、ディレクトリ、サイズ、キャプション、alt属性、exif情報、どの投稿で使われているか、などなどだ。

これらの情報を単体で取り出す関数はおそらくない。

画像の情報の在りか

画像情報のデータベース上の扱い

画像の情報はデータベース上ではwp_postsとwp_postmetaというテーブルに記録されている。付加情報の多くはwp_postmetaの方に記録されている場合が多い。

画像の情報はキーによって管理されている。なので画像IDと特定のキーさえわかってしまえば(若干のデータ構造の理解が必要になる場面もあり)画像の情報を単体取得することは容易だ。

alt属性の在りか

alt属性はwp_postmetaテーブルにおいて「_wp_attachment_image_alt」というキーで記録されている。

alt属性の格納

wp_postmetaテーブルから何らかの情報を取得するには、get_post_meta()関数を使う。

get_post_meta()関数はカスタムフィールドを取得するための関数ではないか?と気づいた人は鋭い。

その通りである。じつはカスタムフィールドもwp_postmetaテーブルにキーを介して記録されている。

カスタムフィールドの場合はキーを自分で設定できるのが違いだが、記録のされ方自体はalt情報などと何ら変わりはないのである。

だから、get_post_meta()関数でalt情報を得ることが可能なのだ。

もしデータベースの中身を見たいと言うことだったら拙著の記事「wordpressのデータベースをデータベース管理ツールで覗いてみる方法」を参照してもらえると幸いだ。

alt属性やキャプションを単体取得する方法

まず画像のIDを取得

get_post_meta()関数には画像のIDを指定する必要があるので、何らかの方法で画像IDを知る必要がある。

画像関係の関数は色々あるが、帯に短し襷に長しと言った感じがするので、データベースから直接引っ張ったほうが楽なのではないかと思う。

ここでは画像IDを$wpdbインスタンスのメソッドを使ってデータベースにクエリ発行して取得してみようと思う。

$wpdbはデータベースへのアクセスを簡単にするためにwordpressが用意してくれているクラス・オブジェクトだ。newしなくてもwordpress側でオブジェクトを作っているので活用させてもらおう。

投稿に使われている画像のIDをすべて取得

画像ID

$wpdbを使って投稿内の画像のIDを取得するコードは以下だ。

$post_id = get_the_ID();
$attachment_ID = $wpdb-&gt;get_results("SELECT ID FROM wp_posts WHERE post_parent='{$post_id}' AND post_type='attachment'");

このコードの戻り値は配列になる。

キャプションや説明

ちなみに画像の「キャプション」はwp_postsのpost_exceptに記録されている。このpost_exceptは投稿でいうなら「抜粋」が入るところだ。画像の場合はキャプションとして使われる。

さらに言うと、画像の「説明」は、wp_postsのpost_contentに記録されている。これは投稿ではおなじみの投稿本文が入っているところだ。

もし、alt属性だけでなく、キャプションや説明を一緒にもしくは単体で取得したい場合は以下の様にするといい。

画像ID、キャプション、説明を一緒に取得する

$post_id = get_the_ID();
$attachment_ID = $wpdb->get_results("SELECT ID,post_content,post_excerpt FROM wp_posts WHERE post_parent='{$post_id}' AND post_type='attachment'");

この場合戻り値は多次元配列となる。

画像IDとキャプション

$post_id = get_the_ID();
$attachment_ID = $wpdb->get_results("SELECT ID,post_excerpt FROM wp_posts WHERE post_parent='{$post_id}' AND post_type='attachment'");

画像IDと説明だけ

$post_id = get_the_ID();
$attachment_ID = $wpdb->get_results("SELECT ID,post_content FROM wp_posts WHERE post_parent='{$post_id}' AND post_type='attachment'");

画像のalt属性を取得

さてこれで投稿内の画像IDを取得できたので、あとはループで回して画像のalt属性を取得していこう。

先ほど$wpdb->get_resultsで取得した値はありがたいことにオブジェクトになっている。データベースのコラム名をそのままプロパティ名にしてくれているので、以下の様にアクセスすることが出来る。

各値のアクセス

//$iはカウンタとして
$attachment_ID[$i]->ID;
$attachment_ID[$i]->post_content;
$attachment_ID[$i]->post_excerpt;

ループにするとこんな感じだろうか。

for($i = 0 , $count = count($attachment_ID) ; $i < $count ; $i++){

    $attachment_alt[] = get_post_meta( $attachment_ID[$i]->ID , "_wp_attachment_image_alt");

}

目的に合わせて、配列を再構築したり、foreachなどを使ってもいいと思う。

注意点

複数の投稿で使われている画像に関しては、ペアレント情報が「追加」では無く「上書き」になるようなので、どうやら一番新しい投稿でしか繋がりが追えないようだ。

なので複数投稿で使用の画像の場合上記のコードでは引っ張ってこれない。

対応としては、同一の画像でも投稿に使う度にアップロードする必要がある(つまり別IDにしてしまうということ)。

また記データベースに画像のペアレント情報が書きこまれないような特殊な方法で投稿に画像を挿入した場合も、このコードでIDを取得することはできない。例えばnoimage画像をfunctions.phpで定義した関数から挿入する場合などだ。

これらの場合、投稿内のURLを正規表現で引っ張り、それを元にデータベースからIDを取得するという面倒な手順を踏むことになる。

これはとても面倒なので、できるだけ画像の使い方はシンプルにしておくのが良いかと思う。(noimage画像を引っ張ってくる必要はあまりないように思えるが)

まとめ

とかく混乱しがちなwordpressの画像関係だが、直接データベースから情報を取得する荒業を使うと意外と簡単になんでも取得できてしまう。

冒頭でwordpressの関数をdisったが、それでもwordpressは良くできていると思う(おべっか)。俺などはすぐ$wpdbに頼ってしまうが、やはりwordpress関数を使いこなしてなんぼだと思うので、あまり独自にちょろまかすのは良くないかもしれない。

でもね、クライアントの要求によっては規定の関数では処理できないこともあるのが現状だから…。

wordpressのWP_Queryクラスが取る引数をまとめてみた

WP_Queryクラスのコンストラクタ引数

wordpressカスタマイズにおいてWP_Queryクラスをnewしてインスタンスを得る場合があるかとおもうが、コンストラクタが取る引数のパラメータが膨大で、値の設定に苦労されている人も多いのではないかと思う。俺がまさにその状態だ。

今回奮起してパラメータを調べてみようと思い、WP_Queryクラスが定義されているwp-includes内にあるquery.phpを覗いてみた。 俺の力量だとソースを読むのはなかなか骨が折れるが、一応パラメータリストを作る事は出来た。

ただ俺の英語力も最悪なのでうまく訳せていない箇所もあるし、そのパラメータがどのように動作するかが判らなかったものもある。 その為完全なリストとは全く言えない。

参考程度にはなるかと思うのであまり期待しないで閲覧してもらえると幸いである。

パラメータまとめ表

パラメータはアルファベット順にしてある。

クエリ 意味 値の型 初期値 許可される値
attachment_id 添付ファイルのポストID int
author 著者ID、または著者IDのカンマ区切りのリスト int|string
author_name 著者のニックネーム string
author__in 著者IDの配列 array
author__not_in 著者IDの配列(NOT) array
cache_results 投稿情報をキャッシュするかどうか bool ture
cat カテゴリIDまたはIDのカンマ区切りのリスト(または子カテゴリ) int|string
category__and カテゴリIDの配列(AND) array
category__in カテゴリIDの配列(OR 子カテゴリなし array
category__not_in カテゴリIDの配列(NOT) array
category_name カテゴリスラッグ string
comments_per_page ページごとのコメント数 int
comments_popup クエリは、コメントのポップアップ内にあるかどうか? int|string empty
date_query WP_Date_Query用の引数の連想配列 array
day int empty 1~31
exact 正確なキーワードで検索するか bool ture
fields フィールド一つで返すか配列で返す string|array all fields ids’, ‘id=>parent’.
hour 時間 int 0~23
ignore_sticky_posts 「この投稿を先頭に固定表示」を無視するか(false) bool 0|false. 1|true, 0|false.
m 任意の4桁の年と月を受け入れ int empty 西暦4桁+1~12
meta_compare 「meta_value」をテストするための比較演算子 string
meta_key カスタムフィールドキー string
meta_query WP_Meta_Query用の引数(連想配列) array
meta_value カスタムフィールドの値 string
meta_value_num カスタムフィールド値番号? int
menu_order 投稿のメニューの順番? int
monthnum int empty 1~12
name 投稿のスラッグ string
nopaging すべての投稿を表示か(ture)、ページ分割するか(false) bool false
no_found_rows カウントをスキップする?tureでパフォーマンスが向上する可能性がある? bool false
offset オフセット int
order 結果表示のソート 昇順または降順指定 string DESC’ ‘ASC’, ‘DESC’.
orderby 結果表示のソート 複数のオプションが可能 string date’ ‘none’, ‘name’, ‘author’, ‘date’, ‘title’, ‘modified’,’menu_order’, ‘parent’, ‘ID’, ‘rand’, ‘comment_count’
p 投稿のID int
page 静的なフロントページのページXに表示される投稿数を表示 int
paged 現在のページの数 int
page_id 固定ページのID int
pagename 固定ページのスラッグ string
perm ユーザーの権限による投稿表示抑制 string
post__in 取得する投稿IDの配列、スティッキーポストを含む array
post_mime_type 投稿のMIMEタイプ string
post__not_in 除外する投稿IDの配列(カンマで区切りは無効) array
post_parent 子ページを取得するためのページID トップレベルの取得には0を指定 int
post_parent__in 子ページを照会する親ページIDを含む配列 array
post_parent__not_in 除外する子ページ、親ページのIDを含む配列 array
post_type 投稿タイプのスラッグ(文字列)または投稿タイプのスラッグの配列 string
post_status 投稿の状態(文字列)または状態の配列 string publish’,’pending’,’draft’,’auto-draft’,’future’,’private’,’inherit’,’trash’,’any’
posts_per_page 照会する投稿の数。 -1指定ですべての投稿を取得する int
posts_per_archive_page 検索ページまたはアーカイブページにおいて照会する投稿の数 int
s 検索キーワード string
second int 0~60
search_terms タームの配列 array
sentence 語句で検索するか bool false
suppress_filters フィルターを非表示にするかどうか bool false
tag タグスラッグ。カンマ区切り(いずれか)、プラスで区切られた(すべて) string
tag__and タグIDの配列(AND) array
tag__in タグIDの配列(OR) array
tag__not_in タグIDの配列(NOT) array
tag_id タグIDまたはIDのカンマ区切りのリスト int
tag_slug__and タグスラッグの配列(AND) array
tag_slug__in タグスラッグの配列(OR)(カンマ区切り無効) 「ignore_sticky_posts」しない限り、真実である? array
tax_query WP_Tax_Query用の引数(連想配列) array
update_post_meta_cache 投稿メタキャッシュを更新するかどうか bool ture
update_post_term_cache 投稿タームキャッシュを更新するかどうか bool ture
w int 0~53
year int 西暦4桁

補足説明

パラメータの多さ

パラメータはなんと66個もある。これを使いこなせたら相当複雑なクエリを発行することが可能になる。機能の設定パラメータも複数あるので、検索値として使えるのは66個よりは少なくなるが、それでも組み合わせは膨大だ。

例えば2014年中に公開、’パソコン’カテゴリに属していて、著者のニックネームが’物書き’の投稿というSQLで書くにはすこし複雑な条件でも

array( 'year' => 2014, 'cat' => 'パソコン', 'author_name' => '物書き' )

で照会することができる。

表示順もデフォルトでは公開日時の降順になっているが、ID順、コメント数順、タイトル順(辞書順だと思われる)、更新日順、スラッグ順(辞書順だと思われる)、著者順、ランダム順などなど(それぞれの降順、昇順)多彩に設定できる。

良く分からないパラメータ

良く分からなかった、うまく訳せなかったパラメータは「?」を付けている。該当するのはcomments_popup、meta_value_num、menu_order、no_found_rows、tag_slug__inの5つだ。

comments_popupはコメントをホップアップさせる機能と関連していると思うのだが、このパラメータの説明である原文「Whether the query is within the comments popup」の意味が分からなかった。訳すと「クエリがcomments_popupの中にあるかどうか」といったような意味になると思うが、何のことなのかさっぱり。

meta_value_numはおそらくカスタムフィールドに関係すると思う。カスタムフィールドはwp_postmetaというテーブルに格納されており、meta_idという値を持っている。このmeta_idがmeta_value_numのことなんじゃないかと思ったのだが、違うみたいだ。指定する値の型はintなので数字になるはずだが…。

menu_orderはおそらく固定ページの表示順の設置に関していると思う(未検証)。

no_found_rowsは英語原文の説明を見る限り、データベースに問い合わせする際の挙動の設定のようだ。照会されたrowに対してカウントを行わない的な内容が書かれているのだが、なんのカウントなのかが判らなかった。またこの設定をtureにするとパフォーマンスが向上する可能性があるとも書かれていた。

tag_slug__inはタグのスラッグの事を指しているのはわかるのだが、英語原文の「unless ‘ignore_sticky_posts’ is true」の注釈の意味が良く分からなかった。なぜ’ignore_sticky_posts’と関係あるのだろう?

以上はあまり使わなさそうなパラメータだが、判らず釈然としない。申し訳ない。

まとめ

WP_Queryクラスはとても便利。データベースに問い合わせする仕組みとして、wordpressでは$wpdbインスタンスを用意してくれているが、これはガチのSQLクエリが必要になるので慣れていない人は大変だろうと思う。

WP_Queryクラスなら、適切にパラメータを設定してあげるだけで、インスタンスを得ることができる。

$wpdbインスタンスは最後の手段としておいて、WP_Queryクラスを使いこなせるようになるのが良いのではないかと思う。

ともすれば$wpdbインスタンスで何とかしようとするのは、俺の悪い癖だ。

javascriptでデバイスの物理画面サイズを推定する方法

物理画面サイズ

昨今ではさまざまなサイズのデバイスがあり、アクセス解析において画面サイズは百花繚乱の体を示している。 そういった状況もありサイト制作やアプリ制作ではデバイスの物理画面サイズを推定することが重要になってきている。

しかしデバイスの物理画面サイズを取得するのはじつに骨の折れることなのだ。

今回の記事ではできるだけ正確にデバイスの物理画面サイズを推定する方法を説明したいと思う。

解像度について

まず単位について少し説明しておきたい。

解像度を表す単位としてppi(Pixel Par inch)とdpi(Dot Par inch)という単位がある。

いずれも1インチ内にいくつピクセル/ドットがあるかを表す。

ppiとdpiの違い

ピクセルもドットも最小構成画素を意味するが、厳密に言うとドットとピクセルは異なるものだ。

本来dpiはプリンタやスキャナで用いられる単位で、プリンタやスキャナは水玉模様の集まりで画像を表現するが、その水玉模様一つがドットということになる。

この場合ドットは■ではなく●なので当然、ドット同士には隙間が生じる。だから隙間分は期待した解像度にならないということが起きる。 そのため印刷分野ではdpiとppiは別もので捉えないと大変だ。

しかしディスプレイの場合、ピクセルとドットを同一視してもほとんど問題がないので混用されているのが現状のようだ。

一応デバイス画面に言及する際はppiを使うのが正しいのだと思う。

物理画面サイズを推定することの難しさ

ピクセルの絶対的な大きさ

ピクセル大きさは絶対的ではなく相対的なものだ。

競馬場の大きなスクリーンでは1ピクセルは何センチもあるだろうし、カーナビのスクリーンでは0.1mm単位だろう。1ピクセルが何ミリということはデバイスによって異なる。だからピクセルを元に物理サイズを推定することはできない。

物理サイズを算定するにはどうしてもppi/dpiが必要だ。1インチに何個ピクセルが入っているかでピクセルの絶対的な大きさが判る。

しかし現状デバイスのppi/dpiを取得するjavascriptAPIは存在していない(標準になっていない)。

つまり、ブラウザ上ではデバイスのppi/dpiを得る術は現状ない。

事前にデバイスの物理サイズを調べておいてデータベース化する?増え続けるデバイスのすべてを網羅するなど気が遠くなる作業である。

CSSを用いる方法では取得できない

dpi/ppiを取得する方法として、1インチのdiv等の要素を用意して、ピクセル値を得ることでdpi/ppiを取得できるとする情報を散見するが、この方法ではデバイス本来のdpi/ppiは取得できない。

なぜならCSS上では論理dpi/ppiが96dpiで定義されているからだ。だからどのデバイスで1インチの要素のピクセルを取得しても「96」が返ってくるはずだ。

devicePixelRatioの活用と論理ピクセル96dpi

devicePixelRatio

ピクセル比を表すdevicePixelRatioというプロパティが存在している。

appleがRetinaDisplayを導入する際に、デバイスピクセルと論理ピクセルの対応を調整する為に提唱したプロパティだそうだが、他のブラウザベンダーも追従実装したのでモダンブラウザでは取得できるはずだ。

このプロパティが物理サイズ推定に対して重要になってくる。

このプロパティの実装は最近なので、残念ながらIE9以下では取得ができないが、問題はないとは思う。

というのも、devicePixelRatioが1以上の場合、そのディスプレイは最近のものであり(少なくともIE8やら9やらの時代じゃない)そのディスプレイを使っている人がレガシーブラウザを使っているとは考えにくい。

さらにタブレットやスマートフォンではIE9以前が動いている心配をしなくていい。

取得できなければ1にしてしまえばいいとも言える。

論理ピクセルは96dpi

先ほどもCSS上では96dpiと定義されていると説明したが、この論理dpiは物理サイズ推定の大きなカギだ。仮の基準としての役割を果たす。

なんとか物理サイズを計算してみる

いろいろあがいてみたが、やはり完全に正確に取得することはできない。ただそれなりにいい線いっている感じの値は導くことができた。 javascriptだが計算式は以下だ。

function windowWidthHeight(){
    var ratio;
    window.devicePixelRatio ? ratio = window.devicePixelRatio : ratio = 1 ;
    return{ 
        windowWidth : window.innerWidth , 
        windowHeight : window.innerHeight,
        devicePixelRatio : ratio
    };
}
function deviceDpi(){
    var POINTDPI = 96 ,
        ratio = windowWidthHeight().devicePixelRatio ,
        width = windowWidthHeight().windowWidth ,
        coefficient ,
        logicalDpi ,
        estimatedActualDpi = logicalDpi * ratio;
    ratio < 2  ? coefficient = -ratio : coefficient = ratio ,
    logicalDpi = ( devicePixelRatio === 1 ) ? 
        ( POINTDPI + Math.sqrt( Math.sqrt( windowWidthHeight().windowWidth ) ) * coefficient ) :
        ( POINTDPI + ( POINTDPI / ratio ) + Math.sqrt( Math.sqrt( width ) ) * coefficient ) ;
    return { 
        logicalDpi : logicalDpi,
        estimatedActualDpi : estimatedActualDpi   
    };
}
function deviceInchSize(){
    var dpi = deviceDpi().logicalDpi ,
        width = windowWidthHeight().windowWidth ,
        height = windowWidthHeight().windowHeight ;
        widthInch = width / dpi ,
        heightInch = height / dpi ,
        diagonalInch = Math.sqrt( Math.pow( widthInch , 2 ) + Math.pow( heightInch , 2 ) );
    return {
        widthInch : widthInch ,
        heightInch : heightInch ,
        diagonalInch : diagonalInch
    }
}

俺のコーディングの下手さには目を瞑ってもらうとして、ざっと読んでもらえれば何をやっているかはわかっていただけるかと思う。

計算根拠は希薄だ。しいて言うならとにかくたくさん試行していたら一番近くなったのがこの計算方法だったからというのが計算根拠である。

 ratio < 2  ? coefficient = -ratio : coefficient = ratio ,

devicePixelRatioが2以下の場合、マイナスになるようにしているのは試行の結果であり、何か根拠があるわけではない。そうした方がより正確になったからだ。

logicalDpi = ( devicePixelRatio === 1 ) ? 
        ( POINTDPI + Math.sqrt( Math.sqrt( windowWidthHeight().windowWidth ) ) * coefficient ) :
        ( POINTDPI + ( POINTDPI / ratio ) + Math.sqrt( Math.sqrt( width ) ) * coefficient ) ;

この式で算出されるのは、デバイスのdpiではなく、devicePixelRatioが1だった場合のdpiであることに注意してほしい。

iphone6だとしたら実際326ppiだが、この計算で大体半分の152ppiが算出されるはずだ。iphone6がRetinaDisplayじゃなかったら約150ppiのデバイスなんだなということである。

 ( POINTDPI + Math.sqrt( Math.sqrt( windowWidthHeight().windowWidth ) ) * ratio ) :

widthの平方根の平方根を算出し、devicePixelRatioを掛けているあたりが苦し紛れもいいところだが、微調整にはなっている。

windowWidth : window.innerWidth , 
windowHeight : window.innerHeight,

window.innerWidthで取得できる幅はviewpointで指定されている論理上の解像度だ。つまりさっき算出した「devicePixelRatioが1だった場合のdpi」だとしたらという仮定で取得できるwidthがこれになるのである。

iphone6だと、Width:375,height:667で取得できると思うが、これは実際のデバイスピクセル数である750x1334をdevicePixelRatioで割った数字だということがお分かりいただけるだろう。

iphone6は論理上のピクセル1つを4つのデバイスピクセルで描画しているということになる。(そりゃきれいな訳だ)

計算してみた

この計算式でいくつかのデバイスを計算してみた。(もっと網羅するべきだろうが、ちょっと面倒になった。実機無いしね)

デバイス ポイント レシオ 算出インチ 公式インチ
iphone5 320×568 2 4.28inch 4.5inch
iphone6 375×667 2 5.01inch 4.7inch
iphone6puls 414×768 3 6.16inch 5.5inch
XPERIA Z3 360×640 3 5.21inch 5.2inch
nexus7(2012) 601×962 1.325 6.94inch 7inch
俺のノートパソコン 1366×768 1 15.27inch 15inch

画面サイズを表すインチは対角線の長さだ。もうアメリカしかヤードポンド法を使っていないのに、亡霊のようにインチが残ることに辟易するが、しょうがない。

誤差は10%くらいあるが、俺の頭ではこのくらいが限度なので勘弁してほしい。汎用的にデバイスのサイズを推定する役目としては十分な精度かなとは思うがどうだろう。

とりあえず実際の物理画面サイズに近い数値を取得できるようにはなると思う。

まとめ

物理画面サイズが推定できると、いろいろな判断に使える。もちろん目的の大半はレスポンシブなサイト制作の為だ。冒頭でも述べたとおり、最近ではデバイスに最適化されたサイト表示が求められている。

俺はこれでデバイスの種類判別を行っている。もちろん画面サイズだけでは判別できないので、OSやユーザーエージェントなどを合わせて判断するが、物理サイズが判ると判別はより正確になる。

タブレットなのかPCなのか解像度を確認しただけで解からないことも多くなってきたし、面倒なことこの上ないが、サイト制作の宿命か。

デバイスを判別する5つの方法

デバイスを推定する

webサイトではユーザがどのようなデバイスでアクセスしているかを判断しなくてはならない場合がある。

デバイスの種類を推定することは、よりそのデバイスに最適化されたページを生成、表示させる為にとても重要な要素だ。

そこで今回はデバイスを推定するために使える情報をまとめてみた。

この記事で言うデバイスとは機種までではなく、スマートフォンかPCかとか、iOSかandoridかとかなどの「種類」のことだ。

ユーザーエージェント

ブラウザには自分が何者かを伝える為に名刺みたいな情報を持っている。この情報はクライアント側でも、サーバ側でも取得することができる。

取得する方法は以下の様になる。

//サーバ側(php)
$userAgent = $_SERVER['HTTP_USER_AGENT'];
//クライアント側(javascript)
var userAgent = navigator.userAgent;

ブラウザの種類、OS、機種名等が取得できるので、情報としてはとても頼もしい。

しかしこのユーザーエージェントはブラウザがサーバを送る時に内容を任意なものに偽装できてしまう。

たとえばPCのブラウザでもiPhoneからアクセスしたようなユーザーエージェントに偽装することが可能だ。その為サーバ側で取得できるユーザエージェントは正確性を保障できない。

一方クライアント側でのユーザーエージェントは偽装がしにくいので、信頼性が高いと言える。

もし信頼度の高いデバイス推定を行いたいのであれば、クライアント側で取得した方がいいのではないかと思う。

ユーザーエージェントは文字列なので、正規表現で目的の文字があるかどうか(たとえばandroidという単語が入っているか)を判断しなくてはならない。

その為、事前にそのデバイスでユーザーエージェントがどのように取得できるのかを知らなければならないという面倒な点はある。

また、仕様の変更などでユーザーエージェントが変更になる場合もあるので、それに合わせて判別プロセスも変化させる必要が生じるかもしれない。

ユーザーエージェントの種類が多くなってしまうスマートフォンに関しては公式ページを確認するのが良いと思う。ちなみにdocomoのサイトはユーザーエージェントの確認がしにくい。

画面の大きさ

画面の大きさはデバイスを推測するのに非常に役に立つ。なぜなら物理値だからだ。昨今ではさまざまな画面サイズが存在し、混乱の極みだが、物理的な画面の大きさは良くあるようにインチで表せる。

画面の大きさからインチ数を割り出してしまえば、それがスマートフォン(4inch~7inch未満)なのか、ファブレット(7inch~10inch未満)なのか、タブレット(おおよそ10inch程度)なのか、PC(おおよそ13inch以上)なのかを類推することはできる。

この情報も改ざんが出来ない訳ではないが、ユーザーエージェントを偽装するよりはるかに偽装される場合が少ない。

この情報はクライアント側から取得するのが基本。ブラウザはサーバ側に画面の大きさの情報は送らないので取得は出来ない(はず)。

OSの種類

javascriptではブラウザが動いている環境のOSを取得することが可能だ。

//クライアント側(javascript)
var os = navigator.platform;

ブラウザはサーバにこの情報を送ることはないので、基本的にサーバからクライアントのOSの種類を取得することはできない。

この情報は偽装が困難なので、信頼性が高い。前述したようにwindowsでiphoneのように見せかける為にユーザーエージェントを偽装したとしても、navigator.platformでOSを取得すると「win32」となる。

その為、この情報でユーザエージェントが偽装されているかどうかを判断することが可能になる。

サポートしている機能

ブラウザはそれぞれにサポートしている機能が異なる部分がある。最新の機能であればあるほどブラウザによって実装はまちまちだ。その状況を逆手にとって、実装されている/いないを判断することで、そのデバイスを推測する材料にすることは可能だ。

プロパティやメソッドが存在しているかどうかを判断するわけだ。

しかし、スマートフォンでも複数のブラウザ選択肢があるため、特定のブラウザでデバイスを推測することは困難と言わざるを得ない。

ただ、サポートされていない機能をエスケープしたり、他の情報と組み合わせてより深い条件分岐を行う事は出来るかと思う。

具体的にはICanUseなどで実装状況を確認して、プロパティやメソッドを条件式で判断するという方法になるだろう。

ブラウザの状況が変わると、判断に使えなくなったりするので、あまり使いたくない手ではある。

IP(サーバ名)

アクセスしているIPを取得するのも一応デバイスを推定する材料にはなる。これはスマートフォンなどの電話機能が備わっているデバイスのみの話だ。

スマートフォンでwifiなどではなく、電話会社の電波で通信していた場合、それはdocomoやauやsoftbankのサーバを経由してくることになるので、IPから正引きしてサーバ名を得れば、docomoやauやsoftbankなどの名前を確認できるはずだ。

一昔前、まだガラケーが全盛だった時代は、このIP判断が非常に強力なデバイス判断方法だった(個体識別を合わせるとほぼ100%ガラケーだということが判った)

デバイス判断のサブ的要素としては現在も使えるのではないかと思う。

まとめ

おそらくクライアント側で取得したOSを基準に、クライアント側で取得したユーザーエージェント、画面の大きさでデバイスを推定するのが最も手軽で信頼性が高い方法だと思う。

OSがlinuxで画面の大きさが15inchならそれはPCだろうし、5inchならたぶんandroidだろう。OSがiOSならiPhoneかiPadだろうし、画面サイズが4inch程度ならiPhone5以前の機種だろう。

最後にユーザエージェントで答え合わせ的に判断すると、かなり正確にデバイスを判別することができるはずだ。

ただ、俺は知らないのだが、navigatorオブジェクトの内容を偽装できる方法があるとしたら、信頼性は担保できなくなる。これの偽装はかなり難易度が高いと思うのだが、もし偽装できるとしたら、なんのために偽装したいのかがよくわからない。

偽装メリットはほとんどないはずだ…が…。

こわやこわや。

wordpressで画像はどのようにデータベースに記録されているか

wordpressの画像に強くなる

wordpressのカスタマイズでは画像を扱うことが結構多い。その為、wordpressの内部で画像がどのように扱われているかをよく知っておく必要があると思う。

今回の記事ではwordpressにおいて画像はどのようにデータベースに記録されているかを説明したいと思う。

データベースを見るに当たっては拙著の記事wordpressのデータベースをデータベース管理ツールで覗いてみる方法が参考になると思う。データベースを見ながら本記事を読んでいただけると面白いのではないかと思う。

画像情報はデータベースに記録される

メディアアップローダを介してアップロードされた画像は、サーバのディレクトリ中に格納されるとともに、wordpressデータベースのwp_postsというテーブルに画像パス(URI)に紐づけてID、ファイル名、アップロード時間などの情報が記録される。

wp_postsテーブルでの画像データの記録のされ方

ID post_author post_date post_date_gmt post_content post_title post_excerpt post_status
ID 投稿者 画像をアップロードした日時 画像をアップロードした日時 本文(画像の場合は空白) 画像のタイトル 抜粋文(画像の場合は空白) 画像の状態
1501 1 2015-02-10 12:07:45 2015-02-10 03:07:45 1139150245147 inherit
comment_status ping_status post_password post_name to_ping pinged post_modified post_modified_gmt
open open パスワード(設定されていなければ空白) 画像の名前  ピン先 ピンバックした履歴 画像を更新した日時 画像を更新した日時
open open 1139150245147 2015-02-10 12:07:45 2015-02-10 03:07:45
post_content_filtered post_parent guid menu_order post_type
画像の場合は意味なし 親投稿(画像が使われている投稿のID) 画像のパス(URI) 画像の場合は意味なし 種類
1502 http://hellooooword.com/wp-content/uploads/2015/02/1139150245147.jpg 0 attachment
post_mime_type comment_count
コンテンツタイプ コメント数
image/jpeg 0

(※記事のスペースの問題で4つ分かれているが、データベース上では分かれていない)

つまり画像IDをからパスやファイル名が引っ張れる状態になるという訳だ。wordpressの画像関係の関数の多くは画像IDをキーにしているものが多い。

また当然ながらメディアアップローダを介さないアップロードはデータベースには登録されないので、wordpressの関数等でハンドリングするのはむずかしくなる。

データベース上での投稿・固定ページ・画像の区分け

wp_postsという名前からわかる通り、このテーブルは画像専用のテーブルではなく、投稿、固定ページも記録される。投稿(post)か固定ページ(page)か、画像(attachment)か等はこのテーブルのpost_typeに記録されており、この情報で仕分けられている。

pic3

IDが飛び飛びになるのはなぜか

記事を公開する場合、前の記事はIDが100だったのに、今回の記事はもうIDが130になっていると不思議に思ったことはないだろうか?

これはwp_postsテーブルに投稿以外の複数の要素が記録される(画像、リビジョンなど)からであり、投稿IDは連続しないのが普通なので安心してほしい。

サムネイルの作成

画像はアップロードした際に、デフォルトであればthumbnail,lmedium,largeと言うサイズの画像が自動的に作られる(元画像はfullというサイズになる)。いわゆるサムネイルだ。

サムネイルは元のファイルがhoge.jpgだとしたら、hoge-150×150.jpgやhoge-300×225.jpgなどといった感じの名前で、元画像と同じディレクトリに入ることになる。

サムネイル名

画像の編集

もしwodpress内のツールを使って画像を編集した場合、デフォルトでは内部処理として画像は複製され、複製された方に編集が施されることになる。この処置によって未編集元画像が失われずいつでも復帰させることが可能となっている。

編集画像は元画像がhoge.jpgだとしたら、hoge-e1423544176444.jpgのようなハッシュ値がついたファイル名になる。さらに編集画像を元にサムネイルが新規に作られる。その際サムネイルはhoge-e1423544176444-150×150.jpgなどと言ったファイル名になる。

サムネイル名

画像編集をたくさんやると派生画像がどんどんできるということになる。すこし大げさな話だが、これはサーバ容量を圧迫する原因にもなりうる。

画像を編集すると、画像は元画像と編集画像に分かれることになるが、記事では編集画像がつかわれ、元画像が使われることはない。当たり前といえば当たり前だが、IDが別途に与えられるわけでもないのにどのように整理しているのだろうか?

仕組みはこうだ。

画像のメタデータにはシリアライズデータとは別に画像の格納されているディレクトリと名前が記録されている。画像を編集した場合、このデータが編集画像のディレクトリと名前に上書きされることになる。

画像を読み込むときにはこのwp_postmetaテーブルを参照するので、元画像のIDからたどると、ディレクトリ&ファイル名は編集画像のものになっているので、元画像が読み込まれることはないのだ。なかなかうまくできていると思う。

meta_id post_id meta_key meta_value
整理ID 画像ID 画像のファイル名に関するデータである事を示すキー ディレクトリ・ファイル名
540 1501 _wp_attached_file 2015/02/1139150245147.jpg

編集後には以下の様に編集画像のディレクトリ・ファイル名で上書きされる

meta_id post_id meta_key meta_value
整理ID 画像ID 画像のファイル名に関するデータである事を示すキー ディレクトリ・ファイル名
540 1501 _wp_attached_file 2015/02/1139150245147-e1423544176444.jpg

サムネイルと編集画像

サムネイルと編集画像のデータベース上の情報は元画像の情報があるwp_postsには無く、元画像のIDに紐づいた形でwp_postmetaという別なテーブルに記録されている。

画像のメタデータ

meta_id post_id meta_key meta_value
整理ID 画像ID 画像のメタデータである事を示すキー シリアライズデータ(メタデータ)
541 1501 _wp_attachment_metadata a:5:{s:5:”width”;i:3008;s:6:”height”;i:2000;s:4:”file”;s:20:…長いので省略

このテーブルで、画像IDに紐づけられた形でシリアライズデータとしてすべてのサムネイルサイズの画像情報(サイズ、URI、ファイル名など)格納されている。(元画像のメタデータもこのテーブルに記録されている)

シリアライズデータ

シリアライズデータとは配列やオブジェクトをデータベースに記録する際に使われるデータ形式だ。簡単に言うと規則性を持った文字列に変換するといったイメージだ。

画像のメタデータを格納しているシリアライズデータは以下のような感じだ。

a:5:
{
	s:5:"width";
	i:3008;
	s:6:"height";
	i:2000;
	s:4:"file";
	s:20:"2014/11/DSC_0087.jpg";
	s:5:"sizes";
	a:3:{
		s:9:"thumbnail";
		a:4:{
				s:4:"file";
				s:20:"DSC_0087-150x150.jpg";
				s:5:"width";
				i:150;s:6:"height";
				i:150;s:9:"mime-type";
				s:10:"image/jpeg";
			}
		s:6:"medium";
		a:4:{
				s:4:"file";
				s:20:"DSC_0087-300x199.jpg";
				s:5:"width";
				i:300;
				s:6:"height";
				i:199;
				s:9:"mime-type";
				s:10:"image/jpeg";
			}
		s:5:"large";
		a:4:{
				s:4:"file";
				s:21:"DSC_0087-1024x680.jpg";
				s:5:"width";
				i:1024;
				s:6:"height";
				i:680;
				s:9:"mime-type";
				s:10:"image/jpeg";
			}
		}
	s:10:"image_meta";
	a:10:{
			s:8:"aperture";
			d:5.5999999999999996447286321199499070644378662109375;
			s:6:"credit";
			s:0:"";s:6:"camera";
			s:9:"NIKON D70";
			s:7:"caption";
			s:0:"";
			s:17:"created_timestamp";
			i:1387209298;
			s:9:"copyright";
			s:0:"";
			s:12:"focal_length";
			s:3:"170";
			s:3:"iso";
			i:0;
			s:13:"shutter_speed";
			s:5:"0.002";
			s:5:"title";
			s:0:"";
		}
	}

これは画像一枚のシリアライズデータだ。長いが読んでもらうと意味は単純なことに気が付いてもらえるかと思う。(s:3やa:4などはデータ型を表している)

元画像の大きさが3008×2008であることや、mediumのサイズが300×199であることが判るかと思う。またimagemetaには画像のメタデータ、いわゆるExif情報が入っている。

読み出し時にデシリアライズ(もとの配列やオブジェクトの形に戻すこと)が行われ、PHPで扱えるデータになる。もちろんwordpressでは内部でデシリアライズがされているので、私たちがその処理を意識することはほとんどないと思う。

Exif

余談だがExifは個人情報に当たる場合もあるので、必要ないなら消したほうがいい。撮影デバイスにも依るが、撮影場所のGPS情報などもこのExifに入るからだ。

wordpressには画像アップロード時にこのExifを消してくれるプラグインがいくつかあるので、興味のある人は調べてみるのをお勧めする。

記事に使われているかどうか

画像が記事に使われているかどうかに関しては、wp_postsのpost_parentに記録されている。記事に画像使われている際、親子関係にあることになる。子(画像)の側のレコードのpost_parentに親(投稿)のIDが入る事で親子関係を維持している。

ただ俺が調べた中で、同じ画像を異なる記事複数に使った場合に、どのような親子関係になるのかが判らなかった。

というのもpost_parentには親記事のIDがすべて記録されているのかと思いきや、常に一つの値(つまり一つの記事ID、どうやら最初に親になった記事のIDが入るようだ)しか入っていないからだ。

画像が複数の記事で使われていることがどこで記録されているかが不明なのでなんとも気持ち悪い。

もしかしたら同じ画像を複数の記事で使うということを想定していないのだろうか?その辺はちょっとよくわからない。

アイキャッチ画像

アイキャッチ画像はデータの関係性だけで表現されている。wp_postmetaテーブルにどの画像がどの記事に対してアイキャッチ画像として設定されているかが記録される。

通常アイキャッチ画像はfunctions.phpで使うサイズを設定するが、そのサイズ指定は結局のところ、対象画像のシリアライズデータを読みに行っているということになる。

アイキャッチ画像の記録

meta_id post_id meta_key meta_value
整理ID 投稿ID アイキャッチに設定されている事を示すキー 画像ID
501 1501 _thumbnail_id 932

記事内の画像の順番は保障されていない

データベースには記事内の画像の順番について記録しているデータはない。

その為、なんらかの理由で画像の順番にこだわる場合はアイデアを練る必要がある。もっとも多いのは投稿本文から正規表現でimgタグを引っ張る方法だ。

マッチングを上から行えば、投稿内で一番上の画像から順に取得できうる。

ただこの方法ではimgタグもしくは画像のURLを取得できたとしても、画像の別サイズつまりサムネイルを取得するのが困難だ。投稿内の画像はおおよそFullサイズで使われているだろうし、リスト表示などの際はFullサイズでは大きすぎるだろうと思う。

サムネイルはIDから取得することは簡単だが、URLを元にサムネイルを取得するwordpress関数はおそらく無い(はず)。なので正規ではない方法でサムネイルに到達しなくてはならないのがやや面倒だ。

だからアイキャッチ画像をつかえと言う事なんだろうと思うが、なぜかアイキャッチ画像を嫌厭している人は多い(俺も)。

まとめ

wordpressはデータベースから攻めると結構理解しやすいのではないかと思っている。データベースのリレーションがwordpressの構造とも言えるし、そこに得手不得手が生まれる。

だからどのようにデータが絡んでいるのかを知っておくのは、今後wordpressを使う上で強みになると思う。今回の記事でwordpressの画像についての知識を深めていただくことができただろうか?

wordpressを仕事で使う人も趣味で使う人にも参考になったらこれ幸い。

(俺はwordpressを仕事で使っているつもりだが、仕事は無い…)

wordpressで使ってみるajaxの基本

wordpressでajaxを使ってみる

最近では当たり前のように使われていて、空気のような存在になっているajax。その所為かネット上では古い情報と新しい情報とが入り乱れており、初心者にとってはなかなか情報を得るのが難しい状況下もしれない。

すでに習熟している人にとっては当たり前すぎる技術であり、その為ライブラリを用いたajaxの解説(主にはjQuey)の例がほとんどで、javascriptネイティブで書かれることは少ない

そんな中、ajaxの基本的な使い方をwordpressを通じて解説してみたいと思う。

ajaxについて

もうご存知かと思うが、一応説明しておこうと思う。

非同期通信である

ajaxは非同期でクライアントとサーバが通信する技術だ。

普通何らかの処理がある場合、その処理が終わるまで他のプロセス(処理)は待っている状態になる。

たとえばHTMLの解析が行われているときに、cssのlinkが出てきたら、styleシートファイルの読み込み完了までHTMLの解析プロセスは待たないといけない。

他プロセスを停止させてしまうので、このような処理はブロッキング処理と呼ばれる。同期処理とはこのブロッキング処理とほとんど同じ意味だ。

では非同期処理はというと、ノンブロッキング処理になるのか?

そのとおり。非同期処理は他のプロセスを停止させない。人間の感覚的には多数のプロセスが同時進行で進むような感じだ(厳密な意味では同時進行ではないが)。

処理が重くなって遅延したり、止まり過ぎてしまう可能性を小さくできるということだ。

ページ遷移なしで更新ができる

サイトの情報を更新する場合にはページ遷移をしなくては更新できなかったが、ajaxを使うと、ページ遷移なしに情報の更新が行える。

ユーザーがフォームに打ち込んだ情報をサーバーに送り、適切な情報を返して画面を更新する…といったようなことがページ遷移なしでおこなえるのだ。

もちろん画面全体でも一部分だけでも、一要素だけでも非同期通信による更新が可能だ。

wordpressでajax

wordpressでajaxを扱うとき、知っておいた方がいい前提条件がいくつかある。

1.wordpressが用意してくれているajax経路

wordpressはajaxの経路を準備してくれている。wp-admin/admin-ajax.phpにリクエストを送る方法だ。この経路が一般的にwordpressでのajax経路として知られているかと思う。

このadmin-ajax.phpを経由するメリットは、ログイン状態、非ログイン状態でレスポンスを簡単に切り分けられるということだ。

すなわち管理画面などでajaxを使う場合にはadmin-ajax.phpにリクエストを送る形でajaxを組んだ方が楽になると思う。

具体的にはfunctions.phpに書いた関数をアクションフィルターに登録して、ajaxのレスポンス関数とする。

2.独自ajax経路

wordpressが用意したajax経路でも大抵のことは行えると思うが、admin-ajax.phpを経由せずに、独自のルートでajaxを組むこともできる。この方法ではレスポンスに使いたい処理はphpファイルにして、適切にデプロイすることになるかと思う。

この場合、リクエストはこのphpファイルに対して行われる形となる。

公開部分(ユーザが見るサイト部分)に対してajaxを行いたい場合は、独自ajax経路を使うのが一般的のようだ。

どちらにするか

ajaxの扱いに慣れていないのなら、wordpressが用意してくれているajax経路を使うのがいいのではないかと思う。確かにこの方法は管理画面カスタマイズ用途とも言えなくはないが、公開部分(ユーザが見るサイト部分)に対して使えない訳ではない。

むしろfunctions.phpを使って手軽にajaxを組むことができるので、俺としてはまずadmin-ajax.phpにリクエストを送る方法に慣れてから、独自ajaxに進んでもいいのではないかと思う。

jQueryを使うべきか?

ajaxはAsynchronous JavaScript + XMLの略なので、当然javascriptを使うことになるが、ajaxの処理をどのように書くかを決めなくてはならない。

一般的にはjQueryのajaxメソッドを使って書くのが一般的だと思う。他のサイトを探してもらっても、おそらくjavascript部分はjQueryを用いて書かれている例がほとんどだと思う。

しかし環境によっては他のフレームワークを使っていたりしてjQueryを使えない、もしくは使いたくない場合もあるかもしれない。そんな時はjQuery以外のajax用ライブラリを使う手もある。

ただjQueryが大多数で使われている状況なので、それぞれのajax用ライブラリの情報は少ないと言わざるを得ない。

この記事を読んでいただいている方はおそらくまだajaxに慣れていないかと思うので、混乱してしまう恐れも無きにしも非ず。

また根本的な事を言えば、jQueryではラッピングがされ過ぎていて(だから楽なのだが)、ajaxを理解する目的には逆に仇となるかもしれない。

そこで今回に関しては(俺の)勉強の意味合いも強いので、ネイティブでの書き方とjQueryでの書き方を併記したいと思う。

ネイティブ書き方とjQueryを比較してもらうことでajaxの仕組みが理解しやすくなるかと思うし、理解出来たなら、楽なjQueryを使うという手順でもいいのではないかと思う。

さらに習熟したら、こんな記事はポイーでご自分でjQueryライブラリを探すのもよし、自分専用ライブラリを作ってしまうのもアリだと思う。

GETとPOST

WebブラウザとWebサーバはHypertext Transfer Protocol(はいぱーてきすととらんふぁぷろとこる)という通信プロトコルで通信している。

その通信に使われているメソッド(機能のようなもの)があり、それがGETとPOSTだ。(他にもいくつかメソッドがあるがほとんど使われないので無視していい)

GETはクライアントから指定されたURIのリソース(ページとか画像とか)を取り出すメソッド。サーバからのレスポンスは通常GETになる。

POSTはクライアント(つまりwebブラウザ)からサーバに情報を送る際に使われるメソッドだ。たとえばフォームに入力した情報を送る場合は通常POSTメソッドでサーバにリクエストすることになる。

「いや嘘を言うな、GETでもクライアントからサーバに情報を送れると聞いた」、という方がいるかもしれない。

確かに情報を送る事は可能だ。URIにクエリとして情報を付加すればいい。GETはURIを送るメソッドだから、クエリを使えば間接的に情報を送ることが可能だ。

ただあまり長い情報を送る事は出来ず、基本的には制限がある。さらにクエリをURIにくっつける処理が意外と面倒だ。少ないクエリならいいのだが、何十個ともなると厄介になってくる。

これらの事を念頭にGETとPOSTを使い分ける必要がある。

wordpressでのajaxの実例

随分と前置きが長くなってしまったが、実際にajaxの処理を行ってみよう。

functions.phpのレスポンス用関数の定義

function testAjax(){
    echo "あいうえお";
    die();
}
add_action( "wp_ajax_testAjax" , "testAjax" );
add_action( "wp_ajax_nopriv_testAjax" , "testAjax" );

まず、functions.phpのコード。レスポンス用の関数を定義する。この関数がブラウザに情報を返すという訳だ。 ともかく簡単でわかりやすい例として、”あいうえお”を返すだけにしたい。

die()

コードでreturnを使っていない理由は、returnを使うとレスポンスに0が返ってしまう為だ。だからdie()で処理を終わらせている。 関数自体はほかに説明することが無いくらいシンプルだ。

ajax用のアクションフックに登録

add_action( "wp_ajax_testAjax" , "testAjax" );
add_action( "wp_ajax_nopriv_testAjax" , "testAjax" );

この関数をアクションフックに登録する必要がある。

アクションフック処理をするadd_actionメソッドが二つあるが、一つ目はログイン状態での登録で、二つ目は非ログイン状態での登録だ。

今回はログイン状態で切り分ける事をしないが、当然関数を二つ用意して、それぞれに別々な関数をフックさせれば、ログイン、非ログインでの処理の切り分けが非常に楽になる事がわかっていただけるかと思う。

add_actionメソッドの第二引数は関数名をしていするが、第一引数は少し変わっていて、アクション名に接頭語を付けなくてならない。

ログイン状態の方は「wp_ajax_」、非ログイン状態は「wp_ajax_nopriv_」を付ける。関数名をくっつけるのが一番わかり良いかと思う。

これでレスポンス用の関数の準備が整った。

リクエスト用のjavascriptの用意 ネイティブでのコード例

今度はリクエスト側だ。さっき作ったtestAjaxに「情報よこせ」と言う為のjavascriptだ。

ネイティブの説明を先にする。コードは短いがちょっと判りにくい部分もあるだろうから、ゆっくり読んでもらえたらと思う。以下がネイティブでのコード例だ。

function ajax(){
    var testRequest = new XMLHttpRequest(); 
    testRequest.onreadystatechange = function (){	
        if( testRequest.readyState === 4 ){
            if( ( 200 <= testRequest.status && testRequest.status < 300 ) ){
                document.getElementById( "hoge" ).textContent = testRequest.response ;
            }else{
                console.log( "リクエスト失敗" );
            }
        }
    };
    testRequest.open( "POST" , "http://hellooooworld.com/wp-admin/admin-ajax.php" , true );
    testRequest.setRequestHeader( "content-type", "application/x-www-form-urlencoded ; charset=UTF-8" );
    testRequest.send( "action=testAjax" );   
}

XMLHttpRequestオブジェクトの生成

var testRequest = new XMLHttpRequest();

まずXMLHttpRequestオブジェクトを生成している。このオブジェクトを介してサーバと通信することになる。XMLHttpRequestオブジェクトはさまざまなプロパティやメソッドを持っていてそれらを使うことで、ajaxを実現させるのだ。

onreadystatechangeイベント

testRequest.onreadystatechange

このイベントはreadyStateプロパティ(後述)が変化した場合に発火する。その為、このイベントにハンドラをアタッチすることでreadyStateプロパティの状態を逐一観察することができる。

readyStateプロパティ

testRequest.readyState

このプロパティで通信状態を取得できる。ajaxではさまざまな通信状態が存在するわけだ。

状態は0~4数字で表され、意味は以下のようになっている。

番号 意味
0 リクエストが初期化されていない
1 サーバとの接続確立
2 (サーバが)リクエストを受信した
3 (サーバの)リクエストの処理中
4 レスポンスの準備完了

readyStateプロパティの値を監視しておけば、今がどういった状態なのかが判る訳だ。事実上0~3が通信中で4が完了と考えてもいいかと思う。先ほども述べたとおりonreadystatechangeイベントをハンドリングすることでreadyStateプロパティの値を監視できる。

しかし注意しないといけないのは、このプロパティでリクエストが成功したかどうかは別問題だということだ。4になってもそれは成功を意味するものではない。単にレスポンスが完了したことを示すだけだ。

statusプロパティ

testRequest.status

通信が成功したかどうかはレスポンスのステータスで判断することになる。それを得るのが、statusプロパティだ。このプロパティにはHTTPステータスコードが入る。よくページが存在しない場合404と言うが、この404はステータスコードだ。

ステータスコードはリクエストがサーバでどう扱われたのかが判るようになっている。ステータスコードの大まかな意味は以下だ。

ステータスコード群 大まかな意味
100番台 リクエスト処理を続行中である
200番台 リクエストが成功した
300番台 リダイレクト
400番台 リクエストが失敗した
500番台 サーバがエラーを起こした

通常、リクエストに成功すると200が返ってくる。200以外の200番台でもおおむねリクエストの成功を表すが、厳密には意味が異なるので、処理を詳細にしたい場合はステータスコードによる分岐処理をする必要はある。

300番台はリクエストがリダイレクトされたことを表すが、これも成功の内とみなすことができる。

今回は複雑な処理を行わないので、200,300番台はリクエストが成功したと見なす。

また同じように400番台、500番台は失敗を示すので、これらのステータスコードが返ってきた場合は失敗と見なすように処理をしている。

responseプロパティ

testRequest.response

レスポンス(サーバから返ってきた情報)はresponseプロパティで取得する。このプロパティはレスポンスの実体なので、サーバがテキストで返して来たらテキストが、JSON形式で返して来たらJSONで、Blobで返して来たらBlobが入ることになる。

もちろんどのような形式でレスポンスするかはサーバ側の実装になるので、クライアント側で決めることはできない。(無理やりパースしたりは出来なくはない)

その他テキスト形式でのレスポンスであるresponseTextプロパティ、DOMDocumentオブジェクト形式のレスポンスであるresponseXmlプロパティも存在しており、使い分けることも可能だ。

openメソッド

testRequest.open("POST" , "http://hellooooworld.com/wp-admin/admin-ajax.php" , true );

このメソッドでリクエストを初期化する。第一引数はリクエストメソッドだ。ほとんどの場合GETかPOSTになる。

wordpressが用意してくれているajax経路を使う場合、レスポンスに使いたい関数を特定するのに、”action=関数名”を送らなくてはいけない。(後述するsendメソッドで送信することになる)

もちろんURIにクエリとして付けることも可能なので、GETで送れなくはないが、今回はリクエストを送る際に一応正規な方法であるPOSTを指定することにした。(GETでの方法は後述)

第二引数はサーバのリソースの在りか、すなわちURIだ。wordpressが用意してくれているajax経路の場合は、wp-admin/admin-ajax.phpになるので指定する。

第三引数は非同期か同期で通信するかを指定する。tureで非同期通信、falseで同期通信となる。前述したように、ajaxの利点は非同期通信、ノンブロッキング通信なので、基本的にはtureを選択する。

setRequestHeaderメソッド

testRequest.setRequestHeader("content-type", "application/x-www-form-urlencoded ; charset=UTF-8");

POSTでリクエストする場合は、HTTPヘッダ(コンテンツタイプ)を送らなくてはいけない。これはそういう決まりだからとしか言えないので、面倒だが仕方あるまい。

コンテンツタイプとは取り扱うデータの種類を指定するMIMEタイプという言葉と同義だ。

POSTでリクエストを行うときは通常application/x-www-form-urlencodedを指定する。

このコンテンツタイプは名前からもわかるようにformデータを送るためのものだ。

データ形式はformA=detaA&fomeB=detaBのように構成され、detaAやdetaBはURLエンコーディングが行われる。

ただ送りたいデータが必ずしもフォームデータだけということもあるまい。そこで代表的なコンテンツタイプを示しておきたい。

コンテンツタイプ 意味
text/plain プレーンテキストを送る場合
text/html HTM文書を送る場合
application/xml Xml形式データを送る場合
application/json JSON形式データを送る場合
application/x-www-form-urlencoded フォームデータを送る場合

sendメソッドchrasetが指定されているが、デフォルトでUTF-8になる。だが念の為、明記しておいた方が安全かと思う。ちなみにUTF-8以外のセットの場合ほぼ間違いなく文字化けする。

testRequest.send( "action=testAjax" );

このメソッドでやっと送信開始と言うことになる。前述したように、POSTの場合にはこのメソッドで送信したいデータを指定する。今回はaction=testAjaxを文字列として送る。

GETの場合

一応GETを指定した場合も載せておきたい。

function ajax(){
    var testRequest = new XMLHttpRequest(); 
    testRequest.onreadystatechange = function (){	
        if( testRequest.readyState === 4 ){
            if( ( 200 <= testRequest.status && testRequest.status < 300 ) ){
                document.getElementById( "width" ).textContent = testRequest.response ;
            }else{
                console.log("リクエスト失敗");
            }
        }
    };
    testRequest.open("POST" , "http:/hellooooworld.com/pe/wp-admin/admin-ajax.php?action=testAjax" , true );
    testRequest.send();   
}

GETの場合にはメソッドをGETで指定して、wp-admin/admin-ajax.phpの後ろに?action=testAjaxのようにクエリを付けてあげればよい。基本的にopenメソッド、sendメソッド、setRequestHeaderメソッドが変わるだけだ。

GETの場合はHTTPヘッダーは必要ない。本来送るデータが無いメソッドなのでコンテンツタイプ等を指定しなくてもよいからだ。

もしGETでリクエストする場合、sendメソッドで何らかの送信データを指定しても無視されてしまう(というか送れない)。

jQueryで書いてみる

今まで説明してきたネイティブでの書き方踏まえ、ネイティブでのデモコードと同等の機能をjQueryで書いてみると以下のようになる。

$.ajax({
    type: "POST",
    url: "http://hellooooworld.com/wp-admin/admin-ajax.php",
    data:{ action : "testAjax"},
    success: function( a ){
            $( "#hoge" ).text( a );
        },
    error: function(){
            alert("リクエスト失敗" );
        },
    complete: function(){
            alert( "Ajax処理終了" );
        }
});

ネイティブより全然短く済むし、何しろ見やすい(何をやっているかわかりやすい)。やはりjQueryで書いたほうが楽なのは確かだ。

ネイティブとの比較

ネイティブと比較してみよう。

type url

type: "POST",
url: "http://hellooooworld.com/wp-admin/admin-ajax.php",

typeはメソッドで、urlは送信先の指定だ。これらはネイティブだとopenメソッドで指定していたものになる。

data

data:{ action : "testAjax"},

次にdataだが、これはネイティブではsendメソッドで指定していたデータだ。jQueryではオブジェクトで指定する形だ。

success error complete

success: function( a ){
            $( "#hoge" ).text( a );
        },
    error: function(){
            alert("リクエスト失敗" );
        },
    complete: function(){
            alert( "Ajax処理終了" );
        }

さらにsuccess、error、completeはコールバック関数だ。

successはネイティブで言うとstatusプロパティが4になり、レスポンスステータスが200番台で返ってきた場合に呼ばれるコールバックだ。つまり通信の終了と成功を意味している。

successのコールバック関数の引数にはレスポンスデータが入るので、引数からデータを得て処理することができる。

errorはネイティブで言うとstatusプロパティが4になり、HTTPステータスコードが400番台(もしくは500番台)で返ってきた場合に呼ばれるコールバックだ。これは通信の失敗を意味する。

completeはネイティブで言うとstatusプロパティが4になった状態に呼ばれるコールバック。成功か失敗かに関わらず通信が完了したことを意味する。(ネイティブのデモコードにはこのcompleteに当たる処理は書いていない)

jQueryでのGET

jQueryではajaxメソッドとは別にgetメソッドが用意されている。まさにGETでリクエストするためのメソッドだ。これはものすごく単純で、リクエスト先のURLとコールバック関数だけの簡素なものだ。

$.get( "http://hellooooworld.com/wp-admin/admin-ajax.php?action=testAjax",
       function( data ){
           alert( data );
       });

ただこのメソッドだと成功、失敗の処理が出来ないので、GETでもできるだけajaxメソッドで書いたほうがいいのではないかと思う。

jQueryのajaxメソッドのリファレンス

jQueryのajaxメソッドにはほかにも多くのプロパティが存在する。ここでは紹介しきれないので、ぜひ公式のリファレンス等で確認していただきたい。

ちょっとした応用編

すこしだけ実用的な処理

今までの説明はなるべく理解を優先して簡単な例を挙げたが実用性はない。そこで(ちょっとだけ、ほんとにちょっとだけ)実用性のある処理を説明してみたい。ここでは最低限のセキュリティについても言及しようと思う。

JSON形式でレスポンスを得る

ajaxでよく使われるデータ形式はJSONだ。このJSON形式はjavascriptと親和性が高い。というのもJSON形式はjavascriptのオブジェクトの記述を元に制定されているからだ。ほとんどjsオブジェクトと同じと言ってもいい。

JSON形式でレスポンスを受けるという応用をやってみたいと思う。

セキュリティに関して

クロスサイトリクエストフォージェリ(Cross site request forgeries,CSRF)という攻撃方法が存在するのをご存じだろうか?ajaxは潜在的にこの攻撃方法にさらされやすいようだ。

そこで、セキュリティとしてクライアントとサーバ間で暗号のやり取りをして、通信をチェックするという方法が取られる。この暗号のことをnonce(必ずしも暗号ではないようだが、予測困難なものでなくては意味がない為どちらにしろ暗号的になる)という。

サーバーがnonceを発行して、クライアントがそれを受け取り、nonceを含めてリクエストする。サーバがそのnonceが正規なものか(サーバが発行したものか)どうかチェックして、不正であれば弾く処理をする。

このnonceは基本的には一回の通信で使い捨てなので、ワンタイムトークンなどとも呼ばれる。

nonseを使えば攻撃のすべて(CSRF以外にもいろいろ攻撃方法は存在する)を必ず防げるという訳ではないが、少なくともCSRFに対しては効果はあるのは確かだ。

困ったことにadmin-ajax.phpを経由する場合、nonceが無くても通信が成立する。なぜ強制しないのか不思議だが、利便性との兼ね合いもあるのだろうか?ともかくnonceを使うにはその為の処理を書かなくてはいけない。

nonceを使うのは難しくは無く、以下のwordpress関数を使うことになる。

//nonce作成用関数
wp_create_nonce()
//ajax用のnonce確認用関数
check_ajax_referer()

余談だが、wordpressを使わない場合、PHPでnonceを作る処理を自分で書く必要がある(ライブラリ等も存在しているようだが)。nonceは容易に推測されてはまずいので、nonceを作るにはそれ相応の暗号化に対する知識が必要になる。

何かを元に暗号を作るにしても、その元が推測されては意味がないからだ。推測され難く、確実にチェックできるするための処理が必要になる。

その点wordpressでは指定したアクションやユーザID、時刻やwodpressが独自に持っている暗号化の為の情報がいくつか併用され、かなりがっちりとしたnonceを作ってくれる。。暗号化の知識に自信が無くても、手軽に強固なnonceを扱えるところがうれしい。

コード

実用的とか言っておきながら、ほとんど実用的ではないことに関しては黙秘する。このajaxでは存在する投稿(POST)をランダムに選び、タイトル、公開日時、投稿IDをJSON形式で返し表示するというものだ。

簡素極まりないがデモページはこちらから。

サーバ側(functions.php)ネイティブとjQuery共通

add_action( "wp_ajax_testAjax" , "testAjax" );
add_action( "wp_ajax_nopriv_testAjax" , "testAjax" );
function testAjax(){
    check_ajax_referer( "testAjax" );
    global $wpdb;
    $myrows = $wpdb->get_results( "SELECT ID FROM $wpdb->posts WHERE post_type=\"post\" AND post_status=\"publish\"" ); 
    $postCount = count($myrows);
    $rand = rand( 0 , $postCount - 1 );
    $postObject = get_post( $myrows[$rand]->ID );
    $json[] = array(
            "title"=> $postObject->post_title,
            "id"=> $postObject->ID,
            "post_date"=> $postObject->post_date,
        );
    header( "Content-Type: application/json; X-Content-Type-Options: nosniff; charset=utf-8" );
    echo json_encode( $json );
    die();

}
}

クライアント側(javascriptとnonceの発行)ネイティブの場合

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <style>
            body{
                padding: 5%;
            }
            #ajaxDiv,
            #title,
            #post_deta,
            #id{
                margin-bottom: 2%;
            }
        </style>
        <script>
            function nativeAjax( data ){
                var testRequest = new XMLHttpRequest(); 
                testRequest.onreadystatechange = function (){	
                    if( testRequest.readyState === 4 ){
                        if( ( 200 <= testRequest.status && testRequest.status < 300 ) ){
                                var responseObject = JSON.parse( testRequest.response );
                                document.getElementById( "title" ).textContent = responseObject[0]["title"] ;
                                document.getElementById( "post_deta" ).textContent = responseObject[0]["post_date"] ;
                                document.getElementById( "id" ).textContent = responseObject[0]["id"] ;
                        }else{
                                console.log("リクエスト失敗");
                        }
                    }
                };
                testRequest.open( "POST" , "http://hellooooworld.com/wp-admin/admin-ajax.php" , true );
                testRequest.setRequestHeader( "content-type", "application/x-www-form-urlencoded ; charset=UTF-8" );
                testRequest.send( data );
            }
            window.onload = function(){
                document.getElementById( "ajaxDiv" ).addEventListener( "click" , function(){
                    //グローバルに出たオブジェクトからアクション名とnonceを拾う
                    var request = "action=" + data.action + "&" + "_ajax_nonce=" + data.nonce;
                    return nativeAjax( request ); 
                });
            };

        </script>
        <title>ajaxデモ</title>
    </head>
    <body>
        <?php
            $action = "testAjax";
            $nonce = wp_create_nonce( "testAjax" );
            //グローバルにオブジェクトを出す
            echo "<script>var data = { nonce : \"" . $nonce . "\" , action : \""  . $action . "\"}</script>";
        ?>
        <div id="ajaxDiv">
            ここをクリックしてください
        </div>
        <div id="title">
            タイトル
        </div>
        <div id="post_deta">
            公開日
        </div>
        <div id="id">
            記事ID
        </div>
    </body>
</html>

クライアント側(javascriptとnonceの発行)jQueryの場合

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js" ></script>
        <style>
            body{
                padding: 5%;
            }
            #ajaxDiv,
            #title,
            #post_deta,
            #id{
                margin-bottom: 2%;
            }
        </style>
        <script>            
           $( window ).on("ready" , function(){
               $( "ajaxDiv" ).on( "click" , function(){
                    $.ajax({
                        type: "POST",
                        url: "http://hellooooworld.com/wp-admin/admin-ajax.php",
                        data:{ action : data.action,
                               _ajax_nonce : data.nonce },
                        success: function( a ){
                                $( "#title" ).text( a[0]["title"] );
                                $( "#post_deta" ).text( a[0]["post_date"] );
                                $( "#id" ).text( a[0]["id"] );
                            },
                        error: function(){
                                console.log("リクエスト失敗" );
                            },
                        complete: function(){
                                console.log( "Ajax処理終了" );
                            }
                    });
                });
            });
        </script>
        <title>ajaxデモ</title>
    </head>
    <body>
        <?php
            $action = "testAjax";
            $nonce = wp_create_nonce( "testAjax" );
            //グローバルにオブジェクトを出す
            echo "<script>var data = { nonce : \"" . $nonce . "\" , action : \""  . $action . "\"}</script>";
        ?>
        <div id="ajaxDiv">
            ここをクリックしてください
        </div>
        <div id="title">
            タイトル
        </div>
        <div id="post_deta">
            公開日
        </div>
        <div id="id">
            記事ID
        </div>
    </body>
</html>

補足説明

まずはクライアント側の処理から説明していきたい。説明はネイティブとjQuery一緒に行う。

nonceの作成

<body>
        <?php
            $action = "testAjax";
            $nonce = wp_create_nonce( "testAjax" );
            //グローバルにオブジェクトを出す
            echo "<script>var data = { nonce : \"" . $nonce . "\" , action : \""  . $action . "\"}</script>";
        ?>

body直下でphpを何行か書いているが、ここは送信するデータを用意している部分だ。ajaxの標的であるアクション名testAjaxは変わらないが、もう一つ下記の部分でnonceを作っている。

$nonce = wp_create_nonce( "testAjax" );

アクション名とnonceはオブジェクトにして、echoで<script>タグに仕立ててグローバルに吐き出している。

あまりグローバルスコープを汚すものではないが、オブジェクトにしてしまえば、名前一個分だけの汚染で済む。それくらいは黙っておこうという流儀だ。(フォームを使用していないのは俺がフォームを憎んでいるからだ)。

ここまではネイティブでもjQueryでも変わらない。

javascriptの処理

//ネイティブ
if( ( 200 <= testRequest.status && testRequest.status < 300 ) ){
                                var responseObject = JSON.parse( testRequest.response );
                                document.getElementById( "title" ).textContent = responseObject[0]["title"] ;
                                document.getElementById( "post_deta" ).textContent = responseObject[0]["post_date"] ;
                                document.getElementById( "id" ).textContent = responseObject[0]["id"] ;
                        }else{
                                console.log("リクエスト失敗");
                        }
//jQuery
success: function( a ){
                                $( "#title" ).text( a[0]["title"] );
                                $( "#post_deta" ).text( a[0]["post_date"] );
                                $( "#id" ).text( a[0]["id"] );
                            },

ajax通信の状態や成功失敗に関しては変えていない。成功した場合の処理は、各DIVの内容を取得したレスポンスの内容で書き換えているだけだ。jQueryでの処理も同じで、textメソッドで書き換えを行っているだけである。

var responseObject = JSON.parse( testRequest.response );

ネイティブでは成功した場合、JSON.parseというjavascript関数が出てきているが、これはJSON形式のデータをjsのオブジェクト形式に変換する為だ。これでjavascriptでサーバからのレスポンスを自由に扱うことができる。

jQueryの場合は勝手にちゃんとパースしてくれるので、自分で処理をする必要はない。

window.onload = function(){ document.getElementById( "ajaxDiv" ).addEventListener( "click" , function(){ 
//グローバルに出たオブジェクトからアクション名とnonceを拾う
var request = "action=" + data.action + "&amp;" + "_ajax_nonce=" + data.nonce; return nativeAjax( request ); });

onloadイベント内での処理としてはsend()で送る為のデータの体裁を作っている。作られたnonceを送るとき、_ajax_nonceというnameにする必要があるようだ。

こうすることによってわざわざ_ajax_nonceを$_POST[“_ajax_nonce”]のような感じで、引き抜いてこなくてもwordpress(check_ajax_referer)が勝手にnonceを引っ張ってくれる。

jQueryの場合ではonメソッドでページの読み込みを待ってからだが、ネイティブのonloadと基本的には変わらない。クリックイベントに関しても同様だ。またグローバルに出したアクション名とnonceはそのままオブジェクトプロパティとして指定している。

後はネイティブでもjQueryでも#ajaxDivがクリックされればnativeAjaxが動いてajax通信が始まる。

サーバ側の処理

少しだけ複雑になっているが、ajax自体に関わるのは以下の部分だ。

check_ajax_referer( "testAjax" );

header( "Content-Type: application/json; X-Content-Type-Options: nosniff; charset=utf-8" );

echo json_encode( $json );

check_ajax_referer

check_ajax_refererはnonceのチェックを行う関数だ。これにajaxのアクション名を与えれば、クライアントから送ったnonceが正規のものかを確かめることができる。

nonceはさまざまな要素(wordpressでは時刻やユーザIDやアクション名やその他難読化するためハッシュ値など)で暗号化されているが、要素がわかれば復号もできる。その為、サーバ(wordpress)は送られてきたnonceが正しいのかを判断できるわけだ。

check_ajax_refererはもしnonceが不正規だった場合、強制的にdie( ‘0’ )で関数を終わらせる仕組みになっているので、特に条件式を組んで場合分けをする必要はない。処理が始まるまえにcheck_ajax_refererが動けばいいので、関数定義の一番上に置くのがいいのではないかと思う。

コンテンツタイプ

今回はJSON形式でクライアントにデータを送るので、それ用ヘッダーも必要になる。JSON形式を送る為にはコンテンツタイプを「Content-Type: application/json;」にする。「X-Content-Type-Options: nosniff」というのがくっついているが後述する。

json_encodeなど

最後にPHPのjson_encode関数でデータをJSON形式にエンコードして送信している。

実はwordpressにはJSON形式データを送る為のwp_send_json関数と、JSON形式データをエンコードするwp_json_encode関数が用意されている。wp_send_jsonに関してはヘッダーを自ら付ける必要が無く、関数内で用意してくれる。

また送信されるレスポンスデータの形式を自動認識するブラウザの機能を停止する「X-Content-Type-Options: nosniff」の付加も自動でやってくれる。

これは誤ったデータ形式を故意に認識させるXSS攻撃に対して有効な手段だ。その為、可能な限りwp_send_jsonを使った方が楽だし安心だとおもう。

wp_json_encode関数に関しても内部でデータのチェックをしているようなので(リファレンスには「with some sanity checks」とある)、PHPの関数であるjson_encodeを使うよりもいいのかもしれない。

今回の例に含めなかったが、関数をご自分でも調べていただけると理解が深まること請け合いだ。

その他

ajaxには直接関係ないが、下記の辺りの処理が気になる方もいるかもしれないので軽く説明だけしておきたい。

 $myrows = $wpdb->get_results( "SELECT ID FROM $wpdb->posts WHERE post_type=\"post\" AND post_status=\"publish\"" ); 
    $postCount = count($myrows);
    $rand = rand( 0 , $postCount - 1 );
    $postObject = get_post( $myrows[$rand]->ID );
    $json[] = array(
            "title"=> $postObject->post_title,
            "id"=> $postObject->ID,
            "post_date"=> $postObject->post_date,
        );

$wpdbはwordpressでデータベースを扱う為のオブジェクトで、このオブジェクトを使うとSQLを簡単に発行でき、データベースから情報を持ってくる事ができる。

「”SELECT ID FROM $wpdb->posts WHERE post_type=\”post\” AND post_status=\”publish\””」というのがデータベースに問い合わせる為のSQL文というものだ。

意味としては「postsというテーブルで、post_typeがpost、post_statusがpublishとなっているIDを返してください」になる。つまり公開されている投稿のIDをよこせと言っているのである。

このSQL文で得たID群を$rand関数でランダムに選び出して、タイトル、公開日時を取得して、JSON形式にしているという感じだ。

もっと実用的な事を考えようと頑張ったが、俺の脳細胞は一日に30分くらいしか働かないようで、愚にもつかない例になってしまったことはこっそりお詫びする。

まとめ

さてながながと説明してきたが、wordpressでajaxを使う際の基本的な内容はお伝えできただろうか?

できるだけajaxの基本を伝えようと頑張ったが、ネイティブの場合のクロスブラウザ対応の件とか、さらにセキュアなajaxの方法、クロスドメインでのajaxに関して、wordpressの独自ajax経路に関して、エラーに関しての対処や確認の方法などなど書き足りない部分もたくさんある。

これらは別途記事にしていきたいと思う。

ajaxはもやはどこでもどんな用途でも使われてる為、初心者にとって意外と実体を把握しにくいと言わざるを得ない。俺など未だに全体像が分からない。

適用範囲が広大なので、複数のやり方が存在するのも確かだ。今回紹介した例は馬鹿正直に書いている例でもあり、現在はjQueryでももっとモダンな書き方をする人が多い。

俺が書くような価値のないコードは論外としても、人によってかなり書き方が異なるだろうと思う。

モダンな書き方がいいかどうかは別としても、まずはちゃんと理解しなくては応用的な書き方にも進めない。

この長ったらしい記事が少しでも役に立ったならこれ幸いなり。

Chrome拡張機能「LiveMd」でお手軽MarkDown生活

MarkDownでメモを取りたいの

とっても便利なMarkDown

MarkDownとは定められた記法で書くとそれに対応したHTMLに変換してくれる言語の事だ。Markdownのようなものは軽量マークアップ言語などと言われるそうだ。

記事を書くのにwordpressでWP-Markdownプラグインを使い始めるようになって、Markdown記法がとても便利だということがわかってきた。

そんなわけで記事以外にも、日常でのメモやアイデアプロットなどもMarkdownで取りたくなってきた。

windows用のMarkdownエディタって…

しかしなぜか、Markdownエディタで有名なMouなどはMac専用だったりする。

windows用でもMarkdownエディタはいくつかあるが、決定版的なものは定まっていない様子。

Chrome拡張機能「LiveMd」

ブラウザだからこその便利さ

そこでいろいろエディタを探していたら、Chrome拡張機能でMarkdownできるエディタ「LiveMd」を見つけるに至った。

この「LiveMd」はかなりシンプルだがブラウザ上でMarkdown出来るのはなんとも素晴らしい。メモを取るのに煩わしいことは何もない。

実のところライトな利用なら、すっとメモを取れる方が機能豊富より重要だったりする。その点、いつも開いているchromeでMarkdown出来るのは便利以外の何物でもない。

実際使ってみるとシンプルだが、もうこれなしではメモが取れないくらい便利だ。

「LiveMd」の機能

「LiveMd」の拡張機能アイコンをクリックするとエディタがタブとして開かれるが、画面はすこぶるシンプル。

livemd

左側がMarkdown記法で記述するスペースで、右側が記述したものがHTMLになって表示されるスペースだ。

メモはいくつも増やすことができ、プラスボタンを押せば新規メモが作られる。

書き溜めたメモは書類マークタブからリスト形式で閲覧することができる。

livemd

メモの削除ははさみマークタブから各メモの右にあるゴミ箱ボタンを押すだけだ。

livemd

目のマークボタンはHTMLになって表示されるスペースだけを表示するモードへの切り替えだ。

livemd

基本的に取ったメモはタブを消しても、Chromeを落としても、消えることはない。

ただメモを他に持っていきたい場合もあると思うので、そんなときは、Markdown記法で記述するスペースの右上にあるダウンロードマークをクリックすると、メモをmd形式ファイルでダウンロードができる。

書いた文字数も表示されるのがちょっとうれしい。

livemd

livemd

このファイルは何のことはないテキストファイルなので、windowsのメモ帳でも開くことができる。

livemd

livemd

Markdown記法について

Markdown記法には方言があるようで、俺が正しい記法になっているかはわからないが、記法自体それほど多くの種類は無く、通常のメモなら今回紹介するくらいの記法を覚えておけばいいのではないかと思う。

もし記法に関してちゃんと知りたい人は以下のサイトで確認するといいと思う。

「LiveMd」でMarkdown記法

代表的な記法の紹介ともに「LiveMd」の感じを紹介してみたい。

livemd

見出しは#(シャープ)を使う。#一個はh1に相当する。#を増やしていけばh2,h3…と対応してくれる。#の後ろには半角スペースが必要なので注意。

段落は基本的に空行で判断される。

太文字は**(アスタリスク)で囲む。半角スペースはいらない。

イタリック文字(斜体)は*で囲む。これも半角スペースはいらない。

打消しは~~で囲む。同じく半角スペースはいらない。

liリスト形式はリストとなる部分の上下に空白行が必要になるので、改行する必要がある。 *を付けるとリストになる。*の後ろには半角スペースが必要。(*の代わりに-や+でもliリストが作れる)

olリストは数字と.(ドット)を使う。これもストとなる部分の上下に空白行が必要。 1.のように記述するが、.の後には半角スペースが必要だ。

***のようにすると水平線が引ける

リンクは[アンカーテキスト](URL)で記述することができる。

livemd

便利なのが、表(テーブル)だ。|をつかって本当の表のよう記述していく。 項目となる行の次の行(2行目)に:-(コロンとマイナス)を入れる必要がある。:-は体裁を整える為に:——-のように書いてもOKだ。 :-が左寄せ、-:が右寄せ、:-:が中央寄せになる。

livemd

\(バックスラッシュ)でMarkdownをエスケープできる。

引用は>の後ろに半角スペース。

紹介したMarkdown記法を表にしてまとめてみた。

種類 対応するタグ 記法
見出し h1~h6 #と半角スペース、#の数で見出しレベルが決まる
太文字 b **で囲む
斜体 em *で囲む
打消し s ~~で囲む
リスト ul リストとなる部分の上下に空行、*と半角スペース
リスト ol リストとなる部分の上下に空行、数字と.と半角スペース
水平線 hr ***
リンク a [アンカーテキスト](URL)
表(テーブル) table |と:-の組み合わせ
エスケープ \
引用 q >

まとめ

俺なんかでも一日のうちに何度となくメモを取る。主にはプログラミングアイデアだったり、仕事の内容だったりだが、以前の自分のメモは単に箇条書きで後から見ると見づらかった。

生産性向上とか胡散臭いことを言うつもりはないが、メモやアイデアプロットが綺麗に残せると、のちのちの自分を楽させてあげられるのは間違いない。

NetBeansIDE8.0.2でpythonを使う為の4つのステップ

pythonを使ってみたい。NetBeansIDEで。

日本ではRubyの方が人気なのだそうだが、欧米ではpythonもなかなかの人気だそうだ。最近新たにpythonを始めようと思い立ち開発環境を考えていたのだが、愛用しているNetBeanIDEでpythonを扱える事が判った。

公式サポートはないが…

NetBeansIDEではバージョン7あたりから公式にはpythonをサポートしなくなったようだ。この経緯については詳しく知らないので説明することはできないが、ともかく8.0系統でpythonは公式サポートではないということである。

しかし、NetBeansIDEのコミュニティーでpythonを使う為のプラグインが開発されており、それを使えばNetBeansIDEでパイソニング[誰によって?]ができてしまうということだ。なるべく開発環境を分散させたくない俺のような人間には、ありがたい。

という訳で今回はNetBeansIDE8.0.2でpythonを使えるようにするまでのステップを説明したい。申し訳ないことに俺の環境はwindowsなので、windowsの場合での説明となる。

NetBeansIDEからpythonを使う為の4つのステップ

1.pythonのインストール

python

※もうすでにpythonをインストールしている人はこの作業をする必要はない。まだインストールしていない人だけ必要な作業だ。

まず当然だがpythonをインストールする必要がある。python公式ページにアクセスして自分のOSに合ったpythonをダウンロードしよう。

pythonには現状2.x系と3.x系の2系統が存在しているが、2.x系は過去のサポート用ということらしく、これ以上のメジャーリリースは無いとのことだ。今後は標準ライブラリも3.x系に合わせて開発されるそうなので、これからpythonを始める人は3.x系で問題ないだろうと思う。

ダウンロードページは以下(おそらくOS判定をしているページなのでMacの人はMac用のダウンロードリンクが出るのだと思うが、念の為ちゃんと確認してほしい)この記事執筆時点では3.4.2が最新バージョンだ。

ダウンロードしたら、さっそくインストールだ。windowsの場合はmsi形式になっているので、インストールは簡単だ。ダブルクリックすればインストーラーが始まる。特に設定などは変える必要は無いと思うので、画面の指示どおり進めば問題ないと思う。

python

デフォルトでpythonはwindowsの場合C:ドライブ直下にpythoh34といったフォルダでインストールされているはずだ。(申し訳ないがMacでどのようにインストールされるかは、説明ができない。)

2.Python4NetBeans802のダウンロード

Python4NetBeans802プラグインは、NetBeansIDEのプラグインメニューからではなく、NetBeans公式サイトのPluginページからダウンロードする必要がある。ダウンロードは以下。

netbeanside

Zip形式ファイルでダウンロードされるので、解凍しておいてほしい。

NetBeansIDEを起動して、メインメニューの「ツール」→「プラグイン」を選択。プラグインダイアログが出たら「ダウンロード済み」タグを選択して、「プラグインの追加ボタン」を押して、先ほど解凍したプラグインフォルダの中身のファイル(10ファイルあるはず)をすべて選択して、「インストール」ボタンを押す。

netbeanside

netbeanside

インストール途中、署名の警告がでるが、そのままインストールして構わない(もちろん署名の無いプラグインをインストールするのが嫌だという人はここでインストールを断念するしかない)

netbeanside

プラグインのインストールが終わったら、念の為NetBeansIDEを再起動しておこう。

3.pythonのパス設定

NetBeansIDEの新規プロジェクトを開くと、pythonプロジェクトテンプレートが追加されているのがわかるかと思う。

netbeanside

しかしプロジェクトを新規しようとすると、赤文字で警告が出ていて新規することができないはずだ。これはpythonにパスが通っていないためだ。パスを通す設定をしよう。

netbeanside

Python Platformの右側に「Manage」ボタンがあると思うのでクリックする。

netbeanside

Platformsという縦長の欄の下側の「New」ボタンをクリック。

netbeanside

pythonの位置を聞かれるので、先ほどインストールしたpythonのディレクトリ(デフォルトではC:直下)までたどり、python.exeを選択する。

netbeanside

そうするとパスが通り、NetBeansIDEからpythonを使えるようになる。

netbeanside

netbeanside

4.NetBeansIDEからpythonで”Helloworld”

先ほどは選択できなかったPlatfromでpython3.4.2が選択できるようになっているはずだ。これでプロジェクトを新規出来る。

さっそく新規するとテンプレートに”Helloworld”を表示させるデモが入っている。ここからpythonのプログラミングすなわちパイソニングが出来る。

netbeanside

まとめ

世の中には優秀なエディタもいろいろあるのでIDEに固執する必要はないのだが、やはり一つのソフトでいろいろ出来るのは楽だ。

NetBeansIDEでpythonが使えるようになったことで勉強も捗りそうだ。理解できるかは別として。

今回導入するプラグインは署名なしのプラグインなのであくまで自己責任でお願いしたい。責任は取りかねるのでご了承いただきたい。

しかし、pythonで何をするのかって?

俺にもよくわからない。

javascriptのEventListenerの基礎知識

基礎知識

イベントとはなにか

イベント(Event)とは「クリックされた」「キーが押された」「ある領域にカーソルが入った」「windowのサイズが変更された」等々さまざまな「事態」ことだ。

ユーザーが起こすイベントばかりではなく、たとえば「ページが読み込まれた」「DOMが構築された」「通信が完了した」等のシステム上の「事態」もイベントとなる。

使えるイベントの数はとても多く、全てを把握するのはとても大変だが、イベントをうまく使うと複雑できめの細かい処理も可能になる。 もしイベントの種類を確認したいなら、以下のページを参照するといいと思う。

イベントの制御

マウスがクリックされたとか、キーが押されたとかの制御、つまりイベントの制御の多くはシステムの仕事なので、イベントが起きたことは(間接的な場合もあるが)システムから知らされるようになっている。

だからイベントの発生(発火などとも言う)に関しては関知する必要はない。システムがイベントの発生を「伝えてくる」のを待つだけでいい。

イベントが起きたら処理を行うので、こういった処理のことをイベント駆動(event-driven)などと呼ぶ。

イベントリスナー(EventListener)

やっと本題にはいるが、イベントリスナーとはシステムからイベントが発生したメッセージを受け取る為の機能だ。

イベントリスニングにはターゲットが必要となる。イベント発生元と言った方が判りやすいだろうか。webならDOMがターゲットになる事が多い。たとえば<div>とか、<p>に対してとか、#hoge、.hogeなどのID、Classなどに対して、windowやdocumentなどに対してなどだ。

これらのターゲットになんらかの「事態」、つまりイベントが起きたときになにか処理を行いたい場合には、ターゲットにイベントが起こったこと知らなけばならない。

先ほど説明したようにイベントは(間接的にでも)システムから「伝えられる」ので、それを「聞く」必要がある。その為にイベントリスナーをターゲットにくっつけるのだ。

<div id=”hoge”>クリックしてね</div>

という要素があったとして、この要素がクリックされたら何らかの処理をするという場合を想定するなら、下記のようになる。

document.getElementById("hoge").addEventListener( "click" , function(){ //なんらかの処理 } );

addEventListener

#hogeに起きるだろうclickイベントをリスリングするようにしている。#hogeがclickされると、システムからfunction(){ //なんらかの処理 } が呼び出されて処理される。

ターゲットには何個でもイベントリスナーをくっつけることができる。

たとえば#hogeがクリックされた場合、カーソルが上に乗った場合、カーソルが外れた場合それぞれにイベントリスナーをくっつけることができるということだ。

document.getElementById("hoge").addEventListener( "click" , function(){ //なんらかの処理 } );
document.getElementById("hoge").addEventListener( "mouseover" , function(){ //なんらかの処理 } );
document.getElementById("hoge").addEventListener( "mouseout" , function(){ //なんらかの処理 } );

まとめ

イベントリスナーの仕組みをなるべく簡単に伝えられるように頑張ってみたが、ちょっと内容不足だろうか?

俺がイベント駆動の勉強をするとき、あまり突っ込んだ説明だと理解が難しかったので今回はあっさり目の記事にしてみた。もちろんこの記事だけの内容ではコードを書けるようにはならないと思うが、仕組みについては理解していただけたら幸いである。

どうでもいいことだが、システムから「声を掛けられる」ようなイメージなので「聴く人」「リスナー」なんだろうな。なんだかイメージ的にはviewとかでもいいような気がするが、俺は英語ができないのでわからない。

知っておきたいRSA暗号の仕組み

暗号化の技術

素因数分解を活用したRSA暗号

プログラミングなどに携わっている人は、「RSA暗号」という言葉を聞いたことがある人は多いかと思う。

公開鍵と秘密鍵を用意して暗号化するアレだ。サーバ認証とかいろいろなところで使われている。

仕組みはいたって簡単で、素因数分解が元になっている。

素因数分解

素数はご存じのとおり、1と自分以外に約数を持たない数のこと。2、3、5、7、11、13、17…と無限に存在する(らしい)。

そして素因数分解というのは、ある数字を「素数の掛け算に分解する」ことだ。たとえば6なら

6 = 2 × 3

に素因数分解できる。一見簡単そうな計算なのだが、素数というのは桁が大きくなるほどに判別がとても難しくなる。

556974を素因数分解をしてみる。基本的には小さい素数から割れるだけ割っていく方法をとるしかない。とにかく素数で割ってみるしかないという総当たり戦だ。

      • 556974 / 2 = 278487
      • 278487 / 2 割り切れない
      • 278487 / 3 = 92829
      • 92829 / 3 = 30943
      • 30943 / 3 割り切れない
      • 30943 / 5 割り切れない
      • 30943 / 7 割り切れない
      • 30943 / 11 = 2813
      • 2813 / 11 割り切れない
      • 2813 / 13 割り切れない
      • 2813 / 17 割り切れない
      • 2813 / 19 割り切れない
      • 2813 / 23 割り切れない
      • 2813 / 29 = 97
      • 97 / 29 割り切れない
      • 97 / 97 = 1

556974を素因数分解すると 2 * 3 * 3 * 11 * 29 *97になることがわかった。このくらいなら何とか手動でも計算できそうだが、一桁増えることに難易度がグンとあがることがわかっていただけるだろうか?

もちろんパソコンなら人間よりもっと多くの桁数を素因数分解できるはずだ。しかし、RSA暗号で使用される数字の桁数は300桁~1000桁ぐらいになる。

300桁と簡単に言うが、大きい数であろう1兆でも13桁なので、300桁はとんでもないくらいの大きな数字であることは間違いない。1兆の1兆倍を二十回以上繰り返して到達する領域だ。

この桁数を素因数分解するのは並大抵の労力ではない。

さっき556974を素因数分解したときのように、この計算は試行回数が桁数によって飛躍的に増える。また掛ける数字が素数であるかどうかの認定が難しい。小さい数であれば素数であること既知だけど、大きい数字なるとそれが素数なのかどうかの判定までしないといけない。

という訳で300桁の数字を素因数分解するには、コンピューターをもってしても、それこそ五劫の擦り切れぐらいの時間が必要となるのである。

こういった性質が解読が困難という訳で暗号に用いられるようになったのである。

実際どうやって暗号が作られているのか

暗号文の生成には素因数分解のほかにmodular arithmetic(モジュラー算術)を使う。これは剰余のこと。エクセルなんかでも余りを求めるときmod関数があったと思う。そのmodのことだ。

暗号は以下の式で作られ、復号される。

暗号文 = 情報のX乗 mod N 元の情報 = 暗号文のy乗 mod N

つまり公開鍵はxとNの組み合わせ、秘密鍵はyとNの組わせということになる。

暗号化したい情報は「150」と仮定しよう。

Nはこれまで説明してきた素因数分解される数字だ。Nは素数A×素数Bであらわすことができる。AとBは本来、百数十桁以上なのだが、ここでは説明の為に小さい数字にしたい。13と17にしよう。つまりNは221になる。

次にNを求める。Nは求めるのに手順がある。まず素数A素数Bから1を引くとA=12とB=16になる。この数字の最小公倍数を求めると48になる。 Nは「1 < N < 48 」かつ「Nと48の最大公約数が1」を満たす数字である必要がある。

この場合であれば、5、7、11、13、19、23、29、31、37、41、43が候補にあがる。今回は5としてみる。これで暗号化するための道具がそろった。150を暗号化すると以下のようになる。

63 = 150^5 mod 221

「150」が「63」になった。暗号化されたわけである。

こんどは復号だ。

150 = 63^y mod 221

が成り立つようにしなくてはいけない。yは 「1 < y < 48 」かつ「5 × y mod 48 = 1」満たす必要がある。計算してみると29だということがわかる。( 5 × 29 mod 48 = 1 = 145 mod 48 = 1 )なので、yは29だ。

150 = 63^29 mod 221

これで復号ができた。

まとめ

もともと1960代後半には理論が存在していたのだそうだが、暗号という性質上またコンピューターの性能がこの暗号を使うまでに至っていなかった理由で秘匿されていたようだ。しかし、1970年代になってこの理論を聞いたある研究者が、わずか30分程度で素因数分解とmodを使ったアイデアで具体案を示したというから驚きだ。

このRSA暗号も万能ではなく、解読されてしまう可能性はある。計算するのではなく、比べることで解読する攻撃方法があるのだそうだ。暗号化は誰でもできるし、同じ公開鍵で暗号化すれば、同じ情報は同じ暗号になる。そうするといくつか暗号を作って照らし合わせれば、推測することが出来なくはない。

今ではコンピューターも進化して桁数によっては解読できうるRSA暗号の種類もあるが、やはり桁数が大きくなるとスーパーコンピューターをもってしても解読は困難なようだ。

NetBeansIDEのコードテンプレートで爆速コーディング

爆速コーディング

俺はどうにも一つのソフトでいろいろ済ませたい願望が強く、統合開発環境がお気に入りだ。一時期eclipseを使っていたのだが、今はNetBeansIDEを使っている。

VimとかEmacsとかSublimeTextなどが爆速コーディングができるエディタとしてよく話題に上がるが、実はNetBeansIDEもコーディング速度に関しては健闘している。

NetBeansIDEにはコードテンプレートという機能があることをご存じだろうか?とっても便利な機能でコーディングスピードががっちり上がること間違いない。

コードテンプレートを活用すると、タイピング量を減らすことができるので、この俺でさえ体感的には2倍くらいコーディングスピードが増した気がする。

コードテンプレート機能を使ってみる。

コードテンプレートは簡単に言うと展開機能だ。あらかじめ決めておいた短縮コードをタイプして、TABキーを押すとコードが展開される仕組みになっている。

javascriptの例ではあるが、以下の様な機能だ。使い方はとても簡単。

clという短縮コードをタイプしてTABキーを押すと…

NetBeansIDE

console.logのコードが展開された。

NetBeansIDE

良く使うif文では…

NetBeansIDE

同じく展開された。

NetBeansIDE

必要なコードを入力してリターンを押すと、次にコーディングするべき場所にカーソルが合うようになっているのでとても便利だ。

NetBeansIDE

さらにelseifを展開してみる…。

NetBeansIDE

ちゃんと展開された。

NetBeansIDE

このようにタイピング量をかなり減らすことが出来る。

コードテンプレート機能を確認してみる

コードテンプレート機能はメインメニューのツール→オプション→エディタ→コードテンプレートで確認することができる。もちろんjavascriptだけでなく、java、C/C++、PHP、HTMLなどの言語のコードテンプレートもある。

NetBeansIDE

基本的な構文はあらかじめ定義されている。javascriptで多く書かなくてはいけないfunctionの短縮コードもいくつか登録されている。

NetBeansIDE

コードテンプレート機能を新規で作ってみる

もちろん自分で新規のコードテンプレートを作ったり、カスタマイズすることも可能。

コードテンプレートの内容としては、${}で囲まれた部分がカーソル移動場所となる。入力が終わりリターンを押すと先頭から順次${}がある場所にカーソルが移動する。${}の文字はそこに入る内容が判るようにするのがいいのだが、特に決まりはないので自由だ。

最後にカーソルを移動させる場所は${cursor}で指定できる。インデントさせたくない場合は${no-indent}を頭につければよい。

codetemp15

タイピングすると長いイベントリスナーとイベントハンドラを定義してみた。

NetBeansIDE

evとタイプしてTABを押すと

NetBeansIDE

イベントリスナーの良く使う形が展開された。わずか2文字をタイプしただけで、すっとイベントリスニング処理をコーディングできてしまう。

NetBeansIDE

まとめ

俺なんかでもやはり生産性というかスピードが速いことにこしたことはないなと思う。ちょっと鈍亀気味のIDEでも、有名なエディタにはかなわないかもしれないが、コーディング速度を上げることはできる。

ますますIDEに愛着が湧いてしまうことだろう。

ただ俺はNetBeansIDEを使い始めてから、大体6か月くらいこの機能がある事に気づいていなかった。なんて情けないんだろうと思う。道具を使いこなすというのは大切なことだなと改めて反省する。

wordpressのデータベースをデータベース管理ツールで覗いてみる方法

wordpressデータベースへのいざない

wordpressのカスタマイズをしていると、データベースってどうなっているのかなと気になりだす時が来る。 今回の記事ではwordpressのデータベースを覗き見する方法を紹介したいと思う。

wordpressが使うデータベースはおおよそMySQLだと思うので、データベース管理ツールを入れて覗き見するのがいいかと思う。もちろんコマンドラインからも見れるのだが、「見る」ともなればGUIの方が胃にやさしい。

Adminerを使ってみる

AdminerはphpMyadminより手軽

データベース管理ツールで有名なのはphpMyadminだ。しかしphpMyadminのインストールは少々面倒だ。ファイルがいっぱいあったり設定をいくつもしないといけなかったりする。

今回のようにちょっと確認するというだけの使い方で苦労をしたくない。そこで「え~phpMyadmin?」派に人気なのがAdminerというデータベース管理ツールだ。

このAdminerは1ファイルだけで構成されている。インストールもただ置くだけ。セキュリティにも優れているし、phpMyadminよりも動作が軽い(と思う)。phpMyadminの方がリッチではあるが、ライトな使用にはAdminerで十分だ。

そんなわけで今回はAdminerを使って説明していきたい。

Adminerのダウンロード&インストール

Adminerのダウンロードは以下のページ。

Adminer – Datebasemanagement in a single PHP file

adminer

ページ中腹にダウンロードリンクがある。 Adminer 4.1.0でも構わないのだが、MySQLでの使用しか考えないので、より軽いMySQL専用版をダウンロードすることにしよう。

adminer

Adminer 4.1.0 for MySQL (.php, 286 kB)をクリックするとSourceForgeリポジトリに遷移するので、ちょっと待てばダウンロードが開始される(SourceForgeには変な広告が貼ってあるので間違ってクリックしないように)。

adminer

ダウンロードされたPHPファイルをサイトの公開範囲直下(wordpressサイトであればindex.phpや.htaccessなどが置かれているところ)にFTPなどで置く(置く場所はどこでもよさそうだが一般的には直下に置くようだ)

adminer

アクセスはドメイン+ファイル名。もしAdminer 4.1.0 for MySQLをダウンロードしたのなら、「http://hoge.jp/adminer-4.1.0-mysql.php」となる。ファイル名を変えても動くので、任意のファイル名を与えても構わない。

アクセスすると言語は勝手に日本語になっているはずだ。またデータベース種類はMySQLのままでOK。

フォームで(データベースの)サーバ、(データベースの)ユーザ名、(データベースの)パスワード、データベース(名)を聞かれるので適切に入力するとログインできる。

adminer

画面デザインは非常に簡素だが、CSSでデザインを変えることもできる。公式サイトにはそれ用のスキンCSSが置かれているので、試してみてもいいかもしれない。

データベース管理ツール使う上での注意

ツールを使うとデータベースが簡単に覗けるが、裏を返せば簡単に編集出来てしまうということだ。Adminerでもテーブル・カラム・データの削除や変更を行うことはたやすい。

その為不用意に操作して大事なデータを消さないように注意をしてほしい。できれば念の為データのバックアップを取っておくことをお勧めする。もし本記事を参考にしてデータベースを覗いてみて、データを失っても責任は負いかねるのでご了承いただきたい。

Adminerの使い方

データベースの見方

Adminerでのログインがされると以下のような画面が現れると思う。これがデータベースの中身だ。中央に表があるが、このwp_から始まる名前の項目はテーブルというものだ。データベースではテーブルがいくつも関連しあってデータ構造を形作っている。

adminer

※wordpressのインストールに仕方によってはwp_xxxxx_commentmetaのようなテーブル名になっているかもしれない。xxxxxxの部分はテーブル接頭語というもので、テーブルを判別しやすくする名前のことだ。データベースが一つしか使えない場合でマルチドメインだと、サイトごとのwordpressでテーブル名がかぶってしまうことになる。その為にテーブル接頭語をつけてユニークなテーブル名にするのである。

テーブルを一つクリックすると(例ではwp_postをクリックしている)、そのテーブルの構造画面が現れる。この画面ではテーブルがどのような項目を持っているのか、またその項目はどのようなデータ形式で格納されているかなどが判るようになっている。

adminer

adminer

今回はデータを見るのが目的なので、この画面は特に気にしなくていい。上部にデータというリンクがあるのでクリックすると、実際にテーブルに入っているデータを閲覧することができる。基本的にデータを見る操作はこれだけだ。

adminer

テーブルの中には項目があり、そのことをColumn(カラム、列のこと)と呼ぶ。このカラムで情報が整理されている訳だ。記録されているデータはRow(ロー、行のこと)一つが一件分という訳だ。

adminer

wordpressのデータベースを覗く

wordpressのデータベーステーブル

wordpressのデータベースはデフォルトで11のテーブルで構成されている。これらのテーブルの関わりでwordpressのデータが管理されている。各テーブルの大まかな役割をまとめてみた。テーブルを確認するときの参考にしてほしい。

テーブル名 役割
wp_commentmeta コメント自体に付属する情報が記録されている。主にはコメント系プラグインで利用されているようだ。コメント系プラグインとしてはwordpressでスタンダードとも言えるAkismetではスパム認定したコメントの履歴などをこのテーブルに残しているようだ。また手動でコメントを削除した場合の履歴もこのテーブルに残るようである。
wp_comments コメント文章本体の他、コメントが付けられた投稿、投稿主、投稿者のIP、コメントした日時、コメントのタイプ(pinbackかtrackbackか)などが記録される。
wp_links リンク作成機能によるリンク情報が記録される。俺はリンク作成機能を使ったことが無いのでちょっと疎いのだが、リンク作成機能自体が非推奨になっているそうなので、あまり気にしなくてもいいテーブルかもしれない。
wp_options 管理画面の「設定」による設定、すなわち一般設定、投稿設定、表示設定、ディスカッション設定、メディア、パーマリンク設定、その他プラグインの設定に関してがこのテーブルに記録される。たとえばサイトURLであれば、「siteurl」と名前つけられ、それに対応するデータが記録されるようになっている。
wp_postmeta 投稿に使われているアタッチメント(画像など)、それらのメタデータやカスタムフィールドなど投稿に付随するメタデータが記録される。
wp_posts 投稿タイトル、投稿本文、投稿日時などなど投稿に関する情報が記録されている。このテーブルにある項目は$postに格納される情報とほぼ同じになる。このテーブルには固定ページも投稿も一緒に入っておりposttypeで見分けられるようになっている。
wp_terms カテゴリ・タグIDと名前、スラッグ、カテゴリ・タグが属しているterm_groupが記録されている。このテーブルではどれがカテゴリでどれがタグかはわからない。後述するwp_term_taxonomyによってカテゴリとタグの分類がなされる。
wp_term_relationships 投稿とカテゴリ・タグの関連付けの情報が記録される。
wp_term_taxonomy カテゴリ・タグの分類やディスクリプション、親子関係、投稿での使用件数などが記録される。
wp_usermeta ユーザーに関するmetaデータ。管理画面のユーザーで設定できる項目などが記録される。
wp_users ユーザーに関する情報が記録されている。1人でサイト運営している場合はすなわち自分のwodpressでのデータのこと。

wordpressのデータ構造

いくつかのテーブルを見ていくことでデータベースの覗き方に慣れてもらえたらと思う。今回はwordpressで中心となるだろうテーブルを4つ覗きながら、データベースの仕組みも簡単に説明したいと思う。

wp_postsテーブル

 wordpressデータベース

データベースの中ではwp_postsテーブルがもっとも核となるテーブルだろう。コンテンツの大本であり、サイトのデータそのものでもある。このテーブルには23のカラムが存在している。

Column名 格納されているデータ
ID 投稿ID
post_author 投稿者
post_date 投稿日時
post_date_gmt 投稿日時グリニッジ標準時
post_content 投稿本文
post_title 投稿のタイトル
post_excerpt 投稿の抜粋
post_status 投稿の状態
comment_status コメント機能の許可・不可
ping_status ピンバックの許可・不可
post_password 投稿のパスワード
post_name 投稿のスラッグ
to_ping ピン先のURL
pinged ピンバックをしたURL
post_modified 更新日時
post_modified_gmt 更新日時グリニッジ標準時
post_content_filtered フィルター適用後のバッファー用途
post_parent 親子関係の投稿ID
guid 投稿URL(パーマリンクではなく?p=xxxの形式URL
menu_order 固定ページの表示順
post_type 投稿の種類(投稿、固定)
post_mime_type 投稿形式がAttachmentの場合のMIME形式
comment_count コメント数

注目してほしいのは、このテーブルの項目はループ内で投稿データを格納するグローバル変数である$postの項目と同じことだ。つまり$postの正体とはwp_postsのrow一つ分ということだ。$postについては「wordpressで重要なグローバル変数$postの中身の説明」を参照してもらえればと思う。

もし投稿データをざざっとみたいのなら、PHPで出力するよりも、データベースを確認した方が早い場合が多い。

また、よく見てもらうとこのwp_postsテーブルではカテゴリやタグに関しての情報を持っていない。投稿データはカテゴリ・タグの情報は持たないように設計されている。一見投稿データにカテゴリやタグが結びついていないように見えるが、別なテーブルで管理されているのだ。

wp_termsテーブル

wordpressデータベース

Column名 格納されているデータ
term_id カテゴリ・タグのID
name カテゴリ・タグの名前
slug カテゴリ・タグのスラッグ名
term_group 親termのID

カテゴリ・タグはwp_termsテーブルに格納されている。ここではterm_id、name、slug、term_groupの項目がある。このテーブルでもtermがカテゴリなのかタグなのかはわからない。また投稿とどのように結びついているのかも不明だ。

wp_term_taxonomyテーブル

wordpressデータベース

Column名 格納されているデータ
term_taxonomy_id/td> wp_term_taxonomyテーブルでのtermID
term_id wp_termsテーブルで記録されているカテゴリ・タグID
taxonomy termの分類(カテゴリかタグか)
description termの説明
parent 親termのID
count termが使われてる回数

termがカテゴリなのかタグなのかを記録しているのはwp_term_taxonomyテーブルだ。このテーブルではwp_termsテーブルのterm_idにtaxonomy(カテゴリなのかタグなのか)やdescripton(termの説明)、親子関係、使用回数を結び付けている。しかしここでも投稿との結びつきはわからない。

wp_term_relationshipsテーブル

wordpressデータベース

Column名 格納されているデータ
object_id/td> wp_postsに記録されている投稿のID
term_taxonomy_id wp_term_taxonomyテーブルでのID
term_order termの表示順

投稿との結びつきはwp_term_relationshipsテーブルで管理されている。object_idは投稿のIDで、term_taxonomy_idはwp_term_taxonomyで格納されていたIDだ。

今回の例ではobject_idはterm_taxonomy_idをキーにしてwp_term_taxonomyテーブルからデータを得て、term_taxonomy_idはterm_idをキーにwp_termsテーブルからデータを得る。

リレーショナルデータベースたる所以

このようにデータを結び付けているのがリレーショナルデータベースのリレーショナルたる所以だ。テーブル同士をキーとなるColumnで関連させデータ構造を作ることをデータモデリングという。データの設計だとイメージしてもらえばと思う。

データをうまいこと各テーブルにばらして格納することでデータをより自由に効率よく扱うことができるようになるわけだ。

今、4つのテーブルの関係を見てきたが、他のテーブルもなんらかしらの関係を持っている。関係性に注目しながら、なぜそのような形でデータを格納させているのかを考えみてほしい。より深くwordpressの理解が深まるはずだ。おそらカスタマイズのアイデアもたくさん出てくるようになると思う。

まとめ

今回の記事ではwordpressのデータベースを覗く為の導入編として書いたがいかがだったろうか?以外と簡単に覗けたのではないかと思う。

wordpressではそれほど複雑なデータ構造にはなっていないので、データベースの勉強としてもうってつけだと思う。

冒頭でも書いたが、不用意な操作をしないように注意していただきたい。データベースを直接見るのは、wordpressの関数からデータベースを触るのとはわけが違う。バックアップを取る、不用意な操作をしない、慣れないうちは閲覧だけに留めるなどが大事だ。

あまり驚かすつもりはないのだが、俺はデータベースを消したことがある。いまでもトラウマだ。

あと画像にぼかしがはいっているのはエッチなことが書いてあるのではなく、個人情報だからだ。勘違いなさらぬように。

wordpressで重要なグローバル変数$postの中身の説明

wordpressをカスタマイズをしたい

wordpressをカスタマイズしているとさまざまな関数や変数やオブジェクトに出くわすことになる。より深くカスタマイズしていくとどうしても用意された関数ではなく、自分でデータを処理したい場面も出てくるだろう。

その時、自分が処理したいデータがどこにどのように入っているのか、また、どんなデータ構造になっているかを知ることはとても大切なことだ。

今回は特に触る事が多いであろうグローバル変数の$postについて説明したいと思う。

wordpressグローバル変数$postとは

$postが出来るまで

$postはループで「今の投稿データ」が格納されるグローバル変数だ。通常$postにデータが入る流れは簡単に説明すると以下の様になる。

  • 1.URLからクエリの解析
  • 2.解析したクエリでデータベースから情報を引っ張る
  • 3.$wp_queryにデータベースから得たデータが格納される
  • 4.テンプレートでのループの開始
  • 5.$wp_queryメソッドのhave_posts()で投稿データの有無を判断する
  • 6.記事があった場合、$wp_queryメソッドのthe_post()で$postに「現在の投稿データ」が入る

$postの使い道

$postの使い道はずばり投稿データのカスタマイズにある。たとえば投稿の中から任意のhtmlタグを抜き出す処理や、ID取得からの他の処理につなげる、リストとシングルページで表示形式を変える為のタイトル加工や日付加工など、考えられる処理は多い。

$wp_queryにはクエリによって得られたすべての投稿データや状態情報が入るので、非常に膨大で複雑な構造になっている。

たくさんのメソッドが存在しており、$wp_queryを操作することも可能ではあるが、投稿データの為に$wp_queryは扱うのには大きすぎる。

そこで、一つ一つの投稿データを格納する$postの出番だ。$postは一つの投稿データ(タイトル、投稿日時、本文など)を格納するので、$wp_queryよりもずっと構造が単純で、格納される情報も少なく扱いやすい。

投稿データをなんらかの形でカスタマイズする場合は$postを主体にアイデアを練るのがお勧めだ。

wordpressグローバル変数$postの中身

$postの各プロパティの意味・型など

以下に$postの各プロパティについてまとめてみた。いろいろ調べてまとめたが、普段使わない値もあり少し説明に不安なところがある。できればWordPressCodex等を合わせて参照してもらえればと思う。

ID
意味
投稿のID
値の型
整数値(int)
値の例
189
値へのアクセス
$post->ID
関係するテンプレートタグ・関数
the_ID()
post_author
意味
投稿の著者
値の型
文字列(string)
値の例
※著者のIDが入る
値へのアクセス
$post->post_author
関係するテンプレートタグ・関数
the_author()
the_modified_author()
get_modified_author()
post_date
意味
作成日時(日本なら日本標準時)
値の型
文字列(string)
値の例
2015-01-18 15:49:02
値へのアクセス
$post->post_date
関係するテンプレートタグ・関数
the_date()
the_time()
get_the_date()
get_post_time()
post_date_gmt
意味
作成日時(グリニッジ標準)
値の型
文字列(string)
値の例
2015-01-18 06:49:02
値へのアクセス
$post->post_date_gmt
関係するテンプレートタグ・関数
the_date()
the_time()
get_the_date()
get_post_time()
post_content
意味
投稿本文
値の型
文字列(string)
値の例
※エディタ等で書かれたhtmlタグを含めた本文がそのまま入っている
値へのアクセス
$post->post_content
関係するテンプレートタグ・関数
the_content()
the_content_rss()
get_the_content
post_title
意味
投稿タイトル
値の型
文字列(string)
値の例
これはタイトルです
値へのアクセス
$post->post_title
関係するテンプレートタグ・関数
the_title()
the_title_rss()
the_title_attribute()
get_the_title()
post_excerpt
意味
投稿の抜粋
値の型
文字列(string)
値の例
※抜粋に記述された文字列が格納されている
値へのアクセス
$post->post_excerpt
関係するテンプレートタグ・関数
the_excerpt()
the_excerpt_rss()
get_the_excerpt()
post_status
意味
投稿の状態
値の型
文字列(string)
値の例
publish(公開済)
future(予約投稿)
private(非公開)
draft(下書き)
Auto-draft(自動下書き)
Inherit(メディア、リビジョン等)
trash(ゴミ箱)
値へのアクセス
$post->post_status
関係するテンプレートタグ・関数
the_author()
get_the_author()
comment_status
意味
コメントの扱い
値の型
文字列(string)
値の例
open(公開)
colse(非公開)
値へのアクセス
$post->comment_status
関係するテンプレートタグ・関数
特になし
ping_status
意味
ピンバックの扱い
値の型
文字列(string)
値の例
open(公開)
colse(非公開)
値へのアクセス
$post->ping_status
関係するテンプレートタグ・関数
特になし
post_password
意味
投稿のパスワード(設定している場合のみ)
値の型
文字列(string)
値の例
※英数字のみ使用可
値へのアクセス
$post->post_password
関係するテンプレートタグ・関数
post_password_required()
post_name
意味
投稿のスラッグ名
値の型
文字列(string)
値の例
car-sport-about
値へのアクセス
$post->post_name
関係するテンプレートタグ・関数
get_page()※ver3.5.0から非推奨
to_ping
意味
ping送信先
値の方
文字列(string)
値の例
※ping送信先のURLが入る
値へのアクセス
$post->to_ping
関係するテンプレートタグ・関数
特になし
pinged
意味
トラックバック送信先(履歴)
値の型
文字列(string)
値の例
※トラックバック送信先のURLが入る
値へのアクセス
$post->pinged
関係するテンプレートタグ・関数
特になし
post_modified
意味
更新日時(日本なら日本標準時)
値の型
文字列(string)
値の例
2015-01-18 15:49:32
値へのアクセス
$post->post_modified
関係するテンプレートタグ・関数
the_modified_date()
the_modified_time()
post_modified_gmt
意味
更新日時(グリニッジ標準)
値の型
文字列(string)
値の例
2015-01-18 15:49:32
値へのアクセス
$post->post_modified_gmt
関係するテンプレートタグ・関数
the_modified_date()
the_modified_time()
post_content_filtered
意味
フィルター処理後の投稿本文(等)
値の型
文字列(string)
値の例
※フィルターでの処理結果をキャッシュする目的等で使われるフィールド
※どちらかというと開発寄りのパラメータ
値へのアクセス
$post->post_content_filtered
関係するテンプレートタグ・関数
特になし
post_parent
意味
親投稿のID
値の型
整数値(int)
値の例
※親投稿のIDが入る
値へのアクセス
$post->post_parent
関係するテンプレートタグ・関数
特になし
guid
意味
投稿のURL
値の型
文字列(string)
値の例
http://hoge.jp?p=100
※パーマリンク設定でのURLではなく?p=形式のデフォルトURLが入る
値へのアクセス
$post->guid
関係するテンプレートタグ・関数
the_permalink()
get_permalink()
get_post_permalink()
get_page_link()
menu_order
意味
表示順のオーダー
値の型
整数値(int)
値の例
※固定ページのパラメータ「順序」にて設定した数字
値へのアクセス
$post->menu_order
関係するテンプレートタグ・関数
特になし
post_type
意味
投稿の形式
値の型
文字列(string)
値の例
post(投稿)
page(固定ページ)
revision(更新記録)
Attachment(添付)
値へのアクセス
$post->post_type
関係するテンプレートタグ・関数
register_post_type()
post_mime_type
意味
投稿形式がAttachmentの場合のMIME形式
値の型
文字列(string)
値の例
image/png
image/jpeg
image/gif
値へのアクセス
$post->post_mime_type
関係するテンプレートタグ・関数
特になし
comment_count
意味
コメント数
値の型
文字列(string)
値の例
※ピンバックとコメントの合計数になるとのこと
値へのアクセス
$post->comment_count
関係するテンプレートタグ・関数
comments_number()
filter
意味
サニタイズの形式
※サニタイズとは特殊文字等を「単なる文字」として扱う為の処理のこと
※エスケープとほぼ同義
値の型
文字列(string)
値の例
raw(数値フィールドの値をサニタイズ)
edit(各種フィルターフックでサニタイズ)
db(各種フィルターフックでサニタイズ)
display(各種フィルターフックでサニタイズ)
attribute(各種フィルターフック後にesc_attr()でサニタイズ)
js(各種フィルターフックの後にesc_js()サニタイズ)
値へのアクセス
$post->filter
関係するテンプレートタグ・関数
esc_attr()
esc_js()

$postの中身を見てみたい人はvar_dump()かprint_r()を使おう。

俺はvar_dump()派。print_r()よりも情報が詳細だ(データの型なども表示される)。見た目も<pre>タグで囲ってしまえば、そこそこきれいだが、やはりちょっと見難い。

var_dump()した場合はページソースを直接確認する方が読みやすい。確認にはページソース閲覧をお勧めする。

以下のようにループ内でvar_dumpを記述してあげれば、ループで得られる投稿の情報がズラズラと表示されるはずである。

<?php if (have_posts()) : ?>	
    <?php while (have_posts()) : the_post(); ?>
        <pre><?php var_dump($post); ?></pre>
        <!--なんらかのhtml-->
    <?php endwhile; ?>
<?php endif; ?>

まとめ

wordpressカスタマイズの一歩は$postからなどともいわれるように[誰によって?]、最初に手を付けるにはうってつけな難易度だと思っている。

$postをうまくハンドリングできるようになると、投稿データをいかようにも扱うことができ、見せ方をいろいろ工夫することができる。もちろんjavascript等と連携すれば、かなり高度な処理にも通じるはずだ。

wordpressを使い倒してやろうではないか。

NetBeansIDEで簡単に出来るGitリポジトリをGoogle Drive™に置く方法

プライベートなリポジトリホスティングをしたい

GitHubでの問題点

Gitリポジトリホスティングサービスといえば代表的なのはGitHubだろうか?使う人も多いと思うが、GitHubは無料使用だとリポジトリが公開されるパブリック形式になってしまう。

趣味のプロジェクトや積極的に公開したいプロジェクトなどならパブリックでも構わないのだが、仕事の案件となるとGitHubを使うのは躊躇せざるを得ない。

もちろん有料使用であればプライベートにできるのだが、課金するまでもないくらいにしか仕事がない俺は、できれば無料でプライベートなリポジトリホスティングが出来ないかと思っていた。

Googleドライブの利用

いくつかそういったサービスもあるようだが、身近に無料で使えるストレージがあることを思い出いした。

Googleドライブである。

昨今ならほとんどの人がGoogleアカウントを持っているだろうし、使うのに障壁が低いクラウドストレージだと思う。

Googleドライブにリポジトリを置ければ、これはプライベートリポジトリホスティングと言えるのではないか。フォルダの共有を行えば、複数人で使える共有リポジトリ置場としても使えそうだ。さっそく俺はGoogleドライブにリポジトリを置くことにした。

注意点

俺はGitをNetBeansIDEから使っているので、コマンドラインからの操作が説明ができない。一応補足でコマンドラインの説明はするが間違っている可能性もあるので注意してほしい。 もしコマンドラインからの操作をしたい場合はこのページを参考にしてもらうといいかもしれない。

Googleドライブにリポジトリを置く方法

NetBeansIDEでの操作順序

NetBeansIDEからGoogleドライブにリポジトリを置くには手順が重要なようだ。以下の様な手順になる

  • Googleドライブでリポジトリを置く為のフォルダを作っておく
  • Googleドライブ上に裸のリポジトリを作る
  • パソコン(以下ローカル)にリポジトリを作り、プロジェクトのファイルをコミットする
  • Googleドライブ上リポジトリにローカルリポジトリのリモートリポジトリに登録してプッシュする

それではNetBeansIDEでGoogleドライブでリポジトリを置く流れを説明したいと思う。

Googleドライブの準備

1.当たり前だが、Googleドライブの使用には、Googleのアカウントが必要なので、無い場合は取得する。もしGoogleドライブのクライアントをインストールしていないようだったらインストールしてほしい。

クライアントはGoogleドライブのページに行き、設定ら「Googleドライブのダウンロード」でダウンロードできる。

Googleドライブの準備ができたら、任意の場所にGitリポジトリを置く為のフォルダを作っておこう。(フォルダをつくらなくてもリポジトリは置けるが整理が大変になる)

NetBeansIDEの準備

2.まだNetBeansIDEを使ったことの無い人はインストールしてほしい。ダウンロードはこちらから

NetBeans IDE ダウンロードバンドルは「すべて」をダウンロードすると最初から機能がもりだくさんなのでお勧め。

インストールに関してはこちらのページを参照のこと。

インストールが済んだら起動して、NetBeansIDEのメニューの「ツール」→「Git」→「リポジトリ・ブラウザ」でリポジトリ・ブラウザを出しておこう。

NetBeansIDE

NetBeansIDE

3.テスト用にプロジェクトを新規する。テストなのでどのプロジェクトでも構わないが、説明ではHTML5アプリケーションで進めたいと思う。

NetBeansIDE

NetBeansIDE

NetBeansIDE

Googleドライブに裸のリポジトリを作る

4.新規プロジェクトを作ったら、本題であるGitに関しての操作だ。プロジェクトを選択して、右クリックメニューから「バージョン管理」→「Gitリポジトリの初期化」をクリックする。

NetBeansIDE

5.どこにリポジトリを作るか聞かれるので、Googleドライブであらかじめ作っておいたリポジトリ用のフォルダを指定する。

NetBeansIDE

6.これでGoogleドライブ上に裸のリポジトリが出来た。コマンドラインではカレントディレクトリがgoogleドライブのリポジトリ用フォルダでのgit init –bareに相当する。

git13

ローカルにリポジトリを作る

7.今度はローカルにリポジトリを作る。プロジェクトを選択して、右クリックメニューから「バージョン管理」→「Gitリポジトリの初期化」をクリックする。

NetBeansIDE

8.デフォルトではプロジェクトファイルが保存されているフォルダが指定されているので今回は、デフォルトのままリポジトリを作ることにする。もちろん任意のフォルダを指定することは可能。

コマンドラインではカレントディレクトリが任意のローカルディレクトリでのgit initに相当

NetBeansIDE

NetBeansIDE

プロジェクトファイルをコミット

9.ローカルに作ったリポジトリにファイルをコミットする。ダイアログがでるので、コミットメッセージを記入して、「HEADと作業ツリー間の変更」ボタンを有効にしておく。今回はindex.htmlだけなので、ちゃんと選択されているか確認して、コミットボタンをクリック。

上手くいけば、ローカルリポジトリのローカルにMasterブランチが出来ているはずだ。

コマンドラインではgit commit index.html -m “Initial commit.”に相当する。

NetBeansIDE

NetBeansIDE

NetBeansIDE

Googleドライブに作ったリポジトリをリモートリポジトリにする

10.これでローカルリポジトリに命が宿る。今度は最初に作ったGoogleドライブリポジトリをリモートリポジトリにする為の操作を行っていく。 ローカルリポジトリを選択して、右クリック「リモート」→「プッシュ」を選択。

NetBeansIDE

11.リモートリポジトリのダイアログが出ててくる。デフォルトで「Gitリポジトリの場所を指定」にチェックが付いていて、先に作っていたGoogleドライブのGitフォルダが指定されているはずだ。

もし指定されていなかったら、GoogleドライブのGitフォルダのパスを指定する。 リモート名はoriginのままでoK。

NetBeansIDE

12.次へを押すとブランチの選択になるが、表示されているブランチすべてにチェックする。ここではmasterブランチしかないので、表示されているのは一つのはずだ。

NetBeansIDE

13.ローカル参照更新ダイアログでも表示されているブランチすべてにチェックする。

終了ボタンを押すと、ローカルリポジトリの内容がGoogleドライブ上のリポジトリーにプッシュされ、同期する。

コマンドラインではgit remote add origin file://[gooogleドライブに作ったgitフォルダのパス]、git push origin masterに相当する。

NetBeansIDE

14.これでGoogleドライブ上のリポジトリがローカルリポジトリのリモートリポジトリになった。

上手くいくと、GoogleDrive上のリポジトリ(リモートリポジトリ)のローカルにMaster-[ハッシュ値]が表示されているはずだ。

また、リポジトリブラウザのローカルリポジトリのローカルにMaster(“origin/Masterと同期”)-[ハッシュ値]と表示され、リモートにorigin/Master-[ハッシュ値]が表示されているはずだ。

Masterのハッシュ値はすべて同じになっているはずである。

git24

あとはローカルリポジトリで作業を進めて、適時プッシュをすればローカルリポジトリとGoogleドライブ上のリポジトリは同じ内容になる。

ローカルにもクラウドストレージにもリポジトリがある事でプロジェクトのバックアップとしても安心だ。

共有リポジトリとして

もし誰かとリポジトリを共有したいのであれば、googleドライブに作ったgitフォルダを共有設定にして、クローンをとってもらえばいい。

セキュリティに関してはGoogleドライブのセキュリティに準拠することになるだろうから、俺は詳しく語れない。ドライブのマニュアルを熟読することをお勧めする。

まとめ

実はNetBeansIDEからGoogleドライブにリポジトリを設置するには骨が折れた。手順を誤ると機能しなかったからだ。なんどか手順をやり直してやっと機能させられるようになった。

特に先にGoogleドライブ上にリポジトリを作らないと、ローカルにリポジトリを作った後だと作りようがなくなってしまう。この辺がコマンドラインでの操作と比べると窮屈に感じてしまう。

Gitサーバを建てたりする人もいるようだが、俺には少し難しい。

Googleドライブならとても簡単に運用できる。Googleドライブは無料では容量が15GBで、俺はそれで困ったことはないが、もしもっとほしい場合には追加(有料だが)することもできる。

ネットで探してもGoogleドライブにリポジトリを置こうとする情報はあんまりなかった。googleドライブの利用規約の件もあるので、嫌厭されているのだろうか?

基本的には操作は同じなので、Googleドライブが嫌な人はDropBoxやOnedriveで構築してもいいかもしれない。

ただDropBoxやOnedriveはアップロードされたファイルの所有権は主張してないが、使用権はどうなんだろう。Googleドライブも所有権に関しては主張していない。

[javascript]即時関数の基本的な記述方法

即時関数

即時関数とは即時に実行される関数である。名前が無いので無名関数の仲間とも言える。javascriptではスコープを実現するために必要不可欠な関数で重要度が高い。今回は即時関数の記述に関してまとめてみた。

文と式の話

即時関数の話の前に、前提を説明しておきたい。

プログラミングの記述には大きく分けて二つの要素がある。

式(expression)文(statement)である。

これらの厳密な説明はwikipediaに任せるとして、javascriptでは簡単に言うと、;で閉じないといけないのが式で、閉じなくてもいいのが文という見分けでも構わない。

式になる関数 文になる関数

文になる関数

名前を付けて関数を定義する場合は文になる。;を付けなくてもエラーにはならない。

function funcA(){
    //なんらかの処理
}
//;が無くてもいい

式になる関数

無名関数の場合、この記述は式となる。( = が代入演算子で評価しているから式になる)

var funcB = function(){
    //なんらかの処理
};
//;が無いとエラーになる

無名関数を裸で記述すると…

それでは無名関数を代入せずに裸で記述してたらどうなるか?

function(){
    //なんらかの処理
}
//;を付けようがが付けまいがエラーになる

この記述だと式なのか文なのかが定まらない為、エラーになってしまうのだ。

即時関数への発展

即時関数は式

エラーになってしまう裸の無名関数を強引に式にしたものが即時関数となる。

(function(){
    //なんらかの処理
});

()で囲まれただけだが、()は「式のグループ化」という役目を担っている。たとえば( (a + b ) * 3 )のような場合も()によってグループ化された式と言える。

()で囲まれたものはそれ全体が式であるという解釈になるのである。

式となるなら()でなくともよいが…

実は即時関数を作るには、()でなくてもいい。下記のような書き方でも演算子によって全体が式と認識されるので、エラーにはならない。

+function(){
    //なんらかの処理
};

-function(){
    //なんらかの処理
};

!function(){
    //なんらかの処理
};

しかしこれらの記述だと、演算子が悪さをして予想もつかない事態を招いてしまうかもしれない。だから外側に何も影響を及ぼさない()を使うのが一番安全という訳なのである。

とりあえずは無名関数を()で包むと即時関数になると覚えてもらえばOKだ。

引数の取り方

即時関数でも引数を取ることができる。以下のように記述する。

( function( a , b ){
    return a + "は" + b + "です";
} )( "にわとり" , "鳥" );

これは少し不思議な記述に見えるかもしれない。俺もjavascriptを勉強し始めたときは、少し不思議に思った。なぜ後ろの()で指定している引数がちゃんと使われるのだろうと。

普通の関数で引数を取る場合を見て整理してみよう。

//関数を定義 
var funcA = function( a , b ){ return a + "は" + b + "です"; }; 
funcA( "にわとり" , "鳥" ); 
//にわとりは鳥です 
//と表示されるはず

引数a,bを取る関数funcAを定義して実行している。funcAの中身はfunction( a , b ){ return a + “は” + b + “です”;}(への参照)なので、funcA( “にわとり” , “鳥” );は

function( a , b ){ return a + "は" + b + "です"; }("にわとり" , "鳥" );

と同じ意味になる。これだとエラーになるので、function部分を式にしたのが以下だ。

( function( a , b ){ return a + "は" + b + "です"; })("にわとり" , "鳥" );

まんま即時関数である。だから、”にわとり”は第一引数(a)に”鳥”は第二引数(b)にしっかりと引き継がれるのである。

まとめ

今回は即時関数の記述方法を説明してみた。この即時関数というのはjavascriptが変態である理由の一つだと思うのだが、クロージャを実現する要素としては重要な事この上なしだ。

ブロックスコープが無いjavascriptにとってスコープを局所的に作る唯一の方法とも言える(関数が)。

ちなみに英語では即時関数を「immediate function」というそうだ。 いやむしろ「immediate function」を訳して即時関数になったというべきか。

変数でjQueryのCSSメソッドに値を指定する方法

jQueryのcssメソッド

jQueryで割と多く使われるのはcssメソッドではないだろうか?

$( ".hoge" ).css( "width" , "100px" );

jQueryの使用動機は、cssの編集に起因する事が多いと思う。俺も最初はcssをいじりたいからjQueryを勉強し始めた。

jQueryのcssメソッドで変数を使う方法は、javascriptにもjQueryにも慣れていなかった俺がつまずいたところだ。これからjQueryを覚えたいという人はぜひ読んでいただきたい。

jQueryのcssメソッドは文字列リテラルを使う

jQueryのcssメソッド

jQueryのcssメソッドでは「セレクタ」、「プロパティ」、「プロパティの値」を指定する形になっている。(セレクタの指定はjQueryメソッドなので厳密にいうとこの説明は合っていないが気にしない)

文字列リテラル

各指定は「文字列リテラル」で行われている。文字列リテラルとは「文字列として解釈される書式で記述された値」のことを言う。javascriptでは通常であれば[“]や[‘]で囲まれているのは文字列リテラルとなる。

すこし語弊があるが、jQueryのcssメソッドは「文章」を使って値を指定すると言える。

“hoge”や”width”や”100px”は文字列リテラルで文章というわけだ。

変数を用いた場合

簡単な例

つまり変数を用いる場合でも最終的に指定が「文章」になっていればいいのである。

var hogeWidth = $( "body" ).width() * 0.5;
 $( ".hoge" ).css( "width" , hogeWidth );

この例は最も単純な例だ。bodyのwidthを取得して、0.5を掛けたものをhogeWidthに格納して、それを.hogeのwidthの値として指定している。

jQueryの内部処理についての注意点

ここで注意が必要だが、width()で取得できる値は文字列リテラルではなく、数値だ。だからhogeWidthに入っている値は数値型(数値リテラル)のはずだ。例ではそれをそのまま指定している。

さっきjQueryのcssメソッドでは文字列リテラルを使うと説明していたのにおかしいではないか。

これについては、単に数値型として指定された場合に、jQueryの中で”px”をつけて文字列にするという処理が行われていることに起因する。

だから数値で指定しても問題がないのだ。つまり以下のような事をjQueryがやってくれているという訳だ。

//hogeWidthは末尾に"px"が付いていることで文字列リテラルになる 
var hogeWidth = ( $( "body" ).width() * 0.5 ) + "px"; 
$( ".hoge" ).css( "width" , hogeWidth );

例えば、bodyが800pxだとしたら、$( “body” ).width() * 0.5 は400となる。これに”px”が付くので、”400px”という文字列リテラルが hogeWidthに代入されるということになる。

任意の単位を付けたいときは

しかし”px”が自動で付いてくれるのはありがたいが、”px”ばかりではなく”%”や”em”など指定したい単位はいろいろあるはずだ。

その為には「文章を作る」ことで指定が可能になる。上記の例のように任意の単位を変数につなげてしまうのだ。

そうすれば文字数リテラルになるので、値の指定ができる。

var hogeWidth = ( ( $( "#main" ).width() / $( "body" ).width() ) * 100 ) + "%";
$( ".hoge" ).css( "width" , hogeWidth );

上記の例ではbodyのwidth値で#mainのwidth値を割ってその答えに”%”を付けている。”70%”とか”80%”といったような文字列リテラル、すなわち文章がhogeWidthに代入されることになる。

複数の値の一括指定の場合は

marginなどのように上下左右の値を一括で指定できるプロパティもある。この場合はどうしたらいいのだろうか。通常では文字列リテラルだと以下の様な形になる。

//普通に文字列リテラルで指定した場合
$( ".hoge" ).css( "margin" , "10px 10px 5px 3px" );

margin-topの指定だけ変数にしたいという場合を想定した場合次のような書き方ができる。

//変数に格納してから指定
var hogeMargin = ( $( ".hoge" ).width() * 0.1 ) + "px 10px 5px 3px"
$( ".hoge" ).css( "margin" , hogeMargin );
//直接指定
$( ".hoge" ).css( "margin" , ( $( ".hoge" ).width() * 0.1 ) + "px 10px 5px 3px" );

$( “.hoge” ).width() * 0.1 の答えに”px 10px 5px 3px”をくっつけているだけである。変数に代入しなくても、直接式を記述しても構わない。

これで”12px 10px 5px 3px”とか”8px 10px 5px 3px”とかという文字数リテラルがhogeMarginに代入される事になる。

複数の値の一括指定の応用

さらに例を挙げたい。今度はmargin-topとmargin-leftの指定を変数にしたい場合は以下のようになる。

//変数に格納してから指定
var hogeMargin = ( $( ".hoge" ).width() * 0.1 ) + "px "+ ( $( ".hoge" ).width() * 0.2 ) + "px 5px 3px"
//直接指定
$( ".hoge" ).css( "margin" , ( $( ".hoge" ).width() * 0.1 ) + "px "+ ( $( ".hoge" ).width() * 0.2 ) + "px 5px 3px" );

これも全部つなげているだけだ。気を付けてほしいのが、( $( “.hoge” ).width() * 0.1 )の後のpxの後ろには半角空白がある。

なんで必要かお分かりだろうか?

これが無いと、hogeMarginに代入されるのは以下のようになってしまう。

“6px12px 5px 3px”

これではmargin-topとmargin-leftの値がくっついてしまっているので、cssの値の指定としてはエラーになり、cssは適用されない。

変数を用いる時は、正しい形の文字列リテラルと「同じ文章」にすることが大事だ。だから空白も適切に繋いであげなければならない。

セレクタもプロパティも文字列リテラル

セレクタもプロパティの指定にも変数を使うことが可能だ。今までの説明と同じように文字列リテラルを作れればOKである。

$(window).on("ready" , function(){
    var bodyWidth = $( "body" ).width();
    var hogeClass = ["Postlist","Article","Index","Catgory"];
    var propertys = ["width","height"];
    var amount = ["20px","30px","40px","50px"];
    var widths = [];
    var sw,i=0;
    if( bodyWidth > 900 ){
        sw = 0;
    }else{
        sw = 1;
    }
    for ( ; i < hogeClass.length ; i++ ){
        //セレクター、プロパティ、プロパティの値すべて変数による指定
        widths[i] = $( ".hoge" + hogeClass[i] ).css( propertys[sw] , amount[i] );
    }
});

すこし複雑に見えるが、やっている事は簡単だ。配列に格納している文字列と”.hoge”をつなげてセレクタ文章を作っている。

またwidthとheightのプロパティも配列に格納して bodyのwidthの条件でどちらにするかを決めている。最終的にはfor文を回して、これまたあらかじめ配列に入れてある値を指定している。

この例には実用性はほとんどないが、セレクタやプロパティを変数で動的に指定できるという可能性を理解してもらえれば幸いである。

まとめ

変数指定が思うように使えるようになってから、jQueryでやれることも大きくひろがった記憶がある。

俺は最初文字列リテラルの意味さえ分からなかったので、marginの値の指定などはどうしたらいいのかさっぱりわからなかった。

一括指定がどうしてもできないから、効率が悪いなと思いながらもmargin-topなどに分けて指定していたほどだ。

そのうち「あ、なるほど」とおもえる瞬間がきて、一気に理解が深まる瞬間がある。

そのお手伝いがこの記事で出来たら俺はうれしい。

VirtualBoxで64bit版Linuxディストリビューションがインストールできない現象の理由

VirtualBoxを使ってみる

仮想マシンを簡単に作れる「VirtualBox」。オラクルが提供するありがたいソフトだ。最近、Linuxの勉強をしたいと思い、VirtualBoxを手持ちのwindows機にインストールし、さまざまなLinuxディストリビューションを試すつもりでいた。

VirtualBoxのインストールはあっさり済み、仮想マシンの作成もwebで調べながら行ったので、難しくはなかった。

仮想マシンに64bit版Linuxディストリビューションがインストールできない

さてそれではCentOSでもぶち込もうかとisoイメージをセットして、仮想マシンを起動したところ、インストールメニューは表示されるが、インストールを選んで先に進むと「カーネルがクラッシュしました」といった旨のエラーメッセージが表示されて止まってしまう。

さてなぜ失敗するんだ。色々見ていたところ、「名前とオペレーションシテム」のダイアログでLinuxを設定すると(32bit)が付いているものしか選べないことに気が付いた。無職に対する嫌がらせか。

32bit版しか選べない

CentOSの最新バージョン(2015/1現在ver7以降)は64bit版オンリーで32bit版のサポートをしていない。無職でも64bit版入れたいのだが。

インストールできない理由

「PAE/NXを有効にする」

いろいろ調べたところ設定>システム>プロセッサーにあるメニュー「PAE/NXを有効にする」にチェックを入れる必要があるという事がわかった。

PAEとは物理アドレス拡張という機能の事で、早い話、32bit環境でも4GB以上のメモリを使えるようにするためのテクノロジーだ。

俺には良くはわからないがとにかく64bit版を入れたいのでチェックしてみた。

しかしこれにチェックを入れても、CentOS7を仮想マシンにインストールすることはできなかった。

仮想化テクノロジーの存在

そこでまたいろいろ検索していると、仮想化テクノロジーという言葉に行きついた。

仮想化技術というのはCPUの機能と密接にかかわっているようで、仮想化を実現するにはCPUがそれをサポートしていることが大切だ(ハードの事はソフトの事以上に詳しくないので説明がおかしいかもしれない。気になる人は自分で調べていただきたい)

パソコン用のCPUの大手メーカーと言えばIntelとAMDだが、どちらも仮想化テクノロジーをCPUに搭載している。IntelはIntel VT-x(Intel Virtualization Technology)、AMDはAMD-V(AMD Virtualization)という名称だ。

インストールできない理由は仮想化テクノロジーの所為?

そしてどうやらその仮想化テクノロジーというのはBIOSでオンオフが出来るCPUの機能であるらしい事がわかった。

そうか、仮想化テクノロジーをONにしたら64bit版がインストールできるのか。では早速BIOSで設定をば。

しかしBIOSをくまなく探せども、そのような設定項目が見つからない。

PCによってBIOSも違うので一概には言えないが、おおよそAdbanced>CPU Configurationの中に、IntelCPUであれば、「Intel(R) Virtualization Technology」という項目があるというではないか。

いや無いぞ。どう考えても無い。何度探しても「Intel(R) Virtualization Technology」の項目は見つからなかったのである。

64bit版をインストールできなかった本当の理由

そこで今度は「Intel(R) Virtualization Technology」を出現させる何かがあると思い、いろいろ調べてみた。なにかハードウェアスイッチみたいのがあるのだろうか。

そうこうしていると検索で価格COMのノートパソコンのレビューでユーザー同士が質疑応答しているページに出くわした。

質問者は「どちらのパソコンがいいか」とCPUの違う二機種を挙げてアドバイスを求めており、それの解答に今回の答えが書かれていたのである。

曰く「…B980はVT-xなどをサポートしていないので、corei3搭載機を勧めます…」と。

B980とは「Intel® Pentium® Processor B980」の事だ。調べてみると俺のノートに入っているCPUもIntel® Pentium® Processor B980だった。

Intelのページを確認してみると、確かにインテル® バーチャライゼーション・テクノロジー (VT-x) のサポートは”NO”になっている。

つまり「Intel(R) Virtualization Technology」の項目が見つからないのではなくて、最初から存在しないのである。

あ、そうサポートしていないの。

うっすら目に涙が溜まるのを感じた。

仮想化テクノロジーのサポート

「Intel(R) Virtualization Technology」はすべてのCPUに搭載されている訳ではないのだ。AMDのCPU搭載機でも同じようなことが言えるのだろう。

仮想化自体は一般のユーザーはあまり使わない機能で、無くても問題ない場合が多い。俺や皆さんのように開発環境をこしらえたい人種が使いたいと思うのが大半である。

だからローエンドクラスのCPUでは仮想化テクノロジーをサポートしていないものも存在する。そして俺のノートのCPUはまさにそれだったのである。

ノートを買うとき開発環境の構築のことなど考えてもいなかった。その時はまだphpもjavascriptもjQueryも知らないようなウブさだったからだ。

失業してるし金もないし彼女もいないし、とりあえず安く買えればいいやと思い、amazonにて4万円前半台で購入したのだった。

パソコンを買うときは気を付けて

気を付けてもらいたいのは、俺のパソコンに搭載されている「Intel® Pentium® Processor B980」は2015/01現在でも出荷されていることだ。

俺が今のノートを買ったのは2013の末だから、それほど前のことではない。現行の廉価版のノートなどにはB980や、それ以外の仮想化未サポートのCPUが乗っている可能性も十分ある。

もしVirtualBoxなどの仮想化マシン作成ツールをフルに使いたいならば、VT-xまたはAMD-VをサポートしているCPUが搭載されたパソコンを買うのが吉である。

俺はフリーランスとはいえ、ほぼ無職なので使えるパソコンがあるのに、さらにパソコンを買うのは苦行に近い。仮想マシンで64bit版のインストールは諦めなければなるまい。

まとめ

やれやれ貧乏人は開発環境の構築もさせてもらえないのか。

はやりのVagrantを使えるようになって「しったか」したかったのに。

しかしとりあえずは32bit版のLinuxであれば仮想マシンにインストールできる点は救いであった。頑張ろうと思う。

wordpressのプラグイン「Crayon Syntax Highlighter」のToolBarが動作しなくなった時に解決できた方法

「Crayon Syntax Highlighter」が動かない

wordpressで記事中にコードを掲載するプラグインとして人気の「Crayon Syntax Highlighter」。俺も使用させてもらっている。

下記のような形でコードを表示してくれるプラグインだ。さまざまな言語に対応しており、とても便利。

// A sample class
class Human {
	private int age = 0;
	public void birthday() {
		age++;
		print('Happy Birthday!');
	}
}

先日サイトを作成していて、記事に「Crayon Syntax Highlighter」を用いたのだが、ToolBar(コード掲載領域の上にある灰色の領域)がうんともすんとも言わなくなった。

「Crayon Syntax Highlighter」は通常であれば、コード掲載領域にマウスホバーするとToolBarがスライドインしてくる。

コードをコピーしたり、折り返させたり、新しいタブで表示させたりするボタンがあり、ユーザーに利便性を提供するものだ。

またコード掲載領域をダブルクリックすると、コードが平文になりコピーも閲覧もしやすくなったりする。

これらの機能が動作しない。ToolBarのボタンを押しても何も起こらないし、ダブルクリックしてもコードが平文にならない。

コードテキストの選択すらできないのでコードをユーザーにコピーしてもらうこともできない。

ToolBarが動かない原因の究明

すわ一大事ということで、原因究明を開始した。結論から言うとjQueryの依存関係が原因だった。

まず前提として、wordpressはjQueryを内包している。wordpressをインストールすれば、自動的にjQueryも使えるようになっている。

具体的には<?php wp_head(); ?>が実行されるとjQueryライブラリーのリンクが挿入されることになる(wp_headアクションに7フックしているスクリプトも)。

wordpressでは記述した覚えもないのに<head>内にいろいろ記述が入っているかと思うが、それらは<?php wp_head(); ?>によるものだ。

俺はテストの為、自分でjQueryのバージョンを指定したくて以下を<head>内に記述した。

<?php wp_deregister_script('jquery'); ?>

wp_deregister_script()はwodpress本体やプラグインが登録しているjavascriptのフックを解除する関数だ。

これを実行し、<?php wp_head(); ?>で行われるjQueryライブラリーのリンク挿入を外した。 それ自体はそれほど問題ないのだが、次に行ったことが迂闊すぎた。

<?php wp_head(); ?>の後にjQueryライブラリーのリンクを記述したのだ。しかも</body>の直前だ。

ページ解析を高速にするためjsファイルの読み込みを遅延させる方法として、</body>の直前に記述することはダメなことではないが、「Crayon Syntax Highlighter」には影響が出てしまったようだ。

本来はwordpressが用意してくれているはずのjQueryライブラリーが読み込まれていない状態で「Crayon Syntax Highlighter」が実行されてしまうので、jQueryに依存している機能が働かなくなったということである。

動かなくなったらとにかくコードを読む

最初全く理由がわからず途方に暮れていたが、意を決して「Crayon Syntax Highlighter」のソースコードを読んでみることにした。PHPファイルもjsファイルもたくさんあり、無職の俺にとってはかなり威圧感があった。

しかしソースコードをなんとかかんとか読んでいるうちに、ToolBarまわりの機能はjQueryに依存して実装されていることが分かった。 そのことが判った時に、header.phpをいじくった事を思い出したのだ。

「jQueryの読み込みのせいか?」

ビンゴである。

<?php wp_deregister_script('jquery'); ?>

を外してみるとあっさり「Crayon Syntax Highlighter」が正常に動いた。生兵法でカスタマイズをしてはいけないということだ。

今回の事でわかった事がある。プラグインの多くはwordpressが用意しているものを前提としていることが多い(wordpressプラグインはwordpressの中で動くので当たり前の話なのだが)。

外部ライブラリーの依存関係というのはなかなかわかりにくいものだ。こういった失敗でもしないと気が付かないのは俺がバカだからだろう。

しかし、やはりトラブルに見舞われたら、急がばまわれの精神でソースコードを丹念に読んでいくのは、結果的に速く問題解決につながっているように感じる。

俺のように独学プログラマーにとっては他人のソースコードを読むという機会が会社勤めの人より絶対的に少ないので、なおさら読む機会は作りたいと思う。

どこかをいじると思わぬところに影響がでるという好例(悪例?)だと思う。

jQueryで画像を隙間なく敷き詰める[レスポンシブ対応]

画像を敷き詰める

画像リストなどを作るとき、表示数を稼ぎたいので画面いっぱいに画像を敷き詰めたくなるときがある。しかし通常であれば、画面の横幅と画像の横幅の比が合っていることなどは少なく、たいてい完全に敷き詰めることはできない。

そこで今回は画像の横幅を状況に応じて計算し、画面(もしくは親要素)の横幅に追従して隙間なく画像を敷き詰められるようにしたい。レスポンシブにも対応する。

要件の定義

実装のイメージ

今回やろうとしている事のイメージは以下の形だ。同じサイズの画像を隙間なく敷き詰めることを目的とする。またレスポンシブ対応の為、画面サイズに関わらず画像をぴったりと敷き詰められるようなロジックを投入したい。

画像敷き詰めのイメージ

この手の画像リストはCMSを介して実現することがほとんどだと思う。今回の場合はwordpressで実装しているが、特にwordpressじゃなければできないわけではないので、他のCMSでも参考になるのではないかと思う。

ロジックのイメージ

ロジックとしては以下の様な感じ。横幅に対してぴったりになる画像サイズに仕立てるようにする。ただなるべく元画像のサイズからかけ離れないように最小限の拡大で済むようにしたい。

画像敷き詰めのロジック

画像敷き詰めの実装

コード

コードは非常に単純だ。

$( window ).on("load resize",function(){
    var containerWidth = $( "#container" ).outerWidth();
    var imgWidth = 150;
    var divideContainer = Math.floor( containerWidth / imgWidth );
    var divideRemainder = containerWidth % imgWidth;
    var newImgSize = imgWidth + ( divideRemainder / divideContainer );  
    $( ".imgSpread" ).css( "width" , newImgSize );
    $( "#containerWidth" ).text( "#containerの横幅" + containerWidth + "px" );
    $( "#imgWidth" ).text( "画像の横幅" + newImgSize + "px" );
});

変数や計算のイメージは以下の通り。

画像敷き詰め計算のイメージ

デモページも用意した。jsのコードやcssはインラインで記述しているので、お使いのブラウザのF12を押してデベロッパーツールで確認してもらえると幸いだ。

デモページはぜひウインドウのサイズをいろいろ変えてみてほしい。動的に画像サイズが変化して敷き詰められるはずだ。

画像敷き詰めデモページ

補足説明

画像について

今回はwordpressのサムネイルのデフォルトサイズである150×150の画像を対象にしてみた。正方形でなくてはいけないことはないので、他のサイズでも実現出来る。

画像サイズを直接 var imgWidth = 150; のように指定しているが、本来は画像サイズを取得するロジックをかませた方がいいと思う。

css単体では難しい

実装してみると何の変哲もない機能だが、cssのみでやろうとすると難しい。たとえば画像のwidthを%指定にすると確かに横幅に追従するが、分割数は変わらないので、画像が意図しない大きさに拡大されてしまう恐れがある。

今回の方法であれば、ほぼ10%程度の拡大で敷き詰めをサイズに対して動的に実現することができる。許容範囲ではないだろうか。

もちろん画像のサイズが大幅に変わっても構わない場合はその限りではない。

画像の下辺の隙間

画像敷き詰め時に注意したいのが、cssのvertical-alignだ。この値によって、画像下辺に隙間が出来る場合がある。これはどの基準で行に対して要素をそろえるかの設定だが、画像はインライン要素なのでvertical-alignの影響を受ける。

ブラウザやcssリセットの仕方で異なるかもしれないが、vertical-alignのデフォルトはbaselineかと思う。もし隙間が発生するとときはbottomに設定してあげれば、隙間を解消できる。

まとめ

今回は画像のサイズがそろっている場合だったが、今度は横幅もしくは縦幅が異なる画像をぴっちり敷き詰める方法を説明したいと思う。最近ではPinterestのような画像敷き詰めデザインも流行っているので、サイズが違う画像の例の方が参考になるかもしれない。

今回の記事は基本編として読んでいただければ幸いだ。

俺は今住んでいる4畳間に札束を敷き詰めたい。

jQueryで「ページトップへ戻る」機能を作る方法(opacityを使用)

「ページトップへ戻る」

webサイトでスクロールが深くなると上に戻るのが大変になる。そこでページ上部に戻るための機能を付けるサイトも多い。

多くの場合は一定スクロールすると、クリックするとページ上部に戻れるアイコンなどを表示する方法だ。

単純な機能だがユーザビリティが高まるし、相対的にスクロールが深くなりやすいスマートフォンサイトなどではぜひ設置したいものだ。

今回の記事ではこの「ページトップへ戻る」機能をjQueryを用いて作りたいと思う。

要件の定義

「ページトップへ戻る」はその名の通りページトップにスクロールさせるという機能だ。

今回ではある程度スクロールすると「ページトップへ戻る」アイコンが出現するようにしたいと思う。出現は透明度を使ってフェードインしてくるようにするつもりである。

また戻りスクロールは「スクロールした」ということを感じてもらう為、スクロール移動はアニメーションにて行いたいと思う。戻りスクロールを一瞬でする方法もあるが、あまりに一瞬だとユーザーに何が起こったのか混乱させる可能性もある。

イメージとしては以下のような感じにしたい。

ページトップに戻る機能イメージ

スクロール量を取得する

.scrollTop()を使う

一定スクロールしてからアイコンを出すので、スクロール量を計測する必要がある。スクロール量を取得するのは、jQueryの.scrollTop()メソッドを使う。

通常画面全体のスクロール量はhtmlかbodyで取得する。

//スクロール量の取得
$( "html" ).scrollTop();
$( "body" ).scrollTop();

クロスブラウザ対応の必要性

単純にどちらかで取得できればいいのだが、めんどくさいことに、主要ブラウザで.scrollTop()の取得され方に差異がある。 chromeはhtmlでスクロール量を取得できず、IE,FireFoxはbodyで取得できない。(これはブラウザのバージョンアップが進むと解消されるかもしれないので、常に確認は必要)

.scrollTop()を取得する場合はクロスブラウザ対応が必要になる。.scrollTop()のクロスブラウザ対応を考えてみた。

$( window ).on( "scroll" , function(){
    var scroll;
    //判別の為とりあえず$( "body" ).scrollTop()で取得してみる
    var scroll_distinction = $( "body" ).scrollTop();
    //body要素でスクロールを取得できているか
    //出来ていなかったら"html"出来ていたら"body"
    if( scroll_distinction === 0 || scroll_distinction == false ){
        scroll = $( "html" ).scrollTop();
    }else{
        scroll = $( "body" ).scrollTop();
    };
});

windowに対してscrollが発生したときに実行される。実行された時点でhtmlかbodyにはスクロール量が発生しているので、仮にbodyで取得してみて値の状態を判断し、条件分岐させている。

分岐条件にscroll_distinction == falseがあるのは念の為0以外の値(undefinedやNaNやnull)になった場合を想定している。(0でもflaseになるので条件はscroll_distinction == falseだけでもいいのかもしれない)

もっとうまいやり方があるかもしれないが、俺にはこれが精いっぱいなので勘弁してほしい。

スクロールしたら

「ページトップに戻る」を表示させるのは、画面一つ分の高さをスクロールしたら…という条件にしたいと思う。これは好みの問題なので、各位が好きなタイミングで構わない。

画面の高さは以下で取得できる。

$( window ).height();

ページトップへ戻る

前述したようにページトップに戻るのはアニメーションを使いたいと思う。

アニメーションというと複雑なように感じるかもしれないが、今回は動きが直線的で構わない上に、戻る場所を複雑に計算する必要もない。以下の記述でページトップにスクロールできる。

$( "html,body" ).animate( { scrollTop: 0 } , "2000" );

これは現在のスクロール位置からhtmlもしくはbodyのスクロール0地点(通常であればページ一番上)にアニメーションするということだ。第2引数の2000はアニメーションに要する時間をミリ秒で指定している。ミリ秒は1/1000秒なので、2000で2秒となる。

実際にはクリックしたときに発動させたいので以下の記述になる。

$( "#pageTopIcon" ).on( "click" , function(){
    $( "html,body" ).animate( { scrollTop: 0 } , "2000" );
});

クリックイベントにイベントハンドラとしてアタッチしている。これで「ページトップに戻る」アイコン(#pageTopIcon)をクリックする と2秒でページトップに戻る事になる。秒数はお好みだがあまり早すぎても遅すぎても良くはないと思う。

徐々に現れるアイコンを実現する

opacityの活用

徐々に現れる効果を出すためにcssプロパティのopacityを使用したいと思う。opacityは透明度を設定するプロパティだ。opacity値の増減でアイコンの表示を制御する。

opacityの値

opacityの値は0.0から1.0までで、0.0が完全な透明、1.0が完全な不透明になる。ただし透明になったとしても要素が消えてなくなるわけではなく、見えないだけでその位置に居続けている。

「ページトップへ戻る」アイコンの表示位置を固定・調整する

positionプロパティ

「ページトップに戻る」アイコンはその機能の性格上、スクロールしてしまっては話にならない。その場にとどまらせる必要がある。その為cssプロパティpositionにてfixdeを設定し位置を固定する。

表示位置の調整

positionプロパティでfixdeを指定した場合、その要素は親要素を基準として表示されることになる。親要素左上隅の位置がデフォルトだ。今回はbody直下にimgタグにてアイコンを設置するが、このままでは具合が悪いので、位置を変える必要がある。

「ページトップへ戻る」アイコンは画面右下あたりに表示させたい。これにはrightプロパティとbottomプロパティを使って位置を決める。 これらのプロパティは右端からどのくらい、下からどのくらいの距離に表示させるという設定が出来る。

「ページトップへ戻る」機能の実装

コード

これまでに説明した事を念頭にコーディングしてみた。

//ページトップへ戻る機能
            $( window ).on("scroll ready resize", function(){
                var windowHeight = $( window ).height();
                var scrollDistinction = $( "body" ).scrollTop();
                //body要素でスクロールを取得できているか
                var scroll;
                //出来ていなかったら"html"出来ていたら"body"
                if( scrollDistinction === 0 || scrollDistinction == false ){
                    scroll = $( "html" ).scrollTop();
                }else{
                    scroll = $( "body" ).scrollTop();
                };
                //出現位置決定用係数(単位は画面の高さ)
                var line = 1.8;
                //出現が始まる位置
                var changeFieldStartPoint = windowHeight;
                //opacityが1.0になる位置
                var changeFieldEndPoint =  windowHeight * line;
                //変化域
                var changeField = changeFieldEndPoint - changeFieldStartPoint;
                //opacityの値
                var opacityAmount = ( scroll - changeFieldStartPoint ) / changeField ;
                if( scroll < changeFieldStartPoint || scroll <= 0){
                    $( "#pageTopIcon" ).css( "opacity" , "0.0" );
                }else if( scroll > changeFieldStartPoint && scroll < changeFieldEndPoint ){
                    $( "#pageTopIcon" ).css( "opacity" , opacityAmount );
                }else if( scroll >= changeFieldEndPoint ){
                    $( "#pageTopIcon" ).css( "opacity" , "1.0" );
                }

                $( "#scrollAmount" ).text( "現在のscroll量 " + scroll + " px" );
                $( "#changeFieldStartPoint" ).text( "スタートポイント " + changeFieldStartPoint + " px" );
                $( "#changeFieldEndPoint" ).text( "opasity値が1.0に到達する場所 " + changeFieldEndPoint + " px" );
                $( "#changeField" ).text( "変化域 " + changeField + " px" );
                $( "#opacityAmount" ).text( "現在のopasity値 " + opacityAmount + " px" );

                //クリックされたらアニメーションでサイトトップにスクロールする
                $( "#pageTopIcon" ).on( "click" , function(){
                    $( "html,body" ).animate( { scrollTop: 0 } , "2000" );
                });
            });

このコードが動くデモページも作成した。コードはすべてhead内に記述しているので、お使いのブラウザのF12を押してデベロッパーツールで確認していただけると幸いだ。

「ページトップに戻る」機能デモページ

また動作のイメージを追いやすいように画像も拵えたので合わせて参照することをお勧めする。

ページトップに戻る

補足説明

変化域の考慮

今回の例ではページの高さに関わらず、設定した変化域でopacityが0.0~1.0に変化するようにしてある。こうするとchangeFieldEndPointの位置でopacityが1.0になるので、透明度変化がスムーズにアニメーションしているかのように見えるかと思う。

もし変化域を考慮していないと、opacityAmountの変化量がページの高さによって変わってしまうのであまり良くない(と思う)。

例ではwindowHeightを「出現スタート位置」=changeFieldStartPointに、windowHeight*lineが「opacityが1.0になる位置」=changeFieldEndPointとして設定している。lineという変数は画面の高さを何個分で基準とするかの係数につかっている。

changeFieldStartPointからchangeFieldEndPointを引くと、「ページトップに戻る」アイコンの透明度変化域=changeFieldを算出できるようにしている。

これで変化域を特定できるので、scrollからchangeFieldStartPointを引いた値をchangeFieldで割ることで、変化域でopacityAmountが0.0~1.0になるようにしている。

scrollを基準にしてopacityAmount値を作り出すことでスクロールが戻るときにもちゃんと透明度変化が行われるようにしている。

本来の機能とは関係のないところだが、どうせならきれいに動作してほしいと思っている。

その他

いくつか数値を表示させる為のコードが入っているが、これは本来の機能と関係ないので無視していただいて構わない。

まとめ

「ページトップに戻る」機能について、ちゃんとした名称があるのかどうか調べたがよくわからなかった。どうもまんま「ページトップに戻る」で呼ばれているようだ。

今回はjQueyでCSSをいじるという簡単な例だったので理解していただきやすいのではないかと思う。俺がスムースな変化に変なこだわりを持たなければもっと簡潔なコードで済むはずだ。むしろ「ページトップに戻る」アイコンがいきなり出現したってかまわないのだから。

余計な実装をすることで未だに9割方無職なのではないかと疑わなくもない。

俺も「ページトップに戻る」で一番よかったあの頃へ戻りたい。

wordpressで記事の文字数をカウントする

この記事は〇〇文字で〇分で読むことが出来ます

よくブログなどで「この記事は〇〇文字で〇分で読むことが出来ます」的な文言が入っている場合がある。

ユーザーに記事の規模を伝えることができ、よい情報だと思う。これもプラグインがいろいろあるようなので、プラグインを使った方が早いのだが、このサイトではそれを良しとせず、なるべく自力で機能を作ってみようと思う。

記事の中身

記事の文字数をカウントするにあたり、記事本文がどのような構造になっているか知らなければなるまい。

wordpressの記事本文つまりthe_content()で取得できる情報は、エディタで書いた文章・htmlタグがそのままが入っている。

文章とhtmlタグが切り分けられていれば、すぐにでも文字数カウントができるのだが、そういう訳にはいかない。

どのように文章の文字数をカウントするか

ここはやはり正規表現の出番だろう。htmlタグをマッチングさせ、その文字数を記事の総文字数から引けば、文章の文字数が判明しそうである。

最初、日本語をカウントした方が早いのではないかと思ったが、文章中にもアルファベットは出てくるので、日本語だけカウントしても記事によっては文字数が実際とかけ離れてしまうかもしれない。

またアルファベットを正規表現でパターンマッチングさせることは簡単だが、htmlタグとの区別が非常に難しく思う。htmlタグであれば“<“”>”が付くのでそれを基準とするとタグであることが特定がしやすい。

以上の事から今回は下記のようにして記事文字数を取得したいと思う。

文章・htmlタグの文字数合計 - htmlタグの文字数 = 文章の文字数

正規表現パターンを考える

タグを特定しうる文字

前述したとおりhtmlタグ“<“”>”で囲まれている。だから“<“で始まって“>”で終わる文字列というパターンが基本になりそうだ。

文章中にタグではない“<“”>”で囲まれた文字列が出てくるかもしれないが、タグとして解釈されてしまうので、“<“”>”を「文章の文字」として使いたい場合はエスケープしてしかるべきだろう。

“<“”>”で囲まれた日本語の文字列があるかもしれないが、これから作る正規表現マッチパターンは日本語のマッチングを行わないのでカウントされることはない。

タグ内で使われるだろう文字

タグ内で使われるだろう文字は以下だとおもう。

アルファベット aからz AからZ 数字 0から9 記号 ” ‘ = . _ # & % @ ? ! : ; – () {} [] $ | ~ ,

タグ自体はほとんどがアルファベット、もしくは数字との組み合わせなので、パターンマッチングさせるのは簡単だが、タグの属性をマッチングさせるのはちょっと複雑だ。

たとえばstyle属性を指定していれば、color:”#000000″;という記述が出てくるかもしれない。ダブルコーテーションではなくシングルコーテーションで囲む場合もあるだろうから、これだけでも‘:”#;と5つも記号がつかわれている。色に関して言えばrgba( 100 , 200 , 100 , 1.0)のような指定方法もあるので、,.()も必要だ。

aタグなどでのURL指定はより想定範囲が広がる。ちょっと大げさな例だが、以下のようなURLは存在しえるだろう。

http://hoge-hoge_hoge.jp/~hoge?a=200&b=300&c=2%de%fege%dke%

グーグルマップのURLでは@が使われているし、俺は既知ではないが他の記号も何らかの形で使われる可能性がある。

タグにマッチさせるパターン

以上の事を踏まえたうえでマッチパターンを考えてみた。

/<[a-zA-Z0-9\s=':;,{}#$%&@~<>\"\!\?\|\/\._\-\( \)\\\[\]]+>/

¥マークがたくさんあって気持ち悪いが、記号の多くはメタ文字(機能を有する文字)なので、でエスケープする必要がある。

このパターンでタグの取得を行う。

日本語URLの対応が出来なかった

リンクに使うURLには日本語URLも存在するので本来はそれもマッチングさせる必要があるが、俺の正規表現パターン構築の能力不足でそれを実現するパターンを作る事が出来なかった。

例えば以下の様な正規表現を考えた。

/<[\W\w]*>/

えらく短いパターンだが、“<“”>”で挟まれているものであれば日本語が入っていてもマッチングさせる事ができる。しかしこれには問題がある。以下のような文字列を想定してみてほしい。

<p>あいうえお</p>

何の変哲もないタグで囲まれた日本語だが、先ほどのマッチパターンでは<p>あいうえお</p>全部がマッチする。なぜかというと一番最初の<と一番最後の>で囲まれているからだ。

途中に入っている><も条件に入ってしまうので、本来ならマッチさせたくない日本語もマッチしてしまう。

もし日本語URLをリンクで用いる際はエスケープすることで、文字数がカウントできない状況を回避できると思う。

文字数をカウントする

コード例

それでは実際にコードを組んでみたいと思う。以下のようなコードになった。

function word_counter(){
	global $post;
	$text = $post->post_content;
	preg_match_all( "/<[a-zA-Z0-9\s=':;,{}#$%&@~<>\"\!\?\|\/\._\-\( \)\\\[\]]+>/" , $text , $match );
        echo "<div>下段の記事本文にてパターンマッチしたタグ</div>";
	for( $i=0; $i < count( $match[0] ); $i++ ){
		$removal_all = $removal_all + mb_strlen( $match[0][$i] , "UTF-8" );
                echo "<div>" . htmlentities( $match[0][$i] , ENT_QUOTES ) . "</div>";
	}
	$content_text = mb_strlen( $text , "UTF-8" ) - $removal_all;
        echo "<div>--------------------</div>";
        echo "<div>全文字数" .  mb_strlen( $text , "UTF-8" ) . "字</div>";            
        echo "<div>タグに使われている文字数" .  $removal_all . "字</div>";
        echo "<div>文章の文字数" .  $content_text . "字</div>";
	return $content_text; 
}

この関数を動作させたデモページも作ってみた。上記の関数はfunctions.phpに記述しているのでページソースからは確認できないが、お使いのブラウザのF12を押していただいてデベロッパーツールでデモページを確認していただけると幸いだ。

文字数カウント参考ページ

補足説明

マッチングにはpreg_match_all

今回の関数の核となるのが、preg_match_allだ。これは対象文字列からマッチパターンにマッチした文字列をすべて格納する。上述したマッチパターンを指定して、$post->post_content;にてマッチングさせている。

これでマッチした文字列(今回であればタグに使われている文字)はマッチするごとにどんどん$matchに格納されていく。$matchは二次元配列の形になるので注意が必要だ。デフォルトでは格納は以下の形になる。

Array ( [0] =>     Array ( [0] => タグ1          [1] => タグ2          [2] => タグ3          [3] => タグ4          [4] => タグ5          [5] => タグ6          ) )

第三引数にflagを与えることでき、配列への格納形式を変えることができるが、今回は特に必要がないので取り上げない。もし気になる場合はPREG_PATTERN_ORDERPREG_SET_ORDERPREG_OFFSET_CAPTUREで検索して調べてみることをお勧めする。(ちなみにデフォルトはPREG_PATTERN_ORDER)

mb_strlenで文字数をカウント

mb_strlenは文字数をカウントする関数だ。似た関数にstrlenがあるが、こちらはマルチバイトに対応していないので、日本語のカウントが上手くできない。mb_strlenはマルチバイトに対応しているので日本語を使っているならmb_strlenを使った方がよい。

第一引数に文字数をカウントしたい文字列を指定して、第二引数に文字エンコードを指定する。文字エンコードは指定しなくても動くが、不具合の原因になり兼ねないので、指定した方がのちのち泣かなくて済む。

例では配列数を条件にしてfor文を回し、$removal_allにタグに使われている文字数をどんどん足している。

こんどは$post->post_content($text)の文字数をカウントしてそこから$removal_all分を引いている。

これでタグではない文字、つまり文章本体の文字数が取得できるわけである。

その他の補足

ところどころechoで注釈文が出力されているが、デモと同じコードだからだ。実際にはreturnしている$content_textに文章文字数が入ることになるので、それだけ出力できれば用は足りる。

読み終わりの目安

文字数から読了時間を計算する

文字数を取得することができたので、読了時間も計算してみたい。これは簡単で1分間で読める文字数で取得した文章の文字数を割ってあげればよい。

1分間で読める文字数に関してだが、人によってスピードは違うので正解はないが、一般的であれば大体500~600文字程度だと言われている。これを参考にしたい。

読了時間関数

以下の様なコードで読了時間を取得してみた。

function read_end(){
    $ave = 600;
    $dev = get_word_count() / $ave;
    if( $dev < 1 ){
	$dev = 1;	
    }
    return round( $dev , 0 );
}

短いコードだ。文章の文字数を取得するget_word_count()を基準となる600で割っている。あとは分で出力する為に、1未満になったら全部1にして、最終的にroundで小数点を丸めている。

それほど正確性を求められないものだと思うので、少しくらいの丸めには目をつぶっておこう。

まとめ

単純な機能だが、ユーザーに情報を得てもらうのはとても大切なことだと思う。しかし俺は正規表現が苦手でいつも死にそうになる。うまくパターンを作れないからだ。どこか不完全になってしまう。できればバリデーションチェックの仕事などしたくはない。

そういう選り好みをしているからいつまでたっても9割方無職なのだろう。

実のところwordpressのフィルター使えばいいんじゃねえかというのもあるが黙っておこう。

プラグインなど使ってたまるか。

サイドバーの終端でスクロールを固定させる方法(スクロールアンカー)

スクロール量によって要素を固定する

サイドバーが消えてしまう

最近は1カラムサイトが流行っているので、2カラム、3カラムサイトを作る機会は減っているだろうか?しかしやはり2カラム以上のサイトも需要はあると思う。

記事などのページの場合、サイドバーよりメインカラムのheightが長く、スクロールするとサイドバーが画面からスクロールアウトしてしまう事が多々ある。せっかくサイドバーにコンテンツを置いているのに、空白になってしまうのはとてももったいない。

そこでサイドバーの終端に行きついたら、サイドバーを固定する処理を施してみようと思う。このことをスクロールアンカーなどと言うそうだ。固定というよりアンカーの方がちょっとかっこいいかもしれない。

jQueryでもwordpressでもこの手の機能を付加するプラグインがあるので、手っ取り早く実現したい場合はそれらを使う手もあるが、例によってこのサイトでは下手でも自分で作ってみようと思う。自作派の方の参考になれば幸いだ。

jvascriptだけでもできるのだが、今回はjQueryを併用することにした。以下の様な構造を想定して話を進めたい。

sidebar-3

固定するにはposition:fixdeを使う

positionプロパティ

サイドバーを固定するには、CSSにてposition:fixdeを指定する必要がある。positionは要素の配置方法を設定するプロパティで、fixdeはその名の通り「固定」を表し、position:fixdeを設定するとスクロールしてもその要素はその場に留まり続ける。

positionのデフォルト値はstaticで、特にposition:staticを設定していなくても最初からstaticになっている。staticは配置基準を通常にする。staticが設定されているときは位置指定プロパティのtop,left,bottom,rightは効かない。

サイドバーをスクロールに追従させるときはposition:staticになっている必要がある。(relativeでもいいのだがちょっと面倒なので無視する)

スクロールがサイドバーの終端にさしかかったら、サイドバーのCSSposition:staticからposition:fixdeにスイッチ、スクロールがサイドバーの終端から内側に入ったらposition:fixdeからposition:staticにスイッチさせるという動作でサイドバー固定は実現できそうだ。

また忘れてはいけないのがフッターの存在だ。フッターが画面内に現れてくるタイミングで、またサイドバーをスクロールさせるようにしなくてはならない。fixedのままだとフッターにサイドバーが貫入してしまう。これを防ぐためにposition:absoluteを使う。

スクロール量の取得

.scrollTop()を使う

今回の動作はスクロールが基準になるので、スクロール量の取得をしなければ話にならない。取得にはjQuery.scrollTop()メソッドを使う。要素に対してのスクロール量を取得するメソッドだ。例えばbodyのスクロール量を取得したければ以下の様に記述する。

$("body").scrollTop();

値はピクセル単位だが、単位名が付いたりはせず数値として取得できるので計算に使う場合は別途加工する必要はない。

.scrollTop()の位置

.scrollTop()は指定した要素の上辺の位置を取得することになる。その為スクロールエンド時の.scrollTop()で取得できる数値は構成要素のheightとは一緒にならない。windowheight分少なくなる。

今回は.scrollTop()ライン(画面上辺)ではなく、画面下辺ラインを判断基準にする必要が出てくる。画面下辺ラインがサイドバー終端に到達したら…、フッタートップに到達したら…という条件づけで固定、追従を切り分けるからだ。

画面下辺ラインを得るには以下の様にする。

var bottom_scroll_line = $( "body" ).scrollTop() + $( window ).height();

ブラウザによる差異

普通body要素は要素の大本親として扱える場合が多く、ページ全体のスクロール量を取得する場合はbody要素を指定すればよさそうだが、一筋縄ではいかない。

非常に面倒なことに、ブラウザによる差異があるからだ。すべてのブラウザで試したわけではないが、少なくともIEchromefireFoxでは値の取得に差が現れる事は確かめた。

端的にいうとIEfireFoxでは$(“body”).scrollTop()でのスクロール量取得は出来ない。その為$(“body”).scrollTop()ではなく$(“html”).scrollTop()にてスクロール量を取得する必要がある。しかしchrome$(“html”).scrollTop()でスクロール量を取得する事が出来ない。

$(window).scrollTop()$(document).scrollTop()に関しても同様で、取得できるブラウザとできないブラウザがある。

だからスクロール量を取得するには$(“body”).scrollTop()$(“html”).scrollTop()を切り替える必要がある。対応したコードを記載しておこうと思う。

$( window ).on( "scroll", function(){

    var scroll;

    //判別の為とりあえず$( "body" ).scrollTop()で取得してみる
    var scroll_distinction = $( "body" ).scrollTop();
    //body要素でスクロールを取得できているか
    //出来ていなかったら"html"出来ていたら"body"
    if( scroll_distinction === 0 || scroll_distinction == false ){
        scroll = $( "html" ).scrollTop();
    }else{
        scroll = $( "body" ).scrollTop();
    }

    ///以下何らかの処理
});

この関数はスクロール時に発動される。つまりこの関数が実行されているということはスクロール量が発生している状態だということだ。だからbodyhtmlどちらかで値の取得ができる状況のはずだ。とりあえず上記ではとりあえずbodyで取得してみて値が得られるかを確かめている。

値が得られていれば$( “body” ).scrollTop()でそうでなければ、$( “html” ).scrollTop()で取得する仕組みとなっている。じゃあ$( “body” ).scrollTop()でも$( “html” ).scrollTop()でも取得できない場合はどうするのか?

おそらくそんなブラウザは無いはずだ。$( “body” ).scrollTop()$( “html” ).scrollTop()のどちらかで確実に値を取得できると断言しておこう(無責任)。

サイドバーの高さの取得

.outerHeight()

さて今度はサイドバーの高さを取得する必要がある。サイドバーの終端を特定するためだ。これは.outerHeight()で取得したい。.outerHeight()padding,borderを含むheightを取得できる。もしサイドバーの外殻にmarginを設定してあるなら.outerHeight(true)とすることでmarginを含めたheightを取得できる。

.outerheight()ではなく.outerHeight()。普通の.height()は全部小文字だが、.outerHeight()は大文字のHになっているところがあるので注意。これで嵌った記憶がある。

サイドバーの最外殻の要素が#sidebarだとするならば以下の記述でheightが取得できるはずだ。

//padding,borderを含める場合
var sidebar_height = $( "#sidebar" ).outerHeight();
//marginも含める場合
var sidebar_height = $( "#sidebar" ).outerHeight( true );

サイドバーの上にある要素の高さを取得

図で示したようにサイドバーの上にはヘッダーなどの要素が乗っかっていることが多い。このサイドバーの上にある要素の高さも取得する必要がある。

この高さを取得してサイドバーの高さと合わせて条件にしないと、サイドバーが中途半端なところで固定されてしまうからだ。

//padding,borderを含める場合
var sidebar_height = $( "header" ).outerHeight();
//marginも含める場合
var sidebar_height = $( "header" ).outerHeight( true );

図では上に乗っかっているのはヘッダーだけだが、もし他にも乗っかっているものがあれば、その要素分のheightも取得する必要がある。

.offset().topではだめなのか

要素を足し合わすより、目的の要素の位置を直接取得するという手も考えた。.offset().topが使えるのではないかと思ったのだが、2つの理由で却下した。

一つはtop,bottom等の位置要素を指定すると取得値に変化が出てしまうからだ。

もう一つは、どうやら.offset().topmarginを考慮していないようだ(完全に確かめたわけではないが、少なくともchromeではmargin分ズレる原因となった)。

要素の足し合わせでも用を足すので今回は.offset().topでの位置取得は行わない。

position:fixde時の位置に対しての準備/h2>

配置位置を補正する

sidebar-1

position:fixdeは何も設定しなければ、画面座標(0.0)に要素の左上が留まることになる。それを意図して使う場合は何ら問題ないが、今回に関してはそれでは困る。position:fixdeにしたとたんにサイドバーの位置がずれてしまうからだ。

位置がずれないように適切な処置をしてあげる必要がある。その為にサイドバーを設置している方向の他の要素のmargin,paddingを取得することになる。

サイドバーを取り巻く環境はサイトの作り方によってバラバラなので、取得するべき要素は状況によって異なるが、基本的にはサイドバーの親要素クラスに設定してあるmargin,paddingの総量を取得するということだ。

サイドバー親要素のmargin,padiing,borderの総量

図ではかなりシンプルにcontainer要素の中にsidebarが存在している状況を想定している。container要素にはmarginpadiingかもしくはその両方が設定されているので、サイドバーはwindowの最右辺には接せず、container要素margin-rightpadiing-rightの総量分左側に寄っていることになる。

position:fixdeになった場合にもwindowの最右辺に対して、container要素margin-rightpadiing-rightの総量分は左に寄せる(最右辺から離す)処理をしなければならない。

これらの値の取得は以下のような記述で行う。

//サイドバーが右にあり、containerにmarginだけの場合
var total_amount = parseFloat( $( "#container" ).css( "margin-right" ) );

//サイドバーが右にあり、containerにmarginだけの場合
var total_amount = parseFloat( $( "#container" ).css( "margin-right" ) );

//サイドバーが右にあり、containerにmarginとpaddingがある場合
var total_amount = parseFloat( $( "#container" ).css( "margin-right" ) ) + parseFloat( $( "#container" ).css( "padding-right" ) );

もしサイドバーが左側にあるなら、margin-left,padding-leftで取得する。parseFloat()というメソッドを使っているがこれはjavascriptのメソッドで、型変換をするためのものだ。

.css()で値を取得すると、値の単位まで付いてくる文字列としての取得になってしまうので、そのままでは計算に使えない。その為、parseFloat()をつかって文字列を数値に変換して(たとえば20pxの20を取り出すような感じ)計算に使えるようにする。

あまりないと思うが、もし親要素にboderを設定してあるならそれも取得して総量に加える必要がある。

var total_amount = parseFloat( $( "#container" ).css( "boder-right-width" ) );

ともかくサイドバー側の画面辺からサイドバーまでの幅を全部取得しよう。

取得した値での実際の位置補正は後述する。

サイドバーのwidthを固定値に

アンカーする対象の要素のwidth%で指定されていると、fixdeしてright,leftプロパティを設定した時点でwidthが変わってしまうようだ。これはおそらくfixdeを設定した場合のwidthの計算根拠に起因するのでないかと思う。

例では#containerの子として#sidebarが存在するので、通常であれば#containerpaddingを除いたwidth#sidebarwidthの計算根拠になる。しかしfixedをした場合、#sidebarwidthの計算根拠はおそらくwindowに移行するのではないかと思う。

そうするとpadding分計算根拠が変わるわけで、#sidebarwidthにも影響がでるという訳だ。

念の為WC3CSS仕様書を読んでみたが、以下の様に書かれていた。

If the element has ‘position: fixed’, the containing block is established by the viewport in the case of continuous media or the page area in the case of paged media.

意訳するなら

「もし要素に’position: fixed’が設定されているなら親要素(containing block)はwindowになるぜ、ナンシー?」

continuous mediaとかpaged mediaとかよくわからないが、ともかくfixedでは親要素がwindowになると思っておけばよさそうだ。

これを回避するにはサイドバーのwidthを実数値にする必要がある。具体的なpx値指定であれば親要素が変わっても問題ない。以下の様にしてサイドバーのwidthを計算することができる。

$( "#sidebar" ).css( "width" , ( $( "#container" ).width() - $( "#main" ).outerWidth( true ) ) );

スクロールアンカーの例

一連のコードにしてみた

長々と駄文を連ねてしまったが、今まで説明してきたことを念頭にスクロールアンカーをコーディングしてみた。

思ったより短いコードになった。

$( window ).on( "scroll ready resize" , function(){
                var scroll;
                //メイン要素がサイドバーより短い場合はアンカーしない
                if( $( "#main" ).outerHeight() >= $( "#sidebar" ).outerHeight()){
                    //各種数値の取得
                    var window_height = $( window ).height();
                    var body_height = $( "body" ).outerHeight();
                    var header_height = $( "header" ).outerHeight( true );
                    var main_height = $( "#main" ).outerHeight( true );
                    var sidebar_height = $( "#sidebar" ).outerHeight( true );
                    var footer_height = $( "footer" ).outerHeight( true );
                    var container_padding_right = parseFloat( $( "#container" ).css( "padding-right" ) );
                    var container_padding_bottom = parseFloat( $( "#container" ).css( "padding-bottom" ) );

                    //判別の為とりあえず$( "body" ).scrollTop()で取得してみる
                    var scroll_distinction = $( "body" ).scrollTop();
                    //body要素でスクロールを取得できているか
                    //出来ていなかったら"html"出来ていたら"body"
                    if( scroll_distinction === 0 || scroll_distinction == false ){
                        scroll = $( "html" ).scrollTop();
                    }else{
                        scroll = $( "body" ).scrollTop();
                    };

                    //スクロール量を基準に固定、追従を切り分ける
                    if(  scroll < window_height || scroll < ( header_height + sidebar_height ) - window_height ){
                        $( "#sidebar" ).css( "position" , "static" );
                    }else if( scroll >= ( header_height + sidebar_height ) - window_height && scroll < ( body_height - window_height ) - footer_height ){
                        $( "#sidebar" ).css( "position" , "fixed" ).css( "right" , container_padding_right ).css( "bottom" , "0" );
                        $( "#sidebar div" ).css( "color" , "#FFD44B" );
                    }else if( scroll >= ( body_height - window_height ) - footer_height && scroll <= body_height - window_height){
                        $( "#sidebar" ).css( "position" , "absolute" ).css( "right" , container_padding_right ).css( "bottom" , footer_height + container_padding_bottom );
                        $( "#sidebar div" ).css( "color" , "#000000" );
                    }
                }
            });

これを実際に動作させているページも作ってみた。

スクロールアンカー参考ページ

cssもjavascriptもhead内記述してあるので、お使いのブラウザのF12を押してデベロッパーツールで確認してみてほしい。

コードを確認するとき合わせて以下の図も参照していただけるといいかもしれない。状態遷移のイメージ図だ。

sidebar-2

補足説明

位置の補正に関して

17行目でrightというプロパティを使っているが、これが前述した位置補正だ。rightは要素の位置を設定するプロパティで「画面最右辺を基準にして指定数値分左に動かす」ことができる。

これに#containerpadding-rightの値を当ててやれば、fixedしても元の位置になるので位置補正が出来るという訳だ。

またbottomプロパティに0を設定しているが、これを設定しないとfixedになった時、サイドバーの上辺が画面上辺にfixされるので、縦方向で位置がずれてしまう。その為、画面下辺にfixさせるためbottom0を設定し、「下から0pxの位置」を確保している。

position:absoluteの位置計算根拠について

footerが画面内に現れるタイミングでposition:absoluteを適用している。これはサイドバーがfooterに重ならないよう、fixedからスクロールに復帰させるため処理だ。

position:absoluteposition:static以外の値が設定された最も近い先祖要素を基準とする。今回の例では#containerrelativeを設定してあるので、position:absoluteになった瞬間に#sidebar#containerを基準に位置を確保しようとする。

しかし、fixedの時にはwindowを基準として位置を確保しようとするので、fixed→absolute(その逆も)に遷移する段階で計算根拠が変わるので、位置ズレの原因になる。

今回の例では#containerにはpaddingを設定してあるので、position:absoluteになった場合、windowとはpadding分基準位置が変わり、結果縦方向にズレる原因となる。bottomfooterheightだけでなく、#containerpadding-bottomも加えているのはそういった理由からだ。

今回の例では#containerrelativeを設定しなければ、それ以上の要素(bodyやhtml)にはrelativeを設定していないので、fixed→absoluteになっても計算根拠に変更は生じない。

しかし、positionはかなり多用されるプロパティなので、他の処理との兼ね合いでどうしても設定しなければならないこともあるだろう。ズレの原因がわかりにくくなる嵌りポイントなので注意が必要である。

その他の補足

今回はサイドバーが右にあるので、rightプロパティで位置補正を行ったが、サイドバーが左側ならleftプロパティを使ってほしい。

#containerpadding-rightの取得にparseFloat()を使っていないが、これは取得値を計算する必要がなく“px”が付いている状態そのままでrightプロパティに当てても問題がないからだ。

まとめ

えらい長い記事になってしまい、飽き飽きされたのではないかと思うが堪忍していただきたい。サイトユーザはクリックよりもスクロールすることを厭わないという調査結果があるそうで、スクロールまわりの機能は重要度が増してくるかもしれない。

今回はまた俺の無能さを露呈している記事だが、少しは参考になれば幸いである。もっとうまいやり方はいくらでもあると思うので、ぜひ挑戦していただきたい。

毎度のことながら間違いがあったらごめんなさい。

周期性を持たせる処理

周期性を持たせたい

プログラミングでは処理に周期性を持たせたい場合がよくある。繰り返しといった方がいいだろうか。ともかく周期的な処理というのが必要になる事が結構あるはずだ。

今回は周期性を持たせる処理について書きたいと思う。

if文を使って周期を作る

j=0;
for(i=0;i<100;i++){
 if(j<=4){
   j=0;
 }
 //何らかの繰り返し処理
 j++;
}

if文を使って周期性を作った例だ(言語は特定していない)。ループのカウンタ[i]とは別に周期用カウンタ[j]を用意している。ループが回るたびに[j]はインクリメントされる。

ループの中にif文で[j]の大きさを検査して、この場合では4になったら、0にする処理をしている。つまり[j]の中身は0,1,2,3という周期性を持つことになる。

しかしif文を使った周期性の表現は入れ子構造が複雑になるきらいがある。もし複数の周期性をループ内で表現する場合には多段ネストする可能性があり、あまり好ましくない。

剰余の活用

この方法よりももう少し簡潔に周期性を表現する方法が剰余の活用である。

剰余は割り算の「あまり」のことだ。剰余には周期性が現れる。

剰余を活用すると以下のような形になる(言語は特定していない)

for(i=0;i<100;i++){
 j = i % 4;

 //何らかの繰り返し処理

}

ループカウンタにしている[i]に対して、規則的に表れてほしい周期で割った時の剰余を求める。つまり4周期なら4で、16周期なら16で、180周期なら180で割った時の剰余を求める事になる。

以下は4周期を表現した場合の剰余の結果だ。(「あまり」を表す時に=を使うべきではないが便宜上)

  • 0 ÷ 4 = 0 あまり 0
  • 1 ÷ 4 = 0 あまり 1
  • 2 ÷ 4 = 0 あまり 2
  • 3 ÷ 4 = 0 あまり 3
  • 4 ÷ 4 = 1 あまり 0
  • 5 ÷ 4 = 1 あまり 1
  • 6 ÷ 4 = 1 あまり 2
  • 7 ÷ 4 = 1 あまり 3
  • 8 ÷ 4 = 2 あまり 0
  • 9 ÷ 4 = 2 あまり 1
  • 10 ÷ 4 = 2 あまり 2
  • 11 ÷ 4 = 2 あまり 3
  • 12 ÷ 4 = 3 あまり 0
  • 13 ÷ 4 = 3 あまり 1
  • 14 ÷ 4 = 3 あまり 2
  • 15 ÷ 4 = 3 あまり 3
  • 16 ÷ 4 = 4 あまり 0
  • 17 ÷ 4 = 4 あまり 1
  • 18 ÷ 4 = 4 あまり 2
  • 19 ÷ 4 = 4 あまり 3
  • 20 ÷ 4 = 5 あまり 0

見事に剰余に0,1,2,34周期が表れている。

この剰余を変数に代入するなどして使えば、ループの中で周期性を表現することが可能になるという訳だ。

もちろん複数の周期を表現することも可能だ。if文を使った時のように入れ子が複雑になることも無い。4周期の剰余と、16周期の剰余を同時に活用するなどもできる。

  • 周期性のある繰り返し処理
  • ある場合だけ適用したい処理

などに有用だ。

レベル5デス

この仕組みはFFのレベル5デスなどと同じだともいえる。

レベル5デスは5で割り切れた場合のみに適用されるようになっている。

レベルに対して5周期で剰余を求め、剰余が無い(割り切れた)場合に、無条件に魔法が適用され、戦闘不能になる。レベル3コンフュ、レベル4フレアなども同じことだ。

まとめ

他にもさまざまに周期性を表現する方法はある。たとえば三角関数を使うなどである。

繰り返し現れることを活用できれば、どのような方法でもかまわないだろう。もちろん処理が非効率になってはいけないので精査は必要だと思う。

javascriptでスタイルシートを自在に操る(styleSheetsオブジェクト)入門基本編

スタイルシートに到達するには生javascriptを使用する必要がある(もしかしたらライブラリがあるのかもしれないが)。あまり需要のない事柄なのかこの手の日本語情報はwebでもあまり見つからない気がする。

jQuery全盛の時代スタイルシートを書き換える必要などどこにあるのか。俺にはあまりわからない。

一つ懸念なのはstyle属性でのcss指定は「デザインを分離する」理念に反するのではないかと思うのだ。SEO的にはどうなんだろうか?

そこらへんの疑問もアリながらだが、生javascriptでゴリゴリするのも知識がついていいのではないかと思う。

javascriptでスタイルシートに到達する

スタイルシートの種類

スタイルシートは大きくわけて2つある。一つは<style></style>で囲まれた以下の様なインラインスタイルシートだ。

//インラインスタイルシートの例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>サイトのタイトル</title>
<style>
body{
    font-family:
    "Lucida Grande", "segoe UI",
    "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", 
    Meiryo, Verdana, Arial, sans-serif;
    word-break: normal;
    word-wrap: break-word;
    line-height:
    float: left;
}
</style>
</head>
<body>
・・・・

もう一つはおなじみだと思うが、スタイルシートファイルで指定されるスタイルシートだ。

/*スタイルシートファイル*/
body{
    font-family:
    "Lucida Grande", "segoe UI",
    "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", 
    Meiryo, Verdana, Arial, sans-serif;
    word-break: normal;
    word-wrap: break-word;
    line-height:
    float: left;
}

以下のようなstyle属性はスタイルシートとしてはカウントされない。

//style属性
<body style="width:980px;">

スタイルシートを取得する

スタイルシート(オブジェクト)を取得するには以下の様に記述する。

//スタイルシート(オブジェクト)を取得する
var stylesheets = document.styleSheets.item( index );

引数にindexとあるが、ここにはスタイルシートの番号が入る。スタイルシートは単体の場合もあるし、サイトデザインによって複数のファイルを使用する場合もあるだろう。wordpressなどの場合プラグイン用のスタイルシートが読み込まれる場合もある。複数のスタイルシートが存在することになる為、スタイルシートを特定する必要がある。

スタイルシートの番号に関しては、通常、指定された順になる。インラインスタイルシートもスタイルシートファイルも<heae>内で指定することになるが、その指定順がそのまま番号になると考えてもらえればと思う。

index0から始まるので、一番目のスタイルシートを特定したい場合は0を指定する。

//最初のスタイルシートを取得する
var stylesheets = document.styleSheets.item( 0 );

スタイルシートの総数を得る

複数のスタイルシートが存在し、その数が既知ではない場合や、他のサイトでもロジックを使いたい場合など、スタイルシートの総数が判らないと不便になってしまう。スタイルシートの総数を知るには以下の様に記述する。

//スタイルシートの総数を取得する
var stylesheets_number = document.styleSheets.length;

styleSheetsオブジェクトlengthメソッドを使用して総数を得ることができる。

このメソッドで得られる数値はから始まっているので注意してほしい。もしこの数字をループ等でdocument.styleSheets.itemの引数に使うと、順番がずれてしまうことになり兼ねない。

スタイルシートの中身に到達する

cssルール

スタイルシートにはセレクターとそのセレクターに対してのcss指定が記述されているかと思う。これを便宜上cssルールと呼びたい。たとえば以下はcssルールが3つあるということになる。

/* cssルールが3つ */
#main{
width: 100px;
}
.class{
width: 50px;
}
.class div{
width: 20px;
}

cssルールに到達する

その一つ一つを取得する為には以下の様に記述する。

//cssルールを取得する
var cssRule = document.styleSheets.item( index ).cssRules.item( index );

indexは前述と同じように順番となる。スタイルシート何番目のcssルールの何番目といった指定の方法になる。

たとえばスタイルシート2番目のcssルール3番目を取得したい場合は以下の様に記述する。

//cssルールを取得する
var cssRule = document.styleSheets.item( 1 ).cssRules.item( 2 );

index0から始まることに注意。

cssルールを取得する際の注意点

上記ではcssRulesを使用したが、IE8以前ではcssRulesは実装されておらず、代わりにrulesを使用する必要がある。俺が調べたところではchromeはcssRulesでもrulesでもオブジェクトを取得できた。IE9以上ではcssRulesでも取得できるようだ。

現在ではIE8以下の対応の必要性はなくなっていくと思うのであまり気にすることではないかもしれないが、それでもブラウザシェアから見るとIE8以下もあながち無視できない。(2014/12現在サポート切れているIE6でのアクセスがそれなりにあるのが俺には理解できない)

その為古いIEの為にクロスブラウザ対応しておくこともできる。以下に具体例を示したい。

window.onload= function(){
    //長ったらしいので変数に代入
    var css = windows.document.styleSheets.item( 0 );
    //論理演算子で存在する方を代入(参照) 
    var rules = css.cssRules || css.rules;
    //CSSルールの数を調べる 
    var rules_length = rules.length;
    //CSSルールの数だけループしてCSSの内容をコンソールに表示する
    for(var i = 0 ; i < rules_length ; i++ ){
        console.log( rules.item( i ).cssText );
    }
};

値が定義されていない場合やプロパティが存在しない場合、その変数なりプロパティなりを参照するとundefinedが返ってくることになる。つまりcssRulesが無ければcss.cssRulesundefinedなり、rulesがなければcss.rulesundefinedになる。

普通に書くとどちらか一方しか使えない訳でブラウザの差を吸収できない。その為に論理演算を使おうということなのである。

式というと1+2のようなものを思い浮かべるかもしれないが、論理演算子は真か偽を計算する演算子。

javascriptの仕様ではundefinedfalseの評価となる。その他にもnull、0、空文字列(””)falseの評価となる奴らだ。

つまりnull、0、空文字列(””)が入っている式はfalseになるということが言える。逆になんであれnull、0、空文字列(””)の値以外が入っていれば今回の論理演算の場合tureの評価となる。

今回の場合、評価の結果、定義されていて値の入っている式が代入されるという具合である。

仮にcssRulesが無かった場合はcssRules→undefined→falseとなり、css.cssRules は却下され、css.rulesが代入されるということになる。

ブラウザの差を吸収するのに使えるテクニックなので参考にしてもらえたらと思う。

cssルールの総数を取得する

ループ処理などを行う場合、cssルールの総数が判っていないと不便だ。cssルールの総数を取得するには以下の様に記述する。

//cssルールの総数を取得する
var cssRule_number = document.styleSheets.item( index ).cssRules.length;

styleSheetsオブジェクトの時と同じようにlengthメソッドでcssルールの総数を取得できる。

セレクター名を取得する

さらにcssの中身に迫っていきたいと思う。今度はセレクター名を取得する方法だ。書き換えたいcssルールをセレクターで特定するときなどに必要になるだろう。以下の様に記述する。

//セレクター名を取得する
var selectorname = document.styleSheets.item( index ).cssRules.item( index ).selectorText;

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例 */
#main{
width: 100px;
}
.class{
width: 50px;
}
.class div{
width: 20px;
}

以下の記述でセレクター名を取得すると”#main”が取得できるはずだ。

//セレクター名を取得する
var selectorname = document.styleSheets.item( 0 ).cssRules.item( 0 ).selectorText;

スタイルシート自体を追加する

スタイルシートの追加

新規でcssルールを追加する場合、余計な混乱を避ける為、新たなスタイルシートの土台を作り、そこに追加していくのも手だと思う。

またブラウザのアドオンやプラグインによって読み込まれるスタイルシート数が異なる可能性もある。IEで表示したときの3番目のスタイルシートが、chromeで必ずしも3番目になるかどうかはわからない。

変更を加えたいスタイルシートが必ず末尾に読み込まれるように工夫するなどが必要になるかもしれない。セーフな方法として、スタイルシート新規追加が重要になってくる可能性もある。スタイルシート自体の追加を覚えておいて損はないかと思う。

直接スタイルシートを追加するメソッドは無い。そのかわり<style></style><head></head>内に挿入してスタイルシートの土台としてしまう手がある。

以下の様にして<style></style>を挿入することができる。

//&lt;style&gt;&lt;/style&amp;gtを挿入する
var tag = document.createElement( 'style' ); 
tag.type = "text/css"; 
document.getElementsByTagName( 'head' ).item( 0 ).appendChild( tag );

createElementstyle要素を作り、headタグ内appendChildで作ったstyle要素を挿入している。headタグは通常複数存在しないので最初のheadということで0指定固定で構わないはずだ。

作ったスタイルシートには

document.styleSheets.item( document.styleSheets.length - 1 );

でアクセスすることが可能だ。

もちろん新規に作ったスタイルシートは中身が空っぽなので後述するcssルールの追加で内容を作っていくことになる。

cssルールの追加・削除を行う

cssルールの追加

まずはcssルールの追加の方法。cssルールとは前述したように「セレクターに対してのcss指定のひとかたまり」のことだ。

以下の様な記述で追加をすることが可能だ。insertRuleというそのままのメソッドが存在するのでそれを使用して追加を行う。

//cssルールの追加
document.styleSheets.item( index ).insertRule( cssrule , insertindex );

cssruleの部分はcss指定をする時のように

“.class{ width: 100px; }”

をそのまま指定する形となる。

insertindexは挿入位置の指定だ。cssは同じセレクター、プロパティが存在した場合、後から指定した方が有効となるように出来ている。その為、場合によっては挿入位置も重要な要素になってくる。0から数え始めるので注意が必要。

以下のようなcssルールを先頭に追加したい場合を想定すると、

//追加したいcssルール
.class{
width: 100px;
}

以下の記述が具体例となる。(オブジェクト指定が長くなってくるので適時変数に格納することをお勧めする)

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.insertRule( ".class{ width: 100px; }" , 0 );

末尾に加えたい場合は以下の様に記述すると良い。

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.insertRule( ".class{ width: 100px; }" , stylesheet.cssRules.length );

stylesheet.cssRules.length1から始まるので、丁度最後のcssルールの次(つまり空白位置)を指すことになる。通常は最後尾に追加することで、問答無用でcssが有効になるし、途中に挿入しなければならない積極的な理由でもない限りデフォルトでこのような記述をしてもいいのではないかと思う。

cssルールを削除する

既存のcssルールを削除したい場合もあると思う。そんな場合は以下の様に記述する。これもまんまなdeleteRuleというメソッドが用意されているので使わせてもらおう。

//cssルールを挿入する
stylesheet = document.styleSheets.item( index );
stylesheet.deleteRule( deleteindex );

deleteindexは削除するcssルールの番号(位置)になる。0が先頭になるので、例えば2番目のcssルールを削除したい場合は以下の様な記述になる。

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.deleteRule( 1 );

これで2番目のcssルールが削除されるはずだ。

プロパティの追加・削除・変更を行う

プロパティのについて

最初に断わっておきたいが、下記はオブジェクトである。オブジェクトに言及すると難しくなりかねないので(むしろ俺がよくわかっていないので)言及するのを避けてきたがプロパティでは少し説明しておきたい。

//オブジェクト
document.styleSheets.item( 0 ).cssRules.item( 0 );

このオブジェクトは実装されているすべてのプロパティをすでに持っている状態だ。試しにご自分のサイト等で中をのぞいてみてほしい。

//cssRulesオブジェクトの中身を見てみる
//※styleSheets.item、cssRules.itemの番号は各位の環境に合わせて存在する番号で
console.log(document.styleSheets.item( 0 ).cssRules.item( 0 ));

コンソールに指定したcssRulesの中身が表示されるかと思う。下の画像はChromeで上記のコードを実行した際のコンソール内容をキャプチャしたものだ。

image3004

style直下に大量のプロパティが並んでいるかと思う。(これは裏を返せばブラウザが実装しているプロパティを確認できるということでもある。余談だがベンダープレフィックス付きで実装されているプロパティも確認することができる。)

そのため本質的には「プロパティを追加・削除する」というより、「プロパティに値をセットする・初期化する」と言ったほうが正しいのかもしれない。

見かけ上のプロパティ文は以下のコードで確認することが可能だ。

//プロパティ文
document.styleSheets.item( 0 ).cssRules.item( 0 ).cssText;

追加削除を行った場合、見かけ上ではこのcssTextに変化が起こる(他にも変化は起こるが)。値をセットしていないプロパティに値をセットすればそれは追加ということになり、プロパティ文が追加されることになる。

後述する削除方法で見かけのプロパティ文は確かに削除されるが、オブジェクトプロパティが削除されるわけではなく、値が初期化されるというイメージに近いことを頭の片隅に入れてもらえればと思う。

プロパティの追加・変更

追加の場合は見かけ上プロパティ文が追加されるが、前述したように本質的には値がセットされていなかったプロパティに値をセットすること、変更はプロパティの値を書き換えるということになる。

その為、追加と変更にはメソッド的な違いが必要ない。プロパティに値をセットするメソッドが存在していればいいからだ。

以下の記述で追加・変更ができる。

//追加・変更する
document.styleSheets.item( index ).cssRules.item( index ).style.setProperty( property , value );

スタイルシート、cssルールを特定した上で、プロパティ(property)値(value)を指定する形だ。具体的に例を示したい。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.classheightの設定値が“2000px”から“500px”に書き換えられることになる。

//追加・変更
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.setProperty( 'height' , '500px' );

以下の様に存在しないプロパティを追加した場合、見かけ上cssText“margin: 5%;”が追加されることになり、オブジェクトプロパティのmargin5%が設定されることになる。

//追加・変更
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.setProperty( 'margin' , '5%' );

プロパティを削除する

何度もしつこいようだが、本質的には値の初期化ということになる。見かけ上はcssTextからは削除される。

以下の記述で削除ができる。

//削除
document.styleSheets.item( index ).cssRules.item( index ).style.removeProperty( property );

削除の場合はプロパティ名(property)の設定のみだ。具体的に例を示したい。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.classheightが削除されることになる。

//削除
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.removeProperty( 'height' );

プロパティ名を取得する

プロパティ名を取得してみよう。しかしここで取得できるのは見かけ上プロパティ文として記述されている各プロパティの名前とは若干違う。

例えば下記のようにmarginを設定した時、

/* cssルール例 */
#main{
margin: 0px;
width: 100px;
}

実際には“margin-top”、”margin-right”、”margin-bottom”、”margin-left”がプロパティ名として現れることになる。marginだけでなくpaddingborderなどのように、細分化された階層があるプロパティは、その細分化された各プロパティ名がすべて現れる。

その為後述するが、プロパティ総数が思っていたより多いということが起きるのだ。この話は後述の「設定されているプロパティの総数を取得する」で説明したい。

ちょっとややこしいが、styleオブジェクト上に現れるプロパティ名は値であり、オブジェクトプロパティのことではない。前述したオブジェクトはプロパティをすべて持っているというのはオブジェクトプロパティとしての話だ。以下の画像を見てもらえるとイメージがつかめるかと思う。

image3132

とりあえずプロパティ名を得るには以下の様に記述する。

//プロパティ名を取得する
var property_name = document.styleSheets.item( index ).cssRules.item( index ).style.item( index );

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例 */
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述でプロパティ名を取得するすると“height”が取得できるはずだ。

//プロパティ名を取得する
var property_name = document.styleSheets.item( 0 ).cssRules.item( 1 ).style.item( 1 );

細分化された階層がないプロパティなら素直に取得できるが、この方法でのプロパティ名取得はあまりお勧めできない。オブジェクトプロパティに対してのロジック(例えば値がセットされているオブジェクトプロパティを取得し、目的のプロパティに到達するなど)がよいのではないかと思う。

設定されているプロパティの総数を取得する

セレクターに紐づいているプロパティの総数を取得するには以下の記述となる。

//プロパティ名を取得する
var property_name = document.styleSheets.item( 0 ).cssRules.item( 1 ).style.length;

ここでもlengthメソッドを使って取得することになる。

プロパティ総数を得ることができれば、ループ回数を特定できるので設定されているプロパティ名をすべて取得するロジックも組むことができる。

しかし前述したように、marginなど細分化された階層があるプロパティは、細分化されたプロパティがすべてカウントされることになる。そのため、marginしか設定していない場合でも、“margin-top”、”margin-right”、”margin-bottom”、”margin-left”が現れる為、length4となる。しかもmarginは現れない。

styleオブジェクトにはちゃんとオブジェクトプロパティとしてmarginが存在しているので、オブジェクトプロパティ名の取得の方が実用性が高いかもしれない。

前述した方法でのプロパティ名の取得とプロパティの総数の取得は十分注意して行う必要がある。

プロパティの値の取得

すでに設定されている値を取得したい場合があるかもしれない。既存の値を加工して用いたい場面はよくあることだ。プロパティの値の取得に関してもメソッドが用意されている。以下の様に記述する。

//値の取得
var property_value = document.styleSheets.item( index ).cssRules.item( index ).style.getPropertyValue( property );

プロパティ名(property)を指定して値を取得する形だ。

具体例を示したい。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.class divwidthの値である20pxが取得されることになる。

//値の取得
var property_value = document.styleSheets.item( 0 ).cssRules.item( 2 ).style.getPropertyValue( 'width' );

プロパティの値の加工parseInt関数とparseFloat関数の活用

getPropertyValueで取得する値は文字列だ。計算をするためには数値にする必要がある。cssの値は多くの場合px%emなどの単位が付いている事が多い。文字列操作で除去しようとすると条件付けが大変になってしまう。

javascriptでは型変換用の便利な関数が用意されている。

parseInt関数parseFloat関数だ。

これらの関数は文字列を数値へと変換してくれる。といってもどんな文字列でも良い訳ではない。ちゃんと数字に変換できる根拠をもっている文字列が対象になる。

たとえば20px96%などは数字部分が存在するので変換が可能だ。他にもいろいろ条件はあるのだが、cssプロパティの値で数字を持つ文字列ならほぼこれらの関数で数値に変換できる。

働きの違いだが、parseInt関数は整数に変換parseFloat関数は浮動小数に変換することができる。以下のコードを試してみてほしい。

//関数の働きの違い
console.log(parseInt("20.66px",10));
//20と表示されるはず
console.log(parseFloat("20.66px",10));
//20.66と表示されるはず

昨今はレスポンシブデザインの影響で小数点値が用いられることが多いので、parseFloatを使った方がトラブルがないのではないかと思う。

第二引数の10は基数の指定だ。おそらく10進数を使うことになると思うので10と指定しておけば問題はない。8なら8進数だし、16なら16進数になる。省くこともできるが、デフォルトがブラウザによって異なるようで、きちんと指定したほうが問題が起きなくていいかと思う。

実際にgetPropertyValueで取得した値を加工する具体例を示したい。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 33.2558%;
}
.class{
width: 23.5698%;
}
.class div{
width: 9.22%;
}

以下の記述で.classwidthの値である文字列“23.5698%”が変換され数値23.5698を得ることができる。

//値の取得
var parse_property_value = parseFloat(document.styleSheets.item( 0 ).cssRules.item( 1 ).style.getPropertyValue( 'width' ));

まとめ

かなり長い記事になってしまったが、基本的な事柄は網羅できたのではないかと思う。動作確認しながら記述したが、間違っていたらごめんなさい。

スタイルシートだが、書き換えた瞬間(体感的には)に適用されるようだ。ウインドウサイズの変更によるレスポンシブロジックテストをjQueryではなく(style要素ではなく)、スタイルシート書き換えで行ってみたが、画面サイズが変わると組み上げたロジックがきちんと追従してきていた。(chromeとIE11でテスト)

少なくとも現行のブラウザではスタイルシート書き換えをレスポンシブデザインに用いることは可能なのではないか。

しかしながらスタイルシート書き換えの弊害(たとえばメモリを食うとか、動作が遅くなるとか)も良く分かっていない。思いつくのは記事内でも上げたようにスタイルシート数の相違とクロスブラウザ対応くらいだ。

逆にstyle要素でのcss付与の弊害もあんまり思いつかない。世は猫も杓子もjQuery。style要素でのcss付与はありふれており、もしかしたら弊害などないのかもしれない。

jQueryがなぜスタイルシート書き換えではなくstyle要素によるcss付与にしたのか調べたかったのだが、いかんせん英語ができないのでよくわからなかった。ここら辺の理由が判ればもっと胸をはってスタイルシートオブジェクトの活用をうたえるだろうか。

9割方無職の俺が気にすることではないのかもしれない。

いずれにしてもDOMの内部を知ることは良いことに思えるので、こんどは応用編も記述したいと思っている。

jQueryで要素のwidth(横幅)取得する

レスポンシブデザインやアニメーションを使ったサイトを構築する場合、要素の横幅を取得し利用する必要が出てくる。今回はjQueryで横幅を取得する方法を紹介したい。

widthを取得する

.width()でwidth値を取得してみる

<div class="hoge"></div>

のwidth値を取得する場合を想定。以下の様に記述する。

//width値を取得する
var div_width = $( ".hoge" ).width();

これで数値としてwidth値を取得することができる。なんらかの計算で用いる場合はこちらの方法で取得するのが望ましい。

取得される値はmargin,border,paddingを省いた横幅となる。これはcssbox-sizingborder-boxを指定してもmargin,border,paddingは省かれた幅になるので注意が必要だ。

text3888

.outerWidth()でwidth値を取得してみる

margin,border,paddingを含めた値を取得できないのは具合が悪い。そこで.outerWidth()の出番である。.outerWidth()はデフォルトではborder,paddingを含めた値を取得できるが、引数にtrueを指定するとmargin,border,paddingを含めた値を取得できる。

具体的には以下の様に記述することになる。

//border,paddingを含めた値を取得する場合
var div_width = $( ".hoge" ).outerWidth();
//margin,border,paddingを含めた値を取得する場合
var div_width = $( ".hoge" ).outerwidth(true);

.innerWidth()でwidth値を取得してみる

.width().outerWidth()を紹介したが、それだけではpaddingだけを含めた値を取得する手段が無い。paddingを含めた値を取得するには.innerWidth()を使うと良い。

具体的には以下の様に記述することになる。

//paddingを含めた値を取得する
var div_width = $( ".hoge" ).innerWidth();

取得できる値の範囲のまとめ

取得できる値の範囲に混乱してしまいそうなので、まとめてみた。

.outerWidth(true) margin,border,paddingを含めた値
.outerWidth() border,paddingを含めた値
.innerWidth() paddingを含めた値
.width() margin,border,paddingを省いた値

.css( “width” )でwidth値を取得してみる

jQueryでは.css(“width”)でもwidth値を取得することができる。

//width値を取得を取得する
var div_width = $( ".hoge" ).css( "width" );

この方法でwidth値を取得した場合、数値に“px”が付いてくる。その為、素直に数値として使うことができない。

もし何らかの理由で.css(“width”)で取得した値を計算に利用したい場合は、javascriptの関数parseInt()を使用して文字列を数値に変換する必要がある。

var div_width = $( ".hoge" ).css( "width" );
var div_width_int = parseInt( div_width , 10 );

parseInt()の第一引数は変換したい文字列、第二引数は基数を指定する。10としているのは10進数で変換することを意味する。

省いても動作するが、ブラウザによって第二引数のデフォルトが8(8進数)である場合もあるようで、泣きそうになる事があるので出来るだけ指定するようにした方が良い。

この関数で処理することで“px”を外すことができる。

まとめ

レスポンシブデザインの台頭で要素の大きさを取得する必要性が大きくなってきている。動的なレイアウトに欠かせないことでもあるので、使い分けが少々面倒だが、しっかりと使えるようにしておきたいと思う。