Vlad Ivanyk
Vlad Ivanyk

Reputation: 57

Uncaught SyntaxError: Unexpected end of JSON input. Can't properly parse the info to JSON from the html

Getting the error when trying to open the modal with product details after products were queried with help of ajax

Error itself:

Uncaught SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
at HTMLButtonElement.<anonymous> (scripts.js:54)
at HTMLDocument.dispatch (jquery-3.3.1.js:5183)
at HTMLDocument.elemData.handle (jquery-3.3.1.js:4991)

To be clear: I have some filters, result of which is filtered in the python filter_items function then it uses JSONResponse to send it to the front-end in form of a dictionary(as_dict() function in Item model) were they are added to hidden input value. JS function takes that hidden input value and renders the results of filtering using the data from that input.

Item Model which is queried with help of filter function:

class Item(models.Model):

ITEM_TYPES = (
    ('UM', 'Umbrella'),
    ('SK', 'Skirt'),
    ('TR', 'Trousers'),
    ('OT', 'Other')
)

BRANDS = (
    ('VS', 'Versace'),
    ('SP', 'Supreme'),
    ('SI', 'Stone Island'),
    ('FP', 'Fred Perry'),
)

title = models.CharField(max_length=256)
image = models.ImageField(upload_to='img/')
brand = models.CharField(max_length=256)
type = models.CharField(choices=ITEM_TYPES, max_length=2)
description = models.TextField(blank=True, null=True)
season = models.TextField(blank=True, null=True)
discount = models.FloatField(blank=True, null=True)
price = models.FloatField()

def __str__(self):
    return self.title + ' ' + self.type

def as_dict(self):
    data = {"title": self.title, "image": self.image.url, "brand": self.brand, "type": self.type,
            "discount": self.discount, "price": self.price, "rus_representation": self.rus_representation,
            "description": self.description, "season": self.season, "images": [self.image.url]}

    if self.images:
        for image in self.images.all():
            data['images'].append(image.image.url)

    # data['dumped'] = json.dumps(data)
    # print(data['dumped'])
    return data

def dumped_as_dict(self):
    return json.dumps(self.as_dict())

@property
def rus_representation(self):

    if self.type == 'UM':
        return 'Зонтик'
    elif self.type == 'SK':
        return 'Юбка'
    elif self.type == 'TR':
        return 'Штаны'
    elif self.type == 'OT':
        return 'Другое'

Class based view with filter function inside of it:

class ProductsListView(ListView):
model = Item
types = Item.ITEM_TYPES
brands = Item.BRANDS

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['types'] = self.types
    context['brands'] = self.brands
    return context

@classmethod
def filter_items(cls, request, *args, **kwargs):
    if request.is_ajax():
        data = request.GET

        queryset = Item.objects.all()

        if not data['type'] == 'ALL':
            queryset = queryset.filter(type=data['type'])

        if not data['brand'] == 'ALL':
            queryset = queryset.filter(brand__contains=data['brand'])
        return JsonResponse({'result': [item.as_dict() for item in queryset]})

JavaScript filter function:

    $('.choice-link').click(function () {
    var choice = $(this).text();

    $(this).siblings().attr('id', '');

    $(this).attr('id', 'active');

    $(this).parent().parent().children('button').text(choice);

    var data = {
        'brand': $('.brand-dropdown').children('#active').attr('data-choice'),
        'type': $('.type-dropdown').children('#active').attr('data-choice')
    };

    $.ajax({
        url: '../ajax/filter/',
        data: data,
        success: function (data) {
            $('.item-block').remove();
            $.each(data.result, function (index, item) {
                if (!item.discount) {
                    var el = '<div class="item-block flex" style="flex-direction: column"><input type="hidden" value='+ JSON.stringify(item) +'><img class="img-fluid" style="box-shadow: 0 0 10px rgba(0,0,0,0.5);" src="' + item.image + '"><h6 class="item-title">' + item.rus_representation + ' "' + item.brand + '"<hr></h6><p class="flex" style="align-items: flex-start"><span class="price-tag">' + item.price + ' $</span></p><button type="button" class="item-btn btn-sm btn btn-outline-info" data-toggle="modal" data-target=".details-modal">Подробней <img style="height: 10px" "></button></div>';
                } else {
                    var el = '<div class="item-block flex" style="flex-direction: column"><input type="hidden" value='+ JSON.stringify(item) +'><img class="img-fluid" style="box-shadow: 0 0 10px rgba(0,0,0,0.5);" src="' + item.image + '"><h6 class="item-title">' + item.rus_representation + ' "' + item.brand + '"<hr></h6><p class="flex" style="align-items: flex-start"><span class="price-tag">' + item.price + ' $</span><span class="discount badge badge-danger">' + item.discount + ' $</span></p><button type="button" class="item-btn btn-sm btn btn-outline-info" data-toggle="modal" data-target=".details-modal">Подробней <img style="height: 10px" "></button></div>';
                }
                $('.items-list').children('hr').after(el)
            });

        }
    })
});

