Backbone.jsの基本
細かいことはサンプルコードで、ということで……
backbone.js v1.0.0
function l(s) {console.log(s)}; function a(s) {alert(s)}; l('*****start*****'); l('*****Backbone.Model*****'); var Todo = Backbone.Model.extend({ defaults: { title: '' ,completed: false } ,initialize: function() { l('*** model initialize***'); this.on('change', function(){l('model has changed');}); this.on('change:title', function(){l('title has changed');}); this.on('invalid', function(){l(this.validationError)}); } ,validate: function(attrs) { if(!_.isBoolean(attrs.completed)) { return 'completedはブーリャンでお願いしますね!'; } } }); var todo = new Todo({title: 'sample'}); l(JSON.stringify(todo)); l(todo.get('title')); l(todo.attributes.title); l(todo.hasChanged());//false l(todo.hasChanged('title'));//false l(todo.hasChanged('completed'));//false todo.set('title', 'changed title'); l(todo.get('title')); l(todo.hasChanged());//true l(todo.hasChanged('title'));//true l(todo.hasChanged('completed'));//false todo.set('completed', 'hoge'); todo.save();// invalid l('*****Backbone.View*****'); var TodoView = Backbone.View.extend({ id: 'sample_view' ,className: 'sample_class1 sample_class2' ,tagName: 'li' ,template: _.template('example') ,events: { 'dblclick label': 'edit' ,'keypress .edit': 'updateOnEnter' ,'blur .edit': 'close' } ,render: function() { this.$el.html(this.template(this.model.toJSON())); this.input = this.$('.edit'); return this; } ,edit: function(){} ,updateOnEnter: function(){} ,close: function(){} }); var todoView = new TodoView(); l(todoView.el); l(todoView.$el); var button = $('<button></button>'); todoView.el = button; l(todoView.el);//jQueryオブジェクトになっちゃってる l(todoView.$el);//前から変わってない todoView.setElement(button); l(todoView.el);//期待通り l(todoView.$el);//期待通り todoView.el = '<input/>'; l(todoView.el);//期待通り l(todoView.$el);//前から変わってない l('*****Backbone.Collection*****'); (function(){ var collection = new Backbone.Collection; collection.add([{id:1, name:'dog'},{id:2, name:'cat'}]); l(JSON.stringify(collection)); collection.add([{id:1, name:'dogdog'}], {merge:true}); l(JSON.stringify(collection));//id:1のモデルのnameがマージされてdogdogになる l(collection.get(1));//上でマージしたやつ l(collection.get(1).idAttribute);//id })(); var TodoList = Backbone.Collection.extend({ model:Todo }); var todoList = new TodoList(); todoList.on('add', function(model){l(model.cid + ' added to collection');}); todoList.on('remove', function(model){l(model.cid + ' removed from collection');}); todoList.on('change', function(model){l(model.cid + ' changed in collection');}); todoList.on('change:title', function(model){l(model.cid + ' title changed in collection');}); todoList.on('reset', function(models,options){ l('collection reset'); l(models); _.each(options.previousModels, function(m){ l(' ' + m.cid + ' removed'); }); _.each(models.models, function(m){//models.modelsってなんだ…… l(' ' + m.cid + ' added'); }); }); var todo = new Todo({title: 'sample'}); l(todo.id);//undefined l(todo.cid);//c1とか l(todo.idAttribute);//id todoList.add([todo]); l(JSON.stringify(todoList)); //l(list.get(0).get('title'));//idがないからidでgetはできない l(todoList.get(todo.cid).get('title'));//sample todoList.add([{id: 0, title: 'sample'}]); //別にnew Todo()しなくても属性値だけでaddできちゃう l(todoList.get(0).get('completed'));//Todoとしてaddされているのでちゃんとfalseと出力される //一旦Collectionにaddしたならmodel単体で操作してもCollection側のイベントハンドラは有効 var temp = todoList.get(0); temp.set('title', 'タイトル変えてみるわ');//Todo,TodoList両方でchangeイベントがハンドリングされる todoList.remove(0); todoList.reset([{id: 0, title: 'dog'}, {title: 'cat'}]);//完全に中身を作り直す todoList.set([{id:0, title: 'dogdog'}, {title: 'mouse'}]);//add,remove,changeを駆使する todoList.reset();//resetイベントのみ発生
Backbone.Model
RubyのActiveRecordみたいにhasChangedで変更有無を調べられるのはよかった。setter(model.set(attr, val)
)でセットしないと変更されたと見なされないのも同じ。
バリデーションはイベントだけが提供されていて、Railsのsexy validationみたいなのはない。
Backbone.View
HTML要素1つに紐付くっぽい。そのdom elementを一から作るなら、
var v = Backbone.View.extend({ id: 'sample_view' ,className: 'sample_class1 sample_class2' ,tagName: 'li' });
と言った調子で表現できる。
既に存在する要素に対しては
var v = Backbone.View.extend({ el: '#sample_view' });
var v = Backbone.View.extend({}); v.setElement($('<button></button>'));
といった調子。newしたあとにv.el
に直接代入するのはよくないっぽいので上記2つ以外はやらない方がたぶんいい。
setElement()
で要素を設定したらel
と$el
両方が自動セットされる。
Backbone.Collection.addでmergeするところがちょっとよくわからなかったんです
var collection = new Backbone.Collection; collection.add([{id:1, name:'dog'},{id:2, name:'cat'}]); l(JSON.stringify(collection)); collection.add([{id:1, name:'dogdog'}], {merge:true}); l(JSON.stringify(collection));
これで
[{"id":1,"name":"dog"},{"id":2,"name":"cat"}] [{"id":1,"name":"dogdog"},{"id":2,"name":"cat"}]
となぜid
をキーにマージされるのかわからなかったけど、これはつまり、add
に渡したオブジェクトはもうBackbone.Model扱いを受けるので、そのidはdom elementのidと同義になるから、だからこのid
属性が同じならdom上でも同じだからマージしちゃうわけですねたぶん。
追記:違った・・・
Backbone.jsのモデルはすべて
- オブジェクトごとに自動付与されるcid
- オブジェクトを識別するid
- オブジェクトを識別する属性を指定するidAttribute
- これのデフォルトがidで、もし対応するテーブルのPKがid_userならそう書き換えて使う。
を持っているんですって。
あーそうかそうか、Backbone.Viewのidとごっちゃにしてしまったのか。失礼しました。
jQuery Mobileでクリックしたliの背景色を変えたいんです
ui-bar-x
クラスを一旦全てのliからremoveしてからクリックされたやつにaddする。
jQuery Mobileでポップアップの中にcolumntoggleなtableを表示したいんです
普通に書けば普通にできる。私は普通ではなかったのでハマった……
ただしカラム選択用ボタンはダメ。1画面1ポップアップとかそんな感じの制限があるっぽく、カラム選択ポップアップを表示すると元のtableを表示していたポップアップの方が消える。data-dismissible="false"
にしても消える。しばらくデバッガで処理追ったけど、やめた。
カラム選択ボタンには.ui-table-columntoggle-btn {display: none;}
で退場願った。
カラム優先順位data-priority
は1から6までしかないので、間違って7、8…と書くと「あれなんでこのカラム消えねーの・・・なんでや・・・」ってなる。
tableはtable-layout:fixed
にしてtdにword-wrap:break-word
にしないと、改行ポイントのない長い英数字があったらそのカラムの幅が長くなってしまう。
参考
- Table: Column Toggle - jQuery Mobile Demos
- Column-Toggle Table Widget | jQuery Mobile API Documentation
- krzm.jp – CSS3モジュール一覧
あまり知らなかったのですがCSS3ってまだこんな状況だったんですね - CSS Text Module Level 3
- white-space - CSS | MDN
- word-wrap-CSS3リファレンス
- css - Word-wrap in an HTML table - Stack Overflow
- table-layout-スタイルシートリファレンス
- Popup - jQuery Mobile Demos
そんなことより
JQM1.3.2だと画面幅狭めて一度消えたカラムが、幅を戻しても戻らないんですね-。公式デモでも確かめられてしまう。JSFiddleでjQuery1.9と併用できるJQM1.3.0b1だとそんなことないんですけどねー。なんで1.3.2でそんなことになったんですかねー。
jQuery Mobileのラジオボタンを選択解除できるようにしたいんです
やり方によるが、「二度押し」で解除させることができなくてハマった。
「もうお馴染みのラジオボタン」は1度選択したら最後解除できない、というのは利用者としてもおおむね常識だと思う。むしろ選択解除できるようにしたいものはプルダウンリストを使う。
スマートフォンだと、プルダウンで表現するとせっかくのタッチデバイスなのにいちいちツータッチ必要で面倒臭い、ラジオボタンにしてワンタッチにしたいという欲求が……ないですかね。
ところがラジオボタンだと選択解除ができないという。
jQuery Mobileでラジオボタンを利用すると、こうラジオボタンというより押しボタン式選択装置のような見た目になりますよね。で利用者としては「これ二度押ししたら選択解除できるんじゃね」と思いそう……ではないですかね。いやそうしてあげたい。
取り得る道として
くらいが思いつきます。
排他チェックボックスは昔何かで作った気がしますが、選択解除できるラジオボタンより排他選択なチェックボックスの方がより異常だと思うので、これはやりたくない。
※追記:そんなことない気が後からしてきた
クリアボタンなんて置きたくない。画面狭いし。二度押し選択解除でいいでしょ(結論ありき)、と思ってやってみました。
でもこれがなかなかできませんでした。
クリアボタン式なら楽です。
$("#clear_button").on("click", function(){ $("#radio input:radio:checked").prop("checked", false).checkboxradio("refresh"); });
これでイケます。色々調べてもだいたいこの書き方が載っています。
ところが二度押しで解除させようとするとダメ。どうもバブリングしたイベントのせいでうまく選択解除されてくれない様子だったけど、stopPropagetion()
してもだめ。
なんでや。。
どうするのかというと、
$radio.prop("checked", false).checkboxradio("refresh"); event.preventDefault(); event.stopPropagation();
preventDefault()
もやる。片方ではダメ。両方書かないと、せっかく$radio.prop("checked", false)
しても後からcheckedにされてしまう。
するってーと
ボタンを押すと選択したラジオのvalueをalertします。
参考
- Checkboxradio Widget | jQuery Mobile API Documentation
API Documentに「checked変えたらrefreshしなさいよ」って書いてある - jQueryのセレクタ解説 | Smart
以前:checked
は見たことがあってあーこれなんだろうなーと思っていました - jQueryにおけるattrとpropの違いと使いドコロまとめ - Qiita
- CSSは分かるけどjQueryは苦手……という人が .attr()と .prop()に親しんでくれるといいなーと思って書きました。 | Ginpen.com
おおざっぱには、inputの属性はprop
で扱いましょうって感じですか…… - e.preventDefault() と e.stopPropagation()まとめ - piglovesyouの日記
jQueryMobileのliのカウントバブルを他のタグで使いたいんです
li内でui-li-count
クラスを付加したspanを書けばカウントバブルになる。この例は探せばいくらでも出てくる。
ただ、それ以外のタグの例となるとほとんど無い。StackOverflowで検索しても、要するにあり合わせのクラスを指定して何とかする例しか見つからなかった。
tdタグ内にカウントバブルを表示させるに当たってはこう書いた。
<td><span class="ui-btn-up-b ui-btn-corner-all">3</span></td>
td span { padding: .2em .5em; }
するってーと
jQuery Mobileのポップアップ内の選択結果を親画面に反映したいんです
よくあるやつ。よく見るのはwindow.opener
がどうたらっていう書き方。
jQuery Mobileのポップアップウィジェットは同一html内に書いてあるものを擬似的にポップアップさすだけなのでwindow.openerもくそもない。
なのでごりごり書くしかないと思われる。
<div data-role="page"> <div data-role="header">header</div> <!-- 親画面 --> <div data-role="content" id="parent"> <a href="#sample_pop" data-role="button" data-rel="popup" data-position-to="window">popup</a> </div> <!-- ポップアップ --> <div data-role="popup" id="sample_pop"> <fieldset data-role="controlgroup"> <legend>select</legend> <input type="checkbox" name="item1" id="item1" value="1" /> <label for="item1">1st</label> <input type="checkbox" name="item2" id="item2" value="2" /> <label for="item2">2nd</label> <input type="checkbox" name="item3" id="item3" value="3" /> <label for="item3">3rd</label> </fieldset> <a href="#" data-role="button" data-rel="back" data-inline="true">cancel</a> <a href="#" data-role="button" data-rel="back" data-inline="true" id="submit">reflect</a> </div> <div data-role="footer">footer</div> </div>
$(document).ready(function () { $("#submit").on("click", function () { // 子画面選択結果反映用のdivを作る if (!$("#selection").get(0)) { $("#parent").append("<div id='selection'></div>"); } // チェックしたチェックボックスのラベルを親画面に反映 $("#selection").text($("#sample_pop input[type='checkbox']:checked").map(function () { return $("label[for='" + $(this).attr("id") + "']").text(); }).get().join(",")); }); });
するとこんな感じ。
なんか画面リフレッシュされちゃいますねー。。なんでや。。面倒なので放っときます。。
参考
- .map() | jQuery API Documentation
$(sel).map()のあとget()しないと配列が取り出せないんですねー - jQueryによる要素の存在チェックまとめ: 小粋空間
$(sel).get(0)で判定するのがいいかなと思った。