如何使用Ajax和JSON制作下拉菜单?

25

这是我在 OpenCart 中用来显示包含不同层级的分类菜单的代码。它能够正常工作,但每次点击后会产生越来越多的 XHR finished loading: POSTXHR finished loading: GET,有时会导致页面停止:

<script type="text/javascript">
 _url = '';

 $(document).ready(function(){                
    $('#mnav a').on('click', function() {
        var cat = $(this).attr('id');
        _url = '&category_id=' + cat;

        $.post('index.php?route=test/category/child' + _url,
            function(data) {
               if(data.length>10){
                    $('#mnav #sub').remove();
                    $(data).insertAfter($('#mnav #' + cat));
               }
            });
    });
 });

$.ajaxPrefilter(function( options, original_Options, jqXHR ) {
    options.async = true;
});
</script>

HTML代码:

<div id="mnav" class="list-group">
  <?php foreach ($categories as $category) { ?>
  <a id="<?php echo $category['category_id']; ?>" class="list-group-item active"><?php echo $category['name']; ?></a>
  <?php } ?>
</div>

控制器代码:

<?php
class ControllerTestCategory extends Controller {
    public function index() {
        if (isset($this->request->get['path'])) {
            $parts = explode('_', (string)$this->request->get['path']);
        } else {
            $parts = array();
        }

        $data['category_id'] = 0;
        if (isset($parts[0])) {
            $data['category_id'] = $parts[0];
        } else {
            $data['category_id'] = 0;
        }

        if (isset($parts[1])) {
            $data['child_id'] = $parts[1];
        } else {
            $data['child_id'] = 0;
        }

        $this->load->model('catalog/cat');

        $data['categories'] = array();

        $categories = $this->model_catalog_cat->getCategories(0);

        foreach ($categories as $category) {
            $children_data = array();

            $filter_data = array(
                'filter_category_id'  => $category['category_id'],
                'filter_sub_category' => true
            );

            $data['categories'][] = array(
                'category_id' => $category['category_id'],
                'name'        => $category['name'],
                'children'    => $category['children'],
                'products'    => $category['products'],
                'href'        => $this->url->link('product/category', 'path=' . $category['category_id'])
            );
        }

        $this->response->setOutput($this->load->view('test/category', $data));
    }
    public function child() {
        if (isset($this->request->get['category_id'])) {
            $this->load->model('catalog/cat');

            $data['categories'] = array();

            $categories = $this->model_catalog_cat->getCategories($this->request->get['category_id']);

            $data['x'] = '<div id="sub">';

            foreach ($categories as $category) {
                $data['x'] .= '<li>' . $category['name'] . '</li>';
            }
            $data['x'] .= '</div>';
        } else {
            $data['x'] = 'NA';
        }
        $this->response->setOutput($this->load->view('test/category', $data));
    }
}

SQL代码:

public function getCategories($parent_id = 0) {
    $sql = "SELECT c.category_id, c.parent_id, cd.name,
        (SELECT COUNT(DISTINCT ch.category_id) from category ch where ch.parent_id = c.category_id and cd.language_id = '" . (int)$this->config->get('config_language_id') . "') as children";

    $sql .= " , (SELECT COUNT(DISTINCT p.product_id) 
FROM product p  
    LEFT JOIN product_description pd ON (p.product_id = pd.product_id) 
    LEFT JOIN product_to_category p2c ON (p2c.product_id = p.product_id) 
    LEFT JOIN category_path cp ON (cp.category_id = p2c.category_id) 
WHERE 
    pd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND 
    p.status = '1' AND 
    p.date_available <= NOW()) AS items";

    $sql .= " FROM category c LEFT JOIN category_description cd ON (c.category_id = cd.category_id) WHERE c.parent_id = '" . (int)$parent_id . "' AND cd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND c.status = '1' ORDER BY c.sort_order, LCASE(cd.name)";

    $query = $this->db->query($sql);
    return $query->rows;
}

