質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.94%

CakePHP+jQuery UIでのオートコンプリート

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 3,671

score 32

■目的
CakePHPでWebシステムを構築中です。
あるデータテーブルAの検索フォームにて、マスタテーブルBのnameフィールドに入っているデータをオートコンプリートで補完入力したいです。

■開発環境
CakePHP 2.7.3
PHP 5.6.8
jQuery 1.11.3
jQuery UI 1.11.4

■参考にしたサイト
http://junichi11.com/?p=423
http://www.buildinsider.net/web/jqueryuiref/0019
http://js.studio-kingdom.com/jqueryui/widgets/autocomplete

■実装手順
1. jqueryとjquery UIをレイアウトファイルで指定。

2. テーブルAのコントローラに、オートコンプリートのデータを取得するメソッドを追加。

TableAController.php
public function autocomplete(){
    $this->loadModel('TableB');
    $field = 'name';
    $term = $this->params['url']['term'];
    // 入力値 無:全てのデータの中から10件返す。
    // 入力値 有:入力値を含むデータを10件返す
    $condition = array();
    if(!empty($term)){
        $condition = array('TableB.'.$field.' like' => "%".$term."%");
    }

    $query = array(
        'fields' => array($field),
        'conditions' => $condition,
        'limit' => 10,
        'order' => array('TableB.'.$field => 'ASC'),
        'group' => $field,
    );

    $data = array();
    $items = $this->TableB->find('all', $query);

    foreach ($items as $item) {
        $cnt = array_push($data, $item['TableB'][$field]);
    }

    // JSON形式の文字列を生成
    $json = json_encode($data);
    echo $json;
}
この時点でautocomplete()メソッドの動作確認をしました。

http://localhost/cakephp/TableA/autocomplete?term=hoge

このURLにアクセスすると、テーブルBからnameフィールドに"hoge"が含まれるレコードがJSON形式で返ってくることが確認できています。


3. テーブルAのビューにjavascriptとテキストボックスを作成。
TableA/index.ctp
<script type="text/javascript">
$(function(){
    $('#autocomplete').autocomplete({
        source: '/TableA/autocomplete',
        autoFocus: true,
        delay: 500,
        minLength: 2
    });
})
</script>

<?php echo $this->Form->input('name', array(
    'type' => 'text',
    'id' => 'autocomplete',
)); ?>

これでテキストボックスが作成されますが、入力してもオートコンプリートが動作しません。
2.の時点でautocomplete()メソッド自体は正常に動作している(と思われる)ので、怪しいのはjavascriptかと思っています。
特にsource: '/TableA/autocomplete',の部分が、ちゃんとしたURLを参照できているのかが疑わしいです。
しかし実際のところどういう動作をしているのか確認する方法が分からず、詰まっています。


2015/10/29追記
ipadcaronさんのアドバイスを元にシンプルな構成で検証してみました。
CakePHPでの動作は一旦忘れて、htmlとphpだけで構成してみました。

json_echo.php
<?php
    echo '[{"id":"1","value":"item1"},{"id":"2","value":"item2"},{"id":"3","value":"item3"}]';
?>
index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery UI Autocomplete - Remote datasource</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
  <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
  <script>
  $(function() {
        function log( message ) {
      $( "<div>" ).text( message ).prependTo( "#log" );
      $( "#log" ).scrollTop( 0 );
    }

    $( "#birds" ).autocomplete({
      source: "json_echo.php",
      minLength: 2,
      select: function( event, ui ) {
        log( ui.item ?
          "Selected: " + ui.item.value + " aka " + ui.item.id :
          "Nothing selected, input was " + this.value );
      }
    });
  });
  </script>
  </head>
<body>

<div class="ui-widget">
  <label for="birds">Birds: </label>
  <input id="birds">
</div>

<div class="ui-widget" style="margin-top:2em; font-family:Arial">
  Result:
  <div id="log" style="height: 200px; width: 300px; overflow: auto;" class="ui-widget-content"></div>
</div>


</body>
</html>

index.htmlを開きテキストボックスに入力してみたところ、item1~3が候補として表示されることを確認しました。
この場合はどんな文字列を入力しても、echoで返されるJSONデータは決まっているので常にitem1~3が表示されるということで、正しい動作だと思います。

