Статьи

Работа с Flex DataGrid и вложенными структурами данных

Часто случается, что данные, которые необходимо просмотреть / представить в Flex DataGrid, поступают из файла XML или JSON с несколькими уровнями вложенности. К сожалению, по умолчанию Flex DataGrid позволяет отображать только вложенные объектные массивы одного уровня.

В этом руководстве показано, как вы можете расширить класс Flex DataGrid для размещения более сложных структур данных. Он также покажет вам, как сделать все столбцы сортируемыми даже при использовании вложенных структур данных.




В этом руководстве предполагается, что вы знаете основы Flex, как использовать Flex Builder и как писать файлы MXML. В вашей системе должна быть установлена ​​копия Flex Builder.

Первым шагом является настройка проекта в Flex Builder. Создайте новый проект в Flex Builder с именем проекта как «NestedDataGrid» и типом приложения как «веб-приложение (выполняется во Flash Player)». Оставьте все остальные параметры по умолчанию и нажмите «Готово».

Создание нового проекта

Данные, которые мы собираемся показать в DataGrid, получены из файла XML. Создайте папку в вашей папке ‘src’ с именем ‘assets’ и поместите данные, показанные ниже, в файл с именем ‘meeting.xml’. Кроме того, вы можете скачать файл XML отсюда и поместить его в папку «assets».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version=»1.0″ encoding=»UTF-8″?>
<meetings>
  <meeting>
    <priority>high</priority>
    <presenter>
      <name>Lisa Green</name>
      <email>[email protected]</email>
      <phone>+330-7593</phone>
    </presenter>
    <date>12th July 2009</date>
    <time>6:00 pm</time>
    <place>Room 405</place>
  </meeting>
  <meeting>
    <priority>medium</priority>
    <presenter>
      <name>Christopher Martin</name>
      <email>[email protected]</email>
      <phone>+330-7553</phone>
    </presenter>
    <date>14th July 2009</date>
    <time>11:00 am</time>
    <place>Room 405</place>
  </meeting>
  <meeting>
    <priority>high</priority>
    <presenter>
      <name>George Rodriguez</name>
      <email>[email protected]</email>
      <phone>+330-7502</phone>
    </presenter>
    <date>18th July 2009</date>
    <time>10:00 am</time>
    <place>Room 771</place>
  </meeting>
  <meeting>
    <priority>high</priority>
    <presenter>
      <name>Jennifer Parker</name>
      <email>[email protected]</email>
      <phone>+330-5380</phone>
    </presenter>
    <date>20th August 2009</date>
    <time>2:00 pm</time>
    <place>Room 562</place>
  </meeting>
</meetings>

Вот краткое описание построения интерфейса для отображения данных и соответствующих значений идентификаторов, необходимых для кода в этом руководстве:

  1. Откройте файл NestedDataGrid.mxml и перейдите в представление дизайна
  2. Перетащите «Панель» из представления «Компоненты». Установите его идентификатор для «MeetPanel» и заголовок для «Meetings»
  3. Установите высоту и ширину панели на 500 и установите значения X и Y на 0
  4. Перетащите «DataGrid» на панель
  5. Установите значения X и Y на 10
  6. Установите ширину {встречиPanel.width-40} и высоту до 45%
  7. Перейдите к исходному виду и в теге «mx: Appication» добавьте атрибут layout = «vertical».

Ваш интерфейс должен выглядеть примерно так, как показано на рисунке ниже:

Интерфейс

MXML в исходном представлении должен выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
<?xml version=»1.0″ encoding=»utf-8″?>
<mx:Application xmlns:mx=»http://www.adobe.com/2006/mxml» layout=»vertical»>
  <mx:Panel x=»0″ y=»0″ width=»500″ height=»500″ layout=»absolute» id=»meetingsPanel» title=»Meetings»>
    <mx:DataGrid x=»10″ y=»10″ width=»{meetingsPanel.width-40}» height=»45%»>
      <mx:columns>
        <mx:DataGridColumn headerText=»Column 1″ dataField=»col1″/>
        <mx:DataGridColumn headerText=»Column 2″ dataField=»col2″/>
        <mx:DataGridColumn headerText=»Column 3″ dataField=»col3″/>
      </mx:columns>
    </mx:DataGrid>
  </mx:Panel>
</mx:Application>

На следующих трех шагах мы создадим компонент HTTPService, прочитаем данные из файла XML и сохраним их в локальной переменной. Это делается в три этапа:

Перейдите в исходное представление файла MXML и добавьте следующий код прямо под тегом mx: Application :

1
2
3
<mx:HTTPService id=»readXML»
  url=»assets/meetings.xml» resultFormat=»object»
  result=»httpResultHandler(event)» fault=»httpFaultHandler(event)» />

