30 October 2008

Flex Data Binding Tricks

IT-companies
Меньше года назад меня вовлекли в проект, для которого необходимо было писать клиента на Flex. Так как я был новичком в этом деле, то в процессе работы я находил что-то новое и совершенно неизвестное мне. В то время я и открыл для себя Flex Data Binding (связыванием данных). Я думаю, что каждый, кто работает с Flex очень скоро сталкивается с Data Binding.
Связывание данных заключается в том, что мы можем с легкостью связывать два объекта (источника данных) между собой, что позволяет поддерживать их синхронизацию. Примером может служить связывание между собой элементов пользовательского интерфейса путем создания некоторых правил поведения, что способствует созданию более интерактивного пользовательского интерфейса.
Имея некоторый опыт в этой области, я решил разобрать все типы механизма связывания данных во Flex. Думаю эта статья будет интересна не только новичкам, но и профессионалы почерпнут из неё что-нибудь для себя.

Простой data binding с использованием MXML


Все документы по data binding (которые я прочитал по крайней мере) начинаются именно с данного типа использования этой замечательной функции. К сожалению это практически и единственный метод, который везде наглядно описан. Итак, предположим, что мы имеем два текстовых поля:

<mx:TextInput id="sourceField" text="" />
<mx:TextInput id="destinationField" text="" />


Мы хотим, чтобы изменения в первом поле отображались и на другом поле. Для данной ситуации достаточно написать такой mxml код:

<mx:Binding destination="destinationField.text" source="sourceField.text"/>

Это приведет к тому, что текст, вписанный в первое текстовое поле будет автоматически устанавливаться во второе текстовое поле. В этом и есть смысл простого data binding. Код простого приложения с использованием data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
   <mx:Binding destination="destinationField.text" source="sourceField.text"/>
   <mx:VBox>
      <mx:TextInput id="sourceField" text="" />
      <mx:TextInput id="destinationField" text="" />
   </mx:VBox>
</mx:Application>


Простой data binding с использованием ActionScript


Пример описанный выше легко переписать на ActionScript. Смысл данной конструкции заключается в том, что мы можем использовать её для динамически создаваемых элементов. Итак, всё те же два текстовых поля:

public var sourceField : TextInput = new TextInput();
public var destinationField : TextInput = new TextInput();


Data binding для этих полей будет выглядеть так:

BindingUtils.bindProperty(destinationField, "text", sourceField, "text");

Первым параметром функции bindProperty является объект «пункт назначения», вторым параметром является строка с именем свойства данного объекта, третьим параметром является объект «пункт отправления» и четвертым параметром является объект-цепочка, который при упрощении может являться строкой с именем свойства объекта «пункта отправления» (о данном параметре будет оговорено более подробно ниже). Код приложения с использованием данного метода data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var sourceField : TextInput = new TextInput();
         public var destinationField : TextInput = new TextInput();
         public function init():void
         {
            sourceField.text = "";
            parentContainer.addChild(sourceField);
            
            destinationField.text = "";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(destinationField, "text", sourceField, "text");
         }
               
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer"/>
</mx:Application>


Объект-цепочка в методе bindProperty класса BindingUtils.


Рассмотрим более подробно объект-цепочку, о котором говорилось ранее. Данный объект используется в качестве 4-го параметра функции bindProperty в классе BindingUtils. Данный объект описывает, каким образом и к какому параметру применить data binding в объекте «пункт отправления». Данный объект может быть представлен в трех различных видах:

Строка

Строка – хранит в себе имя свойства объекта «пункт отправления». То есть, имея текстовое поле с именем «sourceField» мы можем в качестве объекта-цепочки использовать имя опции «text». Об этом виде объекта-цепочки говорилось выше. Дополнительные разъяснения будут лишними.

Массив строк

Массив строк – хранит в иерархию доступа к внутреннему свойству объекта «пункт отправления». Назначение этого вида объекта-цепочки очень легко понять, разобрав простой пример, в котором используются всего лишь два объекта:

package src
{
   public class SimpleObject
   {
      public var myText:String = new String();
      public function SimpleObject():void
      {
         myText = "empty";
      }
   }
}

