/**
---------------------------------------------------------------------------------
jquery.ajaxComboBox 2.0 - 2009-02-21

	Uses code and techniques from following libraries:
		jquery.suggest 1.1(http://www.vulgarisoip.com/)

	All the new stuff written by sutara_lumpur
	(http://d.hatena.ne.jp/sutara_lumpur/20090124/1232781879)
	Feel free to do whatever you want with this file

	■はじめに

	*	このプラグインは、Peter Vulgarisさんが作成した『jquery.suggest 1.1』を、
		sutara_lumpur(すたら・るんぷーる)が改造したものです。

	*	改変はご自由にどうぞ。
		より良いものに磨き上げられていくことを願っています。

	■特徴

	*	Ajaxによるオートコンプリート専用です。

	*	DBから取得した予測候補数、または全件数を表示します。

	*	取得したオートコンプリートの候補の数が1ページに表示する件数を
		超えている場合、ページナビを表示します。

	*	候補が選択されている場合、左右キーでページの移動ができます。
		Shiftキーを押しながらの左右キーで、最初のページ、最後のページへ
		移動します。

	*	テーブルでコンボボックスを整形しているので、テキストを拡大しても
		表示が崩れません。

---------------------------------------------------------------------------------
**/

(function($) {
	$.ajaxComboBox = function(div, options) {

		//**********************************************
		//初期化
		//**********************************************

		var show_hide        = false;  // 候補を、タイマー処理で表示するかどうかの予約
		var timer_show_hide  = false;  // タイマー。フォーカスが外れた後、候補を非表示にするかどうか
		var timer_delay      = false;  // hold timeout ID for suggestion result_area to appear
		var timer_val_change = false;  // タイマー変数(一定時間ごとに入力値の変化を監視)
		var prev_value       = '';     // last recorded length of $input.val()
		var type_suggest     = false;  // キーワードによる予測か、全件取得か？false=>全件 / true=>予測
		var page_num_all     = 1;      // 全件表示の際の、現在のページ番号
		var page_num_suggest = 1;      // 候補表示の際の、現在のページ番号
		var max_all          = 1;      // 全件表示の際の、全ページ数
		var max_suggest      = 1;      // 候補表示の際の、全ページ数
		var now_loading      = false;  // Ajaxで問い合わせ中かどうか？
		var reserve_btn      = false;  // ボタンの背景色変更の予約があるかどうか？
		var reserve_click    = false;  // マウスのキーを押し続ける操作に対応するため、mousedownを検知
		var $xhr             = false;  // XMLHttpオブジェクトを格納
		var key_paging       = false;  // キーでページ移動したか？
		var key_select       = false;  // キーで候補移動したか？？
		var last_keyword     = '';     // 最後にリクエストしたキーワード


		//**********************************************
		//部品の定義
		//**********************************************

		var $combobox_area = $(div).clone()
			.empty()
			.addClass(options.combo_class);

		var $table = $('<table cellspacing="1"><tbody><tr><th></th><td></td></tr></tbody></table>')
			.addClass(options.table_class);

		var $input = $(div).children('input').clone()
			.attr("autocomplete","off")
			.addClass(options.input_class);

		var $obj_th = $table.children('tbody').children('tr').children('th');

		var $button = $table.children('tbody').children('tr').children('td');

		$('<img />').appendTo($button);

		//ボタンのtitle属性初期化
		btnAttrDefault();

		var $result_area = $('<div></div>')
			.addClass(options.re_area_class);

		var $navi = $('<div></div>')
			.addClass(options.navi_class);

		var $results = $('<ul></ul>')
			.addClass(options.results_class);


		//**********************************************
		//表示形式を整える
		//**********************************************

		$obj_th.append($input);

		$result_area.append($navi).append($results);

        $combobox_area
            .append($table)
            .append($result_area)
            .replaceAll(div);

		//テキストボックスの幅を決定
        /*
		$input.width(
			$combobox_area.width() -
			$button.children('img').width() -
			parseInt($obj_th.css('padding-left')) -
			parseInt($obj_th.css('padding-right')) -
			parseInt($button.css('padding-left')) -
			parseInt($button.css('padding-right')) -
			parseInt($button.css('border-left-width')) -
			parseInt($button.css('border-right-width')) -
			0
		);
		*/
       //$input.css({'width':'100%'});

		//----------------------------------------------------------------------
		//イベントハンドラ

		//**********************************************
		//$buttonクリック
		//**********************************************
		$button.mouseup(function(ev) {
			if($result_area.css('display') == 'none') {
				clearInterval(timer_val_change);

				if (timer_delay) clearTimeout(timer_delay);
				timer_delay = setTimeout(suggestAll, options.delay);

				$input.focus();

			} else {
				hideResult();
			}

			ev.stopPropagation();
		});

		$button.mouseover(function() {
			reserve_btn = true;
			if (now_loading) return;
			$button
				.css({
					'background-color' : '#def',
					'cursor'           : 'pointer'
				});
		});

		$button.mouseout(function() {
			reserve_btn = false;
			if (now_loading) return;
			$button
				.css({
					'background-color' : '#cdf',
					'cursor'           : 'default'
				});
		});

		//最初は、mouseoutの状態
		$button.mouseout();


		//**********************************************
		//キー入力
		//**********************************************
		// I really hate browser detection, but I don't see any other way
		if ($.browser.mozilla || $.browser.opera) {
			$input.keypress(processKey);	// onkeypress repeats arrow keys in Mozilla/Opera
		}else{
			$input.keydown(processKey);		// onkeydown repeats arrow keys in IE/Safari
		}


		//**********************************************
		//フォーカス、ブラー
		//**********************************************
		$input.focus(function() {
			show_hide = true;
			checkValChange();
		});

		$input.blur(function(ev) {
			//入力値の監視を中止
			clearTimeout(timer_val_change);

			//候補消去を予約
			show_hide = false;

			//消去予約タイマーをセット
			checkShowHide();

		});

		$input.mousedown(function(ev) {
			reserve_click = true;

			//消去予約タイマーを中止
			clearTimeout(timer_show_hide);

			ev.stopPropagation();
		});
		$input.mouseup(function(ev) {
			$input.focus();
			reserve_click = false;
			ev.stopPropagation();
		});

		$navi.mousedown(function(ev) {
			reserve_click = true;

			//消去予約タイマーを中止
			clearTimeout(timer_show_hide);

			ev.stopPropagation();
		});
		$navi.mouseup(function(ev) {
			$input.focus();
			reserve_click = false;
			ev.stopPropagation();
		});

		$('body').mouseup(function() {
			//消去予約タイマーを中止
			clearTimeout(timer_show_hide);

			//候補を消去する
			show_hide = false;
			hideResult();
		});

		//----------------------------------------------------------------------
		//各種メソッド

		//**********************************************
		//タイマーによる入力値変化監視
		//**********************************************
		function checkValChange() {
			timer_val_change = setTimeout(isChange,500);

			function isChange() {
				now_value = $input.val();

				if(now_value !== prev_value) {
					//ページ数をリセット
					page_num_suggest = 1;

					if (timer_delay) clearTimeout(timer_delay);
					timer_delay = setTimeout(suggest, options.delay);
				}
				prev_value = now_value;

				//一定時間ごとの監視を再開
				checkValChange();
			}
		}

		//**********************************************
		//候補の消去を本当に実行するか判断
		//**********************************************
		function checkShowHide() {
			timer_show_hide = setTimeout(function() {
				if (show_hide == false && reserve_click == false){

					hideResult();
				}

			},500);
		}

		//**********************************************
		//キー入力への対応
		//**********************************************
		function processKey(e) {
			// handling up/down/escape requires results to be visible
			// handling enter/tab requires that AND a result to be selected
			if (
				(/27$|38$|40$|^9$/.test(e.keyCode) && $result_area.is(':visible')) ||
				(/^37$|39$|13$|^9$/.test(e.keyCode) && getCurrentResult()) ||
				/40$/.test(e.keyCode)
			) {
				if (e.preventDefault)  e.preventDefault();
				if (e.stopPropagation) e.stopPropagation();

				e.cancelBubble = true;
				e.returnValue  = false;

				switch(e.keyCode) {
					case 37: // left
						if (e.shiftKey) firstPage();
						else            prevPage();
						break;

					case 38: // up
						key_select = true;
						prevResult();
						break;

					case 39: // right
						if (e.shiftKey) lastPage();
						else            nextPage();
						break;

					case 40: // down
						if (!$result_area.is(':visible') && !getCurrentResult()){
							//suggestAll();
                            last_keyword = '';
                            suggest();
						} else {
							key_select = true;
							nextResult();
						}
						break;

					case 9:  // tab
						key_paging = true;
						hideResult();
						break;

					case 13: // return
						selectCurrentResult();
						break;

					case 27: //	escape
						key_paging = true;
						hideResult();
						break;
				}

			} else {
				checkValChange();
			}
		}

		//**********************************************
		//ロード画像の表示・解除
		//**********************************************
		function setLoadImg() {
			now_loading = true;
			btnAttrLoad();
		}
		function clearLoadImg() {
			//$button.children('img').attr('src' , options.button_img);
			now_loading = false;
			if(reserve_btn) $button.mouseover(); else $button.mouseout();
		}

		//**********************************************
		//Ajaxの中断
		//**********************************************
		function abortAjax() {
			if ($xhr){
				$xhr.abort();
				$xhr = false;
				clearLoadImg();
			}
		}

		//**********************************************
		//Ajax(予想候補取得)
		//**********************************************
		//@param boolean in_list Ajax通信後、候補一覧の先頭を選択状態にするか？
		//                       ページナビ、左右キーからこの関数が呼び出された場合、trueとなる。
		function suggest(in_list) {
			type_suggest = true;

			//var q_word = $.trim($input.val());
            var q_word = $input.val();
			var selectCategory = $('#'+options.category);
			var categoryCode = '';
			if (selectCategory.get(0)) {
				categoryCode = selectCategory.val();
			}

            if (last_keyword == q_word) {
                return;
            }

			if (q_word.length >= options.minchars) {
                last_keyword = q_word;

				//Ajax通信をキャンセル
				abortAjax();

				//ローディング表示
				setLoadImg();

				var start = new Date();
                $.ajax({
                    url: options.source,
                    data:{
                        'q_word'      : q_word,
                        'per_page'    : options.per_page,
                        'category'    : categoryCode
                    },
                    dataType: 'jsonp',
                    scriptCharset: 'UTF-8',
                    jsonp : "callback",
                    success: function(json_data) {

                        var reqcheck = false;
                        if (json_data.q_word == last_keyword) {
                            reqcheck = true;
                        }

                        if (options.suggest_debug) {
                            var now = new Date();
                            var dtime = now.getTime() - start.getTime();
                            var cache_str = '';
                            if (json_data.cache && json_data.cache == 1) {
                                cache_str = '[use cache]';
                            }
                            var skip_msg = '';
                            if (!reqcheck) {
                                skip_msg = ' ** PASS **';
                            }

                            $('#'+options.suggest_debug).prepend('<div><strong>'+q_word+'</strong> (all) '+dtime+'msec '+cache_str+skip_msg+'</div>');
                        }

                        if (reqcheck) {
                            //一致するデータ見つかった場合
                            if(json_data.candidate){

                                setNavi(json_data.cnt, page_num_suggest);

                                var items = hitsDecorate(json_data.candidate, q_word);
                                displayItems(items);

                            }else{
                                hideResult();
                            }
                        }

                        //ローディング表示解除
                        clearLoadImg();

                        //if(in_list) selectFirstResult();
                    }
                });
/*
				//ここでAjax通信を行っている
				$xhr = $.getJSON(
					options.source+'?callback=?',
					{
						'q_word'      : q_word,
						'per_page'    : options.per_page,
						'category'    : categoryCode
					},
					function(json_data) {

						var now = new Date();
						var dtime = now.getTime() - start.getTime();
						var cache_str = '';
						if (json_data.cache && json_data.cache == 1) {
							cache_str = '[use cache]';
						}

						$('#suggest_debug_time').prepend('<div><strong>'+q_word+'</strong> (all) '+dtime+'msec '+cache_str+'</div>');

						//一致するデータ見つかった場合
						if(json_data.candidate){

							setNavi(json_data.cnt, page_num_suggest);

							var items = hitsDecorate(json_data.candidate, q_word);
							displayItems(items);

						}else{
							hideResult();
						}

						//ローディング表示解除
						clearLoadImg();

						//if(in_list) selectFirstResult();
					}
				);
*/
			} else {
                //Ajax通信をキャンセル
                abortAjax();

				hideResult();

                last_keyword = '';
			}
		}

		//**********************************************
		//候補の文字列の中で、検索文字と一致している
		//部分を修飾する（下線を引く）
		//**********************************************
		//@param  array candidate DBから取得した候補の配列
		//@return array items     検索文字と一致した部分を<span>で囲む処理を施した後の配列
		function hitsDecorate(candidate,q_word) {
			var items = [];
			$.each(candidate, function(i,obj){

				items[i] = obj.replace(
					new RegExp(q_word, 'ig'),
					function(q_word) {
						return '<span class="' + options.match_class + '">' + q_word + '</span>';
					}
				);
			});
			return items;
		}

		//**********************************************
		//Ajax(全件取得)
		//**********************************************
		//@param boolean in_list Ajax通信後、候補一覧の先頭を選択状態にするか？
		//                       ページナビ、左右キーからこの関数が呼び出された場合、trueとなる。
		function suggestAll() {
			return;
			type_suggest = false;

			//Ajax通信をキャンセル
			abortAjax();

			//ローディング表示
			setLoadImg();

			//ここでAjax通信を行っている
			$xhr = $.getJSON(
				options.source+'?callback=?',
				{
					'q_word'      : '',
					'per_page'    : options.per_page,
					'category'    : ''
				},
				function(json_data){

					setNavi(json_data.cnt, page_num_all);

					var items = [];
					$.each(json_data.candidate,function(i,obj){
						items[i] = obj;
					});
					displayItems(items);

					//ローディング表示解除
					clearLoadImg();

					//selectFirstResult();
				}
			);
		}

		//**********************************************
		//ナビ部分を作成
		//**********************************************
		//@param integer cnt         DBから取得した候補の数
		//@param integer page_num    全件、または予測候補の一覧のページ数
		function setNavi(cnt, page_num) {

			$navi.text('全 ' + cnt + ' 件 / ' + options.per_page + ' 件ずつ');

			var navi_p   = $('<p></p>');                      //ページング部分のオブジェクト
			var max      = Math.ceil(cnt / options.per_page); //全ページ数

			//ページ数
			if (type_suggest) {
				max_suggest = max;
			}else{
				max_all = max;
			}

			//表示する一連のページ番号の左右端
			var left  = page_num - Math.ceil ((options.navi_num - 1) / 2);
			var right = page_num + Math.floor((options.navi_num - 1) / 2);

			//現ページが端近くの場合のleft,rightの調整
			while(left < 1){ left ++;right++; }
			while(right > max){ right--; }
			while((right-left < options.navi_num - 1) && left > 1){ left--; }


			//----------------------------------------------
			//ページング部分を作成

			//『<< 前へ』の表示
			if(page_num == 1) {
				$('<span></span>')
					.text('<< 1')
					.addClass('page_end')
					.appendTo(navi_p);
				$('<span></span>')
					.text('前へ')
					.addClass('page_end')
					.appendTo(navi_p);
			} else {
				$('<a></a>')
					.attr({'href':'javascript:void(0)','class':'navi_first'})
					.text('<< 1')
					.attr('title', '最初のページへ (Shift + 左キー) ※候補選択中のみ')
					.appendTo(navi_p);
				$('<a></a>')
					.attr({'href':'javascript:void(0)','class':'navi_prev'})
					.text('前へ')
					.attr('title', '前の'+options.per_page+'件 (左キー) ※候補選択中のみ')
					.appendTo(navi_p);
			}

			//各ページへのリンクの表示
			for (i = left; i <= right; i++)
			{
				//現在のページ番号は<span>で囲む(強調表示用)
				var num = (i == page_num) ? '<span class="current">'+i+'</span>' : i;

				$('<a></a>')
					.attr({'href':'javascript:void(0)','class':'navi_page'})
					.html(num)
					.appendTo(navi_p);
			}

			//『次のX件 >>』の表示
			if(page_num == max) {
				$('<span></span>')
					.text('次へ')
					.addClass('page_end')
					.appendTo(navi_p);
				$('<span></span>')
					.text(max + ' >>')
					.addClass('page_end')
					.appendTo(navi_p);
			} else {
				$('<a></a>')
					.attr({'href':'javascript:void(0)','class':'navi_next'})
					.text('次へ')
					.attr('title', '次の'+options.per_page+'件 (右キー) ※候補選択中のみ')
					.appendTo(navi_p);
				$('<a></a>')
					.attr({'href':'javascript:void(0)','class':'navi_last'})
					.text(max + ' >>')
					.attr('title', '最後のページへ (Shift + 右キー) ※候補選択中のみ')
					.appendTo(navi_p);
			}

			//ページナビの表示、イベントハンドラの設定は必要な場合のみ行う
			if (max > 1) {
				$navi.append(navi_p).show();

				//----------------------------------------------
				//ページング部分のイベントハンドラ

				//『<< 1』をクリック
				$('.navi_first').mouseup(function(ev) {
					$input.focus();
					ev.preventDefault();
					firstPage();
				});

				//『< 前へ』をクリック
				$('.navi_prev').mouseup(function(ev) {
					$input.focus();
					ev.preventDefault();
					prevPage();
				});

				//各ページへのリンクをクリック
				$('.navi_page').mouseup(function(ev) {
					$input.focus();
					ev.preventDefault();

					if(!type_suggest){
						page_num_all = parseInt($(this).text(), 10);
						suggestAll();
					}else{
						page_num_suggest = parseInt($(this).text(), 10);
						suggest(true);
					}
				});

				//『次へ >』をクリック
				$('.navi_next').mouseup(function(ev) {
					$input.focus();
					ev.preventDefault();
					nextPage();
				});

				//『max >>』をクリック
				$('.navi_last').mouseup(function(ev) {
					$input.focus();
					ev.preventDefault();
					lastPage();
				});
			}
		}

		//**********************************************
		//候補一覧の<ul>成形、表示
		//**********************************************
		//@params array items DBから検索・取得した値の配列
		//
		//itemsそれぞれの値を<li>で囲んで表示。
		//同時に、イベントハンドラを記述。
		function displayItems(items) {
			if (!items) return;

			if (!items.length) {
				hideResult();
				return;
			}

			var html = '';
			for (var i = 0; i < items.length; i++) {
				html += '<li class="suggest_item">' + items[i] + '</li>';
			}
            if (items.length > 0) {
                html += '<li class="suggest_close" style="text-align:right"><a href="#">閉じる</a></li>';
            }
			$results.html(html);

			$result_area
				.show()
				.width(
					$table.width() +
					parseInt($table.css('border-left-width')) +
					parseInt($table.css('border-right-width'))
				);

			$results
				.children('li.suggest_item')
				.mouseover(function() {

					//Firefoxでは、候補一覧の上にマウスカーソルが乗っていると
					//うまくスクロールしない。そのための対策。イベント中断。
					if (key_select) {
						key_select = false;
						return;
					}

					$results.children('li').removeClass(options.select_class);
					$(this).addClass(options.select_class);
				})
				.mouseup(function(e) {

					//Firefoxでは、候補一覧の上にマウスカーソルが乗っていると
					//うまくスクロールしない。そのための対策。イベント中断。
					if (key_select) {
						key_select = false;
						return;
					}

					e.preventDefault();
					e.stopPropagation();
					selectCurrentResult();
				});

                $results
                    .children('li.suggest_close a')
                    .click(function(){
                        key_paging = true;
                        hideResult();
                        return false;
                    });

			//ボタンのtitle属性変更(閉じる)
			btnAttrClose();
		}

		//**********************************************
		//選択候補を追いかけて画面をスクロール
		//**********************************************
		//キー操作による候補移動、ページ移動のみに適用
		//
		//@param boolean enforce 移動先をテキストボックスに強制するか？
		function scrollWindow(enforce) {
            return;
			var client_height;
			if (document.documentElement.clientHeight == undefined) {
				if (document.body.clientHeight == undefined) {
					client_height = window.innerHeight;
				} else {
					client_height = document.body.clientHeight;
				}
			} else {
				client_height = document.documentElement.clientHeight;
			}

			var scroll_top = (document.documentElement.scrollTop > 0)?
				document.documentElement.scrollTop : document.body.scrollTop;

			var $current_result = getCurrentResult();

			var top_new_select = ($current_result && !enforce)?
				$current_result.offset().top : $table.offset().top;

			var list_height =
				$results.children('li').height() +
				parseInt($results.children('li').css('padding-top')) +
				parseInt($results.children('li').css('padding-bottom'));

			var scroll_bottom = scroll_top + client_height - list_height

			var gap;

			if ($current_result.length) {
				if(top_new_select < scroll_top) {
					gap = top_new_select - scroll_top;

				} else if (top_new_select > scroll_bottom) {
					gap = top_new_select - scroll_bottom;

				} else {
					return;
				}

			} else if (top_new_select < scroll_top) {
				gap = top_new_select - scroll_top;
			}
			window.scrollBy(0, gap);
		}

		//**********************************************
		//候補エリアの操作
		//**********************************************
		//現在選択中の候補を取得
		//@return object current_result 現在選択中の候補のオブジェクト(<li>要素)
		function getCurrentResult() {

			if (!$result_area.is(':visible')) return false;

			var $current_result = $results.children('li.' + options.select_class);

			if (!$current_result.length) $current_result = false;

			return $current_result;
		}
		//現在選択中の候補に決定する
		function selectCurrentResult() {

			//選択候補を追いかけてスクロール
			scrollWindow(true);

			$current_result = getCurrentResult();

			if ($current_result) {
				$input.val($current_result.text());
				if (options.select_callback) {
					options.select_callback($input.val());
				}
				hideResult();

				//added
				prev_value = $input.val();
			}
			$input.focus();
		}
		//選択候補を次に移す
		function nextResult() {
			var $current_result = getCurrentResult();
			if ($current_result) {
                if ($current_result.next().hasClass('suggest_item')) {
    				$current_result
    					.removeClass(options.select_class)
    					.next()
    						.addClass(options.select_class);
                }
			}else{
				$results.children('li:first-child').addClass(options.select_class);
			}
			//選択候補を追いかけてスクロール
			scrollWindow();
		}
		//選択候補を前に移す
		function prevResult() {
			var $current_result = getCurrentResult();

			if ($current_result) {
				$current_result
					.removeClass(options.select_class)
					.prev()
						.addClass(options.select_class);
			}else{
				$results.children('li:last-child').addClass(options.select_class);
			}
			//選択候補を追いかけてスクロール
			scrollWindow();
		}
		//候補エリアを消去
		function hideResult() {

			if (key_paging) {
				//選択候補を追いかけてスクロール
				scrollWindow(true);
				key_paging = false;
			}

			$result_area.hide();

			//Ajax通信をキャンセル
			abortAjax();

			//ボタンのtitle属性初期化
			btnAttrDefault();
		}
		//候補一覧の1番目の項目を、選択状態にする
		function selectFirstResult() {
			$results.children('li:first-child').addClass(options.select_class);

			//選択候補を追いかけてスクロール
			scrollWindow(true);
		}

		//**********************************************
		//ページナビ操作
		//**********************************************
		//1ページ目へ
		function firstPage() {
			if(!type_suggest) {
				if (page_num_all > 1) {
					page_num_all = 1;
					suggestAll();
				}
			}else{
				if (page_num_suggest > 1) {
					page_num_suggest = 1;
					suggest(true);
				}
			}
		}
		//前のページへ
		function prevPage() {
			if(!type_suggest){
				if (page_num_all > 1) {
					page_num_all--;
					suggestAll();
				}
			}else{
				if (page_num_suggest > 1) {
					page_num_suggest--;
					suggest(true);
				}
			}
		}
		//次のページへ
		function nextPage() {
			if(!type_suggest){
				if (page_num_all < max_all) {
					page_num_all++;
					suggestAll();
				}
			} else {
				if (page_num_suggest < max_suggest) {
					page_num_suggest++;
					suggest(true);
				}
			}
		}
		//最後のページへ
		function lastPage() {
			if(!type_suggest){
				if (page_num_all < max_all) {
					page_num_all = max_all;
					suggestAll();
				}
			}else{
				if (page_num_suggest < max_suggest) {
					page_num_suggest = max_suggest;
					suggest(true);
				}
			}
		}

		//**********************************************
		//ボタンのtitle属性変更
		//**********************************************
		//初期化
		function btnAttrDefault() {
			$button.attr('title','全件取得 (下キー) ※ボックス選択中のみ');
			/*
			$button.children('img').attr({
				'src'   : options.button_img,
				'alt'   : '画像:ボタン',
				'title' : '全件取得 (下キー) ※ボックス選択中のみ'
			});
			*/
		}
		//閉じる
		function btnAttrClose() {
			$button.attr('title','閉じる (Tabキー)');
			/*
			$button.children('img').attr({
				'src'   : options.load_img,
				'alt'   : '画像:ボタン',
				'title' : '閉じる (Tabキー)'
			});
			*/
		}
		//ロード中
		function btnAttrLoad() {
			$button.attr('title','ロード中...');
			/*
			$button.children('img').attr({
				'src'   : options.load_img,
				'alt'   : '画像:ロード中...',
				'title' : 'ロード中'
			});
			*/
		}
	};

	//**********************************************
	//処理の始まり
	//**********************************************
	$.fn.ajaxComboBox = function(source, options) {
		if (!source) return;

		//ComboBoxのオプション設定
		options               = options               || {};
		options.source        = source;
		options.db_table      = options.db_table      || null;          //通常   =>接続するDBのテーブル名 , CakePHP=>接続するDBのモデルを指定
		options.img_dir       = options.img_dir       || '';            //ボタン画像へのパス
		options.combo_class   = options.combo_class   || 'ac_combobox_area'; //ComboBox全体を囲む<div>のCSSクラス
		options.table_class   = options.table_class   || 'ac_table';    //ComboBoxの中で常に表示するテキストボックスとボタンを囲む<table>のCSSクラス
		options.input_class   = options.input_class   || 'ac_input';    //テキストボックスのCSSクラス
		options.button_class  = options.button_class  || 'ac_button';   //ボタンのCSSクラス
		options.re_area_class = options.re_area_class || 'ac_result_area'; //ページナビと候補一覧を囲む<div>のCSSクラス
		options.navi_class    = options.navi_class    || 'ac_navi';     //ページナビを囲む<div>のCSSクラス
		options.results_class = options.results_class || 'ac_results';  //候補一覧を囲む<ul>のCSSクラス
		options.select_class  = options.select_class  || 'ac_over';     //選択中の<li>のCSSクラス
		options.match_class   = options.match_class   || 'ac_match';    //入力値と一致した部分を囲む<span>のCSSクラス
		options.button_img    = options.button_img    || options.img_dir + 'combobox_button.png'; //ボタンの画像
		options.load_img      = options.load_img      || options.img_dir + 'ajax-loader.gif';     //Ajaxローディング画像
		options.minchars      = options.minchars      || 1;             //候補予測処理を始めるのに必要な最低の文字数
		options.field         = options.field         || 'name';        //候補として表示するフィールド名
		options.order_field   = options.order_field   || options.field; //ORDER BY(SQL) の基準となるフィールド名
		options.order_by      = options.order_by      || 'ASC';         //ORDER BY(SQL) で、並ベ替えるのは昇順か降順か
		options.delay         = options.delay         || 100;           //候補一覧を消去するまでの待機時間(ミリ秒)
		options.per_page      = options.per_page      || 10;            //候補一覧1ページに表示する件数
		options.navi_num      = options.navi_num      || 10;            //ページナビで表示するページ番号の数(旧 navi_neighbor)
		options.category      = options.category      || 'categoryCode';   //カテゴリコードのセレクトボックス
        options.suggest_debug = options.suggest_debug || '';            //サジェストのデバッグ出力ボックス
		options.select_callback = options.select_callback  || false;    //候補一覧で決定したときのコールバック関数

		this.each(function() {
			new $.ajaxComboBox(this, options);
		});

		return this;
	};

})(jQuery);