Функция httpResultHandler () будет вызываться при получении данных. Если при получении данных произошла ошибка, вызывается функция httpFaultHandler (). Обратите внимание, что это только создает объект HTTPService, данные должны быть получены с помощью явного вызова функции (см. Подэтап 3)

Добавьте тег mx: Script чуть ниже тега mx: Application . Внутри этого, определите переменную, которая будет содержать входящие данные и функции для обработки событий из компонента HTTPService. Код для этого выглядит следующим образом:

1
import mx.rpc.events.FaultEvent;

Переменная ‘dataForGrid’ содержит данные, которые мы собираемся прочитать. Тег ‘[Bindable]’ гарантирует, что всякий раз, когда данные изменяются (когда они считываются), DataGrid соответствующим образом обновляется. XML читается как объект, который передается через событие ResultEvent, а event.result.meetings.meeting обращается к коллекции ArrayCollection объектов Meeting.

На этом этапе выполняется фактический вызов функции для получения данных XML. Функция инициализации назначается событию, которое запускается при каждой загрузке приложения, — событию creationComplete. Добавьте атрибут creationComplete = «getData ()» в тег «mx: Application» и определите функцию «getData ()», как показано ниже (добавляется после функции «httpFaultHandler»):

1
2
3
private function getData():void{
  readXML.send();
}

Это заставляет объект HTTPService получать данные из файла. Как только данные извлечены, запускается событие «result», которое вызывает функцию «httpResultHandler ()». Если при получении данных возникает проблема, запускается событие «ошибка», которое вызывает функцию httpFaultHandler ().

На этом этапе ваш NestedDataGrid.mxml должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version=»1.0″ encoding=»utf-8″?>
<mx:Application xmlns:mx=»http://www.adobe.com/2006/mxml» layout=»vertical» creationComplete=»getData()»>
<mx:Script>
  <![CDATA[
      import mx.rpc.events.FaultEvent;
      import mx.rpc.events.ResultEvent;
      import mx.collections.ArrayCollection;
      import mx.controls.Alert;
         
      [Bindable]
      public var dataForGrid:ArrayCollection;
 
      private function httpResultHandler(event:ResultEvent):void{
        dataForGrid = new ArrayCollection(event.result.meetings.meeting);
      }
       
      private function httpFaultHandler(event:FaultEvent):void{
        Alert.show(«Error occurred in getting string»);
      }
       
      private function getData():void{
        readXML.send();
      }
 
  ]]>
</mx:Script>
    <mx:HTTPService id=»readXML»
      url=»assets/meetings.xml» resultFormat=»object»
      result=»httpResultHandler(event)» fault=»httpFaultHandler(event)» />
  <mx:Panel width=»500″ height=»500″ layout=»vertical» id=»meetingsPanel» title=»Meetings»>
    <mx:DataGrid width=»{meetingsPanel.width-40}» height=»45%»>
      <mx:columns>
        <mx:DataGridColumn headerText=»Column 1″ dataField=»col1″/>
        <mx:DataGridColumn headerText=»Column 2″ dataField=»col2″/>
        <mx:DataGridColumn headerText=»Column 3″ dataField=»col3″/>
      </mx:columns>
    </mx:DataGrid>
  </mx:Panel>
</mx:Application>

Я просто кратко укажу, почему вложенные данные создают проблемы при отображении, сначала продемонстрировав, как вы показываете не вложенные данные. Скажем, из приведенного выше XML-файла вы хотели показать только дату, место и приоритет встреч (а не информацию о докладчике). Приведенный ниже код сможет отображать его без каких-либо проблем (здесь показано содержимое mx: Panel. Все остальные коды такие же):

1
2
3
4
5
6
7
8
<mx:DataGrid width=»{meetingsPanel.width-40}» height=»45%» dataProvider=»{dataForGrid}»>
  <mx:columns>
    <mx:DataGridColumn headerText=»Priority» dataField=»priority»/>
    <mx:DataGridColumn headerText=»Date» dataField=»date»/>
    <mx:DataGridColumn headerText=»Time» dataField=»time»/>
    <mx:DataGridColumn headerText=»Place» dataField=»place»/>
  </mx:columns>
</mx:DataGrid>

Результатом этого будет следующее приложение:


Обратите внимание, что атрибут dataProvider DataGrid может быть непосредственно назначен ArrayCollection ‘dataForGrid’, а каждому внутреннему DataGridColumn присвоен атрибут dataField, который непосредственно соответствует имени свойства. Тем не менее, предположим, что вы хотите получить доступ к информации об имени докладчика, к ней можно обращаться как «Presenter.name». Если вы попытаетесь передать это значение в dataField, вы получите ошибку. Это потому, что Flex не поддерживает вложенные объекты по умолчанию. Читайте дальше, чтобы узнать, как решить эту проблему, расширив класс DataGridColumn и написав собственный код для обработки этого случая.

