Lazy load в элементе Tree фреймворка ExtJs 4.1.1

99
23 апреля 2021, 18:10

Я изучаю ExtJs в связке с Django. Недавно реализовал построение элемента tree из файла json, содержащего данные из базы данных sqlite3.

Код данной реализации поместил на github: https://github.com/ArturV19/for_stack_overflow_extjs_lazy_load

Теперь у меня задача реализовать "ленивую" загрузку, то есть что бы сначала с сервера загружался только список жанров, а уже при нажатии на определённый жанр подгружались авторы, пишущие в данном жанре.

Код модели книги в models.py:

class BookModel(models.Model):
title = models.CharField(max_length=250)
author = models.CharField(max_length=200)
genre = models.CharField(max_length=190)
def __str__(self):
    return self.author + ': ' + self.title + ' (' + self.genre + ')'

Код html-страницы templates/Index_LazyDownload.html:

{% load staticfiles %} 
 
<html> 
<head> 
    <meta charset="utf-8"> 
 
    <title>Combobox</title> 
 
    <link rel="stylesheet" type="text/css" href="{% static 'extjs-4.1.1/resources/css/ext-all-debug.css' %}"> 
    <script type="text/javascript" src="{% static 'extjs-4.1.1/ext-all-debug.js' %}"></script> 
    <script type="text/javascript" src="{% static 'lazyload_tree.js' %}"></script> 
 
</head> 
<body> 
</body> 
</html>

Код приложения ExtJs:

Ext.onReady(function () { 
 
    var store = Ext.create('Ext.data.TreeStore', { 
        proxy:{ 
            type: 'ajax', 
            url: '../lazy_download_get_genres/' 
        } 
    }); 
 
    var tree1 = Ext.create('Ext.tree.Panel', { 
        title: 'Жанры', 
        width: 400, 
        height: 400, 
        store: store, 
        rootVisible: false, 
    }); 
 
    var formPanel = Ext.create('Ext.Panel', { 
        title: 'Форма ввода', 
        width: 250, 
        autoHeight: true, 
        bodyPadding: 10, 
        defaults: { 
            labelWidth: 100 
        }, 
        items: [{ 
            xtype: 'textfield', 
            id: 'searchField1', 
            fieldLabel: 'Поиск по названию:', 
            name: 'searchField', 
        }, 
            { 
                xtype: 'button', 
                text : 'Начать поиск', 
                margin:'15 0 0 25', 
                listeners: { 
                    click: function() { 
                        panel.setLoading('Loading...') 
                        store.load( 
                            { 
                                type: 'ajax', 
                                url: '../get_json_search_books/'+Ext.getCmp('searchField1').getValue() 
                                , 
                                callback: function(records, operation, success) { 
                                    // the operation object 
                                    // contains all of the details of the load operation 
                                    panel.setLoading(false) 
                                }}); 
                    } 
                } 
            } 
            ], 
        renderTo: Ext.getBody() 
    }); 
 
    var panel = Ext.create('Ext.panel.Panel', { 
        title: 'Книжная полка с попыткой "ленивой" загрузки:', 
        width: 1072, 
        padding:10, 
        height: 400, 
        autoload: true, 
        layout: { 
                type: 'hbox', 
                align: 'stretch' 
            }, 
        items: [tree1, formPanel], 
        renderTo: Ext.getBody() 
    }); 
});

Далее идут ещё фрагменты кода python

TreeApp/urls.py:

from django.urls import path
from TreeApp import views
urlpatterns = [
path('', views.tree_index),
path('get_json_for_tree/', views.get_json_for_tree),
path('get_json_search_books/<str:text>', views.get_json_search_books),
path('lazy_load/', views.lazy_download_page),
path('lazy_download_get_genres/', views.lazy_download_get_genres),
]

TreeApp/view.py:

from django.http import HttpResponse
from django.shortcuts import render
from TreeApp.methods_for_lazy_load import json_of_genres_lazy_load
from TreeApp.work_with_database import get_string_json_genres_and_authors, 
get_string_json_genres_and_authors_search

