カテゴリー: javascript

AngularJSでsortableのサンプル

AngularJSでsortableのサンプル

最近ちょっとAngularJsを触りだしました。

まだ本家のチュートリアルドットインストール
やったくらいのレベル。

今後業務でsortableなリストとか作る
必要がありそうなので調査。

どうも「AngularUI」を使ってみるとイイっぽい。

■AngularUI
http://angular-ui.github.io/

以下のサイトでも紹介されています。

■AngularUIってどんなもの?
http://knightso.hateblo.jp/entry/2014/04/02/182924

「AngularUI」内の「UI-Modules」にある「ui-sortable」を利用して
作成してみます。

「ui-sortable」の使い方は以下。
angular-ui/ui-sortable
https://github.com/angular-ui/ui-sortable

で、実際にsortableなものを作ってみたのがこちら。

DEMO

一応ソース

<html ng-app="myApp">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>AngularJs sortable sample</title>
	<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.1.0/css/bootstrap.min.css"></link>
	<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
	<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
	<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.1.3/angular.min.js"></script>
	<script src="http://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script>
	<script>
		var app = angular.module('myApp', ['ui']);

		app.controller('myCtrl', function($scope) {

		// サンプルデータ
		$scope.items = [
		{'title' : 'aaaaa'},
		{'title' : 'bbbbb'},
		{'title' : 'ccccc'},
		{'title' : 'ddddd'},
		{'title' : 'eeeee'},
		{'title' : 'fffff'},
		{'title' : 'ggggg'},
		{'title' : 'hhhhh'},
		{'title' : 'iiiii'},
		];

		// jQueryUIのsortableのオプションが使える。
		$scope.sortableOptions = {
			axis: 'y'
		};

	});
	</script>
</head>
<body ng-controller="myCtrl">
	<div class="container" style="margin-top:20px;">
		<h1>AngularJs sortable sample.</h1>
		<div class="row">
			<div class="col-md-4">

				<div class="panel panel-default">
					<div class="panel-heading">
						↓並び替え
					</div>
					<div class="list-group" ui-sortable="sortableOptions" ng-model="items">
						<a class="list-group-item" href="#" ng-repeat="item in items">{{item.title}}</a>
					</div>
				</div>

			</div>
			<div class="col-md-8">
				<h3>↓データの状態</h3>
				<pre>{{items|json}}</pre>
			</div>
		</div>
	</div>
</body>
</html>

すっきりしてていい感じ。


PHPとBootstrap3でプログレスバーの表示

PHPとBootstrap3でプログレスバーの表示メモ。

ちょっと急ぎで作成。。

html側。
jqueryとか使ってます。
■html.phpファイル

<head>
        <meta charset="utf-8" />
        <title>PHP Progress Sample</title>
        <link href="dist/css/bootstrap.css" rel="stylesheet">
</head>
<body>

        <div class="row">
                <div class="col-md-6 col-md-offset-3">
                        <h1>Progress Sample</h1>
                        <h2>ロード中</h2>
                        <div class="progress">
                                <div id="progress_elem" class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">0%</div>
                        </div>
                </div>
        </div>

        <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
        <script type="text/javascript">
                function updateBar(progress) {
                        var status = progress+"%";
                        $("#progress_elem").css({width: status}).text(status);

                }
        </script>
</body>

php側

■index.phpファイル

<?php

// htmlの出力
require('html.php');

echo str_pad(" ",4096)."<br />\n";

ob_end_flush();
ob_start('mb_output_handler');

$count=0;
$max_count = 15;

for($i=0; $i <= $max_count; $i++){
    $nCount = floor($count / $max_count * 100);
    echo '<script type="text/javascript">updateBar("' . $nCount . '")</script>';
    ob_flush();
    flush();

    $count++;
    sleep(1);
}

実際のサンプルはこちら

出力後に最下部にscriptタグを吐き出して無理矢理動かしている感じが
否めない。。

html開く⇒ajaxでバックエンドの処理をコール⇒進捗を定期的にjsが取りに行くっていう実装のほうが良いかも。

う~む。

FuelPHPのRestコントローラでBackbone.jsからPOSTのデータを受け取る方法

今頃「Backbone.jsアプリケーション開発ガイド」を
写経してみているのですが、チュートリアル2
「ライブラリ(RESTを使ったBackbone.jsアプリケーション)」
ではまったのでメモ。

