Как отфильтровать Ext.List, заполненный иерархическими данными из магазина?

Вопросы:

Каков рекомендуемый способ фильтрации Ext.List, заполненного иерархическими данными из хранилища? Мне нужно отфильтровать дочерние объекты, принадлежащие родительскому объекту, который я выбрал для фильтрации. Дети, в этом случае игры должны заполнить список.

Что мне нужно

  • Ext.List должен быть отфильтрован по раунду, то есть "Omgång 1", "Omgång 2" и так далее. ("Omgång" = "Round" на шведском языке) При выборе "Omgång 1" в качестве фильтра в списке должны отображаться только игры этого раунда. Смотрите документ JSON ниже.
  • Список должен быть сгруппирован по дате ("kickOff") и отсортирован по ASC "gameId".

Что я сделал

  • Создан список Ext.List, заполненный данными, извлеченными через Ext.data.Store, который получает данные из документа JSON, считываемого через прокси-сервер ReST.
  • Ext.List считывает свои данные из раундов магазина (см. Ниже). Проблема в том, что я вижу только одну игру за раунд "Omgång 1", когда должно быть 8 игр.

Это то, чего мне удалось достичь до сих пор. Список фильтруется с помощью кнопок, но отображается только один из элементов списка.

Пример списка и фильтров

EM.model.Round

Модель для тура. Он имеет отношения один-ко-многим с Match.

Ext.define('EM.model.Round', {
extend: 'Ext.data.Model',

init: function() {},

config: {
    storeId: 'Rounds',

    fields: [
        'name', 
        'lockedDate',
    ],
    associations: { 
        type: 'hasMany', 
        model: 'EM.model.Match', 
        primaryKey: 'gameId',
        name: 'matches',
        autoLoad: true,
        associationKey: 'games'
    }   
},
});

EM.model.Match

Модель для матча. Это принадлежит Раунду.

Ext.define('EM.model.Match', {
extend: 'Ext.data.Model',

init: function() {},

config: {
    fields: [
        { 
            name: 'gameId', 
            type: 'int'
        }, 
        {
            name: 'firstTeam',
            type: 'string'
        },
        {
            name: 'firstTeamClass',
            type: 'string',

            convert: function(value, record) {
                return util.convertFieldValueToLowerCase('firstTeam', record);
            }
        }, 
        {
            name: 'secondTeam',
            type: 'string'
        },
        {
            name: 'secondTeamClass',
            type: 'string',

            convert: function(value, record) {
                return util.convertFieldValueToLowerCase('secondTeam', record);
            }
        },
        'kickOff',
        {
            name: 'kickOffHour',
            convert: function(value, record) {
                var timestamp = new Date(util.convertUnixTimeToMilliseconds(record.get('kickOff')));

                return Ext.Date.format(timestamp, 'H:i');                   
            }
        },
        { 
            name: 'firstTeamGoals', 
            type: 'int', 
            defaultValue: 0 
         },
        { 
            name: 'secondTeamGoals', 
            type: 'int', 
            defaultValue: 0 
         },          
        { 
            name: 'firstTeamGoalsBet', 
         }, 
        { 
            name: 'secondTeamGoalsBet', 
         },
        'points',
        {
            name: 'pointsEarned',
            convert: function(value, record) {
                var className = 'no-points-earned';
                var points = record.get('points'); 

                if (typeof points == 'undefined') {
                    return '';
                }

                if (points > 0) {
                    className = 'points-earned';
                }

                return '<div class="' + className + '">' + points + '</div>'
            }
        },
    ],

    associations: {
        type: 'belongsTo',
        model: 'EM.model.Round',
        name: 'round',
        autoLoad: true
    }

}
});

EM.store.Rounds

Магазин, который читает документ JSON. Этот магазин затем используется для заполнения Ext.List.

Ext.define('EM.store.Rounds', {
extend: 'Ext.data.Store',

config: {
    model: 'EM.model.Round',
    storeId: 'Rounds',
    filters: [{
        property: 'name',
        value: 'Round 1'
    }],
    /*grouper: {
        groupFn: function (item) {
            //var kickOff = new Date(util.convertUnixTimeToMilliseconds(item.get('kickOff')));
            //return kickOff.format('d mmmm yyyy');
        },
        //sortProperty: 'kickOff'
    },*/        
    proxy: {
        type: 'rest',
        url : 'resources/json/matches.json',
        reader: {
            type: 'json',
        }
    },
    autoLoad: true,
}
});