# Возвращает html-страницу
def tree_index(request):
    return render(request, "Index.html")

# Возвращает JSON-объект для генерации дерева
def get_json_for_tree(request):
    return HttpResponse(get_string_json_genres_and_authors())

# Возвращает JSON-объект для генерации дерева
# из книг, соответствующих условию
def get_json_search_books(request, text):
    return HttpResponse(get_string_json_genres_and_authors_search(text))
############################################################################
# Далее идут методы для 'ленивой' загрузки
############################################################################

def lazy_download_page(request):
    return render(request, "Index_LazyDownload.html")

def lazy_download_get_genres(request):
    return HttpResponse(json_of_genres_lazy_load())

work_with_database.py:

from TreeApp.models import BookModel

# Получение авторов, пишущих в данном жанре
def get_authors_in_this_genre(genre):
    book_list_in_genre = BookModel.objects.filter(genre=genre)
    output = set()
    for book in book_list_in_genre:
        dict_author = {book.author}
        output.update(dict_author)
    return output

# Набор жанров
def get_set_of_unique_genres():
    book_list = BookModel.objects.all()
    output = set()
    for book in book_list:
        dict_genres = {book.genre}
        output.update(dict_genres)
    return output

def get_string_books_of_author_in_this_genre(genre, author):
    target_books = BookModel.objects.filter(author=author, genre=genre)
    string_books = '['
    for book in target_books:
        string_books += '{"text" : "' + book.title + '" , ' + '"leaf" : "true"},'
    # Удаления запятой в конце
    string_books = string_books[:-1] + '],'
    return string_books

def get_string_books_of_author_in_this_genre_search(genre, author, books):
    target_books = books.filter(author=author, genre=genre)
    string_books = '['
    for book in target_books:
        string_books += '{"text" : "' + book.title + '" , ' + '"leaf" : "true"},'
    # Удаления запятой в конце
    string_books = string_books[:-1] + '],'
    return string_books

# Получение json-строки для двухуровнего дерева,
# где сначала идёт список жанров
def get_string_json_genres_and_authors():
    genres_list = get_set_of_unique_genres()
    # Итоговая строка
    string_request = '['
    for genre in genres_list:
        # Строка, относящаяся к отдельному жанру
        string_genre = '{"text" : "' + genre + '", "children" : '
        list_authors_in_genre = get_authors_in_this_genre(genre)
        # Строка, относящаяся к отдельному автору в жанре
        string_authors = '['
        for author in list_authors_in_genre:
            string_authors += '{"text" : "'+author+'" , "children" : '
            string_authors += get_string_books_of_author_in_this_genre(genre, author)+' "leaf" : "false"},'
        string_authors = string_authors[:-1]
        string_genre += string_authors
        string_genre += '], "leaf" : "false"},'
        string_request += string_genre
    string_request = string_request[:-1]+']'
    return string_request

def get_string_json_genres_and_authors_search(search_string):
    books = BookModel.objects.filter(title__contains=search_string)
    # Список жанров
    genres_output = set()
    for book in books:
        dict_genres = {book.genre}
        genres_output.update(dict_genres)
    # Итоговая строка JSON
    string_request = '['
    for genre in genres_output:
        # Строка, относящаяся к отдельному жанру
        string_genre = '{"text" : "' + genre + '", "children" : '
        book_list_in_genre = books.filter(genre=genre)
        # Список авторов
        output_authors = set()
        for book in book_list_in_genre:
            dict_author = {book.author}
            output_authors.update(dict_author)
        # Строка, относящаяся к отдельному автору в жанре
        string_authors = '['
        for author in output_authors:
            string_authors += '{"text" : "'+author+'" , "children" : '
            string_authors += get_string_books_of_author_in_this_genre_search(genre, author, books)+' "leaf" : "false"},'
        string_authors = string_authors[:-1]
        string_genre += string_authors
        string_genre += '], "leaf" : "false"},'
        string_request += string_genre
    string_request = string_request[:-1]+']'
    return string_request

methods_for_lazy_load.py:

