ページ内ヌルヌル

ページ内 ヌルヌルでgoogle検索するといっぱいでてきます。ヌルヌルって言い出したのはだれなんでしょうか。

http://webnonotes.com/javascript-2/pagescroll/これが一番上に出てきます。

this.hashこれで、ハッシュの要素を取得できるようです。
$(hash).offset().topこれで位置を取得できるようです。
$(‘html,body’).animate({scrollTop: offset}, 800);これでヌルヌル移動できるようです。800がスピードでしょう。

これだと、URLは変わらないのか。backborn使ったときみたいなURLも変えつつ、移動したいな。トップに戻るとかならこれで全然いいんだな。とりあえず、ついにページトップに戻るボタンをつくりたいと思います。

トップにヌルヌル戻る

HTMLのどこかに、このようなものを作ります。

<div id="back_top" onclick="back_top();"></div>

そして、このようなjavascriptを作ります。

function back_top(){
    $("html,body").animate({scrollTop: 0}, 800);
}

これだけです。画面のどこにいるかを確認して、上から500pxくらいになったらTOPに戻るボタンを表示するようにしたい。

上から500pxになったらTOPに戻るボタンを表示する

$(window).scroll(function () {
    if($(this).scrollTop() > 500){
        $('#back_top').fadeIn();
    }else{
        $('#back_top').fadeOut();
    }
});

これだけです。

ページ内ヌルヌル移動

クリックしたらuRL変わるようにしたいし、URL変えつつヌルヌルするのめんどくさいからやめよう。

Backbone.jsとMarionette

http://backbonejs.org/ここのdevelopバージョンを使ってみます。Underscore.jsに依存します。http://underscorejs.org/
jQueryも使います。

モデル

var Hoge = Backbone.Model.extend({
    defaults:{
        'name': 'taro',
        'age': 20,
        'updateTime': new Date()
    },
    initialize: function(){
        console.log('create hoge : ' + this.cid + ' : ' + JSON.stringify(this));
    }
});

var hoge = new Hoge();
hoge.set({
    name: 'jiro',
    age: 30
});

console.log('hoge : ' + hoge.cid + ' : ' + JSON.stringify(hoge));

こんな感じで、モデルを作成できる。defaultsで初期設定、initializeでコンストラクタの設定ができる。newで、モデルのインスタンスを作成し、setで、各値をインスタンスにセットできる。getで取得できる。hoge.get(‘name’);

var hoge = new Hoge({
    'name': 'saburo',
    'age': 30
});

こんな風に、newのときに、引数に値を渡すことでセットすることもできる。newでインスタンスを作成すると、cidというものが自動的に設定される。これはインスタンスを一意に識別するためのもの。

hoge2 = hoge.clone();

clone()で、インスタンスを複製できる。cidだけは、ユニークとなる。

その他、モデルのメソッド・プロパティは、attributes、clear、has、unsetがある。

モデルに関数を追加するのはこんな感じでできる。

var Hoge = Backbone.Model.extend({
    defaults:{
        'name': 'taro',
        'age': 10,
        'updateTime': new Date()
    },

    initialize: function(){
        console.log('create hoge : ' + this.cid + ' : ' + JSON.stringify(this));
    },

    add_age: function(){
        this.set({age: this.get('age') + 1});
    }
});

var hoge = new Hoge();
hoge.set({
    name: 'jiro',
    age: 20
});
console.log(hoge.get('name') + ' : ' + hoge.get('age'));

hoge.add_age();
console.log(hoge.get('name') + ' : ' + hoge.get('age'));

属性の値を1追加するのに、こんなめんどうな書き方しかできないとは思えないんだけど、this.age ++;とかではエラーになった。
その他、モデルの変化に応じてイベントを発動したり、どこが変化したのかチェックしたり、モデルの変化が妥当な変化かをチェックするバリデーションルールの設定などができる。

モデルのサーバとのやりとり

はて、Ajaxでサーバとやりとりするのも、Bacbone.jsは便利になっているもよう。サーバ側をcakePHPでつくりながら試してみる。

モデルで、サーバとやりとりする際は、APIのURLを設定する。

var Dev = Backbone.Model.extend({
    urlRoot: 'http://local.com/devs/hoge/'
});

