2

I have a country, state and city chained dropdown which loads a state depending on the country and loads the cities depending on the state. The code works perfectly fine on my local server but when pushing to the live one, the dropdown is not functioning correctly, for example in some cases it works fine for all 3 but for some cases it just does not load anything and in the console I am seeing a simple false and nothing else.

Here is the JS:

$(document).ready(function() {    
    var country_id = localStorage.getItem("select2CountryValue");
    var state_id = localStorage.getItem("select2StateValue");
    var page_load = true; //added this 

    // Triggering the deleteLocalStorage function in case the client is not 
    // created and the back button is clicked
    $('.del-ls').click(function() {
        deleteLocalStorage();
    });

    // This function is also called by PHP using script tags when the create
    // client form is successfully submitted
    function deleteLocalStorage() {
        var country_id = localStorage.getItem("select2CountryValue");
        var state_id = localStorage.getItem("select2StateValue");

        localStorage.removeItem('select2CountryValue');
        localStorage.removeItem('select2StateValue');
    }

    //$('.csc-select').select2(); 
    $('#country').select2({
        placeholder: 'Select Country'
    });

    $('#state').select2({
        placeholder: 'Select State/Region'
    });

    $('#city').select2({
        placeholder: 'Select City'
    });

    $('select[name="country"]').on('change',function() {
        var country_id= $(this).val();
        localStorage.setItem("select2CountryValue", country_id);
        if (country_id) {
            $.ajax({
                url: "/src/Pages/world/getStates.php",
                type: "GET",
                data: {'country_id':country_id},
                dataType: "json",
                success: function(data) {
                    console.log(data);
                    $('select[name="state"]').empty();
                    $('select[name="state"]').append('<option value="">Select State</option>');
                    $.each(JSON.parse(data), function(key,value) {
                        $('select[name="state"]').append('<option value="'+value.id+'">'+value.name+'</option>');
                    });
                    //check if the change is called on page load
                    if (page_load == true) {
                        $('#state').val(state_id).trigger('change'); //assign slected value after element option is added in dom
                        page_load = false; //adding this so that next time this doesn't get execute
                    }
                }
            });
        } else {
            $('select[name="state"]').empty();
       }
    });
    
    $('#country').val(country_id).trigger('change');

    $('select[name="state"]').on('change',function() {
        var country_id = $('#country').val();
        var state_id = $(this).val();
        localStorage.setItem("select2StateValue", state_id);
        if (state_id) {
            $.ajax({
                url: "/src/Pages/world/getCities.php",
                type: "GET",
                data: {'country_id': country_id, 'state_id': state_id},
                dataType: "json",
                success: function(data) {
                    console.log(data);
                    $('select[name="city"]').empty();
                    $('select[name="city"]').append('<option value="">Select City</option>');
                    $.each(JSON.parse(data),function(key,value) {
                        $('select[name="city"]').append('<option value="'+value.id+'">'+value.name+'</option>');
                    });
                }
            });
        } else {
            $('select[name="city"]').empty();
        }
    });
});

And this is the HTML and just a some simple PHP to load the countries which is working fine:

<p>
    <span>Country</span>
    <select class="csc-select" name="country" id="country">
        <option value="">Select Country</option>
        <?php foreach($countries as $country) { ?>
        <option value="<?php echo $country[$columnName['COLUMN_NAME']]; ?>"
        >
            <?php echo $country['name']; ?>
        </option>
        <?php } ?>
    </select>
</p>
<p>
    <span>State</span>
    <select class="csc-select" name="state" id="state">
        <option value="">Select State</option>
    </select>
</p>
<p>
    <span>City</span>
    <select class="csc-select" name="city" id="city">
        <option value="">Select City</option>
    </select>
</p>

I am a bit clueless now since locally it works perfectly however on the live server it doesn't work for every option you pick, I rechecked the countries, states and cities database and all the info is there so its not missing, the database is identical to the one I am using in the local version too. If anyone has any idea or suggestions, I would appreciate it a lot.

And here is the console log error that appears sometimes:

> Uncaught SyntaxError: Unexpected end of JSON input
>     at JSON.parse (<anonymous>)
>     at Object.success (add:977:29)
>     at c (jquery-3.6.0.min.js:2:28327)
>     at Object.fireWith [as resolveWith] (jquery-3.6.0.min.js:2:29072)
>     at l (jquery-3.6.0.min.js:2:79901)
>     at XMLHttpRequest.<anonymous> (jquery-3.6.0.min.js:2:82355)

and now I am adding a screenshot of the console.log, this happens when I choose the country United States, the state Caliornia, so it does not show anything for California but it should:

enter image description here

Here is the getStates.php file code:

