Как отфильтровать 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 ответа
Мне удалось найти решение этой проблемы с помощью тестового сценария, который похож на тот, который у меня есть.
Решение
То, что я сделал, было:
- Пусть модель Division обрабатывает прокси и считывает данные из match.json вместо того, чтобы хранилище Division считывало данные.
- Настройте отношения между моделями Дивизиона и Команды, чтобы они знали друг друга.
- Настройте два магазина. Один для отдела и один для команды. При загрузке хранилища подразделения я использовал обратный вызов, чтобы заполнить хранилище команды данными о команде из хранилища подразделения.
- Затем я заполнил список хранилищем команд, в котором есть объекты команд, которые теперь знают об их ссылках на модель / магазин подразделения.
- Фактическая фильтрация выполняется путем поиска объекта 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, чтобы при необходимости выгрузить эти списки из области просмотра.
Обновите ответ после лучшего понимания вашей проблемы и кода