如果您能提供所有必要的JavaScript、jQuery和JSON代码,我将非常感激,因为我对这些主题了解甚少 :-(


1
这不是预期的吗?onclick钩子没有关闭并包含Ajax post。还有这个消息在哪里显示?开发者工具? - user2648008
你的菜单是用hover CSS显示的还是其他方式?假设你在使用JS,那么每次点击都调用AJAX是一种不好的实践! - Bourbia Brahim
控制器代码在哪里? - Junius L
6个回答

11

您可以将post请求的结果存储在JavaScript数组中,以便重复使用,如下所示:

var cachedObj = [];

$(document).ready(function(){                
  $('#mnav a').on('click', function() {
    var cat = $(this).attr('id');
    _url = '&category_id=' + cat;
    getData(cat, _url); //<-- Get data from ajax or cache
  });
});

//This function replaces the $.post call (just for example) 
function dummyPost(id, url){
  //url should be used to make the post call
  var data = "<span class='sub'>Test " + id + "</span>";
  return data;
}

function getData(id, url){
  //Try to get data from cache
  var data;
  if(cachedObj[url]) {
    data = cachedObj[url];
    console.log("Data retrived from cache");
  }
  else {
    data = dummyPost(id, url);
    cachedObj[url] = data;
    console.log("Data retrived from post");
  }
  
  $('#mnav .sub').remove();
  //$(data).insertAfter($('#mnav #' + id));
  $('#mnav #' + id).append($(data));
}
.sub{
  color: red;
  font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mnav" class="list-group">
  <a id="1" class="list-group-item active">One</a>
  <a id="2" class="list-group-item active">Two</a>
  <a id="3" class="list-group-item active">Three</a>
</div>

我已经创建了dummyPost函数,需要修改以进行POST请求。

你可以在我的示例日志中看到,第一次单击链接时,它将使用“post”检索其子菜单,而下一次则从缓存数组cachedObj中获取数据。

希望能对你有所帮助。再见。

更新:应用于您的代码应该像这样:

<script type="text/javascript">

  var cachedObj = []; //<-- Add an array to cache submenus

  //Add a function to retrieves data from cache or REST
  function getData(url){
    //Try to get data from cache
    if(cachedObj[url]) {
      console.log("Data retrived from cache");
    }
    else {
      $.ajax({
         type: 'GET',
         url: 'index.php?route=test%2Fcategory%2Fchild' + url,
         success: function(data) {
               cachedObj[url] = data;
               console.log("Data retrived from post");
            }),
         async:false
      });
    }
    return cachedObj[url];
 }

 $(document).ready(function(){                
    $('#mnav a').on('click', function() {
        var cat = $(this).attr('id');
        var url = '&category_id=' + cat;

        var data = getData(url); //<-- Call the new function to get data
        if(data.length>10){
           $('#mnav #sub').remove();
           $(data).insertAfter($('#mnav #' + cat));
        }
    });
 });
</script>

我无法测试它,因此可能包含一些错误。


先生,如果您能帮我解决这个问题,我将不胜感激。 我不确定如何在我的项目中使用您的方法。 - Kardo
在控制台日志中,您收到了多少次“从帖子检索数据”的消息? - Alessandro
当页面刚加载时,第一次点击时我得到以下信息:[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.
XHR 完成加载:GET ... 4
从 post 中检索到的数据... 7
XHR 完成加载:GET ... 4
- Kardo
看起来你在点击事件上添加了许多监听器。在“onclick”函数的开头添加一个日志消息。 - Alessandro
你确定在文档准备好时只附加了一次 $('#mnav a').on('click'... 吗? - Alessandro
显示剩余7条评论

6
问题可能是您没有阻止锚点标记的默认操作。尝试添加event.preventDefault。这样,浏览器将触发POST请求,而不是GET请求。
此外,如果您通过ajax添加新的#mnav a元素,则最好将事件绑定到文档而不是元素。
 $(document).ready( function() {                
    $(document).on('click', '#mnav a', function( event ) {
        event.preventDefault();
        // some code
    });
 });

a标签没有href,所以不需要使用preventDefault() - Junius L

5
在编写任何代码之前,看起来您需要制定策略,关于如何让客户端和服务器进行通信。
我对您的问题的理解是:“每次单击菜单都会生成ajax请求,我不希望这样做。”
但是这是您构建代码的方式:由jQuery设置的菜单的onclick处理程序中有一个$.post()调用。
另一种方法是将填充菜单所需的所有数据预先发送到客户端。然后,在每个菜单单击时,从已经存在于内存中的数据中提取,而不是发送ajax请求。
我能想到几种方法来实现这一点。选择哪种策略取决于您对系统的熟悉程度和/或控制程度,以及页面速度的优先级
  1. 让服务器准备好这些数据,并将其写入服务器端模板,使数据可以立即使用,类似于:<script><?php echo 'var menuData = '.json_encode($my_data).'; ?></script>。这是最快的选项。
  2. 使用现有的ajax URL,在页面加载时触发一系列ajax请求。将所有结果发送到某个中央数据对象中,就像选项1中那样。一旦所有数据返回,菜单将正常工作。这里的优点是您无需更改任何服务器端代码,但是大量调用将需要时间。
  3. 创建一个新的ajax URL,能够一次性返回所有菜单数据。这比选项2更快,但您需要对服务器api进行修改。

谢谢您的回答,但是作为一个完全不懂 AJAX 和 Json 的小白,我真的不知道该怎么做!如果您能给我提供完整的实用代码,我将不胜感激。非常感谢! - Kardo
如果您能提供一个完整实用的解决方案,让我可以复制粘贴,我将非常感激。 - Kardo

5

在我的ajax驱动项目中遇到了这个问题。希望我的解决方案可以帮到你。这可以防止创建更多的调用。

// DOM ready
$(function() {

    $(document).off('click', '#mnav a').on('click', '#mnav a', function(e){

      e.preventDefault();

      // your stuff here

    });
});

3
您可以按照以下步骤完成此操作。
使用JavaScript创建下拉菜单:

function myFunction(){
var port_button = document.getElementById("port").value;
if(port_button == 0){
var newhref;
var newhrefid;
var name = ["jquery ajax", "dropdown menu", "db"];
var links = ["api.jquery.com/jquery.ajax/", "www.w3schools.com/howto/howto_js_dropdown.asp", "www.w3schools.com/php/php_ajax_database.asp"];
var div=document.getElementById("myDropdown"); 
for(var i = 0; i<3; i++){
  newhref= document.createElement("a");
  newhref.href="http://"+links[i];
  newhref.innerHTML= name[i];
  newhrefid = "idhr_"+i;
  newhref.setAttribute('id', newhrefid );
  div.appendChild(newhref);
}
document.getElementById("myDropdown").classList.toggle("show");
document.getElementById("port").value = "2";
}
else if(port_button == 1){
document.getElementById("myDropdown").classList.toggle("show");
document.getElementById("port").value = "2";
}
else{
document.getElementById("myDropdown").classList.toggle("hide");
document.getElementById("port").value = "1";
}


}


function filterFunction() {
    var input, filter, ul, li, a, i;
    input = document.getElementById("myInput");
    filter = input.value.toUpperCase();
    div = document.getElementById("myDropdown");
    a = div.getElementsByTagName("a");
    for (i = 0; i < a.length; i++) {
        if (a[i].innerHTML.toUpperCase().indexOf(filter) > -1) {
            a[i].style.display = "";
        } else {
            a[i].style.display = "none";
        }
    }
}
.dropbtn {
    background-color: #4CAF50;
    color: white;
    padding: 16px;
    font-size: 16px;
    border: none;
    cursor: pointer;
}

.dropbtn:hover, .dropbtn:focus {
    background-color: #3e8e41;
}
#myInput {
    border-box: box-sizing;
    background-image: url('searchicon.png');
    background-position: 14px 12px;
    background-repeat: no-repeat;
    font-size: 16px;
    padding: 14px 20px 12px 45px;
    border: none;
}

.dropdown {
    position: relative;
    display: inline-block;
}

.dropdown-content {
    display: none;
    position: absolute;
    background-color: #f6f6f6;
    min-width: 230px;
    overflow: auto;
    box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
    z-index: 1;
}

.dropdown-content a {
    color: black;
    padding: 12px 16px;
    text-decoration: none;
    display: block;
}

.dropdown a:hover {background-color: #ddd}

.show {display:block;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="dropdown">
<button onclick="myFunction()" class="dropbtn">Dropdown</button>
  <div id="myDropdown" class="dropdown-content">
     <input type="text" placeholder="Search.." id="myInput" onkeyup="filterFunction()">
  </div>
</div>
<input type="hidden" id = "port" value = "0">

您可以创建一个包含“input”、“p”、“a”等元素的下拉菜单。
如果您想选择特定的ID,请将此HTML添加到您的代码中:
<form>
<select id = "name_of_user" name="users" onchange="showUser(this.value)">
  <option value="">Select a person:</option>
  <option value="1">Peter Griffin</option>
  <option value="2">Lois Griffin</option>
  <option value="3">Joseph Swanson</option>
  <option value="4">Glenn Quagmire</option>
  </select>
</form>

如果您想将数据库数据放入选择联接,请使用以下代码:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT id, name FROM user";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
    // output data of each row
    $option = "";
    while($row = $result->fetch_assoc()) {
       $option .= '<option value = "'.$row["id"].'">'.ech$row["name"].'</option>';
}
    }
$conn->close();
?>
<form>
<select id = "name_of_user" name="users" onchange="showUser(this.value)">
<?php echo $option; ?>     
</select>
</form>

现在你需要创建并加入 AJAX 到下拉菜单代码中:
function myFunction(){
    var port_button = document.getElementById("port").value;
    if(port_button == 0){
    var newhref;
    var newhrefid;
    var div=document.getElementById("myDropdown"); 
    var val_1 = document.getElementById("name_of_user");//If you want to do a where select.
    $.ajax({
        url: 'test.php',
        type: 'POST',
        datatype: 'Json',
        data: {'q': val_1},
        success: function (response) {
           var newhref;
           var newhrefid;
           var div=document.getElementById("myDropdown"); 
           for(var i = 0; i<response.nunber_of_rows; i++){
             newhref= document.createElement("a");
             newhref.href= response.tabel[i];
             newhref.innerHTML= response.tabel[i];
             newhrefid = "idhr_"+i;
             newhref.setAttribute('id', newhrefid );
             div.appendChild(newhref);
           } 
        }
    });
     document.getElementById("myDropdown").classList.toggle("show");
 else if(port_button == 1){
    document.getElementById("myDropdown").classList.toggle("show");
    document.getElementById("port").value = "2";
 }
 else{
    document.getElementById("myDropdown").classList.toggle("hide");
    document.getElementById("port").value = "1";
     }
  }

您需要让您的php与数据库连接。
<?php
$q = intval($_POST['q']);
$error_state = "";
$con = mysqli_connect('localhost','peter','abc123','my_db');
if (!$con) {
    die('Could not connect: ' . mysqli_error($con));
}

mysqli_select_db($con,"ajax_demo");
$sql="SELECT links FROM user WHERE id = '".$q."'";
$result = mysqli_query($con,$sql);
$i = 0;
while($row = mysqli_fetch_array($result)) {
 $tabel[$i] = array($row['link']);
 $i++;
}
$nunber_of_rows=$i;
mysqli_close($con);
echo json_encode (array(
                    'tabel'=>$tabel,
                    'nunber_of_rows'=>$nunber_of_rows
));
?>

我提供的示例源代码链接在代码片段中。如有疑问,请随时向我寻求帮助。


2
我认为问题可能出在javascript代码方面。我假设你的PHP和数据库相关代码已经正确地返回了结果。 更好的解决方案是从这里下载ajax菜单加载器插件。 你可以将PHP代码设置为在文件中返回结果,这样前端就不会受到任何干扰。你可能需要遵循HTML结构,并更新你的CSS以应用新的类名来设置样式。我希望这能更快地解决你的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接