บทที่ 9 การค้นหาข้อความด้วย AJAX

ารค้นหา เป็นอีกรูปแบบหนึ่งที่ AJAX ทำได้ดีเนื่องจาก AJAX สามารถให้ผลลัพท์การค้นหาได้ในทันทีที่กรอกคำค้นหา โดยที่ไม่ต้องเสียเวลารอคอยหรือกด submit ก่อนถึงจะได้ผลลัพท์ หรือจะเรียกการค้นหาแบบนี้ได้อีกอย่างหนึ่งว่า Type-ahead Suggest 

ตัวอย่างหนึ่งของการค้นหาก็คือ การค้นหาของ Google ที่เว็บไซต์ http://www.google.com/...complete=1&hl=en

สำหรับโค้ดที่แนบมาด้วย เป็นโค้ดที่ผมใช้จริงบนเว็บไซต์แห่งนี้ ซึ่งเป็นการค้นหาบนฐานข้อมูลแบบ Text สำหรับท่านที่ใช้งานฐานข้อมูลแบบอื่นคงต้องดัดแปลงในส่วนนี้ด้วย ในการนำโค้ดไปใช้งานให้ include("searchmodule.php"); เข้าไปในตำแหน่งที่ต้องการแสดงผลก็ใช้งานได้เลยครับ

หลักการในการค้นหา ก็คือ เมื่อ user พิมพ์ข้อความลงในช่องค้นหา AJAX ก็จะทำงานส่งข้อมูลไปที่ Server เพื่อทำการค้นหาข้อความที่เกี่ยวข้องกับข้อความในช่องกรอกข้อความ(ที่ส่งไป) เมื่อ Server ได้ผลลัพท์การค้นหา ก็จะส่งข้อมูลกลับมาที่ Browser เพื่อแสดงผลต่อไป

สาเหตุหนึ่งที่ช่วยให้ AJAX ทำการค้นหาได้อย่างรวดเร็วก็คือ เมื่อ AJAX ทำการค้นหาเสร็จแล้ว AJAX จะเก็บข้อมูลการค้นหาไว้บน cache ซึ่งหากการค้นหาในครั้งต่อไปเป็นข้อมูลทีเคยค้นหาไว้แล้ว AJAX จะไปทำการเรียกข้อมูลการค้นหา จากที่เคยค้นหาไว้แล้วแทน โดยที่ไม่ต้องส่งข้อมูลการค้นหาไปยัง Server ใหม่ เป็นการลดภาระให้กับ Server อีกทาง ซึ่งอันนี้เป็นข้อได้เปรียบที่ AJAX มีเมื่อเทียบกับวิธีอื่นๆ

สำหรับการค้นหาด้วย AJAX นี่ก็มีข้อเสียอยู่เหมือนกัน ข้อเสียที่ว่าก็คือจากการที่ Browser มีการส่งข้อมูลไปยัง Server เพื่อค้นหาตลอดเวลาที่มีการพิมพ์ ดังนั้น หากฐานข้อมูลมีขนาดใหญ่มาก หรือข้อมูลที่ค้นหาได้มีจำนวนมาก หรือ มีผู้ใช้งานพร้อมๆกันหลายๆคน ก็อาจส่งผลให้ Server ทำงานหนักได้(ซึ่ง อาจเกิดขึ้นได้กับวิธีอื่นๆที่ต้องการผลลัพท์ แบบเดียวกันเช่นกัน)

<?
  //ลิสต์รายการกลุ่มออกมาแสดง
  $listofgroup=$db->listRec("webboardgroup");
  echo "<table border=0><tr><td><font color=red>ค้นหา</font>โค้ดบนเว็บไซต์นี้ : <select id=cbsearchmodule onchange='groupchange(this)'> ";
  echo "<option id='-1'>เลือกกลุ่ม</option> ";
  for ($i=0; $i<count($listofgroup); $i++) {
    $name=$listofgroup[$i][group_name];
    $id=$listofgroup[$i][group_id];
    $topic=$listofgroup[$i][group_topic];
    echo "<option value=$name id=$id>$topic</option> ";
  }
  echo "</select></td> ";
  
  echo "<td id=td_ajax_search></td><td><font color=green size=1>(<font color=red>Ajax</font> search beta)</font></td></tr></table> ";
  echo "<div id=search_hint><font color=navy>คำแนะนำ :</font> กรุณาเลือกว่าจะค้นหาจาก How to หรือ เว็บบอร์ด</div> ";
  echo "<div id=search_result onmouseover='delayhidesearch()' onmouseout='hidesearch()'></div> ";