<?php 

use App\Session;
use App\Login;
use App\Location;

require_once("../../../vendor/autoload.php");

$objSession = new Session();
if(!$objSession->isLogged()) {
    Login::redirectTo("/login");
}

$country_id = $_GET['country_id'];

if(isset($_GET['country_id'])) {
    $objLocation = new Location();
    echo json_encode($getStates = $objLocation->getStates($country_id));
}

and here is the getCities.php file code:

<?php 

use App\Session;
use App\Login;
use App\Location;

require_once("../../../vendor/autoload.php");

$objSession = new Session();
if(!$objSession->isLogged()) {
    Login::redirectTo("/login");
}

$state_id = $_GET['state_id'];
$country_id = $_GET['country_id'];

if(isset($_GET['state_id']) && isset($_GET['country_id'])) {
    $objLocation = new Location();
    echo json_encode($getCities = $objLocation->getCities($state_id, $country_id));
}

and this is the code in the Location class that fetches the data:

public function getStates(string $id): ?string
    {

        $sql = "SELECT `id`, `name` FROM {$this->table_3}
                WHERE `country_id` = '". $this->escape($id) ."'
                ORDER BY `name` ASC";
                $result = $this->fetchAll($sql);
                return json_encode($result);

    }

    public function getCities(string $state, string $country): bool|string
    {

        $sql = "SELECT `id`, `name` FROM {$this->table_4}
                WHERE `state_id` = '". $this->escape($state) ."'
                AND `country_id` = '". $this->escape($country) ."'
                ORDER BY `name` ASC";
                $result = $this->fetchAll($sql);
                return json_encode($result);

    }
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
Blu3
  • 143
  • 2
  • 19
  • If it works locally but not remotely then it's more likely to be a configuration issue, not a code issue. Check the console when running on the remote. More than likely there will be an error message you can debug – Rory McCrossan Apr 08 '22 at 11:08
  • @RoryMcCrossan yes sometimes this error appears (I have added it in my question at the end) it says unexpected end of JSON input, any ideas? – Blu3 Apr 08 '22 at 11:30
  • you have a `console.log(data);` on your JS can you show the result? – Sean Reyes Apr 11 '22 at 01:38
  • your line error is this `$.each(JSON.parse(data),function(key,value) {` on you JS file – Sean Reyes Apr 11 '22 at 01:38
  • Also a quick tip... on Developer Console, there is a Network tab... on that tab you can find your AJAX Output without using `console.log` just filter on `XHR` then run your AJAX code. – Sean Reyes Apr 11 '22 at 01:40
  • @SeanReyes yes I also use that for debugging but it doesnt give me much apart from the false out put and thats its just false – Blu3 Apr 11 '22 at 13:52
  • You have `dataType: "json"` and also doing `JSON.parse(data)` .Why ? – Swati Apr 11 '22 at 14:45
  • @Swati because when I am not specifying the data type, it does not work even on the local site. It gives this error "Uncaught TypeError: Cannot use 'in' operator to search for 'length'" if I remove the dataType: "json" but if I leave it there it works fine but only on the local site – Blu3 Apr 11 '22 at 16:45
  • why not just check if data is false... then you should do something else... temporary fix while you find out what's happening on the backend that's causing a return of "false" – Sean Reyes Apr 12 '22 at 06:31
  • and in this case, can you provide the code that handles the request? specifically `getCities.php` and `getStates.php` – Sean Reyes Apr 12 '22 at 06:33
  • @SeanReyes I have added the get states and cities code and the location class info to my question above as for the check if data is false and do something else, I am not sure how to proceed in that case but the thing is that everything works perfectly fine locally and everything is the same on the live server too but it just doesnt work for every country and state and city like it does on the local site, so even on the live server for some countries, states, cities it finds all the info correctly but for some it does not while locally it finds everything. – Blu3 Apr 12 '22 at 07:35
  • Please use prepared statements. Read up on SQL injection. – Tschallacka Apr 12 '22 at 08:04
  • @Tschallacka thanks for that however everything is escaped using the mysqli escape string function. The $this->escape() method being called is what applies it. – Blu3 Apr 12 '22 at 08:14
  • @Blu3 Prepared statements are much more secure. I heavily reccomend you using that. There are enough examples of how they can get around the escape string functions. See the top answer on this question: https://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string Make sure your connection settings are set correctly, and your character sets. – Tschallacka Apr 12 '22 at 08:27
  • You are already returning json_encode on Location Class... I think you can remove it in on your getCities.php and getStates.php – Sean Reyes Apr 13 '22 at 12:56
  • also you kept on mentioning that it's working on Local but not on Live.. are you sure both have the same data on the database? – Sean Reyes Apr 13 '22 at 13:00