Мы переопределяем некоторые функции в классе столбцов DataGrid, чтобы обойти описанную выше проблему. Сначала создайте папку в каталоге ‘src’ под названием ‘classes’. Создайте новый класс ActionScript в этой папке с именем «NestedDataGridColumn». В поле «Суперкласс» нажмите «Обзор …» и выберите «DataGridColumn» из всплывающего списка. Оставьте все остальное в значениях по умолчанию и нажмите «Готово». Новый файл должен быть создан и заполнен следующим кодом:

01
02
03
04
05
06
07
08
09
10
11
12
13
package classes
{
  import mx.controls.dataGridClasses.DataGridColumn;
 
  public class NestedDataGridColumn extends DataGridColumn
  {
    public function NestedDataGridColumn(columnName:String=null)
    {
      super(columnName);
    }
     
  }
}

В классе NestedDataGridColumn добавьте общедоступную привязываемую переменную с именем nestedDataField. Мы будем использовать это вместо стандартного свойства dataField для передачи имени поля. Это жизненно важно, потому что если используется свойство dataField по умолчанию, появляется сообщение об ошибке: Критерии поиска должны содержать хотя бы одно значение поля сортировки. произойдет, когда мы попытаемся отсортировать DataGrid после определения пользовательской функции сортировки позже.

Как видите, новый класс, который мы создали, уже заполнен конструктором. Оставьте конструктор как есть, и ниже добавьте следующую функцию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
override public function itemToLabel(data:Object):String
    {
      var fields:Array;
      var label:String;
         
      var dataFieldSplit:String = nestedDataField;
      var currentData:Object = data;
       
      //check if the nestedDataField value contains a ‘.’
      if(nestedDataField.indexOf(«.») != -1)
      {
        //get all the fields that need to be accessed
        fields = dataFieldSplit.split(«.»);
       
        for each(var f:String in fields)
        //loop through the fields one by one and get the final value, going one field deep every iteration
          currentData = currentData[f];
        
        if(currentData is String)
        //return the final value
          return String(currentData);
      }
       
      //if there is no nesting involved
      else
      {
        if(dataFieldSplit != «»)
            currentData = currentData[dataFieldSplit];
      }
       
      //if our method hasn’t worked as expected, resort to calling the default function
      try
      {
        label = currentData.toString();
      }
      catch(e:Error)
      {
        label = super.itemToLabel(data);
      }
         
      //return the result
      return label;
    }

Переопределение функции itemToLabel — ключ к возможности доступа к вложенным данным в вашей DataGrid. Функция itemToLabel контролирует то, что отображается в строках DataGrid. Поэтому мы используем его здесь, чтобы попросить Flex показать вложенные данные указанным нами способом.

Как видите, определение функции начинается с ключевого слова override, что означает, что предопределенная функция по умолчанию с тем же именем переопределяется в пользу функции, которую вы определили. Каждое утверждение объясняется в комментариях. Вкратце, функция выполняет проверку доступа к вложенным полям данных (если присутствует «.»). Если это так, получите каждое имя поля данных и итерируйте по dataProvider, углубляя каждый шаг, получая доступ к дочернему полю.

Функция ‘itemToLabel’ вызывается Flex с аргументом, который содержит ArrayCollection, который был указан как dataProvider для dataGrid. Все атрибуты NestedDataGridColumn (когда он используется в файле mxml) доступны напрямую, и любым открытым свойствам, определенным в этом классе, может быть присвоено значение в MXML. В нашем файле NestedDataGrid.mxml мы заменяем компоненты «mx: DataGridColumn» компонентом «classes: NestedDataGridColumn» и назначаем конкретные элементы, которые мы хотим отобразить в столбцах, атрибуту «nestedDataField» (который был объявлен в « Файл NestedDataGridColumn.as). DataGridColumn теперь должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
<mx:DataGrid x=»10″ y=»10″ width=»{meetingsPanel.width-40}» height=»45%» dataProvider=»{dataForGrid}»>
  <mx:columns>
    <classes:NestedDataGridColumn headerText=»Priority» nestedDataField=»priority» width=»60″/>
    <classes:NestedDataGridColumn headerText=»Presenter Name» nestedDataField=»presenter.name» sortable=»false»/>
    <classes:NestedDataGridColumn headerText=»Presenter Phone» nestedDataField=»presenter.phone» width=»90″ sortable=»false»/>
    <classes:NestedDataGridColumn headerText=»Date» nestedDataField=»date» width=»110″/>
    <classes:NestedDataGridColumn headerText=»Time» nestedDataField=»time» width=»70″/>
    <classes:NestedDataGridColumn headerText=»Place» nestedDataField=»place» width=»70″/>
  </mx:columns>