?>

ในส่วนแรก จะเป็นการสร้างตัวเลือกต่างๆ โดยในครั้งแรกจะต้องมีการเลือก กลุ่มที่จะทำการค้นหาก่อน หลังจากเลือกกลุ่มแล้ว ก็จะเป็นการเลือกว่าจะค้นหาจากกลุ่มไหน แล้วถึงจะเป็นการค้นหาจาก กลุ่มและหมวดที่เลือกไว้อีกที

เมื่อมีการเลือกรายการกลุ่ม(cbsearchmodule) จะเกิดเหตุการณ์ onchange='groupchange(this)' ขึ้น ซึ่งฟังก์ชั่นนี้จะไป เรียกใช้ AJAX เพื่อทำการลิสต์รายชื่อหมวดที่สัมพันธ์กันกับกลุ่มที่เลือกมาอีกที (อันนี้เป็นตัวอย่างหนึ่งของการใช้ combobox 2 ตัวที่สัมพันธ์กัน) หลังจากนั้นเราก็จะไปเลือกหมวดอีกที เมื่อเลือกหมวดได้แล้วก็จะมีฟอร์มค้นหาให้ ซึ่งเมื่อเราพิมพ์ข้อความใดๆ ลงในช่องรับข้อความ ก็จะเกิดเหตุการณ์ onkeyup ขึ้นเราก็เอาข้อความที่ได้จากเหตุการณ์ไปทำการค้นหาต่อไป

สำหรับการแสดงผลการค้นหา ผมใช้วิธีการสร้าง Layer ใหม่เพื่อแสดงผลเหนือ Layer อื่นๆมาแสดงอีกทีซึ่งก็คือ div id=search_result นั่นเอง โดยเป็นการแสดงผลแบบ Popup สำหรับโค้ดควบคุมการแสดงผลในส่วนนี้ อยู่ในส่วนของ โค้ด window popup ครับ

<style>
#search_result {
  position: absolute;
  overflow: auto;
  border: 1px solid orange;
  background-color: white;
  padding: 2px;
  display: none;
}
</style>

โค้ดส่วนนี้ เป็น CSS กำหนดรูปแบบของพื้นที่แสดงผลการค้นหาครับ

<script language=Javascript>
function Inint_AJAX() {
   try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {} //IE
   try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {} //IE
   try { return new XMLHttpRequest(); } catch(e) {} //Native Javascript
   alert("XMLHttpRequest not supported");
   return null;
};

var req_search = Inint_AJAX();

function groupchange(obj) {
  req_search.abort();
  document.getElementById("search_hint").innerHTML="<font color=navy>คำแนะนำ :</font> กรุณาเลือกว่าจะค้นหาจากหมวดใด ?";
  var id=obj.options[obj.selectedIndex].id;
  if (id=='-1') {
    document.getElementById("td_ajax_search").innerHTML='';
  } else {
    
    req_search.onreadystatechange = function () {
      if (req_search.readyState==4) {
        if (req_search.status==200) {
          var ret=req_search.responseText; //รับค่ากลับมา
          document.getElementById("td_ajax_search").innerHTML="<table><tr>"+ret+"</tr></table>";
          document.getElementById("cbsearchcategory").focus();
        }
      }
    };
    req_search.open("GET", "ajaxsearch.php?id="+id); //สร้าง connection
    req_search.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // set Header
    req_search.send(null); //ส่งค่า
  }
}

function categorychanged(obj) {
  if (obj.selectedIndex!=0) {
    document.getElementById("search_ajax_form").style.display='block';
    document.getElementById("search_hint").innerHTML="<font color=navy>คำแนะนำ :</font> ใส่ คำทีต้องการค้นหาในช่อง <b>คำค้นหา</b> ถ้าไม่พบ ลองเปลี่ยนไปค้นหาในกลุ่มอื่น";
    txt_search.focus();
  } else {
    document.getElementById("search_ajax_form").style.display='none';
    document.getElementById("search_hint").innerHTML="<font color=navy>คำแนะนำ :</font> กรุณาเลือกว่าจะค้นหาจากหมวดใด ?";
  }
}