3 Answers3

2

So, I have asked on the comments and you have provided... here is what I think is happening... and what I think you should do.

your method getCities on the Location::class is returning a boolean... and sometimes a string...

json_encode will only return false on failure, this means json_encode has failed on encoding your sql result...

you can use json_last_error() and json_last_error_msg() to debug your json_encode() problem

you can find the documentation here

You can debug your problem on Local but, please make sure your local machine is running the same things on the live server, to replicate the error...

  1. PHP version
  2. Database Data
  3. MySql Version (But I don't think it's necessary)
  4. Browser (but just for testing, app should work on all browser)

if you can't replicate the error, you need to do it LIVE. (please be very careful, this should be your LAST RESORT)

I could not provide a specific answer because I don't know if $this->fetchAll() only returns an array or maybe sometimes it returns an error message...

I have provided you this answer, because your main concern on this question is why your program returning a simple false value...

for example in some cases it works fine for all 3 but for some cases it just does not load anything and in the console I am seeing a simple false and nothing else.

Sean Reyes
  • 1,636
  • 11
  • 19
  • 1
    you were right to point me in that direction to check out the json_encode and once doing so I found some interesting stuff including 2 ways to actually fix the issue: 1: Adding a JSON_INVALID_UTF8_SUBSTITUTE or JSON_INVALID_UTF8_IGNORE to the json_decode with errors. 2. But the better and easier and was suppose to have the of been in place is the mysqli charset, as soon as I set the charset to "utf8mb4" I did not need to apply the first solution or actually change anything in the code itself. – Blu3 Apr 14 '22 at 16:24
0

Hi I think it could be the $.ajax AND the echo json_decode the issue....

I made my own AJAX function that I share in github https://github.com/jintor/stubajax

async function stubajax (divid,phphat,postix = [],pend = 'html') {

    var pcache = (Math.floor(Math.random() * 100000000) + 1);
    
    postix["preventcache"] = pcache; // prevent browser caching
    postix["divid"] = encodeURIComponent(divid);
    postix["mojax_height"] = encodeURIComponent($(window).height());
    postix["mojax_width"] = encodeURIComponent($(window).width());

    // if you need to send cookies
    postix["cookies"] = decodeURIComponent(document.cookie); 
    
    // if you need to send localStorage or sessionStorage stuff
    for (var i = 0; i < localStorage.length; i++){ postix[localStorage.key(i)] = localStorage.getItem(localStorage.key(i)); }
    for (var i = 0; i < sessionStorage.length; i++){ postix[sessionStorage.key(i)] = sessionStorage.getItem(sessionStorage.key(i)); }
        
    await fetch(phphat+"?pcache="+pcache, {
      method: "POST", body: JSON.stringify(Object.assign({}, postix)), headers: {"Content-type": "application/json; charset=UTF-8"}
    }).then( response => { return response.text(); }).then( html => { 
        switch ( pend ){
            case 'append' : $("#"+divid).append(html); break;
            case 'prepend' : $("#"+divid).prepend(html); break;
            default : $("#"+divid).html(html); break;
        }
    }).catch( err => console.log(err) );
      
}

AND YOU USE IT LIKE THIS

<input onchange="stubajax (\'div_id_that_will_reseive_the_ajax_message\',\'/path/to_php.php\',{\'extra1\':this.value},'html');

On the PHP side : a the end simply echo (WITHOUT json_encode)....

John R
  • 277
  • 3
  • 10
0

So I am adding the solution which @SeanReyes led me to.

Actually it led me to 2 solutions, and they are:

Solution 1. I could simply do an if condition in my Location::class to check if the json_encode is working correctly and if not simply add a JSON flag like so (JSON_INVALID_UTF8_IGNORE or JSON_INVALID_UTF8_SUBSTITUTE):

   public function getStates(string $id): ?string
    {

        $sql = "SELECT `id`, `name` FROM {$this->table_3}
                WHERE `country_id` = '". $this->escape($id) ."'
                ORDER BY `name` ASC";
                $result = $this->fetchAll($sql);
                if(json_encode($result)) {
                    return json_encode($result);
                } else {
                    return json_encode($result, JSON_INVALID_UTF8_IGNORE);
                }

    }

and did the same with the getCities method.

Solution 2. While this did fix it, it displayed the latin characters incorrectly and so that made me find another easy solution by simply adding a charset to my mysqli connection like so:

        $this->db->set_charset("utf8mb4");

and like magic everything worked perfectly, but this was a rookie mistake on my end as this should of been set anyway, thanks to everyone for pointing me in the right direction.

Blu3
  • 143
  • 2
  • 19