Документ JSON

Это документ JSON, который читает прокси в EM.store.Rounds.

[
{   
    "name": "Omgång 1",
    "lockedDate": 1325420111,
    "games": [
        {
            "gameId": 1,
            "firstTeam": "Pol",
            "secondTeam": "Gre",
            "kickOff": 1339178400,
            "firstTeamGoals": 0,
            "secondTeamGoals": 3,
            "firstTeamGoalsBet": 0,
            "secondTeamGoalsBet": 3,
            "points": 3
        },
        {
            "gameId": 2,
            "firstTeam": "Rus",
            "secondTeam": "Cze",
            "kickOff": 1339188300,
            "firstTeamGoals": 4,
            "secondTeamGoals": 1,
            "firstTeamGoalsBet": 1,
            "secondTeamGoalsBet": 2,
            "points": 0
        },{
            "gameId": 3,
            "firstTeam": "Ned",
            "secondTeam": "Den",
            "kickOff": 1339264800,
            "firstTeamGoals": 2,
            "secondTeamGoals": 1,
            "firstTeamGoalsBet": 4,
            "secondTeamGoalsBet": 2,
            "points": 2
        },
        {
            "gameId": 4,
            "firstTeam": "Ger",
            "secondTeam": "Por",
            "firstTeamGoalsBet": 4,
            "secondTeamGoalsBet": 0,
            "kickOff": 1339274700
        },
        {
            "gameId": 5,
            "firstTeam": "Spa",
            "secondTeam": "Ita",
            "firstTeamGoalsBet": 3,
            "secondTeamGoalsBet": 2,
            "kickOff": 1339351200
        },  
        {
            "gameId": 6,
            "firstTeam": "Irl",
            "secondTeam": "Cro",
            "kickOff": 1339361100
        },
        {
            "gameId": 7,
            "firstTeam": "Fra",
            "secondTeam": "Eng",
            "kickOff": 1339437600
        },
                    {
            "gameId": 8,
            "firstTeam": "Ukr",
            "secondTeam": "Swe",
            "kickOff": 1339447500
        }
    ]
},
{
     "name": "Omgång 2",
     "games": [
         {
            "gameId": 4,
            "firstTeam": "Gre",
            "secondTeam": "Cze",
            "kickOff": 1339524000
        }
     ]
},
{
     "name": "Omgång 3",
     "games": [
         {
            "gameId": 4,
            "firstTeam": "Gre",
            "secondTeam": "Rus",
            "kickOff": 1339869600
        }
     ]
},
{
     "name": "Kvart",
     "games": [
         {
            "gameId": 4,
            "firstTeam": "1A",
            "secondTeam": "2B",
            "kickOff": 1340311500
        }
     ]
},
{
     "name": "Semi",
     "games": [
         {
            "gameId": 4,
            "firstTeam": "#25",
            "secondTeam": "#27",
            "kickOff": 1340829900
        }
     ]
},
{
     "name": "Final",
     "games": [
         {
            "gameId": 4,
            "firstTeam": "#29",
            "secondTeam": "#30",
            "kickOff": 1341175500
        }
     ]
}
]

EM.view.MatchList

Представление списка, которое отображает список совпадений.

Ext.define('EM.view.MatchList', {
extend: 'Ext.List',
xtype: 'matchlist',

requires: [
    'Ext.TitleBar',
    'EM.store.Rounds'
],

config: {
    id: 'match-list',       
    store: 'Rounds',
    //grouped: true,
    scrollable: false,

    items: [
        {
            xtype: 'titlebar',

            scrollable: {
                direction: 'horizontal',
                directionLock: true
            },

            items: [
                {
                    xtype: 'button',
                    text: 'Omgång 1',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Omgång 1');
                        console.log(sto);
                    }
                },
                {
                    xtype: 'button',                        
                    text: 'Omgång 2',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Omgång 2');

                    }
                },
                {
                    xtype: 'button',
                    text: 'Omgång 3',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Omgång 3');

                    }
                },
                {
                    xtype: 'button',                        
                    text: 'Kvart',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Kvart');

                    }
                },
                {
                    xtype: 'button',                        
                    text: 'Semi',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Semi');

                    }
                },
                {
                    xtype: 'button',                        
                    text: 'Final',
                    handler: function() {
                        var sto = Ext.getStore('Rounds');
                        sto.clearFilter();
                        sto.filter('name', 'Final');

                    }
                }                           

            ],
        },
        {
            xtype: 'panel',
            html: 'Senast uppdaterad: Idag kl 20:12'
        }
    ],

    itemTpl: [

        '<div class="match-meta-data">',
        '<tpl for="matches">',          
            '<div class="team-wrapper home-team">{firstTeam} <div class="flag {firstTeamClass}"><span></span></div> <span class="goals-scored">{firstTeamGoals}</span></div>',
            '<div class="kick-off-time">{kickOffHour}</div>',
            '<div class="team-wrapper away-team"><span class="goals-scored">{secondTeamGoals}</span> <div class="flag {secondTeamClass}"><span></span></div> {secondTeam}</div>',
            '<div class="bet-meta-data">',
                '<img class="user-icon" src="resources/images/user-22x26.png" />',
                '<div class="home-team goals-bet">{firstTeamGoalsBet}</div>',
                '<div class="away-team goals-bet">{secondTeamGoalsBet}</div>',
                '{pointsEarned}',   
            '</div>',
        '</tpl>',
        '</div>',

        ].join('')
    },

});