あとは、saveメソッドを呼出すだけで、新規作成か更新処理をしてくれるということで、作成処理はPOSTアクセス、更新処理は、PUTアクセスする。saveメソッド呼び出し時に、id属性の指定があれば、更新処理とし、なければ新規作成とする。らしい。

便利そうでありつつ、色々注意が必要そうですが、一旦データ取得から確認してみたい。データ取得は、fetchメソッドらしい。

参考:試して学ぶ Backbone.js入門2この記事は分かり易そう。

cakePHPのcontrollerで下記のようなものをつくりました。Devモデルというのを既に作成済みです。

public function hoge($id = null){
	if(!$this->request->is('ajax')) throw new BadRequestException();
	$this->autoRender = false;

	//取得(fetch)
	if($this->request->is('get')){
		if($id){
			$devs = $this->Dev->findById($id);
		}else{
			$devs = $this->Dev->find('all');
		}
		return json_encode(compact('devs'));
	}

	return null;
}

これにfetchアクセスする、backboneのコードをつくってみます。

var Dev = Backbone.Model.extend({
    urlRoot: 'http://local.com/backborn/devs/hoge/'
});

var dev = new Dev();
dev.fetch({
    success: function(model, response, options){
        console.log(response.devs[0]['Dev']['title']);
    }
});

これでできました。idを渡してみます。

var Dev = Backbone.Model.extend({
    urlRoot: 'http://local.com/backborn/devs/hoge/'
});

var dev = new Dev();
dev.set({id: 3});
dev.fetch({
    success: function(model, response, options){
        console.log(response.devs['Dev']['title']);
    }
});

できました。分かりました。取得後に、parseというのを使って、レスポンスに対して色々やったりするらしい。

コレクション

コレクションは、モデルのリストらしい。

View

viewは、モデルの変更に目を光らせて、レンダリングするやつらしい。モデルが変更されたらBackbone.Eventsによって、Viewに通知されて、その通知内容に基づいてレンダリングする内容をつくるらしい。そして、テンプレートに渡すという流れのようでありんす。つまり、cakePHPのモデルとbackboneのモデルは同じだけど、cakePHPのcontrollerとbackboneのviewが同じで、cakePHPのviewとbackboneのテンプレートが同じみたいな感じかなと思った。

Viewは、モデルやコレクションの変更に目を光らせるだけではなく、DOMの変更にも目を光らせる。そして、一つのViewは、一つのDOMに紐づける必要がある。DOMとの紐づけは、elプロパティが使える。紐づくDOMの条件をelの値として設定することができる。その他、tagName、className、id、attributesなどを設定することで、動的にel属性を作成することができる。elとtagNameとかが同時に設定されている場合、elの設定が優先されるし、el設定が存在しないdomを表している場合は、tagName等が設定されていても、elはundifinedになる。elを設定されている場合は、必ず存在するDOM条件を設定する必要があるようだ。elもtagName等も設定されていない場合は、空のdivがel属性の値になる。新たに作成されたel属性の場合は、el属性が作成された段階ではDOMに追加されないらしい。

HTML

<div id="hoge"></div>

Js

var DevView = Backbone.View.extend({
    el: '#hoge'
});

var dev_view = new DevView();
console.log(dev_view.el);

これのログへの出力結果は、<div id=”hoge”></div>となる。

viewのコンテンツを表示するには、renderメソッドを使う。

var DevView = Backbone.View.extend({
    el: '.hoge',
    render: function(){
        this.$el.append('hogehoge');
        return this;
    }
});

var dev_view = new DevView();
dev_view.render();

テンプレートは、Backbone.js自体に標準搭載されているわけではなく、一番シンプルなのは、Underscore.jsのテンプレート機能を利用することらしい。簡単そうなので、後で調べる。次にルーター機能について確認したいと思います。

ルーター

var MyRouter = Backbone.Router.extend({
    routes: {
        'hoge': 'hoge',
        'aiu': 'aiueo'
    },

    hoge: function(){
        console.log('hogehogehoge');
    },

    aiueo: function(){
        console.log('aiueoaiueo');
    }
});

var router = new MyRouter();
Backbone.history.start();

こんな感じでつくる。ということは、まずルーターのroutesがURLを検証して、何かに合致したら該当する関数を呼出すところからアプリケーションが始まるわけであります。そこから、viewを作って、そこで色々モデルを使いながら表示する内容をつくって、テンプレートに渡して表示するわけであります。実際的にはViewは、cakePHPのレイアウトとか、view、エレメントとかに分けて作成してそれを組み合わせていくわけであります。

