私は Ember を初めて使用し、Google のような検索インターフェイスを Solr API に実装しようとしています。つまり、入力時に結果がページ上で自動的に更新されるようにしたいと考えています。
私が最初に考えたのは、SearchInput と SearchResults の 2 つの子ビューを持つ、再利用可能な TextSearch CollectionView を作成することでした。
SearchInput に入力するとイベントがトリガーされ、そのイベントは TextSearch CollectionView によってキャッチされ、SearchResult 子要素が更新されます。
イベントは正常に生成されましたが、TextSearch ContainerView は、アクション、関数、またはイベント マネージャーのいずれかを使用してイベントをインターセプトできません。ただし、何らかの理由で IndexController でイベントをインターセプトすることはできますが、代わりに再利用可能な TextSearch CollectionView で処理する必要があります (と思います)。
最後に、SearchResults でビューを更新できず (IndexController でイベントを処理しようとすると)、ページの読み込み時に SearchResult モデルが起動しません。
私は Ember でまったく新しいので、ここでばかげたことをしていると確信しています。ここまで来るのに、ずいぶん長い道のりでした。
どんなアドバイスでも大歓迎です!
ペーストビン: http://jsbin.com/ezomOkO/3/edit
HTML:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{view App.TextSearchView }}
</script>
<script type="text/x-handlebars" data-template-name="textSearch">
{{view App.SearchInputView }}
{{view App.SearchResultsView }}
</script>
<script type="text/x-handlebars" data-template-name="searchInput">
{{input type="text" value=query size="50"}}
</script>
<script type="text/x-handlebars" data-template-name="searchResults" >
<ul>
{{#each concept}}
<li>{{title}}</li>
{{else}}
Sorry, nobody is here.
{{/each}}
</ul>
Javascript:
App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
appName: 'Snomed Search'
});
App.Router.map(function() {
this.route("index", {path: "/"});
});
// INDEX
App.IndexRoute = Ember.Route.extend({
});
App.IndexController = Ember.Controller.extend({
});
//TEXT SEARCH
App.TextSearchView = Ember.View.extend({
actions:{
search: function(search) {
this.get('controllers.searchResults').set('model', App.TextSearch.find(search));
return false;
}
},
needs: "searchResults",
templateName: 'textSearch'
});
// SEARCH INPUT
App.SearchInputController = Ember.Controller.extend({
query: 'Family'
});
App.SearchInputView = Ember.View.extend({
templateName: 'searchInput',
keyUp: function(evt) {
this.get('controller').send('search', this.get('controller.query'));
}
});
// SEARCH RESULTS
App.SearchResultsController = Ember.Controller.extend({
model: function(){
return App.TextSearch.find('Family');
},
afterModel: function(posts, transitions){
alert('model');
},
isPublic: true
});
App.SearchResultsView = Ember.View.extend({
templateName: 'searchResults'
});
App.SearchResults = Ember.Object.extend({
total: 0,
start: 0,
concepts: Ember.A()
});
App.Concept = Ember.Object.extend({
id: null,
title: null,
active: null,
effectiveTime: null
});
App.TextSearch = Ember.Object.extend({});
App.TextSearch.reopenClass({
find: function(searchString){
return Ember.Deferred.promise(function(p) {
p.resolve($.getJSON("http://solr.sparklingideas.co.uk/solr/concept/select?q=title:" + searchString + "&wt=json&indent=true")
.then(function(solr) {
var returned = App.SearchResults.create();
returned.total = solr.response.numFound;
returned.start = solr.response.start;
solr.response.docs.forEach(function (doc) {
var concept = App.Concept.create();
concept.id = doc.id;
concept.title = doc.title;
concept.active = doc.active;
concept.effectiveTime= doc.effectiveTime;
returned.concepts.push(concept);
});
return returned;
}) //then
);//resolve
});//deferred promise
}//find
});//reopen
アップデート
助けてくれてありがとう、ジェレミー、それが私が必要としていたすべての情報でした。素晴らしい。
学んだ教訓の一部を次に示します。
次のように、結果を使用する前に JSON promise が解決されるのを待つ必要があります。
search: function(search) { var results = App.TextSearch.find(search); var _this = this; // results is a jquery promise, wait for it to resolve // Ember can't resolve it automatically results.then(function(results){ _this.get('controllers.searchResults').set('model', results); }); return false; }
ビュー、レンダー、およびコントローラーの違いを認識していませんでした (十分に文書化されているわけではありませんが、Google 検索でわかります)。しかし、これらは「部分的」ヘルパーとどのように関係しているのでしょうか? わからない。
「&json.wrf=?」を追加して Solr URL の末尾に追加すると、XSS セキュリティのためにブラウザーで呼び出しがブロックされるのを回避できます。
ビューからモデルを参照する方法について、私は非常に混乱しました。これが正しい方法です (この例の場合):
<script type="text/x-handlebars" data-template-name="searchResults" > Total : {{model.total}} <ul> {{#each model.concepts}} <li>{{title}}</li> {{else}} Sorry, nobody is here. {{/each}} </ul> </script>
テンプレートで {{view}} ヘルパーを使用すると、コントローラー (!) がないため、イベントがビュー コントローラーでキャッチされませんでした。代わりに {{render}} ヘルパーを使用してください。
将来の読者のために、提案されたソリューションのコメントに対する1つのマイナーな修正:
App.SearchResultsController = Ember.Controller.extend({
// This controller could be removed.
// Ember will auto generate one for you.
});
実際、このコントローラーを削除すると Javascript エラーが発生します。
Assertion failed: <App.TextSearchController:ember242> needs controller:searchResults but it does not exist
SearchInputController の needs 属性のためです。ただし、最後の例では、入力コントローラーから searchResults への依存関係を削除することになりました。
Ember では、次のように「actions: {}」パラメーターでイベント ハンドラーをラップする必要があると思います。
actions:{
search: function(search) {
var results = App.TextSearchController.find(search);
var _this = this;
// results is a jquery promise, wait for it to resolve
// Ember can't resolve it automatically
results.then(function(results){
_this.get('controllers.searchResults').set('model', results);
});
return false;
}
}
ご注意ください:
JSBin は、数か月前の EmbeJS RC6 を使用します。'action' 属性でイベント ハンドラーをラップする上記の変更は、RC6 以降に導入されたようです。Ember の最新バージョンを使用している場合 (必要に応じて)、(イベント) コードは (デフォルトの Ember ライブラリを使用して) JSBin で動作しません。ただし、アクション ラッパーを使用しない場合、コードはブラウザーで動作しません。うーん... github.com/emberjs/ember.js/releases を参照してください
以下の JSBin の例では、イベント ハンドラーから「action:」タグを削除したため、アプリが実行されます。
また、いくつかの小さなデザイン変更を加えました。
- InputController を SearchResultsController から分離するために、代わりに検索イベント ハンドラを InputController からそれを含む TextSearchController に移動しました。
- また、ページ読み込み時の最初の検索を削除したため、ユーザーは白紙の状態から始めます。
- 最後に、TextSearch オブジェクトを削除し、代わりに solr 統合を TextSearchController に移動しました。
最終版はhttp://jsbin.com/ezExeCI/1/にあります。
あなたの助けに感謝します、ジェレミー。あなたなしではできなかった。必ず先払いにします。
どうもありがとう!