function searchsubmit(value) {
  var module=document.getElementById("cbsearchmodule").value;
  var category=document.getElementById("cbsearchcategory").value;
  loadpage(module, category, value, 1, 'list');
}

function searchkeyup(value) {
  req_search.abort(); //หยุดการทำงานก่อนหน้า ป้องกันการกดคีย์อย่างรวดเร็ว
  if (value.length<2) { //ถ้ามีข้อความน้อยกว่า 2 ตัวอักษร ไม่ต้องค้นหา เพราะจะให้ผลลัพท์ มากเกินไป
    hidesearch();
    return ;
  }
  var module=document.getElementById("cbsearchmodule").value;
  var category=document.getElementById("cbsearchcategory").value;
  req_search.onreadystatechange = function () {
    if (req_search.readyState==4) {
      if (req_search.status==200) {
        var ret=req_search.responseText; //รับค่ากลับมา
        //คืนค่าที่ค้นหาได้ไปแสดงผล
        document.getElementById("search_result").innerHTML="<table>"+ret+"</table>";
        //แสดง ผลการค้นหา
        overlay("txt_search", "search_result");
      }
    }
  };
  req_search.open("GET", "ajaxsearch.php?module="+module+"&category="+category+"&search="+value); //สร้าง connection
  req_search.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // set Header
  req_search.send(null); //ส่งค่า
}

//โค้ด window popup
function getposOffset(overlay, offsettype){
  var totaloffset=(offsettype=="left")? overlay.offsetLeft : overlay.offsetTop;
  var parentEl=overlay.offsetParent;
  while (parentEl!=null) {
    totaloffset=(offsettype=="left")? totaloffset+parentEl.offsetLeft : totaloffset+parentEl.offsetTop;
    parentEl=parentEl.offsetParent;
  }
  return totaloffset;
}

function overlay(curobjstr, subobjstr) {
  if (document.getElementById) {
    var subobj=document.getElementById(subobjstr);
    var curobj=document.getElementById(curobjstr);
    subobj.style.display="block";
    var xpos=getposOffset(curobj, "left");
    xpos=xpos-subobj.offsetWidth+curobj.offsetWidth;
    if (xpos<0) xpos=0;
    subobj.style.left=xpos+"px";
    var ypos=getposOffset(curobj, "top");
    subobj.style.top=ypos+curobj.offsetHeight+2+"px";
    
  };
}

var timedelay=0;

function hidesearch() {
  timedelay=setTimeout('document.getElementById("search_result").style.display="none"', 250);
}

function delayhidesearch() {
  clearTimeout(timedelay);
}
</script>

Javascript เหล่านี้คือโค้ดส่วนควบคุมต่างๆ รวมถึง AJAX ด้วยครับ โดยมีการเรียกใช้ AJAX ใน 2 กรณีคือ กรณีแรก groupchange เป็นการเรียกใช้ AJAX เพื่อไปทำการลิสต์รายชื่อหมวดออกมาแสดง และกรณีที่ 2 searchkeyup ซึ่งจะถูกเรียกเมื่อมีการกดคีย์บอร์ด ในช่องกดข้อความ เพื่อไปทำการค้นหาจากฐานข้อมูล

ผมเลือกใช้เหตุการณ์ onkeyup ในการรับค่าการกดคีย์บอร์ดเพื่อค้นหา เพื่อให้ข้อความที่เกิดขึ้นในช่องรับข้อความเป็นข้อความล่าสุดที่จะนำมาแสดง