Marionette

http://marionettejs.com/ Backbone.jsのコードをよりシンプルに、管理し易くしてくれるやつらしい。Backbone.js使うなら使った方がいいらしい。

参考:
Marionette.jsまとめ その1 Application, Controller, AppRouter
実践Backbone.Marionette 現場の悩みと解決まで
“Backbone.Marionette.js: A Gentle Introduction” を今更ながら勉強してみた

上記3つを見ると色々分かりそうだなーと思いました。ありたがいです。

jquery – テーブルに検索、ソート、ページネーション機能をもたせるDataTablesプラグイン

DataTables

fuelphpの検索結果をテーブルにして、ソートとページネーションをAjaxな感じでやりたいので、これを使ってみる。下記サイトをみて知った。

DataTables(日本語で紹介してるサイト)

ダウンロードはここでできた。

おお、お手軽だ。さすがプラグイン。
プラグインを読み込んで、tableにidを設定して、下記のようにすればできた。tableのidが#resultの場合の例。

$(document).ready(function() {
    $('#result').DataTable({
        searching: false
    });
} );

optionについては、http://www.datatables.net/reference/option/に色々書いてある。searching:falseは、検索ボックスを非表示にしている。

HTML5 Canvas のIE8対応

最初ExplorerCanvasを使ってしまって、全然動かなかった。

参考:Internet Explorer 8でCanvasを動かす時のメモ

VMLCanvasを使うとよかったです。
https://code.google.com/p/mofmof-js/wiki/VMLCanvas

VMLCanvas.js は uuCanvas.js から Silverlight と Flash バックエンドを省略し、 コンパクトにパッケージしなおした JavaScript ライブラリです。mofmof.js に依存せず単体でも動作します。 ExplorerCanvas に比べ、より多くの機能をサポートしています。

canvasでグラフを表示してマウスオーバーでポップアップするようなものでしたが、結果的には全てIE10や、chromeなどと同じように表示することができました。

IEだけVML Canvasを読み込みます。

<!--[if IE]>
<?php echo $this->Html->script('VMLCanvas-1.1.1.min')?>
<![endif]-->

ie8かチェックする関数をつくります。

function check_ie8(){
	var userAgent = window.navigator.userAgent.toLowerCase();

	if (userAgent.indexOf('msie') != -1) {
		var appVersion=window.navigator.appVersion.toLowerCase();

		if (appVersion.indexOf("msie 6.") != -1) {
			return true;
		} else if (appVersion.indexOf("msie 7.") != -1) {
			return true;
		} else if (appVersion.indexOf("msie 8.") != -1) {
			return true;
		}else{
			false;
		}
	}else{
		return false;
	}
}

ie8以外の場合は、$(window).loadを使い、ie8の場合は、window.oncanvasreadyを使います。

$(window).load(function () {
	if(check_ie8()) return;

	canvas = document.getElementById("hoge_canvas");
	C = canvas.getContext("2d");
	init();
	images[num].onload = function(){
		draw();
		timerID = setInterval ('check_on_mouse()', 33);
	}
});

window.oncanvasready = function(canvasNodeList) {
	if(!check_ie8()) return;
	ie8 = true;

	canvas = document.getElementById("hoge_canvas");
	C = canvasNodeList[0].getContext("2d");

	init();
	draw();
	timerID = setInterval ('check_on_mouse()', 33);
};

これで全部表示されました。スクリプト分けるとメンテが大変なので一つにしようと思って上記のようにしました。images.onloadを使うとie8で動かなかったのでとりあえず外しました。

マウスオーバーチェックは、下記のようになりました。

function check_on_mouse(){
	canvas.onmousemove = getMousePoint;

	function getMousePoint (e) {
		if(ie8){
			e = event;
			mouse_x = e.x;
			mouse_y = e.y;
		}else{
			var rect = e.target.getBoundingClientRect();
			mouse_x = e.clientX - rect.left;
			mouse_y = e.clientY - rect.top;
		}

		//mouse_xとmouse_yがプロットされてるアイテムに重なってればポップアップする処理など
	}
}

ie8の場合、e.targetというのが使えないので、その変わりにsrcElementというのを使うのですが、それでやってもrectの値がおかしくて使えませんでした。e.xがmouse_xとほぼ同じになるので、上記のようにしました。