この結果を受けてCakePHPで以下のように実装しました。
TableAController.php
    public function autocomplete(){
        echo '[{"id":"1","value":"item1"},{"id":"2","value":"item2"},{"id":"3","value":"item3"}]';
    }
TableA/index.ctp
  <script>
  $(function() {
        function log( message ) {
      $( "<div>" ).text( message ).prependTo( "#log" );
      $( "#log" ).scrollTop( 0 );
    }

    $( "#birds" ).autocomplete({
      source: '<?php echo $this->Html->url(array('controller' => 'OrderLedgers', 'action' => 'autocomplete')); ?>',
      minLength: 2,
      select: function( event, ui ) {
        log( ui.item ?
          "Selected: " + ui.item.value + " aka " + ui.item.id :
          "Nothing selected, input was " + this.value );
      }
    });
  });
  </script>

HTML部分は同一

これを実行してみましたが、動作せず。。。

そしてたった今、Chromeのブラウザコンソールに500 (Internal Server Error)が表示されていることに気付きました。
(途中でeripongさん、tozjpさんが言っていたのはこういう所を見ろということだったんですね・・・)
少し前進した気がしますので、もう少し調べてみたいと思います。



解決しました!

根本的な原因は、tozjpさんのご指摘の通り、autocompleteメソッドからのレスポンスが純粋なJSONではなかったことでした。
CakePHPがビューなどを自動で付与したデータを返していたため、javascript側で正常に処理できなかったのだと思われます。

http://digape.com/201210/cakephp2-json%E5%BD%A2%E5%BC%8F%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E6%89%8B%E8%BB%BD%E3%81%AB%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B/
http://www.sssg.org/blogs/hiro345/archives/14793.html
上記のリンクを参考に、JSONのみを返すアクションに修正しました。
まずは予め用意した配列を返せるか検証。
TableAController.php
    public function autocomplete(){
        $result = array(
            ['id' => '1', 'value' => 'item1'],
            ['id' => '2', 'value' => 'item2'],
            ['id' => '3', 'value' => 'item3'],
        );
        $this->viewClass = 'Json';
        $this->set(compact('result'));
        $this->set('_serialize', 'result');
    }
autocompleteアクションを単体で実行し、JSONのみが表示されることを確認しました。
テキストボックスに入力してみると、item1~3の候補がドロップダウンされることも確認。

続いて元々やりたかった別テーブルからのデータを取得するように修正。
TableAController.php
public function autocomplete(){
    $this->loadModel('TableB');
    $field = 'name';
    $term = $this->params['url']['term'];

    // 入力値 無:全てのデータの中から10件返す。
    // 入力値 有:入力値を含むデータを10件返す
    $condition = array();
    if(!empty($term)){
        $condition = array('TableB.'.$field.' like' => "%".$term."%");
    }

    $query = array(
        'fields' => array($field),
        'conditions' => $condition,
        'limit' => 10,
        'order' => array('TableB.'.$field => 'ASC'),
        'group' => $field,
    );

    $data = array();
    $items = $this->TableB->find('all', $query);

    foreach ($items as $item) {
        $cnt = array_push($data, $item['TableB'][$field]);
    }

    // JSONデータのみを返す
    $this->viewClass = 'Json';
    $this->set(compact('data'));
    $this->set('_serialize', 'data');
}
ビュー側はこうです。
TableA/index.ctp
<script type="text/javascript">
$(function(){
    $('#autocomplete').autocomplete({
        source: '<?php echo $this->Html->url(array('controller' => 'TableA', 'action' => 'autocomplete')); ?>',
        autoFocus: true,
        delay: 500,
        minLength: 2
    });
})
</script>

<?php echo $this->Form->input('name', array(
    'type' => 'text',
    'id' => 'autocomplete',
)); ?>

これにより、ちゃんとテーブルBのデータがオートコンプリート表示されるようになりました。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

'/TableA/autocomplete'

http://localhost/TableA/autocomplete
と解決されます。(cakephp/が足りません。)

