Пейджинг на лету
На одном из проектов возникла производственная необходимость отобразить данные в виде таблицы. Казалось бы проще репы, но избыточность данных бесстыдно ставила контрол на локти. Стоит отметить, что он имел 46 колонок и 100 строк, количество же данных могло исчисляться десятками тысяч. Упомяну, что интеграция происходила на базе веб-сервисов, так что LCDS нам только снился.
Очевидно, что в нашем случае, чтобы поднять производительность необходимо было лимитировать количество данных в провайдере. Таким образом, накопав в доках пару красноречивых намёков на паджинацию тут и тут , мы начали погружение.
Сверхзадачей было добиться относительной нативности скроллинга без скачков и излишнего напряжения (чтобы недостающие данные, по пришествии, не вставали в нулевой индекс, а аккуратно подлипали в конец).
Прольем же немного света на базовые механизмы паджинации поддерживаемые стандартными компонентами, являющими собой производные List-а, а именно HorizontalList, TileList, DataGrid, Menu, Tree, ну и List конечно. Техника базируется на использовании класса mx.collections.errors.ItemPendingError. Выбросив такую ошибку из метода getItemAt() коллекции, которая является дата-провайдером для List-based копонент, можно заставить контрол впасть в ожидание pendent данных. Абстрактный например:
- public class PagingCollection implements IList
- {
- override public function getItemAt(index:int, prefetch:int=0):Object
- {
- if (index < 0 || (_length != -1 && index >= _length))
- throw new RangeError();
- if(!cache.hasItem(index))
- throw new ItemPendingError("");
- return cache.getItemAt(index);
- }
- }
При этом контрол поймав эту ошибку подписывается к ней с помощью респондеров и ждет их срабатывания. Код в листинге ниже взят из метода scrollHandler() класса mx.controls.List (line 1451).
- try
- {
- if (!iteratorValid)
- iterator.seek(CursorBookmark.FIRST, pos);
- else
- iterator.seek(CursorBookmark.CURRENT, delta);
- if (!iteratorValid)
- {
- iteratorValid = true;
- lastSeekPending = null;
- }
- }
- catch(e:ItemPendingError)
- {
- lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos);
- e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending));
- iteratorValid = false;
- }
Когда данные готовы, дергаем за респондеры из нутра кастомной коллекции и контрол, в свою очередь, начинает дергать getItemAt() используя индексы paged данных. Абстрактный код ниже (в контексте кастомной коллекции) иллюстрирует обход всех респондеров, что фактически приводит к вызову обработчиков, добавленных контролом в момента отлова ItemPendingError (e.g. seekPendingResultHandler из предыдущего листинга).
- protected function onResult(val:PagingResult):void
- {
- cache.addItems(val.result);
- itemPendingError.responders.forEach(
- function(responder:IResponder, index:int, arr:Array):void
- {
- responder.result(val.result);
- });
- }
Интересно упомянуть, что начинается всё с обращения к геттеру length() коллекции. То есть при присвоения нового провайдера Листу, чтобы корректно отрисовать скролл, первым делом он пытается узнать его размер. Вначале, при отсутствии данных, возвращаем -1. Это важно чтобы отличить начальное состояние от возможно пустого массива в полученных данных в дальнейшем и делаем начальный запрос.
- override public function get length():int
- {
- if (_length == -1 && !lengthPending)
- {
- lengthPending = true;
- requestData();
- }
- return _length;
- }
Узнав общее колличество выставляем длину и пинаем контрол с помощью события CollectionEvent.COLLECTION_CHANGE.
- protected function onResult(val:PagingResult):void
- {
- cache.addItems(val.result);
- if(_length < 0)
- {
- _length = val.totalSize;
- lengthPending = false;
- var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
- event.kind = CollectionEventKind.RESET;
- dispatchEvent(event);
- }
- }
Весьма отмечу также, что интенсивное таскание тумбы в режиме liveScrolling представляло из себя совершенно удручающее зрелище, даже с задержкой данных в 1 секунду, контрол выбрасывал ItemPendingError. Отнаследовавшись и перекрыв scrollHandler() c hot-plug мы вернули его к жизни:
- public class ListExtended extends List
- {
- public function ListExtended()
- {
- super();
- }
- override protected function scrollHandler(event:Event):void
- {
- /*
- value can be null and it
- results in throwing our custom
- PagingCollectionItemPendingError.
- In overridden method it is not
- wrapped in try/catch block.
- */
- if (iterator.bookmark.value)
- super.scrollHandler(event);
- }
- }
Творение сие не есть алмаз, а лишь фантазия воображения, но мы счастливы и живы, и солнце, по обыкновению, снова греет наши затылки.
Довольно живописную картину можно получить ознакомившись с приведенными выше ссылками и исходниками ниже.
Качать пример отсюда (некоторые комментарии в коде на англицком). view source.
Трекбек адрес этой записи
URL трекбека (щелкните правой кнопкой мыши и скопируйте ссылку)
8 комментариев
Красотища! Интересно, как у нас в Pronto сделано. Спрошу у пацанов
Статья интересная и тема животрепещущая. Но Автор! смените язык которым вы выражаете свои мысли - настолько "сленговое" описание повергает в некоторый шок. "паджинация" - зная англ понял, "относительной нативности скроллинга" -а вот этот оборот так и остался загадкой. "нового провайдера Листу" - Лист это который композитор? "интенсивное таскание тумбы" - вы еще и грузчиком подрабатываете?
Отличная статья!ZloyDEAD Нормальный (и главное более понятный) язык для блога разработчика и читателей разработчиков. Когда Вертекс книжку писать будет, тогда и перепишет
2ZloyDEADПрошу великодушно за свой слэнг, но мне казалось, что "скроллинг" и "нативность" довольно употребляемы, хотя это субъективно вполне.
относительной нативности скроллингаотносительная естественность прокрутки.
вы еще и грузчиком подрабатываете?было и такое..
2BuilderНу я б так не сказал
Во-первых, я никак здесь не претендую на эксклюзив и первозданную свежесть. Похоже, товарищи с widget-labs, к моему неудивлению, уже были молодцами более полутора лет назад (в отличии от меня) - респект им.
Во-вторых, использование средств фреймворка, согласитесь, трудно назвать плагиатом, а архитектура и реализация этого проекта была полностью высосана из пальца (моего).
Жаль что не угнал этот велосипед раньше
Статья сия прекрасна, используйте язык и невзирайте ни на что. Хотя, честно признаться, "некоторая нативность скроллинга" и меня поставила в некоторую тупиковость восприятия, но это сущие мелочи; сохраняйте красоту мышления!
Когда ждать новых статей?
"все уже украдено до нас"