Это мое первое приложение Sencha Touch, поэтому не стесняйтесь указывать на любые плохие практики, которые вы видите в коде. Может ли кто-нибудь привести пример того, как достичь того, к чему я стремлюсь? Я потратил много времени, пытаясь понять это.

Полный проект можно скачать с GitHub по адресу https://github.com/eriktoyra/EM-Tipset. Последняя ветка - _filter-match-list.

2 ответа

Решение

Мне удалось найти решение этой проблемы с помощью тестового сценария, который похож на тот, который у меня есть.

Решение

То, что я сделал, было:

  1. Пусть модель Division обрабатывает прокси и считывает данные из match.json вместо того, чтобы хранилище Division считывало данные.
  2. Настройте отношения между моделями Дивизиона и Команды, чтобы они знали друг друга.
  3. Настройте два магазина. Один для отдела и один для команды. При загрузке хранилища подразделения я использовал обратный вызов, чтобы заполнить хранилище команды данными о команде из хранилища подразделения.
  4. Затем я заполнил список хранилищем команд, в котором есть объекты команд, которые теперь знают об их ссылках на модель / магазин подразделения.
  5. Фактическая фильтрация выполняется путем поиска объекта Division для каждого элемента Team в списке и сравнения свойства имени Division с тем, которое предоставлено фильтром.

Полное решение приведено ниже, а также доступно на GitHub.