バックエンドをFuelPHPのRestコントローラで
記述していたのですが、Backbone側でcollection.create(model.save)した
際に$_POSTが空っぽでデータが受け取れなかった。

調べてみると同様の症状を発見。

Backbone.emulateJSON = true;

上記を記述すればバックエンド側で以下のように取得できるようになった。

$model = json_decode(Input::post('model'));

Backbone.jsには以下のように書いてありました。

// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone.emulateJSON = false;

■参考URL
Backbone.js Model.save()によるPOSTデータをPHPで読み込む
FuelPHP application with Backbone.js model.save()
Backbone.SyncでSAStrutsにFormをPOSTする方法
Backbone.jsを使ってみた

fullcalendar.jsで予定が重なったときの表示順番を指定する

fullcalendar.jsで予定が重なったときの表示順番を指定する

すごく優秀なfullcalendar.js

元々の「fullcalendar.js」では予定が重なった場合に
タイトルを比較してどちらを左に出すのか決めているみたい。

これだと予定に色をつけた場合にタイトルで左右が
決まってしまうから同じ色の予定でも左右バラバラに
表示されて美しくない。

ということでfullcalendar.jsを変更。

function compareSlotSegs(seg1, seg2) {
    return seg1.start - seg2.start || // earlier start time goes first
        (seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first
        (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
}

もともと週表示では↑のようになっているので

function compareSlotSegs(seg1, seg2) {
    return seg1.start - seg2.start || // earlier start time goes first
        (seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first
        (seg1.event.color || '').localeCompare(seg2.event.color) ||
        (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
}

↑このように変更。
タイトルで比較する前にcolorプロパティで比較するのを追加。
登録順とかも気にする場合はidとかで比較するのもいいかも。

// A cmp function for determining which segments should appear higher up
function compareDaySegments(a, b) {
    return (b.rightCol - b.leftCol) - (a.rightCol - a.leftCol) || // put wider events first
        b.event.allDay - a.event.allDay || // if tie, put all-day events first (booleans cast to 0/1)
        a.event.start - b.event.start || // if a tie, sort by event start date
        (a.event.title || '').localeCompare(b.event.title) // if a tie, sort by event title
}

↑月表示側も

// A cmp function for determining which segments should appear higher up
function compareDaySegments(a, b) {
    return (b.rightCol - b.leftCol) - (a.rightCol - a.leftCol) || // put wider events first
        b.event.allDay - a.event.allDay || // if tie, put all-day events first (booleans cast to 0/1)
        a.event.start - b.event.start || // if a tie, sort by event start date
        (a.event.color || '').localeCompare(b.event.color) ||
        (a.event.title || '').localeCompare(b.event.title) // if a tie, sort by event title
}

↑同じ感じで修正

■参考URL

jQuery fullcalendar integration with PHP and MySQL

FullCalendarの日本語化やオプションいろいろ

FullCalendarの導入からカレンダー毎の色指定まで

HTML5のwindow.postMessage()でクロスドメインでのiframe内から 親のwindowにメッセージを送る方法

HTML5のwindow.postMessage()でクロスドメインでのiframe内から
親のwindowにメッセージを送る方法。

クロスドメインのページをiframeで読み込んだときに
iframe内のページ(長いページ)が遷移したら親ページもページの
上部に移動するようにしたくて調査した。

■参考サイト
window.postMessage

window.postMessage() を使って、クロスドメインの iframe の高さを設定する検証

window.postMessage()の学習

上記のサイトを参考にして以下のようなコードを作成

■親ページ側に追加したjsのサンプル

$('#next_click').live('click', function(){
     var target = parent.postMessage ? parent : (parent.document.postMessage ? parent.document : undefined);
     if (typeof target != "undefined"){
         target.postMessage("page change", "*");
     }
});

上記でid:next_clickがクリックされたら親に「page chage」というメッセージが送られる。
※jQuery使用

■子ページ側に追加したjsのサンプル

$(function(){
        $(window).on("message", function(e){
        if (e.originalEvent.origin == 'http://許可するドメイン') {
            var data=e.originalEvent.data;
            if(data==='page change'){
                $('html,body').animate({scrollTop:0}, 400);
            }
        }
    });
})

上記で子でメッセージを受けとった際にページを上部まで移動させられる。
※jQuery使用
最初にあげた参考サイトではaddEventListenerを使っていたけど↑はjQueryで
イベントをセットしている。
jQueryを使う場合は「e.origin」や「e.data」でデータが取得できないので
「e.originalEvent.origin」や「e.originalEvent.data」でデータを取得する。
※eをconsole.dirすればわかる。

jQuery Mobileで作成したサイトにgoogle analyticsを埋める

jQuery Mobileで作成したサイトにgoogle analyticsを埋める

jQuery Mobileで作成したサイトにgoogle analyticsを埋める際に
ただタグを埋めればいいわけではなかったのでメモ。

そもそもjQuery Mobileはajaxが有効な場合にリンク時に
data-role=page部分だけを読み込むので、ヘッダやフッタが
読み込まれない。
だからトラッキングコードの箇所が読み込まれず、最初の
ページだけがトラッキングされてしまうらしい。

最初に参考にしたサイトはこちら
jQuery MobileでGoogle Analyticsを使う方法
だた元々のanalyticsのタグがちょっと違うなーと思ったので

こちらのサイトのコードを参考にさせてもらいました。

Google アナリティクスをjQuerymobile内で使う

やっている方法としては同じもので
pageshowイベント時に

_gaq.push(['_trackPageview']);

するってことみたい。

ただ上の場合だと#以降がgoogle analytics上で表示されないので今回は
こうしてみた。

$('[data-role="page"]').live('pageshow', function(){
var u = location.pathname + location.hash;
u ? _gaq.push(['_trackPageview', u]) : _gaq.push(['_trackPageview']);
});

こうするとgoogle analytics上で
/aaaa/bbbb/ccccc#xxxxxxx
という感じで表示される。
↓この本にも説明が載ってた。。。

jQueryと一緒にRequireJSを使ってみる

jQueryと一緒にRequireJSを使ってみる

RequireJSをちゃんと使うべく勉強。
WEB+DB PRESS vol.69に
「RequireJSでらくらくモジュール管理」
というのがあったのでこれを参考にする。

まずはRequireJSのメリット3つ
□カプセル化されたモジュールの定義
AMD(Asynchronous Module Definition)モジュールと呼ばれるカプセル化された再利用性の高いモジュール定義が行える。
AMDはコールバック関数を使って定義するのでグローバル変数に頼らずに他のモジュールを呼び出せる

□ネストした依存関係の自動解決
依存関係が多段にネストしていても利用するモジュールを呼べば自動でRequireJSが依存を解決してくれる。

□非同期読み込みと最適化ツールによるパフォーマンスの向上
非同期に読み込むことでレンダリングのブロックを回避できる。
最適化ツールが用意されている(今回の実験ではやらない)

ではとりあえず書いてみる。
元々記事に記載されていたコードをjQueryを使った
バージョンに書き換えてみる。

RequireJSはここのページの「Sample RequireJS 2.1.2 + jQuery 1.8.2 project」をおとしてくる
※2012/12/07時点です。
ここから先のサンプルコードで使っている「require-jquery.js」は
解凍したフォルダの中の
jquery-require-sample\webapp\scripts\require-jquery.js
を使っています。

■ファイルの構成

.
|– js
| |– component.js
| |– config.js
| |– counter.js
| |– maxheight.js
| |– page.js
| |– require-jquery.js
| |– require.js
| `– util.js
`– page.html

require.jsとrequire-jquery.jsの両方があるけど
jquery使う場合はrequire-jquery.jsがいいらしい。
ただrequire-jquery.jsはrequire.jsとjquery.jsを
連結しただけのものらしい。
でもこうすることでjqueryのプラグインが非同期で
ロードされる前に本体がロードされていることを保障
できるってことらしい。

■page.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <!-- 現在の時刻を表示する -->
        <h1 id="label"></h1>
        <div id="clock"></div>
        <hr /><!-- 単純なjqueryプラグインの実験↓ -->
        <label>一番大きいdivの高さ</label>
        <p id="height"></p>
        <hr /><!-- 複数のライブラリ中でjqueryプラグンが呼び出されたときの実験↓ -->
        <label>呼び出された回数</label>
        <p id="count"></p>
        <script>
            var require = {
                // キャッシュの防止
                urlArgs: 'bust=' + (new Date()).getTime()
            };
        </script>
        <script src="js/require-jquery.js" data-main="js/page" async></script>
    </body>
</html>

■js/config.js

define(function() {
    return {
        label: '現在の時刻',
        format: '%Y/%m/%d %H:%M:%S'
    };
})

■js/util.js

define(function() {
    function pad2(num){
        return ('0' + num).slice(-2);
    }

    function formatDate(date, formatStr) {
        return formatStr.
            replace('%Y', date.getFullYear()).
            replace('%m', pad2(date.getMonth() + 1)).
            replace('%d', pad2(date.getDate())).
            replace('%H', pad2(date.getHours())).
            replace('%M', pad2(date.getMinutes())).
            replace('%S', pad2(date.getSeconds()));
    }

    return {
        formatDate: formatDate
    };
});

■js/component.js

define(['jquery', 'config', 'util', 'counter'], function($, config, util) {
    function display($elem){
        $elem.text(util.formatDate(new Date(), config.format));
    }

    function render($elem) {
        display($elem);
        setInterval(function() {
                $(this).counter();
                display($elem);
            }, 1000);
    }

    return {
        render: render
    };
});

■js/page.js

require(['jquery', 'config', 'component', 'maxheight', 'counter'], function($, config, clock) {
    // configのラベルを表示(config依存)
    $('#label').text(config.label);

    // 現在の時刻を表示(component依存)
    clock.render($('#clock'));

    // ここから下はおまけの実験
    // ページ内のdiv要素で最も高いもののheightを返してみる(maxheight依存)
    $('#height').text($('div').maxHeight());

    // 5秒に1回たまったカウントを表示(counter依存)
    setInterval(function() {
        $('#count').countDisplay();
    }, 5000);
});

■js/maxheight.js

define(['jquery'], function($) {
    $.fn.maxHeight=function(){
        var max = 0;
        this.each(function() {
            max = Math.max( max, $(this).height() );
        });
        return max;
    };
});

■js/counter.js

define(['jquery'], function($) {
    var count=0;
    $.fn.counter=function(){
        count++;
    };
    $.fn.countDisplay=function(){
        this.text(count);
    }

});

■実際にうごかした画面。。。

実際に書いてみると以外に簡単に使えるんだなという印象。
自作したライブラリはとりあえずdefineで囲めばいいっぽいし。
defineで囲みたくない場合はshim設定で利用すれば擬似的に
モジュールで使えるということらしい。

後は今回でいうpage.jsの中でKAYACのURL dispatcher的な
ものを使えばよりいい感じになるのかもしれない。

そこそこ規模が大きくても何とかなるjavascriptの設計(URL dispatcherの薦め)

■参考URL

JavaScriptを分割&非同期で読み込めるRequireJS

RequireJS 2.0による依存関係の明示、フォールバック、複数バージョンの混在

勉強会資料シェア Getting Started with RequireJS

java-ja.js #2 RequireJS実践編

jQueryのプラグインをつくってみよう

条件付きコメント(バージョンベクタ)

条件付きコメント(バージョンベクタ)

IE7で動かないjsがあって条件付きコメントを書く必要があったのでメモというかリンクまとめ。
※条件付コメントっていう名称が出てこなかっただけ・・・。

IE10以降は条件付きコメント機能は廃止され、ただのコメント扱いになるのは知らなかった。

ウィキペディア:条件付きコメント

バージョンベクタを使用したブラウザ検出

バージョンベクタ?

バージョン ベクタは、ブラウザの起動時に読み取られる
レジストリ キーに格納されている、Windows Internet Explorer
の内部バージョン番号を表します。Web 開発者は、バージョン
ベクタを使用して、閲覧者が Web サイトの表示に使用している
ブラウザを検出できます。

jstdutilをインストールした

jstdutilをインストールしたい。

jstdutilは↓を提供してくれる便利なやつ。
・JsTestDriverのコマンドラインを簡略化するjstestdirver
・ファイル更新時に自動的にテストを実行するjsautotest

jstdutilはrubyのgemからインストールできるらしい。
でもそもそもrubyすら入っていない・・・。
とりあえず手元にあったCentOS release 5.7にrubyを入れる。

yumで見たらVersionが1.8.5だった・・・。

# yum info ruby
Loaded plugins: fastestmirror, security
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * updates: ftp.iij.ad.jp
base                                                                                                                                  | 1.1 kB     00:00
extras                                                                                                                                | 1.9 kB     00:00
updates                                                                                                                               | 1.9 kB     00:00
updates/primary_db                                                                                                                    | 837 kB     00:00
vz-base                                                                                                                               |  951 B     00:00
vz-updates                                                                                                                            |  951 B     00:00
Available Packages
Name       : ruby
Arch       : i386
Version    : 1.8.5
Release    : 24.el5
Size       : 277 k
Repo       : base
Summary    : An interpreter of object-oriented scripting language
URL        : http://www.ruby-lang.org/
License    : Ruby License/GPL - see COPYING
Description: Ruby is the interpreted scripting language for quick and easy
           : object-oriented programming.  It has many features to process text
           : files and to do system management tasks (as in Perl).  It is simple,
           : straight-forward, and extensible.

rubyのサイトを見るとどうやら最新の安定版は「ruby 1.9.3-p286」らしいので
ソースからビルドしてみる。

Ruby Install Guide::UNIX全般

↑このとおりやってみた。

で、今回の目的だった「jstdutil」を入れようとする。

$ gem install jstdutil

入ったのかな?よくわかんないので動かしてみる。

$ jsautotest
/usr/local/lib/ruby/1.9.1/yaml.rb:56:in `<top (required)>':
It seems your ruby installation is missing psych (for YAML output).
To eliminate this warning, please install libyaml and reinstall your ruby.
/usr/local/lib/ruby/gems/1.9.1/gems/watchr-0.7/lib/watchr.rb:111: Use RbConfig instead of obsolete and deprecated Config.
Using config file public_html/Javascript/JsTestDriver.conf
An error occurred
invalid byte sequence in UTF-8

どうも怒られているらしい。

Ruby 1.9.3 を導入するにあたってソースからビルドする人が気をつける事とはいったい…?!

上記サイトの内容に沿って最新のlibyamlをいれてrubyをconfigure;make;make install

$ java -jar  $JSTESTDRIVER_HOME/JsTestDriver-1.3.5.jar --port 4224
setting runnermode QUIET

サーバ立てて・・・ブラウザで「Capture This Browser」クリックして・・・

$ jstestdriver --config=path/JsTestDriver.conf --tests all
setting runnermode QUIET
.......
Total 7 tests (Passed: 7; Fails: 0; Errors: 0) (2.00 ms)
  Microsoft Internet Explorer 9.0 Windows: Run 7 tests (Passed: 7; Fails: 0; Errors 0) (2.00 ms)

動いた。でも相変わらず jsautotest はだめ

$ jsautotest
/usr/local/lib/ruby/gems/1.9.1/gems/watchr-0.7/lib/watchr.rb:111: Use RbConfig instead of obsolete and deprecated Config.
Using config file public_html/Javascript/JsTestDriver.conf
An error occurred
invalid byte sequence in UTF-8

よくわかんないけどwatcharとかいうのを入れてみる。

# gem install watchr
Successfully installed watchr-0.7
1 gem installed
Installing ri documentation for watchr-0.7...
Installing RDoc documentation for watchr-0.7...

さらに、、、どうもコンフィグのファイル名が悪いらしいのでファイル名を変更
「JsTestDriver.conf」から「jstestdriver.conf」

で今一度

$ jsautotest
/usr/local/lib/ruby/gems/1.9.1/gems/watchr-0.7/lib/watchr.rb:111: Use RbConfig instead of obsolete and deprecated Config.
Using config file jstestdriver.conf

おっ動いたかも。
この状態でテストケースとかを変更してみる。。。。すると。

$ jsautotest
/usr/local/lib/ruby/gems/1.9.1/gems/watchr-0.7/lib/watchr.rb:111: Use RbConfig instead of obsolete and deprecated Config.
Using config file jstestdriver.conf
2012-10-29 15:14:14 Running all
setting runnermode QUIET
Microsoft Internet Explorer: Reset
Microsoft Internet Explorer: Reset
.......
Total 7 tests (Passed: 7; Fails: 0; Errors: 0) (3.00 ms)
  Microsoft Internet Explorer 9.0 Windows: Run 7 tests (Passed: 7; Fails: 0; Errors 0) (3.00 ms)

おー動いた。rubyよくわかんないっす。。