package src
{
   public class ComplexObject
   {
      public var simpleInstance: SimpleObject = new SimpleObject();
      public function ComplexObject():void
      {
         //some logic
      }
   }
}


Допустим, у нас есть экземпляр класса ComplexObject, с которым нам надо связать текстовое поле, а именно свойство myText (которое находится во внутреннем объекте класса ComplexObject) необходимо связать со свойством text текстового поля. Код инициализации:

public var destinationField : TextInput = new TextInput();
public var complexInstance : ComplexObject = new ComplexObject();


Как было сказано выше, нам необходимо связать complexInstance.simpleInstance.myText с destinationField.text. При помощи data binding это делается так:
BindingUtils.bindProperty(
		destinationField, 
		"text", 
		complexInstance, 
		["simpleInstance","myText"]);


В качестве объекта-цепочки в данном примере используется массив иерархии. В дальнейшем все элементы массива объединяются через точку в одну строку, которая используется в качестве свойства объекта «пункт отправления». Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import src.ComplexObject;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var complexInstance :  ComplexObject = new ComplexObject();
         public function init():void
         {
            destinationField.text = "";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(destinationField, 
				"text",
				complexInstance, 
				["simpleInstance","myText"]);
         }
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer"/>
</mx:Application>


Составной объект

Составной объект – в этом случае объект-цепочка представляет из себя объект типа:

var object : Object = {name:<имя свойства>, getter: <функция получения значения>};

имя свойства — есть строка, в которой содержится имя свойства «объекта отправления»
функция получения значения – данная функция имеет вид:

function (host:<тип объекта «пункта отправления» >) : <тип возвращаемого значения>
{
	//тело функции в которой мы можем обращаться к параметру host,
	// зная что это объект «пункт отправления»
}


Для понимания данного вида объекта-цепочки можно использовать очень простой пример data binding. Например, у нас есть экземпляр класс ArrayCollection и текстовое поле. Условием нашей задачи будет отключение текстового поля при достижении количества объектов содержащихся в нашей коллекции 10 штук. Объявление необходимых переменных:

public var array : ArrayCollection = new ArrayCollection();
public var destinationField : TextInput = new TextInput();


Использование data binding в данном случае:
BindingUtils.bindProperty(
		destinationField, 
		"enabled", 
		array, 
		{
			name:"length",
			getter : function (host : ArrayCollection):Boolean 
						{ return host.length<10; } 
		});


Как мы видим, код прост и понятен. В данном случае data binding будет работать так, как нам необходимо: отключать текстовое поле при количестве элементов в коллекции больше либо равным 10 и соответственно включать при изменении количества элементов в меньшую сторону. Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.collections.ArrayCollection;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var array : ArrayCollection = new ArrayCollection();
         public function init():void
         {
            destinationField.text = "some text";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindProperty(
				destinationField, 
				"enabled", 
				array, 
				{
					name:"length",
					getter : function (host : ArrayCollection):Boolean 
								{ return host.length>=10; } 
				});
         }
      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer">
      <mx:Button label="add element" click="array.addItem(0);"/>
      <mx:Text text="array length: {array.length}"/>
   </mx:VBox>
</mx:Application>


Метод bindSetter класса BindingUtils.


Рассмотрим другой способ data binding. Данный способ заключается в том, что мы можем вызывать подобие call back функции при изменении свойств объекта «пункта отправления».
Рассмотрим простой пример. Допустим, у нас есть коллекция и текстовое поле. Условия задачи таковы: в коллекции хранятся числовые значения, данные значения добавляются в коллекцию извне. Нам необходимо отображать сумму всех элементов коллекции в текстовом поле (причем сумма должна быть всегда актуальной). Т.е. с точки зрения data binding мы должны, при изменении длинны коллекции, пересчитывать сумму всех её элементов. В данном случае используется метод bindSetter класса BindingUtils.

Для начала инициализация переменных:

public var destinationField : TextInput = new TextInput();
public var array : ArrayCollection = new ArrayCollection();


А теперь опишем data binding операцию:

BindingUtils.bindSetter(calculateSum , array, "length");
Где calculateSum функция вида
function  ( input : <тип свойства объекта «пункт отправления»>):void
{
	//тело функции   
}


