2012年6月12日火曜日

jqueryrotateのアニメーションが再生されない?

要素を回転アニメーションできるjQueryプラグインが
http://code.google.com/p/jqueryrotate/
ありまして、使ってみたのですがアニメーションの連結を行うとはじめのアニメーションが再生されない問題がありました。
しかも、特定のページだけ。
アニメーションの連結は.rotateの完了コールバックに次のアニメーションを突っ込んでいたのですが、間違っているのかと思って先日のタスクマネージャを作ってみたりしたわけなのですが、一向に改善されない。
まあ、はじめっからデバッガで追えばよかったのですが、追ってみたところさっさと完了コールバックが返ってきていました。
バグかと思ってソースを見ているとアニメーションが実時間でのみ処理されていまして、いわゆるフレームスキップしまくってアニメーションが再生されていないように見えていたわけでした。

ゲームを作る際に経験があると思うのですが、各状況で処理負荷が不定なのでそれを考慮してデルタタイムで処理書きますよね。
その際に最低フレームレート設定しませんか?
例えば、フレームレートが60fpsより高い際は垂直同期とって、60-30fpsの時は垂直同期無視して、30fpsを切ったら処理落ちさせて内部のデルタタイムを33.3ms(30pfs)以下にしないとか(力学処理なんかはデルタの振れ幅を小さくしたり固定したいしますけども)。

