javascript – How to use Ajax and JSON for making dropdown menu?-ThrowExceptions

Exception or error:

Here is the code I used to show a category menu in OpenCart with different levels. It works, but after each click it produces more and more XHR finished loading: POST and XHR finished loading: GETs which stops the page by clicking sometimes:

<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 Codes:

<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>

Controller codes:

<?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 codes:

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;
}

I’d highly appreciate if you kindly help me by providing all necessary JavaScript, jQuery and JSON codes because I know these subjects with little ­čÖü

How to solve:

You could store the result of the post request in a javascript array, so you could reuse it, see following please:

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>

I’ve maked the dummyPost function that should be modified in order to do the post request.

You could see in my example’s log, that the first time you click on a link it retrieves his submenu with a “post”, the next times instead, it get data from the cached array cachedObj.

I hope it helps you. Bye.

Update:
Applied to your code should be like:

<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>

I can’t test it, so it could be contains some errors.

Answer´╝Ü

The problem might be that you are not preventing the default action of the anchor tag. Try adding event.preventDefault. This way the browser will fire the POST request, not the GET one.

Besides it’d be best to bind the event to the document not to the elements if you are adding new #mnav a elements via ajax.

 $(document).ready( function() {                
    $(document).on('click', '#mnav a', function( event ) {
        event.preventDefault();
        // some code
    });
 });

Answer´╝Ü

Before writing any code, it looks like you need to strategize about how you want the client and server to communicate.

My understanding of your problem is “the menu generates ajax requests with every click, and I don’t want it to.”

But this is how you’ve built the code: the menu’s onclick handler set up by jQuery has a $.post() call in it.

The other way to do it is to send all the data necessary to populate the menu to the client up front. Then with every menu click, draw from data that’s already in memory rather than sending an ajax request.

There are a few ways to do this I can think of. Which strategy you choose depends on your comfort and/or control over the system, and your priorities for page speed.

  1. Have the server prepare this data, and write it into the server-side template so the data is immediately available, something like <script><?php echo 'var menuData = '.json_encode($my_data).'; ?></script>. This is the fastest option.
  2. Use your existing ajax URLs, but fire off a series of ajax requests when the page loads. Send all the results to some central data object, like in option 1. Once all the data returns, your menu will work. The advantage here is you don’t have to change any server side code, but lots of calls will take time.
  3. Create a new ajax URL that is able to return all the menu data in one batch. This would be faster than option 2, but you’d have to work on your server api.

Answer´╝Ü

I have faced this problem in my ajax driven project. I hope my solution may help you. This may prevent from creating more calls.

// DOM ready
$(function() {

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

      e.preventDefault();

      // your stuff here

    });
});

Answer´╝Ü

You can do this by following the steps.

create drop-down with 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">

You can create a dropdown of elements like ‘input’, ‘p’, ‘a’, and others.

Add this html to your code if you want to select a specific ID:

<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>

To put database data in select join this code:

<?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>

Now you need to create and join AJAX to the drop menu code:

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";
     }
  }

You need your php to connect to the database.

<?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
));
?>

The sources for my example are in the links in the snippet example.

Any doubt, just ask for my help.

Answer´╝Ü

I think there might be a problem in javascript code side. I am assuming that your PHP and DB related code is throwing result properly.
Better solution would be download ajax menu loader plugin from here

You can set your PHP code for result throwing in file so front will not disturb at all. You might need to follow HTML structure and you can update your CSS with new class name to apply your style. I hope it will be quicker to solve your issue.

Leave a Reply

Your email address will not be published. Required fields are marked *