Question

I'm rendering display objects to the stage depending on the given XML elements, as you can see here:

PageRenderer.as

private static var curElements:Dictionary = new Dictionary();

//renders the current page
        private static function renderCode(pageCode:XML):void
        {

            if (pageCode)
            {
                //loop all elements from top to bottom
                for each (var child:XML in pageCode.descendants())
                {
                    var render:DisplayObject = ElementRenderer.render(child);
                    if (render)
                    {
                        WebBuilder.mainDisplay.addChild(render);
                        curElements[child] = render;    
                    }
                }   
            }
        }

So, each XML element has an associative rendered shape. If you have the XML element, you can access the shape like this: var shape:DisplayObject = curElements[xmlElement];

This works fine within the same class.

However, now I also have the ElementSelector class which deals with selection of shapes and reflects the actions done with the shape to the xml element. To do this, one needs to get the XML element when the shape has been clicked:

ElementSelector.as

private static var currentSelection:XML;    

//fired when the stage has been clicked
            private static function stageClicked(event:MouseEvent):void
            {
                //if the element selector has been enabled
                if (enabled)
                {
                    var allPages:Array = CodeHandler.getPageCodes();
                    var mainElement:XML = allPages[CodeHandler.getCurPageId()];
                    var targetElement:XML = CodeHandler.getDeepestElementAtPos(mainElement, 0, event.stageX, event.stageY)["xml"];
                    if ((targetElement.localName() != "page") && (targetElement != currentSelection))
                    { //we have a new element selected
                        Utils.log("ElementSelector now selecting: " + targetElement.localName());
                        select(targetElement);
                    }
                    else if ((targetElement.localName() == "page") && (currentSelection))
                    { //the selection has been lost
                        Utils.log("ElementSelector now dropping selection.");
                        deselect();
                    }
                }
            }

            //sets the new selection
            private static function select(element:XML):void
            {
                if (currentSelection) deselect();
                currentSelection = element;

                var curElements:Dictionary = PageRenderer.getElements();
                var render:DisplayObject = curElements[element];
                trace(render);
            }

    //drops the current selection
            private static function deselect():void
            {
                currentSelection = null;
            }

I added the StageClicked event function only so you have an idea of how my procedure works. That function itself works fine. The problem seems to lie within the select() method.

Now, the strange thing is, that curElements[element] returns undefined and render returns null.

I tried to debug it like this (at bottom of select method):

for (var key:Object in curElements) 
                {
                    if (key === element)
                    {
                        trace("key === element");
                    }
                                trace(curElements[key] === curElements[element]);
                    trace(curElements[key]);
                    trace(curElements[element]);
                }

It returns:

key === element
false
[object Shape]
undefined

Why is this happening? If a === b, then dic[a] === dic[b], right? Well, apparently not.

So, the key really is there... and it's the same as the key it was set with.

Why isn't it returning the associative display object?

Was it helpful?

Solution

I'm sorry if this doesn't help, but I think that the way you've constructed your application, using XML objects as dictionary keys, is flawed in the first place. I would greatly suggest having some other way to index the elements, such as having an id attribute in the XML that is used for the key. Or you could simply use an incremented integer index.

Another option would be to create a class (or interface) for the objects returned by ElementRenderer.render(), and have your ID as a property in that interface:

public interface IRenderedElement
{
  function get id() : String;
}

You could then store your elements in a flat array or Vector.

If the reason you are using the XML nodes is that you need to store the data that is declared in the XML, then I would strongly suggest that you parse the XML and store the data in a custom Element class instead of inside the weakly-typed, dynamic XML object. The above interface would be a good way to place properties for such data.

If all you need to do is find which element was clicked, then I would suggest adding event listeners to all of the elements, and using the Event.currentTarget property to identify the element.

var element:DisplayObject = ElementRenderer.render(child);
render.addEventListener(MouseEvent.CLICK, handleElementClick);

/* ... */

function handleElementClick(ev : MouseEvent) : void
{
  var element : IRenderedElement = ev.currentTarget as IRenderedElement;

  // do something here
}

You could also simply listen on the parent of all the elements, i.e. WebBuilder.mainDisplay, and then use the Event.target property to find the exact object that was clicked.

OTHER TIPS

Dictionary uses strict equality (===) for key comparison on non-primitive object keys. So key == elements might return true but key === elemets will be returning false in your case. As element and key object are different.
Try using associative array instead of Dictionary.

I've run across this problem, and couldn't find a solution. But I think I at least understand what's happening.

If you trace the value of your 'child' node in your code above, like this:

for each (var child:XML in pageCode.descendants()) {
  trace(child);
}

you'll get the VALUE of that node. So if the node is <tag>content</tag>, 'content' will trace out.

This is maybe annoying, probably surprising. Most other actionscript objects trace their object type by default when traced. But numbers and strings will trace their value, so maybe it's not so surprising...

I've done some tests, and it seems like xml nodes can be added to a dictionary just fine. If you do so and trace the contents of the dictionary, they'll be there. And you can even verify that the keys of the dictionary are strictly equivalent to the nodes in your original xml.

But when you try to retrieve the value from the dictionary using the node as a key, it fails. My theory is that when try to get the value you want by saying:

curElements[child]

the value between the brackets is being evaluated and returning its node value, not the object itself. So, if this was the node you added: <tag>content</tag>, the compiler thinks you want to retrieve curElements["content"] from the dictionary. And that was never added to the dictionary, so it returns null or undefined.

Well, if I'm right, and I think I am because the same problem drove me crazy for several hours this morning, then this is a drag! Despite the other posters comments, it would sometimes be very convenient to use xml nodes as dictionary keys. There are certainly cases where it's the simplest path.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top