画像をクライアント側で縮小する

スマホからアップロードしようとするとスマホで撮影した画像ファイルのサイズが大きいので大体エラーになります。クライアント側で縮小しつつ、向きも合わせるとう処理が必須であります。

画像アップロード前にクライアント側で縮小してプレビューし、アップロード

ここにそのまんまのことが書いてあります。ありがたい。これをやってみたいと思います。幅・高さの最大値を決めてそれより大きい場合縮小するということをしてます。iPhoneの対策なんかもしております。リサイズしたものをFormdataにいれてAjaxで登録しております。

FormDataからFormに書き込むことはできないらしい。ということはAjax不要のFormでも、クライアント側リサイズする場合、全部Ajaxに変えないといけないっぽい。めんどい。

今Ajaxでやってるんだけど、cakephpの場合、fileだけ$this->request->params[‘form’]に入ってる。

やっとできた。

chrome window.printが効かない

謎の現象がおきています。localのchromeであれば問題なくうごきますが、さくらサーバでやると動きません。といっても他のページだと問題なく動くのですが特定ページのみ、さくらサーバだとwindow.print()が効きません。厳密に言うと、window.printを実行してもうんともすんともいわないものの、その後、そのページから離れる直前に、printプレビュー画面が表示されます。。。なぜ??

safariでもfirefoxでも大丈夫なのにchromeだけ変だ。window.print実行後に別のページ行くか、リロードするかしようとすると表示される。なんなんだこれは。

<a href="javascript:hoge();">print</a>
<script>
function hoge(){alert(123);window.print();alert(324)}
</script>

とかってやると、alert()は表示される。どちらも。でもpreviewだけリロードとかしないと表示されない。あらローカルだとpreviewは表示されるが、逆にalertが表示されない。なんだこりゃ。

なんとchromeを終了して再度開きましたら、なおりました。。

いい感じにくっつけたフッターとヘッダーの間にあるコンテンツの背景を白く塗りつぶしたい

ヘッダーとフッターの間にあるコンテンツ空間はコンテンツ量によって高さが変動しますので、普通にbackgroundの背景色を設定しても、コンテンツ量が少ない場合、余白が出来てしまいます。この余白をださずに背景色で埋めたいです。

display: flexってやつが使えないだろうかと思いましたが、flexでコンテンツかこっちゃうというのは今更現実的でないので、結局javascript使っちゃうことにしました。

$(document).ready(function(){
    var f = 94;
    var height = $('#wrap').height();
    var h = $('#header').height();
    $('#contents').height(height - h - f);
});

f = 94というのは、コンテンツのfooterと重ならないようにするためのpadding-bottomの値になります。jqueryのheight関数は、paddingを除いた高さのことみたいです。

他のところをクリックしたら閉じる javascript

下のスクリプトでやったらパソコンとAndroidはうまく閉じるけど、iPhoneで試したら閉じない。

function toggle_sub_menu(){
	$('#sub_menu').slideToggle(300);
	event.stopPropagation();
}

$(document).on('click', 'body', function(e){
	if(!$(e.target).is('.el-icon-lines')){
		if(!$(e.target).is('#sub_menu') && !$(e.target).closest('#sub_menu').size()){
			if($('#sub_menu').is(':visible')){
				$('#sub_menu').slideUp(200);
			}
		}
	}
});

safariも大丈夫だけどiPhoneだけだめで、bodyのクリックイベントを受けてないようだ。
iPhoneのclickイベントの挙動

ここに色々書いてくれてるのでこれみたら解決しそう。
sub_menu見えてるかチェック先にやった方がいいか。

画像をinputにドラッグして表示する

IEはダメらしいですが、typeがfileのinputはドラッグ&ドロップでファイルを登録できます。なので、ドロップしたらinputにchangeイベントがおこります。changeイベントが発生したら、FileReaderを使って表示させます。

$(document).on('change', '.img_input', function(e){
	var file = e.target.files[0];
	if(! file.type.match('image.*')) return;

	var reader = new FileReader();
	reader.onload = (function(f) {
		return function(e) {
			$('.img_list').html('<img src="' + e.target.result + '" alt="' + f.name + '"');
		};
	})(file);

	reader.readAsDataURL(file);
});

フォームのファイルをAjaxでアップロードする

