XML события или XMLCover + XMLNotifier = Bindable XML

Как-то блуждал по блогам и наткнулся на страничку с описанием интересной скрытой возможности XML в ActionScript 3 – метод XML.setNotification(). Этот метод позволяет получать данные об изменениях в XML дереве, за которым установили такое наблюдение. Я воспользовался этим методом и написал ряд классов, которые позволяют получать события об изменениях в XML дереве и использовать биндинг с XML во Flex.

Список используемых классов:

  • aw.xml.XMLNotifier – Класс используемый для привязки EventDispatcher'а к XML узлу. Вещает событие XMLEvent.CHANGED при каждом изменении XML дерева, к которому привязан.
  • aw.xml.cover.XMLCoverProxy – Базовый класс позволяющий пользоваться синтаксисом E4X и обращаться к внутреннему XML/XMLList объекту без препятствий.
  • aw.xml.cover.XMLCoverList – Используется для прослушивания изменений в узлахсодержащихся в XMLList и обращения к его свойствам и методам.
  • aw.xml.cover.XMLCover – Используется для прослушивания узла XML и обращения к его свойствам и методам.
  • aw.events.XMLEvent – Событие изменения XML дерева. Содержит константы доступных событий.

XMLCover, основной класс, используемый для управления XML деревом. Но, если вам необходимо просто прослушивать изменения в XML дереве, то можете просто пользоваться классом XMLNotifier. Экземпляры этого класса, создаются только через статический метод getInstance().

Пример использования XMLNotifier:

  1. import aw.events.XMLEvent;
  2. import aw.xml.XMLNotifier;
  3. var xml:XML = <root></root>;
  4. var notifier:XMLNotifier = XMLNotifier.getInstance(xml);
  5. notifier.addEventListener(XMLEvent.CHANGED, this.changedHandler);
  6. function changedHandler(e:XMLEvent):void{
  7. trace(e.changeType);
  8. }
  9. xml.@attr = true; // attributeAdded
  10. xml.appendChild(<child/>); // nodeAdded
  11. xml.child = 'text value'; // textSet
  12. xml.child.setNamespace('urn:some-uri'); // namespaceSet

Весь пакет можно использовать вместе с Flex SDK и отдельно, к примеру, во Flash CS3. Но только при использовании совместно с Flex SDK для вас будет доступен биндинг. Первое название его было XMLWatcher, но пришлось переименовать из-за конфликта имён с mx.binding.XMLWatcher.

Классы XMLCover и XMLCoverList хранят ссылку на XML/XMLList объекты, но не мешают доступу к их свойствам, узлам и методам, т.е. создав экземпляр XMLCover, вы сможете через него делать практически любые операции с XML деревом. Практически любые, потому что я не смог реализовать фильтрацию списков через синтаксис E4X, к примеру

	xml.child.(@id=="5")[0].setName("newName");

выражение в круглых скобках и есть фильтр.

Оба класса содержат собственные объекты EventDispatcher и оба могут испускать события, сообщая о происходящем. Но сами они не слушают XML дерево, они пользуются услугами класса XMLNotifier, пересылая его события об изменении дерева.

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

Конструктор класса XMLCover принимает такие, не обязательные аргументы:

  • узел XML об изменениях состояния, которого нужно оповещать.
  • булево значение, указывающее на создание дочерних объектов XMLCover/XMLCoverList через синтаксис E4X. Любая попытка получить дочерний узел или список узлов будет приводить к созданию соответствующего объекта XMLCover или XMLCoverList, соответственно. Это необходимо для реализации биндинга во Flex и это простой способ подписаться на события дочерних узлов. По умолчанию, "true".
  1. import aw.xml.XMLCover;
  2. import aw.utils.object_utils;
  3. import flash.utils.getQualifiedClassName;
  4. var xml:XML = <root><child/></root>;
  5. var cover:XMLCover = new XMLCover(xml); // createTree = true, по умолчанию
  6. trace(getQualifiedClassName(cover.child)); // aw.xml.cover::XMLCoverList
  7. trace(getQualifiedClassName(cover.child[0])); // aw.xml::XMLCover
  8. cover.object_utils::createTree = false;
  9. trace(getQualifiedClassName(cover.child)); // XMLList
  10. trace(getQualifiedClassName(cover.child[0])); // XML

Конструктор XMLCoverList принимает объект XMLList в качестве первого аргумента, а второй аргумент совпадает со вторым аргументом конструктора класса XMLCover.
Оба класса содержат методы, описанные IEventDispatcer и несколько контрольных свойств в пространстве имён aw.utils.object_utils.

Свойства класса XMLCover, в пространстве имён aw.utils.object_utils:

  • xml – оригинальный объект XML. Свойство только для чтения.
  • notifier – возвращает ссылку на объект XMLNotifier используемый с данным объектом. Свойство только для чтения.
  • createTree – указывает на создание дочерних объектов XMLCover/XMLCoverList через синтаксис E4X.