В нашем случае реализация функции calculateSum будет такова:

public function calculateSum(input : Number):void
{
	var sum:Number = 0;
	for (var i:int = 0; i<input; i++)
	{
		sum += array[i];
	}
	destinationField.text =  sum.toString();
}


Т.е. все как мы и предполагали. При изменении длинны коллекции, пересчитывается сумма
её элементов и выводится в текстовое поле.
Полный код приложения использующего данный метод data binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.collections.ArrayCollection;
         import mx.controls.TextInput;
         import mx.binding.utils.BindingUtils;
      
         public var destinationField : TextInput = new TextInput();
         public var array : ArrayCollection = new ArrayCollection();
         public function init():void
         {
            destinationField.text = "some text";
            parentContainer.addChild(destinationField);
            
            BindingUtils.bindSetter(calculateSum , array, "length");
         }
         
         public function calculateSum(input : Number):void
         {
            var sum:Number = 0;
            for (var i:int = 0; i<input; i++)
            {
               sum += array[i];
            }
            destinationField.text =  sum.toString();
         }

      ]]>
   </mx:Script>
   <mx:VBox id="parentContainer">
      <mx:Button label="add element" click="array.addItem(Math.random());"/>
      <mx:List width="100" height="200" dataProvider="{array}"/>
   </mx:VBox>
</mx:Application>


Использование метода bindSetter с составным видом объекта-цепочки


Это самый сложный вид data binding, но это не значит, что он очень сложен для понимания. Традиционно вернемся к постановке задачи.
Необходимо написать класс-коллекционер который собирает данные о изменениях произошедших с наблюдаемым компонентом. Класс будет хранить массив из информационных объектов (с шириной, высотой и позицией наблюдаемого компонента).
Напишем для начала класс:

package src
{
   import mx.collections.ArrayCollection;  
   public class Collector
   {
      public var array : ArrayCollection;      
      public function Collector():void
      {
         array = new ArrayCollection();
      }      
      public function collect(str:String):void
      {
         array.addItem(str);
      }
   }
}


Приложение будет выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
   <mx:Script>
      <![CDATA[
         import mx.core.UIComponent;
         import mx.binding.utils.BindingUtils;
         import src.Collector;
         public var collector : Collector = new Collector();
         
         public function init():void
         {
            BindingUtils.bindSetter(collector.collect, button, {name:"width", getter: returnInfo});
            data.dataProvider = collector.array;
         }
         
         public function returnInfo(host:UIComponent):String
         {
            return "width:" + host.width + "; height:" + host.height;
         }
         
         public function changeButton():void
         {
            button.width = Math.round(Math.random() * 200);
            button.height = Math.round(Math.random() * 100);   
         }
      ]]>
   </mx:Script>
   <mx:VBox>
   <mx:Button label="change" click="changeButton();"/>
   <mx:List id="data" width="200" height="400" />
   <mx:Button id="button" width="200" height="100" label="mybutton"/>
   </mx:VBox>
</mx:Application>


Обращаем внимание на функцию init() а именно строку

BindingUtils.bindSetter(collector.collect, button, {name:"width", getter: returnInfo});

Она несет в себе информацию о том, что при изменении свойства «width» компонента button у нас будет вызываться функция returnInfo, которая сформирует данные о кнопке, эти данные будут автоматически переданы в функцию collector.collect() в которой они будут соответствующим образом обработаны.

Вот собственно и всё, что я хотел рассказать.

P.S. Топик специально писался для блога Adobe Flex.
Я думаю, он туда обязательно попадёт.
Tags:Flexdata bindingпривязка данныхсвязывание данныхинтерактивные взаимодействия
Hubs: IT-companies
+25
2.9k 44
Comments 19
Popular right now
Курс по аналитике данных
December 7, 202064,200 ₽SkillFactory
Data Analyst
December 8, 2020102,000 ₽SkillFactory
Python для анализа данных
December 9, 202024,900 ₽SkillFactory
Профессия Data Scientist
December 9, 2020162,000 ₽SkillFactory
Специализация Data Science
December 9, 2020114,000 ₽SkillFactory