cakephp/を書いてしまえば解決なのですが、そもそも CakePHP では URL を直に書くことはせず、Helper を使用して生成しましょう。
あとから URL が変更になっても柔軟に対応できます。
http://book.cakephp.org/2.0/ja/core-libraries/helpers/html.html#HtmlHelper::url
<script type="text/javascript">
$(function(){
    $('#autocomplete').autocomplete({
        source: <?=json_encode($this->Html->url(["controller" => "TableA", "action" => "autocomplete"]))?>,
        // ・・・
    });
})
</script>

先の方のご回答にある source プロパティの形式は確かに気になるところですが、貼っていただいた解説サイトのサンプル一つで URL が指定されているようなので大丈夫ということでしょうか。プラグイン自体をこちらでは試していませんので、そこはちょっと気をつけて確認してみてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/29 15:36

    CakePHPを離れて久しいので記憶が曖昧なのですが、

    > http://localhost/cakephp/TableA/autocomplete?term=hoge
    > このURLにアクセスすると、テーブルBからnameフィールドに"hoge"が含まれるレコードがJSON形式で返ってくることが確認できています。

    これってちゃんと JSON "のみ" が返ってきていますか?
    つまり、表示された JSON から、ブラウザのソースコードを見る機能で確認して、純粋な JSON ファイルと同じテキストデータになっていますか?

    アクションメソッド内でビューを無効化している様子がないのですが、これってどこか他の場所で無効化できるんでしたっけ?

    アクションメソッド内で文字列を直接 echo しちゃうのは、Cake 的にはマナー違反かと思います。
    JSON ビューという特別なビューが用意されているので、それを使ったほうが良さそうです。
    http://book.cakephp.org/2.0/ja/views/json-and-xml-views.html

    キャンセル

  • 2015/10/29 15:45

    > tozjpさん
    そうなんですね。その確認も必要そうです。
    ただ、今は500エラーなのでそもそも応答が返せていないのでは?
    と思いリクエストの比較を提案しました。

    キャンセル

  • 2015/10/29 17:35

    無事、解決することができました。
    皆さんの意見はどれも参考にさせて頂きましたが、CakePHPということを重点的に考慮して下さったtozjpさんの回答をベストアンサーとさせていただきます。
    最初に回答いただいたipadcaronさん、eripongさんもありがとうございました!

    キャンセル

+1

http://www.buildinsider.net/web/jqueryuiref/0017
ここみると、source プロパティは、URL ではなくて、結果の配列を指定するようでづね。
ちょっと探してみたら、コールバックめそっどが指定できるようです。