Свойства класса XMLCoverList, в пространстве имён aw.utils.object_utils:

  • list – оригинальный объект XMLList. Свойство только для чтения.
  • notifierList – возвращает массив объектов XMLNotifier используемых с данным объектом. Свойство только для чтения.
  • createTree – указывает на создание дочерних объектов XMLCover/XMLCoverList через синтаксис E4X.
    1. import aw.xml.XMLCover;
    2. import aw.utils.object_utils;
    3. import aw.xml.cover.XMLCoverList;
    4. var xml:XML = <root><child/><child/><child/></root>;
    5. var cover:XMLCover = new XMLCover(xml);
    6. trace(cover.object_utils::xml.toXMLString()); // <root>...
    7. trace(cover.object_utils::notifier); // [object XMLNotifier]
    8. trace(cover.object_utils::createTree); // true
    9. var coverList:XMLCoverList = cover.child;
    10. trace(coverList.object_utils::list.length()); // 3
    11. trace(coverList.object_utils::notifierList); // [object XMLNotifier],[object XMLNotifier],[object XMLNotifier]

    События классов XMLCover/XMLCoverList:

    • changed – Событие, сопровождающее любое изменение в XML дереве. Узнать, что точно произошло, можно через свойство changeType объекта XMLEvent.
    • attributeAdded – Событие добавления атрибута.
    • 	xml.@attribute = 'value'; // attributeAdded
    • attributeChanged – Событие изменения значения атрибута.
    • 	xml.@attribute = 'new value'; // attributeChanged
    • attributeRemoved – Событие удаления атрибута.
    • 	delete xml.@attribute; // attributeRemoved
    • nodeAdded – Событие добавления узла.
    • 	xml.appendChild(<child/>); // nodeAdded
    • nodeChanged – Событие изменения узла, сработает при подмене узла.
    • 	xml.child = <newChild attribute="1"/>; // nodeChanged
    • nodeRemoved – Событие удаления узла.
    • 	delete xml.child[0]; // nodeRemoved
    • namespaceAdded – Событие добавления пространства имён.
    • 	xml.addNamespace('urn:some-uri'); // namespaceAdded
    • namespaceRemoved – Событие удаления пространства имён.
    • 	xml.removeNamespace('urn:some-uri'); // namespaceRemoved
    • namespaceSet – Событие установки пространства имён.
    • 	xml.setNamespace('urn:some-uri'); // namespaceSet
    • textSet – Событие добавления текстового узла, секции CDATA, если хотите. Если присваивать текст несуществующему узлу, то сначала сработает "nodeAdded".
    • 	xml.child = 'value'; // textSet
    • nameSet – Событие изменения имени у узла.
    • 	xml.child.setLocalName('newChild'); // nameSet

    Все события, на которые подписались через объекты XMLCover/XMLCoverList в свойстве "target" будут иметь соответствующий объект XMLNotifier, потому что в этих классах методы описаные в IEventDispatcher переадресовывают вызовы на необходимые объекты XMLNotifier. Константы с типами всех событий находятся в классе XMLEvent.

    Дополнительные свойства объекта XMLEvent:

    • xmlTarget:Object – Ссылка на XML узел или другой объект XML дерева который вызвал событие.
    • currentXmlTarget:Object – Ссылка на результирующий XML узел или другой объект XML дерева, которого коснулись изменения.
    • changeType:String – Строка указывающая тип изменения вызвавшего событие.
    • value:Object – Использованное значение.
    • detail:Object – Дополнительный параметр объект.

    В зависимости от события некоторые свойства могут иметь пустые значения.

    Пример использования событий:

    1. import aw.xml.XMLCover;
    2. import aw.events.XMLEvent;
    3. import aw.utils.object_utils;
    4. var xml:XML = <root><child id="1"/></root>;
    5. var cover:XMLCover = new XMLCover(xml);
    6. cover.addEventListener(XMLEvent.NODE_ADDED, this.nodeAddHandler);
    7. cover.addEventListener(XMLEvent.NAME_SET, this.nameSetHandler);
    8. cover.addEventListener(XMLEvent.CHANGED, this.changedHandler);
    9. function nodeAddHandler(e:XMLEvent):void{
    10. trace('nodeAddHandler', e.xmlTarget);
    11. }
    12. function nameSetHandler(e:XMLEvent):void{
    13. trace('nameSetHandler', e.xmlTarget);
    14. }
    15. function changedHandler(e:XMLEvent):void{
    16. trace('changedHandler', e.changeType, e.xmlTarget);
    17. }
    18. cover.child.appendChild(<grandchild/>);
    19. /*
    20. nodeAddHandler <child id="1">...
    21. changedHandler nodeAdded <child id="1">
    22. */
    23. xml.appendChild(<child id="2"/>);
    24. /*
    25. nodeAddHandler <root>...
    26. changedHandler nodeAdded <root>...
    27. */
    28. var node:XML = xml.child.(@id=="2")[0];
    29. node.setName(new QName('urn:some-uri', 'renamedChild'));
    30. /*
    31. nameSetHandler
    32. changedHandler nameSet
    33. */
    34. node.removeNamespace(new Namespace('urn:some-uri'));
    35. /*
    36. changedHandler namespaceRemoved
    37. */

    Пример биндинга:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="creationCompleteHandler();">
    3. <mx:Script>
    4.  
    5. import flash.utils.setTimeout;
    6. import aw.xml.XMLCover;
    7. public var xml:XML = <root><child attr="some value"/></root>;
    8. public var cover:XMLCover = new XMLCover(xml);
    9. public function creationCompleteHandler():void{
    10. setTimeout(resetValue, 3000);
    11. }
    12. public function resetValue():void{
    13. this.cover.child.@attr = 'Attribute value changed';
    14. lblInfo.text = 'changed';
    15. }
    16.  
    17. </mx:Script>
    18. <mx:Label text="{cover.child.@attr}"/>
    19. <mx:Label text="" color="0xff0000" id="lblInfo"/>
    20. </mx:Application>

    Пример биндинга, использование тега mx:Binding:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="creationCompleteHandler();">
    3. <mx:Script>
    4.  
    5. import flash.utils.setTimeout;
    6. import aw.xml.XMLCover;
    7. public var xml:XML = <root><child attr="some value"/></root>;
    8. public var cover:XMLCover = new XMLCover(xml);
    9. public function creationCompleteHandler():void{
    10. setTimeout(resetValue, 3000);
    11. }
    12. public function resetValue():void{
    13. this.cover.child.@attr = 'Attribute value changed';
    14. lblInfo.text = 'changed';
    15. }
    16.  
    17. </mx:Script>
    18. <mx:Binding source="cover.child.@attr" destination="lblAttributeValue.text">
    19. </mx:Binding>
    20. <mx:Label text="" id="lblAttributeValue"/>
    21. <mx:Label text="" color="0xff0000" id="lblInfo"/>
    22. </mx:Application>

    Пример биндинга, использование модели:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="creationCompleteHandler();">
    3. <mx:Script>
    4.  
    5. import aw.events.XMLEvent;
    6. import mx.binding.utils.BindingUtils;
    7. import flash.utils.setTimeout;
    8. import aw.xml.XMLCover;
    9. public var xml:XML = <root></root>;
    10. public var cover:XMLCover = new XMLCover(xml);
    11. public function creationCompleteHandler():void{
    12. this.cover.appendChild(<child attr="some value"/>);
    13. BindingUtils.bindProperty(this.lblAttributeValue, 'text', this.bindingModel, 'bind');
    14. setTimeout(resetValue, 3000);
    15. }
    16. public function resetValue():void{
    17. this.cover.child.@attr = 'Attribute value changed';
    18. lblInfo.text = 'changed';
    19. }
    20.  
    21. </mx:Script>
    22. <mx:Model id="bindingModel">
    23. <Model>
    24. <bind>{this.cover.child.@attr}</bind>
    25. </Model>
    26. </mx:Model>
    27. <mx:Label text="" id="lblAttributeValue"/>
    28. <mx:Label text="" color="0xff0000" id="lblInfo"/>
    29. </mx:Application>

    Пример биндинга, использование класса BindingUtils:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="creationCompleteHandler();">
    3. <mx:Script>
    4.  
    5. import aw.events.XMLEvent;
    6. import mx.binding.utils.BindingUtils;
    7. import flash.utils.setTimeout;
    8. import aw.xml.XMLCover;
    9. public var xml:XML = <root></root>;
    10. public var cover:XMLCover = new XMLCover(xml);
    11. public function creationCompleteHandler():void{
    12. this.cover.appendChild(<child attr="some value"/>);
    13. BindingUtils.bindProperty(this.lblAttributeValue, 'text', this.cover.child[0], '@attr');
    14. setTimeout(resetValue, 3000);
    15. }
    16. public function resetValue():void{
    17. this.cover.child.@attr = 'Attribute value changed';
    18. lblInfo.text = 'changed';
    19. }
    20.  
    21. </mx:Script>
    22. <mx:Label text="" id="lblAttributeValue"/>
    23. <mx:Label text="" color="0xff0000" id="lblInfo"/>
    24. </mx:Application>

    Ложка дёгтя:

    • Если использовать дерево XMLCover для обращения к дочерним XML узлам и их свойствам, то скорость работы с E4X падает в 6-12 раз.
    • Во Flex Builder 3 появляются варнинги при использовании биндинга через MXML синтаксис. Он просто не может обнаружить динамически создаваемые свойства.
    • При использовании объектов XML с классом Dictionary наткнулся на странную особенность класса Dictionary, которую посчитал багом. Поэтому для поиска нужного узла в Dictionary, воспользовался цыклом for…in, который тоже влияет на производительность.

    Скачать весь пакет.

    Метки: , , , ,

    Добавить комментарий

    Вы должны авторизоваться для отправки комментария.