protoload.jsをIE7でも動かす

Ajaxリクエスト時のローディングインジケーターをよしなにこなしてくれるprotoload.js

Supports partially: IE 7, Opera 8
Element.offsetParent should be the next ancestor element with style.position != 'static', in IE 7 and Opera 8 it is the next ancestor element, a bug I think.
Just add position='relative' to the next ancestor element to prohibit "buggy behavior".

ってな通りIE7は一部しか動かないよママン!ということで見事にズレた。

時期的(リリースが2007/09/07)な問題で、このprotoload.jsが書かれたのはprototype.jsが1.5.1.1だと推測。
Element.offsetLeft,offsetTopはブラウザによって返る値が変わる(特にIEがうんこ)のを自前でなんとかしなきゃいけないという時代でした。
Element.cumulativeOffset()はprototype.js-1.6からなのでオフセット計算によるブラウザ依存吸収が無かったんですね。合唱。

そんなわけで勝手にバージョンアップします。

protoload.js-ie7.patch

Index: /protoload.js
===================================================================
--- /protoload.js	(0.1beta)
+++ /protoload.js	(hoge)
@@ -43,12 +43,12 @@
 		element._loading.className = className;
 		window.setTimeout((function() {
 			if (this._waiting) {
-				var left = this.offsetLeft, 
-					top = this.offsetTop,
+				var offset = this.cumulativeOffset();
+				var left = offset.left, 
+					top = offset.top,
 					width = this.offsetWidth,
 					height = this.offsetHeight,
 					l = this._loading;
-					
 				l.style.left = left+'px';
 				l.style.top = top+'px';
 				l.style.width = width+'px';
  • 動作確認はIE7/Firefox3しかやってません。
  • prototype.js 1.6以上だったらたぶん動きます。Element.cumulativeOffset()次第。

おまけ

protoload.jsのローディングインジケータをAjax.Updater全部におっかぶせる簡単な方法

	Ajax.Responders.register({
		onLoading: function(elem){
			$(elem.container.success).startWaiting('bigWaiting');
		},
		onLoaded: function(elem){
			$(elem.container.success).stopWaiting('bigWaiting');
		}
	});

よしなに。

AjaxHelper::editorあれこれ

昨日ハマったんで備忘録的に。Ajax.InPlaceEditorについて。

AjaxHelper::editorはほんとに地獄だぜー(AA略

CakePHP: AjaxHelper::editorでonFailureは出来ない?を参照していただくとわかるとおり、$options['onFailure']が投げれない。

正確には

$ajax->editor($id,$url,array('onFailure'=>"alert('hoge');"))

というとき、

new Ajax.InPlaceEditor('id','url',{ajaxOptions:{asynchronous:true, evalScripts:true, onFailure: function(){ alert('hoge'); }}}

のようにajaxOptions内にレンダリングされてしまうのが問題の根幹。

場当たり的な対応だが、これはAjaxHelperのajaxOptionsとeditorOptionsの値をインスタンス生成後に書き換える事で一応回避できる。
つまり、AjaxHelperを使おうとするviewの*.ctp内の最初の行あたりに

$ajax->editorOptions = am($ajax->editorOptions,array('onFailure'));
unset($ajax->ajaxOptions['onFailure'])

で、onFailure項がAjaxHelper::__optionsForAjax()から_buildCallbacks()にかけて解釈されないようにしてしまう。

逆に言うとこのプロパティの調整によりajaxOptions側にonFailureが投げれなくなるが、Ajax.Responders.registerで
対応するのが筋って気がしてるので気にしない事にする。

これでめでたくonFailureが投げられるのだが、_buildCallbacks()経由じゃないから、function(){}でラッピングしてくれないので自分でやらないとダメ。

$ajax->editor($id,$url,array('onFailure'=>"function(ipe,transrator){ alert('hoge'); }"))

こんな感じ。

これで動くと思ったら大間違い

ここでさらに問題。今度はAjax.InPlaceEditor側(Javascript側)の問題。
RoR本家でもいくつかチケットが出てたが、InPlaceEditorのonFailureがトリガされないというもの。
このBlogで紹介されているパッチscript.aculo.usの後に読み込んで置くことでこのバグは解消されます。ただし将来のバージョンでどーなるか不明なので自己責任で!

で、ぶっちゃけCakePHP側でバリデーションどうすんのよ?

上記のコードでいうところの$urlに対してPOSTで値がやってくるのですが、エラーが発生したらどうするか?
$url=array('controller'=>'hoges','action'=>'edit',$id)だとして

class HogesController extend App_Controller{
    function edit( $id ){
        $field = $this->params['form']から適当にパース;
        $data = $this->params['form']から適当にパース;
        $this->Hoge->id = $id;
        if(!$this->Hoge->saveField($field, $data, true){
            $this->redirect(null,500,true); // redirect()でexitしちゃう 
        }
    }
}

status 500を返すのがHTTP的な良し悪しはともかくとして、Ajax.InPlaceEditor側のonFailureは目出度く動作するはず。

Ajax.InPlaceEditor(正確にはAjax.Updater)はonFailureがトリガされたときはevalScripts:trueで投げててもevalしてくれないので

// views/hoge/index.ctp

// onFailure的なおまじない
$ajax->editorOptions = am($ajax->editorOptions,array('onFailure'));
unset($ajax->ajaxOptions['onFailure'])

// onFailureでもevalしちゃえ><
$ajax->editor($id,$url,array(
    'onFailure'=>"function(ipe,transrator){ transrator.responseText.evalScripts(); }"
));

で、バリデータ側は

class HogesController extend AppController{
    function editor( $id ){
        $field = $this->params['form']から適当にパース;
        $data = $this->params['form']から適当にパース;
        $this->Hoge->id = $id;
        if(!$this->Hoge->saveField($field, $data, true){
            loadHelper('javascript');
            $javascript = new JavascriptHelper();
            $this->redirect(null,500); //exitしない
            echo $javascript->codeBlock(sprintf("alert('%s');",$errorMessage)); //Javascirptはいちゃえ
            exit(); 
        }
        // views/hoge/editor.ctpに行く
    }
}

と、こんな感じで動きましたとさ。
動作環境 : script.aculo.us-1.8.1 + InPlaceEditorパッチ + CakePHP 1.2RC3

ついでにFormHelper::datetimeの$attirbutes['interval']バグ

https://trac.cakephp.org/changeset/7711 本家リポジトリのChangesetの通りだけど、$timeFormat = '24'の時
intervalが無視されちゃう件。パッチ当てる以外解決方法無し!

検証(バリデータ)をすっきりさせる

CakePHPやらSimpleTestやらをEclipseスクリプトバリデータにかけると問題(Probrems)にエラーと警告が山ほど出るのだが、こいつがたまらなくウザイ。

本当に出ているエラーや警告が埋もれて、S/N比的に使い物にならなくなってしまう"問題"の問題を解消する方法がわかったので(以下略さない。

ちなみに以下の手順はPDT2.0-all-in-one + Pleadesでのこと。

  1. メニューバーの「プロジェクト」->「プロパティ」 -> 「検証」
  2. 「プロジェクト固有の設定を可能にする」にチェック
  3. 下のグリッドが有効になったら「HTML構文バリデータ(PHPファイル用)」の「...」って書いてあるとこをクリック
  4. 設定画面が開くので、除外グループを追加
  5. 除外グループが左のツリーに現れるので、選択してから「ルールの追加」ボタンを押す
  6. 拡張子による判別、フォルダ指定、ファイル指定の3つのパターンがあるが、フォルダ指定で/cake/やら/app/vendors/を追加する。
  7. プロジェクトプロパティ画面は一旦閉じる。
  8. 「プロジェクト」->「クリーン」(再検証のやり方いろいろあるけどね!)で再検証させる。
  9. きれいさっぱり問題が無くなるはず。
  • ちなみにCakePHP1.2RCだと/app/config/acl.ini.phpもモニョる。ファイル直接指定すると良いです。
  • .ctp, .thtmlを「HTML構文バリデータ(PHPファイル用)」の対象にしとくのもいいかも?
  • .htmlファイルとかのエラーの場合は「HTML構文バリデータ」の方にも追加するとおっけーです。
  • プロジェクト設定で上書きできる状態じゃないと(デフォルトそうなってるけど)無理。

Enjoy Happy Eclipse Life!

Eclipseでbakeを焼きたい貴方に

  1. 「実行」->「外部ツール」->「外部ツールダイアログを開く」->「プログラム」->「新規」。名前はbake - projnameとかしとけばいいと思う。
  2. ロケーション : ${workspace_loc:/hoge/php/cake/console/cake.bat}
  3. 作業ディレクトリ : ${workspace_loc:/hoge/source/php/app}
  4. 引数 : bake

appディレクトリを作業ディレクトリにするとapp指定いらんのでらくちんこ。

WikiIncludePlugin論争

0.11用のWikiIncludePluginについて。
いろいろモメてるようですが、さくっと添付されてるbz2展開して、0.11/setup.pyの最後の行を

setup(name=PACKAGE,
      version=VERSION,
      packages=['wikiinclude'],
      entry_points={'trac.plugins': ['wikiinclude.wikiinclude=wikiinclude.wikiinclude']})

とかやれば幸せになれると思う。
バグチケ踏み潰すぐらいならAuthor譲ればいいのに。

cronとかの書き方

サーバ側の自動処理なんかをCakePHPで書きたいときにどうするかというお話。Cronとか。
APPはアプリのパス、COREはCakePHPおいてるところね。

■STEP 1 - APP/vendor/shellsにまずはディスパッチャを置く。

例えばcron.phpというファイルにCronShellというクラスを書いておくと

 cake cron

とやるとcake bakeやるときのように実行できる。

■STEP 2 - Taskを書く(コントローラでいうところのAction)

STEP 1で書いたCronShellクラスにmain()関数を作って、そこで処理を呼び出す。
ハンドリングしたいならパラメータをパースして適当によしなにする。
実際の処理はAPP/vendor/shells/tasks/test.phpみたく、shells/tasksの中に置いておく。
同じShellクラスの継承だが、名前は*Taskにしておくこと。
CORE/console/libs/bake.phpのコードを参考にすると良い。

APP/vendor/shells/cron.php

class CronShell extends Shell{
    var $tasks = array('test'); /* 呼び出すTaskを列挙しておく */
    function main(){
        $this->Test->execute();
        exit(0); /* Shellだからいちおうリターンコード返しておく */
    }
}
APP/vendor/shells/tasks/test.php

class TestTask extend Shell {
    var $uses = array('Hoge');  /* HogeModelを使うことにする */
    function execute(){
        $r = $this->Hoge->findAll();
        var_dump( $r );
    }
}

■その他のネタ

  • templateがあるっぽいがいまいち使いどころわからんちん
  • ShellクラスはObjectクラスの継承になってるのでrequestActionメソッドが叩けるw
  • コントローラが存在しないので、コンポーネントが使えないっぽい。->vendorでやればいいんじゃね?

EclipseでCake開発の小ネタ

EclipseでCakeのコードを書いてるとき

class UserController extend AppController {
   var $uses = array('User');

   function index(){
       $this->User->
   }       
}

User->の後ろでコード補完(Ctrl+Space)やってもメソッドやプロパティは出ないんだが(あたりまえだけど)

class UserController extend AppController {
   var $uses = array('User');
   /**
    * @var User
    */
   var $User;   

   function index(){
       $this->User->
   }       
}

とかコメント打っておくとちゃんとコード補完してくれるっぽい。
Sessionとか大概デフォルトで読んでるコンポーネントとかはAppControllerで同様に

   /**
    * @var SessionComponent
    */
   var $Session;

とかやっておくとハッピーかもしれない。