https://jqueryui.com/autocomplete/#remote-jsonp
このページの remote jsonp data source ボタンクリックして、view source リンクを選択。
やりたいことがまんま書いてあるので適当ににコピペしてやってみてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/28 15:41

    http://api.jqueryui.com/autocomplete/#option-source
    ドキュメントみると、
    array or function or string で、 stringの場合は、json 形式を返すサーバー処理へのurl と見なすようでづ、失礼しました。

    キャンセル

  • 2015/10/28 16:39

    http://book.cakephp.org/2.0/ja/core-libraries/helpers/form.html
    formhelper がちょっと難しいのでづが、画面のソースをみて、input タグの name に指定されている文字列が、
    <?php echo $this->Form->input('name', array(
    'type' => 'text',
    'id' => 'autocomplete',
    )); ?>
    このタグが展開されたもので、
    パラメーターとして受け取るには、
    public function autocomplete(){
    $this->loadModel('TableB');
    $field = 'name';
    $term = $this->params['url']['term'];

    この$term の右辺に name の記述があってないと受け取れないのでは、と思います。
    term は、php の autocomplete メソッド内にのみ登場し、html にはどこにも term
    が見当たりません。

    キャンセル

  • 2015/10/28 16:54

    回答有り難うございます。
    http://api.jqueryui.com/autocomplete/#option-source
    こちらのドキュメントのMultiple types supported:のStringの項を見ると、入力したテキストはURLの末尾に?term=~~~という形でくっついて送信される、と書いてあるように思われます。
    なのでautocomplete側ではtermが来るという前提で処理を書けばいいと判断しました。
    引き続き検証してみます。

    キャンセル

  • 2015/10/29 00:16

    とりあえず、source:function の書き方で動くことを確認しませんか。
    source:url に拘るならば、
    http://jqueryui.com/autocomplete/#remote
    このページに、source に url を指定した記述があります。
    このサンプルでは、選択結果の取得に、autocomplete#select プロパティのコールバックで処理しています。
    ドロップ一覧の JSON 配列は、以下の形式を想定しているようですが、サーバー処理の返す結果は相違ありませんか?
    [
    { "id" : "任意の固有名称" , "value" : "任意の固有名称の値" }
    ,
    { 以下、続く。

    ようするに、サーバー処理は、JSON形式で、
    一次配列を返す。
    配列要素は、id と value をキーに持つマップである。
    となります。


    キャンセル

+1

java / jsp ですが、試しました。
https://jqueryui.com/autocomplete/#remote
このページの View Source リンクからサンプルを全コピー。
source プロパティだけちょろっと変更。
NewFile.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
[
    {"id" : "id01", "value" : "United State of America" },
    {"id" : "id02", "value" : "Hawaii America" },
    {"id" : "id03", "value" : "Alaska America" },
    {"id" : "id04", "value" : "South America" },
    {"id" : "id05", "value" : "North America" }

]

コード

index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jQuery UI Autocomplete - Remote datasource</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
  <script src="//code.jquery.com/jquery-1.10.2.js"></script>
  <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
  <link rel="stylesheet" href="/resources/demos/style.css">
  <style>
  .ui-autocomplete-loading {
    background: white url("images/ui-anim_basic_16x16.gif") right center no-repeat;
  }
  </style>
  <script>
  $(function() {
        function log( message ) {
      $( "<div>" ).text( message ).prependTo( "#log" );
      $( "#log" ).scrollTop( 0 );
    }

    $( "#birds" ).autocomplete({
      source: "NewFile.jsp",
      minLength: 2,
      select: function( event, ui ) {
        log( ui.item ?
          "Selected: " + ui.item.value + " aka " + ui.item.id :
          "Nothing selected, input was " + this.value );
      }
    });
  });
  </script>
  </head>
<body>

<div class="ui-widget">
  <label for="birds">Birds: </label>
  <input id="birds">
</div>

<div class="ui-widget" style="margin-top:2em; font-family:Arial">
  Result:
  <div id="log" style="height: 200px; width: 300px; overflow: auto;" class="ui-widget-content"></div>
</div>


</body>
</html>
コード

これで期待する結果が得られましたよ。

なので、php のメソッドautocomplete が返却する json が NewFile.jsp の書式と同じであるか
どうか確認してください。


投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/29 01:24

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    [
    <%--
    // この書き方はうまく動く
    {"id" : "id01", "value" : "United State of America" },
    {"id" : "id02", "value" : "Hawaii America" },
    {"id" : "id03", "value" : "Alaska America" },
    {"id" : "id04", "value" : "South America" },
    {"id" : "id05", "value" : "North America" }
    --%>
    <%--
    // この書き方もうまく動作する一覧は、value の値が並び順で表示される
    { "name" : "OK1n", "value" : "OK1v", "id": "id11" },
    { "name" : "OK2n", "value" : "OK2v", "id": "id12" },
    { "name" : "OK3n", "value" : "OK3v", "id": "id13" },
    { "name" : "OK4n", "value" : "OK4v", "id": "id14" }
    --%>
    <%--
    //要素がマップではない場合でもOK。
    "value1",
    "value2",
    "value3",
    "value4"
    --%>
    ]

    value がない場合は単純に配列でOKみたいですね。
    <%-- --> はJSPコメント、3つの中身をコピペして確認してみてください。
    まずは、autocomplete メソッド(PHP)の、中身を、
    echo だけのシンプルなものでうまく動く事を確認してから、DBアクセスなりを
    実装すると手戻りが少なくて済みそうですね。

    キャンセル

  • 2015/10/29 01:36

    [{"id" : "xxx" , "value" : "valueed"} , ... ]
    形式ではないJSONデータの場合は、_renderItem(ui, item) コールバックを実装することで
    解決できるみたいですね。表示名と選択された時の値を設定した <li /> を自前で作成し、ui.appendToするそうです。
    ココ
    http://api.jqueryui.com/autocomplete/#method-_renderItem

    >このURLにアクセスすると、テーブルBからnameフィールドに"hoge"が含まれるレコードがJSON形式で返ってくることが確認できています。

    このくだりを見落としていました。"name" は autocomplete 内処理では、見てないみたいです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.94%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る