Я оставлю вопрос без ответа в течение нескольких дней, если кто-то еще найдет лучшее решение или укажет на некоторые улучшения.

    /**
     * @description The main purpose of this mockup is to test how to filter a list using hierarchical data. The code is adapted from the example 
     * "Filtering data in an Ext.List component in Sencha Touch 2" by Peter deHaan.  
     * @see <a href="http://senchaexamples.com/2012/03/15/filtering-data-in-an-ext-list-component-in-sencha-touch-2/">Filtering data in an Ext.List component in Sencha Touch 2</a>.
     * @author <a href="mailto:erik.toyra[at]gmail.com">Erik Töyrä</a>      
     */
    Ext.application({
        launch: function () {
            /**
             * Division model
             */
            Ext.define("Division", {
                extend: 'Ext.data.Model',
                config: {
                    fields: [
                        'division'
                    ],
                    // Setup a hasMany relations between Division and Team
                    hasMany: {model: 'Team', name: 'teams'},

                    // Load data from teams.json into the Division model
                    proxy: {
                        type: 'rest',
                        url : 'teams.json',
                        reader: {
                            type: 'json'
                        }
                    }
                }
            });

            /**
             * Team model
             */
            Ext.define("Team", {
                extend: 'Ext.data.Model',
                config: {
                    fields: [
                        'name', 'league'
                    ],
                    // Setup a belongsTo relationship between Team and Division
                    belongsTo: {model: 'Division'},
                }
            });                

            /**
             * Division store
             */
            var divisionStore = Ext.create('Ext.data.Store', {
                model: "Division",
                storeId: 'divisionStore',
            });

            /**
             * Team store
             */
            var teamStore = Ext.create('Ext.data.Store', {
                model: "Team",
                storeId: 'teamStore', // This is the store we will reference in our Ext.list below.
                fields: ['division', 'leage', 'name'],
            });

            /**
             * Load the Division store which holds all of the data read from teams.json. 
             * After the data has been loaded we add the team data to the teamStore.
             */
            divisionStore.load({
                callback: function() {
                    // Loop through each division and retrieve the all teams that resides as 
                    // childs to the division. Then we add each team to the teamStore.
                    divisionStore.each(function(division) {
                        division.teams().each(function(team) {
                            teamStore.add(team);
                        });
                    });
                }
            });

            /**
             * Create the list that should be filtered by Division and display a filtered list with Team objects.
             */
            Ext.create('Ext.List', {
                fullscreen: true,

                items: [{
                    xtype: 'titlebar',
                    docked: 'top',
                    ui: 'neutral',

                    items: [{
                        text: 'West only',
                        handler: function () {
                            return util.doFilter('West');
                        } // handler
                    }, {
                        text: 'Central only',
                        handler: function () {
                            return util.doFilter('Central');
                        } // handler
                    }, {
                        text: 'East only',
                        handler: function () {
                            return util.doFilter('East');                               
                        } // handler
                    }, {
                        text: 'Clear filters',
                        ui: 'decline',
                        align: 'right',
                        handler: function () {
                            Ext.getStore('teamStore').clearFilter();
                        } // handler
                    }
                    ] // items (toolbar)
                }], // items (list)
                store: 'teamStore',
                itemTpl: '{name}, {league}',
            }); // create()


            /**
             * Utility functions
             */
            var util = (function() {
                var util = {};

                /**
                 * Filter the teamStore by the passed in Division name.
                 */
                util.doFilter = function(filterOption) {
                    var store = Ext.getStore('teamStore');

                    // Clear all existing filters first...
                    store.clearFilter();

                    // ... then apply the selected filter
                    store.filterBy(function(record, id) {
                            return record.getDivision().get('division') == filterOption;
                        }, this);
                }

                return util;
            })();
        } // launch
    }); // application()

Данные JSON

[
{
    division: 'East',
    teams: [
        {
            name: 'New York Yankees',
            league: 'AL',
        }, {
            name: 'Tampa Bay',
            league: 'AL',
        }, {
            name: 'Boston',
            league: 'AL',
        }, {
            name: 'Toronto',
            league: 'AL',
        }, {
            name: 'Baltimore',
            league: 'AL',
        }
    ]
},
{
    division: 'Central',
    teams: [
        {
            name: 'Detroit',
            league: 'AL',

        }, {
            name: 'Cleveland',
            league: 'AL',
        }, {
            name: 'Chicago White Sox',
            league: 'AL',
        }, {
            name: 'Kansas City',
            league: 'AL',
        }, {
            name: 'Minnesota',
            league: 'AL',
        }                            
    ]
},
{
    division: 'West',
    teams: [
    {
            name: 'Texas',
            league: 'AL',
        }, {
            name: 'Los Angeles Angels',
            league: 'AL',
        }, {
            name: 'Oakland',
            league: 'AL',
        }, {
            name: 'Seattle',
            league: 'AL',
        }
    ]
}
]

Это действительно две проблемы. Я не знаю, почему весь ваш магазин не отображается в виде списка; некоторые шаги и паузы могут пролить больше света на это. Если у вас есть демо-версия, я с удовольствием посмотрю на нее.

Что касается вашей проблемы с многократной фильтрацией, я могу подумать о двух вещах прямо сейчас:

  • Насколько я могу судить, выполняя filter действие над хранилищем приводит к тому, что оно "теряет" данные, т.е. выбрасывает все данные, которые не соответствуют фильтру (а не скрывает их от методов доступа). Если вы загружаете все свои раунды за один и тот же серверный вызов в один магазин, я не уверен, что фильтрация - это то, что вам нужно. Я могу ошибаться, но я только что попытался отфильтровать магазин в одном из моих приложений из консоли Javascript; когда я применил фильтр, который не соответствовал ни одному из предметов, магазин опустел. Исходя из этого, может быть, фильтрация не лучшая вещь, чтобы сделать здесь.

  • Создать отдельный listview за каждый раунд каждый использует один и тот же общий магазин раундов. Использовать itemTpl конфиг и XTemplate решить, какие совпадения должны войти в каждый список. Используйте кнопки Omgång, чтобы при необходимости выгрузить эти списки из области просмотра.

Обновите ответ после лучшего понимания вашей проблемы и кода

Другие вопросы по тегам