<?
  //ajaxsearch.php

  //กำหนด Header ให้รองรับภาษาไทย
  header("content-type: application/x-javascript; charset=TIS-620");
  
  //ค่ากำหนดของฐานข้อมูล
  //จากตัวอย่างเป็นการแสดงข้อมูลจากเว็บไซต์แห่งนี้
  include("config/config.php"); //ฟังก์ชั่น
  include("config/tdb.class.php"); //เรียกใช้ คลาส text database
  include("config/function.php"); //ฟังก์ชั่น
  
  $db=new tdb("$config[data]", "db.tdb"); //เปิดฐานข้อมูล
  $db->get("webboard", $config); //โหลด config

  
  $id=$_GET["id];
  $module=$_GET["module];
  $category=$_GET["category];
  $search=$_GET["search];
  if (isset($id)) {//ส่วนการลิสต์รายชื่อกลุ่ม
    //แสดงรายการในกลุ่ม
    echo "<td><select id=cbsearchcategory onchange='categorychanged(this)'> ";
    echo "<option value=-1>เลือกหมวด</option> ";
    
    //ลิสต์รายการภายในกลุ่มที่เลือกออกมาแสดง ในส่วนนี้ถ้าเป็น MySQL ให้เปลี่ยนโค้ดเอา
    $listofcategory=$db->basicQuery("webboardcategory", "cat_groupid", $id); //ลิสต์รายการกลุ่ม

    for ($i=0; $i<count($listofcategory); $i++) {
      $topic=$listofcategory[$i][cat_topic];
      $name=$listofcategory[$i][cat_name];
      echo "<option value='$name'>$topic</option> ";
    }
    echo "</select></td> ";
    
    echo "<td id=search_ajax_form style='display:none'> ";
    echo "คำค้นหา : <input type=text name=txt_search id=txt_search value='$search' onKeyUp='searchkeyup(this.value)' onblur='hidesearch();' onfocus='searchkeyup(this.value)' onmouseup='searchkeyup(this.value)'> ";
    echo "<input type=button name=submit value=ค้นหา onclick='searchsubmit(txt_search.value);'></td> ";
  } else { //ส่วนค้นหาข้อมูล
    //ค้นหาข้อมูล ในส่วนนี้ถ้าเป็น MySQL ให้เปลี่ยนโค้ดเอา
    $db->webboardQuery($category, $search, $listrec);
    sortby($listrec, wb_id); //เรียงลำดับตาม id

    //แสดงผลการค้นหา
    $count=count($listrec)-1;
    if ($count<0) echo "<tr><td><font color=red>ไม่พบข้อมูลที่มีคำว่า </font>\"$search\"</td></tr> ";
    else {
      for ($i=$count; $i>=0; $i--) { //แสดงจากกระทู้ล่าสุดไปหากระทู้แรก
        $id=$listrec[$i][wb_id];
        $topic=$listrec[$i][wb_topic];
        $no=sprintf("%04d", $id);
        $date=$listrec[$i][wb_date];
        echo "<tr><td><img src=images/point2.gif> <font color=#2281A4 size=1>[$no]</font> <a href='view.php?module=$module&category=$category&wb_id=$id&search=$search' target=_blank>$topic</a> <font color=#666666 size=1>($date)</font></td></tr> ";
      }
    }
  }
?>

อันนี้เป็นโค้ดในหน้าที่ถูก AJAX เรียกไปครับ โค้ดนี้มี 2 หน้าที่ครับคือ การลิสต์รายการหมวด และแสดงผลลัพท์การค้นหา โค้ดส่วนที่เป็นสีน้ำเงิน คือส่วนที่เป็นการทำงานของฐานข้อมูล ครับ ถ้าหากมีการใช้งานฐานข้อมูลแบบอื่น ให้เปลี่ยนโค้ดในส่วนนี้

rar (1,504)
  
ถ้าต้องการนำโค้ดไปใช้งาน ให้ include("searchmodule.php"); แค่นั้นครับ

แต่เนื่องจากผมใช้ฐานข้อมูล Text บนเว็บผม ในโค้ดัวอย่างจึงเป็นการค้นหาจากฐานข้อมูล Text รวมทั้งอาจมีรูปแบบการค้นหาที่ต่างกันด้วย ดังนั้นในบทความจึงเป็นเพียงหลักการ (ที่ผมใช้อยู่จริง) เพื่อทำความเข้าใจเกี่ยวกับเทคนิค ซึ่งหากต้องการใช้งานคงต้องดัดแปลงเอาตามความจำเป็นของแต่ละคนครับ

  โค้ดที่เกี่ยวข้องกับ db จะเป็นส่วนสีน้ำเงินครับ
ผู้เขียน goragod โพสต์เมื่อ 03 เม.ย. 2551 เปิดดู 22,904 ป้ายกำกับ AJAX
^