from TreeApp.work_with_database import get_set_of_unique_genres

def json_of_genres_lazy_load():
    genres_list = get_set_of_unique_genres()
    # Итоговая строка
    string_request = '['
    for genre in genres_list:
        # Строка, относящаяся к отдельному жанру
        string_genre = '{"text" : "' + genre + '"'
        string_genre += ', "leaf" : "false"},'
        string_request += string_genre
    string_request = string_request[:-1]+']'
    return string_request

Вывод в данный момент выглядит так:

Очень надеюсь на вашу помощь, из официальной документации нужной информации получить не смог.

Важно, нужна реализация именно в версии ExtJs 4.1.1

Answer 1

Ответ на данный вопрос был найден. Код решения выложен на GitHub по адресу: https://github.com/ArturV19/for_stack_overflow_extjs_lazy_load. Теперь, постараюсь подробно объяснить, как я организовал "ленивую" загрузку.

Во-первых, код самого приложения ExtJs (https://github.com/ArturV19/for_stack_overflow_extjs_lazy_load/blob/master/static/lazyload_tree.js) выглядит так:

Ext.onReady(function () {
    let store = Ext.create('Ext.data.TreeStore', {
        proxy:{
            type: 'ajax',
            url: '../lazy_download_get_data/'
        }
    });
    let tree1 = Ext.create('Ext.tree.Panel', {
        title: 'Жанры',
        width: 400,
        height: 400,
        store: store,
        rootVisible: false,
        listeners: {
            //Перед открытием нажатого узла дерева:
            beforeitemexpand: function (node) {
                //console.log('expand, ' + node.getDepth() + ' ');
                //console.log('expand, ' + node.getDepth() + ' ' + node.parentNode.data.text);
                //Устанавливаем значения дополнительных параметров:
                store.getProxy().setExtraParam("node" , node.data.text);
                store.getProxy().setExtraParam("level", node.getDepth());
                store.getProxy().setExtraParam("parent_text", node.parentNode.data.text);
            }
        }
    });
    Ext.create('Ext.panel.Panel', {
        title: 'Книжная полка с "ленивой" загрузкой',
        width: 1072,
        padding:10,
        height: 400,
        autoload: false,
        layout: {
                type: 'hbox',
                align: 'stretch'
            },
        items: [tree1],
        renderTo: Ext.getBody()
    });
});

Вот так выглядит страница при загрузке:

Тут нужно остановиться на элементе tree.Panel подробнее. Дело в том, что при нажатии на любой элемент дерева будет происходить загрузка из хранилища store. Store, в свою очередь, выполняет запрос на сервер (в данном случае - ajax-запрос по адресу '../lazy_download_get_data/') и передаёт полученные с сервера данные "дереву". Поэтому, если не менять никаких параметров, то при нажатии на какой-либо жанр снова будет открываться список жанров, как было в моём вопросе до этого:

Теперь же, с каждым нажатием на элемент дерева, на сервер передаются такие данные как:

  1. Текст записи - node.data.text (например, при нажатии на жанр "Юмор", передаётся "Юмор")
  2. Глубина узла в дереве (при нажатии на какой-либо жанр передаётся 1, а при нажатии на писателя 2)
  3. Текст записи родительского элемента (например, при нажатии узла "Стивен Кинг" в жанре "Ужасы", передастся именно "Ужасы"; название интересующего жанра нужно знать, чтобы не загружать книги из других жанров):

Кстати, следует отметить, что для формирования дерева tree JSON объект, получаемый с сервера, должен быть валидным, а передаваемые внутри JSON объекты должны содержать необходимые поля, например "text", "leaf" и другие. Более подробно советую почитать тут: https://metanit.com/web/extjs/7.6.php. Валидность кода я проверял с помощью сервиса https://jsonformatter.curiousconcept.com/.

Во-вторых, рассмотрим более подробно python-код, отвечающий за формирование текста, который станет JSON-объектом (https://github.com/ArturV19/for_stack_overflow_extjs_lazy_load/blob/master/TreeApp/methods_for_lazy_load.py):

from TreeApp.work_with_database import get_set_of_unique_genres, get_authors_in_this_genre, \
    get_string_books_of_author_in_this_genre

# Получение JSON-файла с данными для формирования дерева
#
# node - текст записи, которая должна "раскрыться" при нажатии
# node==root - родительский элемент дерева, в который при открытии страницы загружается список жанров
#
# level - "глубина", на которой находится элемент дерева, который нужно раскрыть
#
# parent_text - текст, определяющий "родителя". В данном случае parent_text нужен, чтобы узнать, в котором именно
# жанре искать книги данного автора
def json_of_data_lazy_load(node, level, parent_text):
    string_request = None
    # Если только открываем страницу, формируем строку для JSON-файла со списком жанров
    if str(node) == 'root':
        # Список всех жанров
        genres_list = get_set_of_unique_genres()
        # Итоговая строка
        string_request = '['
        for genre in genres_list:
            # Текст, относящийся к отдельному жанру
            string_genre = '{"text" : "' + genre + '"'
            string_genre += ',"leaf" : "false"},'
            string_request += string_genre
        # Удаления запятой в конце и добавление ']'
        string_request = string_request[:-1] + ']'
    # Если нажимаем на жанр, формируем JSON-строку с авторами, которые пишут в данном жанре
    elif str(level) == '1':
        # Список всех авторов, у которых есть хотя бы одна книга в данном жанре
        authors_list = get_authors_in_this_genre(str(node))
        # Итоговая строка
        string_request = '['
        for author in authors_list:
            # Строка, относящаяся к отдельному автору
            string_author = '{"text" : "' + author + '"'
            string_author += ', "leaf" : "false"},'
            string_request += string_author
        string_request = string_request[:-1] + ']'
    # Если нажимаем на имя автора, формируем JSON-строку с книгами, которые автор написал в данном жанре
    elif str(level) == '2':
        print('level  2')
        string_request = get_string_books_of_author_in_this_genre(str(parent_text), str(node))
        string_request = string_request[:-1]
    print(string_request)
    return string_request

При загрузке страницы формируется строка:

[{"text" : "Научная фантастика","leaf" : "false"},{"text" : "Ужасы","leaf" : "false"},{"text" : "Антиутопия","leaf" : "false"},{"text" : "Учебная литература","leaf" : "false"},{"text" : "Юмор","leaf" : "false"},{"text" : "Фантастика","leaf" : "false"},{"text" : "Тёмное фэнтези","leaf" : "false"}]

При нажатии на на жанр, например "Научная фантастика", будут переданы соответствующие параметры, и сформирован ответ:

[{"text" : "Рэй Брэ'дбери", "leaf" : "false"},{"text" : "Питер Уоттс", "leaf" : "false"}]

А при нажатии на автора, например, "Рэй Брэ'дбери", будет ответ:

[{"text" : "Марсианские хроники" , "leaf" : "true"},{"text" : "И грянул гром" , "leaf" : "true"}]

Думаю, для понимания кода этой информации будет достаточно. Кстати, так как пример учебный, то параметры для входа в admin-панель соответствующие: Username: admin, Password: pass. Надеюсь, информация будет полезна для людей, которые, как и я, изучают ExtJs.

READ ALSO
Проблема с setInterval и animation

Проблема с setInterval и animation

Необходимо, чтобы синий(С) квадратик перемещался за зеленым(З) по оси ХНо если во время движения С квдарата переместить З квадрат, начинается...

102
Как в AJAX сделать GET запрос, чтобы url не менялся [закрыт]

Как в AJAX сделать GET запрос, чтобы url не менялся [закрыт]

Хотите улучшить этот вопрос? Добавьте больше подробностей и уточните проблему, отредактировав это сообщение

115
Правильная реализация меню

Правильная реализация меню

Подскажите, как правильно реализовать два "меню"

108
Компиляция Less в css в Brackets и VSCode

Компиляция Less в css в Brackets и VSCode

Я только начинаю работать с препроцессором LessНо компилировать его через Koola не хочется

87