それが必要なようでした。
なので、

    _animate:function()
    {
         var actualTime = +new Date;
         var checkEnd = actualTime - this._animateStartTime > this._parameters.duration;

         // TODO: Bug for animatedGif for static rotation ? (to test)
         if (checkEnd && !this._parameters.animatedGif)
         {
             clearTimeout(this._timer);
         }
         else
         {
             if (this._canvas||this._vimage||this._img) {
                 var angle = this._parameters.easing(0, actualTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration);
                 this._rotate((~~(angle*10))/10);
             }
のところを


     _animateStart:function()
    {
       .......

       this._animateLastTime = this._animateStartTime;

    }
     _animate:function()
    {
      var minTime = 33;
         var intervalTime = +new Date - this._animateLastTime;
         if(minTime<intervalTime) intervalTime = minTime;
         this._animateLastTime += intervalTime;
         var checkEnd = this._animateLastTime - this._animateStartTime > this._parameters.duration;

         // TODO: Bug for animatedGif for static rotation ? (to test)
         if (checkEnd && !this._parameters.animatedGif)
         {
            clearTimeout(this._timer);
         }
         else
         {
             if (this._canvas||this._vimage||this._img) {
                 var angle = this._parameters.easing(0, this._animateLastTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration);
                 this._rotate((~~(angle*10))/10);
             }


とかにいじってみました。
minTime =33ms以下の際は処理落ちさせていることを望んでいます。

ひとまず、軽く動作は確認できました。


2012年6月11日月曜日

javascriptでタスクマネージャつくりはじめ


jQueryのプラグインなどのいろんな機能で一連の処理を組み立てたいのですが、
どうも、連続処理のやり方が不慣れでしっくり来ないため。
タスクをタイムライン的に登録して実行出来ればいいなと思い
javascript自体がよくわかってないのですが、ちょっと挑戦中です。
http://developmentor.lrlab.to/postal/jquery/jquery.delay.html
こちらを参考にさせていただいています。

<script type="text/javascript">
  (function($) {
    $.taskTable =
    {
      queue: []
    };
    $.taskNext = function(group)
    {
      $.taskStart(group);
    }
    $.taskStart = function(group)
    {
      var self = this;
      var group = group || 'anonymous';
      var obj = $.taskTable.queue[group].shift();
      if(void 0 !== obj)
      {
        if(obj.func)
        {
          obj.func();
          if(0<=obj.interval)
          {
            setTimeout(function(){$.taskStart(group);}, obj.interval);
          }
        }
      }
    };

    $.fn.taskTable = function(fn,interval,group)
    {
      var self = this;
      var group = group || 'anonymous';
      if(void 0 === $.taskTable.queue[group])
      {
         $.taskTable.queue[group] = Array();
      }
      $.taskTable.queue[group].push(
      {'func': function()
        {
          self.each(fn);
        },
        'interval':interval
      });
    };
  })(jQuery);

  function test(name, fnc)
  {
    alert(name);
    if(void 0 !== fnc)
    {
      fnc();
    }
  }

  $(document).ready(function(){
    $('#test1').taskTable( function(){test('test1A',function(){$.taskNext('test')})},-1,'test' );
    $('#test1').taskTable( function(){test('anonymous',function(){$.taskNext('test')})},-1,'test' );
    $('#test2').taskTable( function(){test('test1B',function(){$.taskNext('test')})},-1,'test' );
    $('#test2').taskTable( function(){test('anonymous',function(){$.taskNext()})},-1 );
    $.taskStart('test');
  });
</script>
<body>
<div id="test1">test1</div>
<div id="test2">test2</div>
</body>
とか。

参考のページでは起動関数の引数で一定間隔の実行をしていますが、
今回はそれぞれで次に進むためのインターバルか次に進むためのコールバックにて次々に実行させています。

 $('selecter').taskTable( function(){alert('test1')},1000);
 $('selecter').taskTable( function(){alert('test'2)},1000);
 $.taskStart();

これが処理の登録で第一引数の無名関数が順番に行って欲しい処理自体です。
で第二引数の1000っつーのがつぎの処理まで1000m秒待てっつーことです。
で、ここで時間指定して先に進むんじゃなくて処理が終わったら次みたいのを

 $('#selecter').taskTable( function(){test('test1',function(){$.taskNext()})},-1);
 $('#selecter').taskTable( function(){test('test2',function(){$.taskNext()})},-1);
 $.taskStart();

testつーのは処理が割ったらコールバックを呼ぶ仕組みなのでそのコールバックから
$.taskNext()を呼ぶことで次に進むようにしてます。
最後の-1っつーブッサイクなのはコールバック使っているから時間とか指定しないっつー不細工です。
タスクの登録時にgroupを指定したりしていますが、これもブサイクな作りで
$.taskStart('group');はいいとしてもコールバックに
$.taskNext('group');と書かないといけないとかだっさくて、以下に初心者かよくわかるものとなっております。さっきの-1もそうです。

まあ、ひとまずはこれをもうちょっといい感じにしていこうかと思うのですが、
本当にこれ車輪の大発明であって欲しいのです。
めんどうなので、いい感じのjQueryプラグイン無いですか?
本当にだれか教えて欲しいのです。
ぐぐってもみつけられないのですが、キーワード何を入れればいいのでしょうか。
僕の方向性が間違っているんでしょうか。

助けてください。









2012年6月7日木曜日

テキストエリアの入力文字数のリアルタイムカウント

twitterの文字数のカウンタを実装します。
まあ、調べると以下の感じです。
<textarea></textarea>
<p id="counter">140</p>

   $("textarea").bind("keyup",(function(){
        var counter = 140 - $(this).val().length;
        $("#counter").text(counter);
     }));

色つけたりは調べてください。

んで、これ微妙に反応悪い。
キーリピートに反応しない。

んで、イベントをkeydownにすると今度はコピペが取れない。
じゃあkeydownとkeyupの両方を取るとキーリピートの一発目が取れない。
わけわかんねーですが、いろいろ調べていたら
ちょっと遅れて値をとればいいらしい。
完成したコードは

    $("textarea").bind("keydown focus",(function(){
      var self = $(this);
      setTimeout(function() {
        var counter = 140 - $(self).val().length;
        $("#counter").text(counter);
      }, 10);
    }));

でたーセルフハック。
なんかあとで処理するようなコールバックの処理ではthisが不明になります。
まあ、実際に呼ばれるときはこの関数抜けてるからね。
で、selfにthisを保存しておくと言う奴で切り抜ける。
selfはなぜ生きているのかといえばこれがクロージャーなんでしょうね。
thisは意味が特殊なのでシチュエイションでちゃんと意味が変わらないといけないでしょうね。
focusイベントも念のため入れて、これで本家twitterのカウンターにだいぶ近いんじゃないでしょうかね。

2012年6月6日水曜日

symfonyでobject配列をjavascriptに渡す

具体的にはajaxで適当な情報をオブジェクト配列で取得してきたものをjQueryを使ってギュンギュン表示したいのでjavascriptに取得してきたデータをオブジェクト配列で渡したかったのです。
しかも自分actionで処理してview(twig)を経由しないと何も組めないのでその流れを使っています。
まずjavascript側から
      $.ajax({
        url: "getlist",
        cache: false,
        type: "POST",
        data: {id: id},
        success: function(data){
          callback(data);
        },
        error: function(data){
          alert('失敗');
        },
        complete: function(){}
      });
こんな感じでurl:getlistとか適当なところにリクエストかけます。
getlistはまあ自分のactionなんですけれども
で、アクションですが

  public function executeGetlist (sfWebRequest $request)
  {
    $this->setLayout(false);
    /*なんらかのサービスにデータを問い合わせた結果が$data_listにオブジェクトの配列で   入ってると思って下さい
       $data_list = array( array('id'=>0,'text'=>'aaaaa'),array('id'=>1,'text'=>'bbbb'));
とかそんな感じ?
   */
    $this->data_list = json_encode( (array)$data_list );

  }
ってやってこいつのviewではgetlistSuccess.html(twig)
{{ data_list  }}
とだけ書いておいてですね。


     callback: function(data) {
          var self = this;
          var dataList = eval(data);
          if (dataList.length > 0) {
              var data = dataList.pop();
              alert(data.id+data.text);
          }
          setTimeout(function() {
               self.render(dataList);
          }, 2000);
      }

ってやると順次表示される感じでした。
とにかく
json_encode()してviewでそれをまんま表示してeval()で変数にすればいいようです。

その後の補足
どうもevalに関してはnew Functionを使う方法もあるみたいで

     var dataList = eval("("+data+")");
     var dataList = (new Function("return ("+data+")"))();
どっちでもいいみたい
速度とそのコードのスコープに差があるようです。
また、上では知らなかったのだがdataを文字列()でくくる必要があるみたい。


さらにviewの部分ですがviewを使わないでgetJsonで呼び出してactionでは

public function executeRefresh()
{
  $output = '{"title":"My basic letter","name":"Mr Brown"}';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');
 
  return sfView::HEADER_ONLY;
}

とやるのが一番良いようですがもう面倒なのでajaxでやったしまったのですが
{{ data_list  }}
とかviewでやるよりもactionで
    return $this->renderText(json_encode($result));  
とやってテンプレート使わないほうがいいみたい。



2012年5月24日木曜日

jquery.lettering.jsで改行を処理したい

jquery.lettering.animate.js
で文字列をかっこよく表示したいと思ったら、改行に関しては処理してくれないので
こいつが使っている
jquery.lettering.js
をちょっと都合よく改造してしまいました。


function injector(t, splitter, klass, after) {
    var html = t.html().split((/<*?.[Bb][Rr].*?>/g));
    var inject = '';
  if (html.length) {
        $(html).each(function(j, html_item) {
          var a = html_item.split(splitter);
          if (a.length) {
            $(a).each(function(i, item) {
              inject += '<span class="'+klass+(i+1)+'">'+item+'</span>'+after;
            });
            inject += '<BR />';

            t.empty().append(inject);
          }
        });
  t.empty().append(inject);
      }
}

一旦<br>をセパレータにして文字列の配列を分離してから
その中身を従来の処理にして従来の処理の最後で
<span></span>
を通さずに<br>を打つって感じですか。
苦労したのがIEだとsplit("<br>")をうまく処理してくれなくて
正規表現でまかないました。
IEはどうも大文字の<BR>を使うみたいですね。

2012年5月11日金曜日

TopUp jQueryプラグインにてウインドウを閉じるリンクを作成する方法の一つ

TopUp betaを使っています。
よく考えたらbetaなんですね。本番はなにか違う形で存在しているんでしょうか?
知りたいです。
とても、いいものだと思っていまして、採用しています。
http://gettopup.com/

ウインドウがpopupして来てかっこいいものですが、
まず前提としてページをまるまるウインドウに出したいのでiFrameにて運用しています。
すると、当然ウインドウ内にリンクやらの自前のコントロールが存在してきます。
そこで、このウインドウを閉じるボタン(リンク)と言う要望が出てきました。
いろいろ調べたのですが、jsとhtmlの仕様といろいろ知らない事だらけで難航していましたが、
ひとまず、ウインドウを閉じるリンクを実現できました。
方法の一つになりますでしょうか。

<a href = javascript:$(".te_close_link",parent.document).click();>ウインドウを閉じる</a>

って感じでしょうか。
もう何が何だかわからなかったので、chromeにてどういうタグが何をになっているのかを調べて
どうやらデフォルトで存在する×ボタンが上記のte_close_linkに記述されているようだったので、
iFrameの仕様を調べて親のte_close_linkをクリックすることで実現しました。

この動作をfunctionとしてtopup.js自体に
function topup_close()
とでも定義してしまって

呼び出す側は

<a href = javascript: topup_close();>ウインドウを閉じる</a>
みたいにして使っています。

追記
これ
parent.TopUp.でアクセスできるので素でclose呼んでも出来ますね。。。

以上。

2012年5月1日火曜日

symfony backendでファイルのアップロード管理 元のファイル名とアップロードしたファイルを消す

symfonyのbackendはいろいろ自動でやってくれるけれども、何がどう関係しているのかさっぱりわかりません。
わかるんですかね?
DBにファイル名だけを管理させ、実際のファイル自体は
web/image/xxxなどどっかにアップロードさせて管理したいです。


まず、backendでgenerator.ymlとかいじる系はやってしまって。
フォームでファイルをアップするのを付け加えるにはバリデータとかウィジットとか登録するらしい。
こんなの知るかよ。
class ImageForm extends BaseImageForm
{
  public function configure()
  {
    parent::configure();

    unset(
      $this['created_at'], $this['updated_at']
    );    

    $this->widgetSchema['fileName'] = new sfWidgetFormInputFileEditable(array(
      'label'     => 'fileName',
      'file_src'  => sfConfig::get('app_image_path').$this->getObject()->getFileName(),
      'is_image'  => true,
      'edit_mode' => !$this->isNew(),
      'with_delete' => true,
      'template'  => '
%file%
%input%
%delete% %delete_label%
',)); $this->validatorSchema['fileName'] = new sfValidatorFile(array( 'required' => true, 'path' => sfConfig::get('app_image_path'), 'validated_file_class' => 'CustomValidatedFile', 'mime_types' => 'web_images', )); } }
いろいろ細かいファイルパスとかはうまいことやってください。

で、勝手にぐちゃぐちゃしたファイル名を付けやがるので、オリジナルのファイル名で管理したければ

 sfValidatorFileのコンストラクタに
      'validated_file_class' => 'CustomValidatedFile',
を追加して
こうするらしいです。
class CustomValidatedFile extends sfValidatedFile {
    public function generateFilename()
    {
      return $this->getOriginalName();      
    }
}
知るか!


さらに、deleteしてもファイル自体は消えないので
class Image extends BaseImage
{

  public function postDelete($event)
  {
    $filename = $this->getFilename();

    $filepath = sfConfig::get('app_image_path') .  $filename;

    @unlink($filepath);
  }
}
とするらしいです。
知るか!