Java Script function which fills the modal with related data:

$(document).on('click', '.item-btn', function () {
var data = JSON.parse($(this).siblings('input').val()); (line 54 where error message points)

$('.product-title').html(data.rus_representation + ' "<i>' + data.brand + '</i>"' + '<hr>');

if(data.description) {
    $('.product-description').text(data.description);
}else{
    $('.product-description').html('<h4>Описнаие пока не добавлено</h4>')
}

$('.carousel-inner').empty();
$.each(data.images, function (index, img) {
    if(index === 0){
        var el = '<div class="carousel-item active"><img class="d-block w-100" src="'+ img +'"></div>'
    } else {
        var el = '<div class="carousel-item"><img class="d-block w-100" src="'+ img +'"></div>'
    }

    $('.carousel-inner').append(el)
});

$('.product-brand').html('<i>' + data.brand + '</i>');
$('.product-type').text(data.rus_representation);
$('.product-season').text(data.season);

if (data.discount){
    $('.discount-in-modal').html('<span class="discount badge badge-danger" style="position: relative; top: -5px">'+ data.discount +' $</span>');
}

$('.product-price').text(data.price);

});

HTML:

            {% for item in object_list %}
            <div class="item-block flex" style="flex-direction: column">
                <input type="hidden" value="{{ item.dumped_as_dict }}">
                <img class="img-fluid" style="box-shadow: 0 0 10px rgba(0,0,0,0.5); max-height: 300px" src="{{ item.image.url }}">
                <h6 class="item-title">
                    {{item.rus_representation}} "{{item.brand}}"
                    <hr>
                </h6>
                <p class="flex" style="align-items: flex-start">
                    <span class="price-tag">{{ item.price }} $</span>
                    {% if item.discount %}
                        <span class="discount badge badge-danger">{{ item.discount }} $</span>
                    {% endif %}
                </p>
                <button type="button" class="item-btn btn-sm btn btn-outline-info" data-toggle="modal" data-target=".details-modal">Подробней <img style="height: 10px" src="{% static 'img/arrow.png' %}"></button>
            </div>
        {% endfor %}

IF do console.log(JSON.stringify(item));:

{"title":"tst","image":"/media/img/_58A1259_sm.jpg","brand":"GUCCI","type":"SK","discount":9000000,"price":9,"rus_representation":"Юбка","description":"LoL Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","season":"","images":["/media/img/_58A1259_sm.jpg","/media/img/_58A7975_sm.jpg"]}

How it should look like:

opening the modal on initial load

What I have:

if use filtering and them trying to open details modal

Adding view from Inspector: For some reasons string is not fully added to value attr

Upvotes: 1

Views: 2489

Answers (1)

Pointy
Pointy

Reputation: 413757

When you build that HTML string, your code does something like this:

var el = '<div ... value=' + JSON.stringify(x) + ' ... >';

The HTML result will therefore be

var el = '<div ... value={ ... } ... >';

Because the attribute value for "value" is not quoted in the resulting HTML source, the first space character in the JSON is the end of the attribute value, as far as the HTML parser is concerned.

You need to include quotes at least:

var el = '<div ... value=\'' + JSON.stringify(x) + '\' ... >';

I would also strongly suggest that you use an HTML entity encoder to encode any HTML metacharacters in the string, something like

function scrubHtml(s) {
  return s.replace(/[<>'"&]/g, function(meta) {
    return "&#" + meta.charCodeAt(0) + ";";
  });
}

Upvotes: 0

Related Questions