FormData オブジェクトの利用ここにドンズバのことが書いてあって有り難いです。非常に便利っぽいですね。

var fd = new FormData(document.getElementById("form_data"));

このfdを渡してAjaxでやったらできた。いやーよかったよかった簡単で。
jqueryでやる場合、この2つを設定しないとだめらしい。
processData: false,
contentType: false,

$.ajax({
	type: 'post',
	url: 'hogehoge.com',
	data: fd,
	dataType: 'html',
	async: true,
	processData: false,
	contentType: false,
	success: function(data){
		hoge();
	},
});

jquery ui sortable で並び順を取得する(serialize)

参考:http://api.jqueryui.com/sortable/#method-serialize

ここで、item_1、item_2といった感じで連番ふるよといいましたが、どちらかというと並び替えの場合、並び替え対象でDBのIDで管理しないといけないと思いますので、item_idという感じにした方がよかったです。

cakePHPだと、下記のような感じにするといいのではないでしょうか。

item_<?php echo h($item['Item']['id'])?>

$(this).sortable(‘serialize’);で下記のようなデータが得られます。

item[]=2&item[]=1

2と1がIDであれば、これをそのままAjaxでsortアクションなんかに渡します。
以下は例です。

function save_sort(sort_data){
	$.ajax({
		type:'post',
		url: '<?php echo $this->Html->url(array( 'controller' => 'hogehoge', 'action' => 'sort'))?>',
		data: sort_data,
		dataType: 'html',
		async: true,
		success: function(data){
			alert('sort ok!');
		}
	});
}

$(function(){
	$('#sortable').sortable({
		cursor: 'move',
		opacity: 0.7,
		placeholder: "placeholder",
		forcePlaceholderSize: true,
		cancel:false,
		handle:'.move_btn',
		update: function(event, ui){
			save_sort($(this).sortable('serialize'));
		}
	});
});

これでhogehogeコントローラーのsortアクションにアクセスしてくれて、$this->request->dataの中にソートデータが下記のように入っております。Formのシリアライズと一緒です。便利です。

スクリーンショット 2014-06-25 0.49.26

firefoxでformにdisabled設定するとリロードしてもdisabledのままになっちゃう

firefoxはFormの入力内容を勝手ながら保存してくれます。便利なときもあれば不便なときもあります。

disabledをjavascriptで動的に設定した場合は不便です。

入力内容を覚えておいておかないようにするか、javascriptで、Firefoxのために初期化(disabledを外す)するかなどが必要になります。formに、autocomplete=”off”という設定を入れるのが一番楽でした。

javascript ドラッグ&ドロップで並び替え

HTML要素をドラッグアンドドロップで移動して、並び順を変更したい。

今知りましたが、sortabaleというドンピシャの機能があるんですねえ。ちなみに、ドットインストールにも載ってました。

$(function(){
	$('#sortable').sortable();
});

cursor:’move’でカーソルを移動用のカーソルに変更できますし、opacityで移動中透明にできますし、placeholderで、ドロップ先の場所を表示できますし、forcePlaceholderSizeで、placeholderを移動中の要素の大きさにすることができます。ドラッグ開始イベントなども沢山用意されていて、activateでとれますし、その他並び順が変更されたときのイベントなどもありますので、効率よく並び順の管理もできそうだなあと思います。並び順のデータも簡単にとれます。

詳細情報は、下記に詳しく日本語で載っております。
Sortable
Sortableを使ってみよう

ちなみに、ドラッグ可能な要素を限定するのは、handleでできますが、これにボタンを設定しようと思ったら動きませんでした。もともとボタンはcancelでデフォルト設定されており、ボタンの上ではドラッグができないようになっていました。なので、cancel:falseにするか、ボタンをaタグでつくるかなどをするとボタンも使えるようになりました。

あと、並び順のデータをとるupdateですが、並び順を取得したい対象に、item_3といったid名をつける必要があります。item_1、item_2、といった感じで連番を振っていきます。item_1、item_2が並び順が変更された時点で、updateイベントが発動して、$(this).sortable(‘serialize’)という命令にてその並び順を取得することができるようになります。例えばitemを追加した場合で、追加したアイテムに、item_3といったIDを振ったとしても、追加された時点では、updateイベントは発動しません。ですが、追加後に並び替えをしたら、item_3も$(this).sortable(‘serialize’)の並び順データに登録されます。