</mx:DataGrid>

Обратите внимание, что здесь я прямо указываю свойство ‘nestedDataField’ как «Presenter.name» и «Presenter.phone». Кроме того, я добавил ширину столбцов и установил ширину компонента «mx: Panel» равным 600px для лучшего отображения. Я добавил свойство «сортируемый», чтобы оно было ложным для двух новых столбцов. Если вы удалите это (или установите для него значение true) и запустите программу, столбец не будет сортироваться. Мы решим это на следующем шаге, определив нашу собственную функцию сортировки. На данный момент приложение должно выглядеть так:


Теперь осталось только определить пользовательскую функцию сортировки, чтобы сортировка также была включена во всех полях (например, вы хотите отсортировать имена докладчиков по алфавиту). Добавьте функцию mySortCompareFunction ниже функции itemToLabel следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private function mySortCompareFunction(obj1:Object, obj2:Object):int{
  var fields:Array;
  var dataFieldSplit:String = nestedDataField;
  var currentData1:Object = obj1;
  var currentData2:Object = obj2;
   
  if(nestedDataField.indexOf(«.») != -1)
  {
    fields = dataFieldSplit.split(«.»);
   
    for each(var f:String in fields){
      currentData1 = currentData1[f];
      currentData2 = currentData2[f];
    }
 
  }
  else
  {
    if(dataFieldSplit != «»){
        currentData1 = currentData1[dataFieldSplit];
        currentData2 = currentData2[dataFieldSplit];
     }
  }
   
  if(currentData1 is int && currentData2 is int){
    var int1:int = int(currentData1);
    var int2:int = int(currentData2);
    var result:int = (int1>int2)?-1:1;
    return result;
  }
  if(currentData1 is String && currentData2 is String){
    currentData1 = String(currentData1);
    currentData2 = String(currentData2);
    return (currentData1>currentData2)?-1:1;
  }
  if(currentData1 is Date && currentData2 is Date){
    var date1:Date = currentData1 as Date;
    var date2:Date = currentData2 as Date;
    var date1Timestamp:Number = currentData1.getTime();
    var date2Timestamp:Number = currentData2.getTime();
    return (date1Timestamp>date2Timestamp)?-1:1;
  }
   
  return 0;
}

Эта функция будет вызываться Flex с двумя объектами, и ожидается, что она вернет либо -1,0, либо 1, в зависимости от того, будет ли первый объект больше, равен или меньше, соответственно, второго объекта. Flex заботится о фактической сортировке.

Эта функция использует ту же логику, что и функция itemToLabel, чтобы получить соответствующее вложенное значение. Затем в зависимости от типа значения (будь то int, String или Date) он сравнивает его соответствующим образом и возвращает -1, если значение ‘currentData1’ больше, чем ‘значение currentData2’, 0, если они равны, и 1, если значение ‘currentData2 ‘больше, чем’ currentData1 ‘.

Если вы заметили, «customSortCompareFunction» не является предопределенной функцией в классе DataGridColumn, который мы переопределяем. Эта функция должна быть назначена как функция сортировки другим способом. Мы должны присвоить предопределенной переменной «sortCompareFunction» имя функции сортировки, в нашем случае это «customSortCompareFunction». Это должно быть сделано внутри конструктора. Конструктор теперь выглядит так:

1
2
3
4
5
6
public function NestedDataGridColumn(columnName:String=null)
    {
      //the custom sort function being assigned to the pre-defined variable
      sortCompareFunction = mySortCompareFunction;
      super(columnName);
    }

Как только это будет сделано, все готово. Теперь у вас есть собственный класс, который может обрабатывать произвольно вложенные данные для отображения в DataGrid. И вы можете сортировать сетку, как вы хотите.

Сегодня вы узнали, как обойти ограничение класса FlexDataGrid, чтобы получать произвольно вложенные данные и отображать их в DataGrid. Вы также узнали, как определить свою собственную функцию сортировки, чтобы сетка оставалась сортируемой. Теперь вы можете использовать этот NestedDataGridColumn во всех ваших приложениях без каких-либо накладных расходов.

Вы можете дополнительно расширить функцию itemToLabel, включив в нее другой произвольный формат доступа — скажем, доступ к массивам внутри дочерних объектов или доступ к атрибутам xml. Вы также можете дополнительно расширить функцию сортировки для сортировки других типов данных. Вы также можете добавить другие функции, такие как раскраска строк в зависимости от приоритета встречи и отображение дополнительных сведений о докладчике, щелкнув строку.

Спасибо за прочтение 🙂