Drupal:ajax_facetsおよび履歴API

おそらく、すべてのWeb開発者は、サイトに検索を実装する必要に直面しています。 かなり一般的なソリューションはApache Solrです。 Drupal開発の世界では、これも例外ではありません。 SolrをDrupalと統合し、ファセット検索を実装するために、 search_apisearch_api_solrおよびfacetapiモジュールがあります 。 ただし、ほとんどの場合、ページ、つまりajaxをリロードせずに検索結果とファセットフィルターを更新する必要があります。 そして、 drupalの世界ではいつものように、 d.orgには、時間とユーザーによってテストされた(または幸運としてテストされていない)必要なことを行うモジュールがあります。 この場合、 ajax_facetsです。



Ajaxファセットは、ファセット検索フィルターで使用できるいくつかのタイプの「ウィジェット」を提供するモジュールです。 これらは「範囲スライダー」、「複数のチェックボックス」、「選択ボックス」、「リンク」です。 これらの「ウィジェット」の値を変更すると、フィルターと検索結果がajaxによって更新されます。 わあ ただし、モジュールが履歴APIのフレンドであれば、さらに良いでしょう。 つまり、フィルターの各状態を履歴に保存します。これにより、ユーザーはページをリロードすることなく、ブラウザーの戻るボタンと進むボタンを使用して検索履歴をたどることができます。



挑戦する



もちろん、この機能の必要性と実装への関心はそれ自体では生じませんでした。 プロジェクトの1つは、ajax_facetsをhistory APIと友達にすることでした。 私が話したいこと。



解決策



通常、問題の解決策は、既製の解決策、または少なくともパッチを見つけることから始まります。 既成のソリューションはありませんでしたが、パッチが見つかりました 。 プロジェクトの問題トラッカーの説明から判断すると 、彼は必要なことだけを行いました。 しかし、残念なことに、パッチは古く、古いモジュールブランチ(7.x-2.x)にのみ適していました。 そのアイデアは非常に簡単です。検索結果とフィルター自体を更新するためにajax_facetsがサーバーから正常な応答を受け取った瞬間に、ブラウザーの履歴にフィルターの現在の状態を保存します。 また、「戻る」ボタンと「進む」ボタンをクリックして、履歴からフィルターの保存状態を取得し、保存状態のパラメーターでフィルターと検索結果を更新する要求を送信します。



アイデア自体の効率をテストするために、見つかったパッチを現在のモジュールブランチ(7.x-3.x)に移植しました。 すべてがうまくいきました。 ただし、改善が必要でした。 つまり、この機能が、履歴APIをサポートしていない古いブラウザーで動作することを望みます。 タスクは簡単です。 history APIをエミュレートするhistory.jsがあります。 一方、 ライブラリモジュールを依存関係に追加することになるため、このライブラリにハード依存関係を追加したくありませんでした。 誰もそのようなパッチを受け入れなかったでしょう。 ajax_facetsモジュールを更新しており、依存関係に不要なライブラリがあると想像してください。 はい。また、history.jsの形式の古いブラウザーのサポートも必要ありません(たとえば、古いブラウザーをサポートしないだけです)。 そのような状況を避けるために、私はすべてをもう少し柔軟にすることにしました:



  1. サーバー側では、モジュールライブラリとhistory.jsライブラリの可用性を確認します。 依存関係が見つかった場合、フロントエンド側にフラグ「history.jsが使用可能です。履歴APIを使用できます」を渡します。
  2. クライアント側では、ブラウザが履歴APIをサポートしているかどうかを確認します(ネイティブまたはhistory.jsを使用)。 その場合、期待どおりにすべてを実行します。 それ以外の場合、標準のajax_facetsの動作を取得します(パッチの前と同じ)。




実装



最初の項目は次のように達成されます。

依存関係が見つからない場合は、「ステータスレポート」ページにヒントを示します。

/** * Implements hook_requirements(). */ function ajax_facets_requirements($phase) { $requirements = array(); $t = get_t(); switch ($phase) { case 'runtime': $description = $t('For now browser ajax history feature works only in HTML5 browsers. If you want to get this feature on HTML4 browsers you need to install libraries module and download history.js library.'); $value = $t('Libraries module not installed.'); if (module_exists('libraries')) { if (!libraries_get_path('history.js')) { $description = $t('For now browser ajax history feature works only in HTML5 browsers. If you want to get this feature on HTML4 browsers you need to download history.js library.'); $value = $t('Library history.js not found.'); } else { $description = $t('For now browser ajax history feature works both in HTML4 and HTML5 browsers.'); $value = $t('Works with history.js library'); } } $requirements['ajax_facets_message'] = array( 'title' => $t('Ajax Facets'), 'description' => $description, 'value' => $value, 'severity' => REQUIREMENT_INFO, ); break; } return $requirements; }
      
      







history.jsライブラリが見つかった場合は、フラグをフロントエンド側に転送します。

 /** * Add required JS and handle single inclusion. */ function ajax_facets_add_ajax_js($facet) { static $included = FALSE; if (!$included) { ... // Add history.js file if exists. if (module_exists('libraries')) { $history_js_path = libraries_get_path('history.js'); if ($history_js_path) { $history_js_exists = TRUE; drupal_add_js($history_js_path . '/scripts/bundled/html4+html5/jquery.history.js', array('group' => JS_LIBRARY)); } } ... $facet = $facet->getFacet(); $setting['facetapi'] = array( .... 'isHistoryJsExists' => $history_js_exists, ); drupal_add_js($setting, 'setting'); drupal_add_library('system', 'drupal.ajax'); } }
      
      







2番目の段落の実装は、例としてpushStateラッパー関数とともに示されています。

 /** * Pushes new state to browser history. * * History.js library fires "statechange" event even on API push/replace calls. * So before pushing new state to history we should unbind from this event and after bind again. */ Drupal.ajax_facets.pushState = function (state, title, stateUrl) { // If history.js available - use it. if (Drupal.settings.facetapi.isHistoryJsExists) { var $window = $(window); $window.unbind('statechange', Drupal.ajax_facets.reactOnStateChange); History.pushState(state, title, stateUrl); $window.bind('statechange', Drupal.ajax_facets.reactOnStateChange); } else { // Fallback to HTML5 history object. if (typeof history.pushState != 'undefined') { history.pushState(state, title, stateUrl); } } };
      
      







ところで、history.jsには、考慮する必要のある興味深い機能が1つあります: statechangeイベント 、ブラウザーの履歴ボタンが押されたとき、および例えばHistory.pushState()メソッドを呼び出して履歴がプログラムによって更新されたときに発生します ネイティブ履歴APIの実装では、ブラウザーにはonpopstateイベントがあり、ブラウザー履歴ボタンがクリックされたときにのみ呼び出されます。 不必要にトリガーされるstatechangeを回避するには、ブラウザーの履歴を更新する前にこのイベントからサブスクライブを解除してから、再度サブスクライブする必要があります。



おわりに



ターンキーソリューションを見つけて適用できるとは限りません。 しかし、それは非常にクールです。 これにより、以前使用されていたモジュールが内部でどのように機能するかを確認することができます。 そして、最終的に、あなたが提案するソリューションが人気のあるプロジェクトにコミットするのは素晴らしいことです。 これは、次回誰か他の人がそのような問題を抱えることがないことを意味します。



ここで完全な差分を見ることができます



All Articles