#1
  1. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Sep 2006
    Posts
    660
    Rep Power
    31

    Finding the exact length of an XML node? Attempting XML dynamic menu.


    I have decided to give XML a go, so I thought I would start with something small : ) and give creating a dynamic menu a try.

    I load my menu headings using textField and I can not get my code to find out the actual length of each menu heading. It all produces 100. I wanted to get each individual lengths so I can divide it by 2 and positon that to the center of my menu buttons.

    I am also try to turn my menu buttons into buttons, so I can add my eventlisteners, but it doesnt seem to work. The menu text seems to get in the way, even though I made them non selectable.

    If you think there is a better way of writing my code, then please say so, so that I can learn the proper way of writing code.

    This is my XML code
    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <menu_list>
    <menu title="1">Home</menu>
    <menu title="2">About Us</menu>
    <menu title="3">Services</menu>
    <menu title="4">Contact Us</menu>
    </menu_list>
    This is my AS3 code
    Code:
    // create var to hold menu_mc class
    var menu_btn_holder:menu_btn_mc = new menu_btn_mc();
    var menu_btns:Vector.<MovieClip> = new Vector.<MovieClip>(5,true);
    var textHead:TextField;
    
    // create a variable that will hold the XML class
    var myXML:XML = new XML();
    
    // create an instance of the XML Loader class to load the XML file to
    var myLoader: URLLoader = new URLLoader();
    
    // menu_btns amount
    var menuLength:Number;
    
    
    // load the XML file into the instance
    myLoader.load(new URLRequest("menu.xml"));
    
    // process the XML file, listen to when xml is loaded and execute function
    myLoader.addEventListener(Event.COMPLETE, processXML);
    
    function processXML(e:Event):void{
    	// ignore white spaces in the XML data
    	XML.ignoreWhitespace = true;
    	myXML = new XML(e.target.data); // define myXML as the data listened to in myLoader
    	MovieClip(root).menuLength = myXML.menu.length(); // send menu amount created to var menuLength
    	MovieClip(root).menuNumber_txt.text = String(menuLength); // display how many menu buttons there are
    	// cycle through myXML and create menu buttons
    	for(var i:uint=0; i<myXML.menu.length(); i++){
    		trace(myXML.menu[i]);
    		menu_btn_holder = new menu_btn_mc; // create a new menu_btn_mc and load into var menu_btn_holder
    	    menu_btn_holder.x = i*stage.stageWidth/4; // position menu_btn_holder on stage
    		
    		menu_btns[i] = menu_btn_holder; // load menu_btn_holder to menu_btns array
    		stage.addChild(menu_btns[i]); // add menu btn to stage
    		menu_btns[i].buttonMode=true; // turn menu_btns into buttons
    		
    		// create text menu
    		var menuText:TextField = new TextField;
    		menuText.selectable = false;
    		// text format
    		var menuTextFormat:TextFormat = new TextFormat();
    		menuTextFormat.font="Impact";
    		menuTextFormat.size=14;
    		menuTextFormat.italic = true;
    		menuText.text = String(myXML.menu[i]);// load XML menu names into menu_head dynamic field
    		var menuTextWidth:Number;
    		menuTextWidth = menuText.length;
    		menuText.x = menu_btn_holder.x+menuTextWidth/2;
    		menuText.setTextFormat(menuTextFormat);
    		stage.addChild(menuText);
    		trace(menuText.width);
    	}
    }
    Thanks
  2. #2
  3. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    Hi, I made up a short sample to simulate what you have and it works for me. I wasn't sure if you meant these lines where giving you trouble:
    Code:
    MovieClip(root).menuLength = myXML.menu.length();
    MovieClip(root).menuNumber_txt.text = String(menuLength);
    For me menu.length() is 4 which is correct based on your sample XML. Here's my simple mockup which uses inline XML for the sake of easiness on my part:
    Code:
    var myXML:XML = <menu_list>
    <menu title="1">Home</menu>
    <menu title="2">About Us</menu>
    <menu title="3">Services</menu>
    <menu title="4">Contact Us</menu>
    </menu_list>;
    
    // Traces 4 which is correct since there are 4 menu items.
    trace(myXML.menu.length());
    
    // Traces 8 because "About Us" is 8 characters including the space.
    trace(myXML.menu[1].toString().length);
    Unless you really need the menuLength variable later on then I'd recommend not setting it as a root variable like that, just create a local variable for use within the processXML function and then forget about it afterwards.

    I just noticed something else. This line:

    menu_btn_holder = new menu_btn_mc;

    Should read:

    menu_btn_holder = new menu_btn_mc();

    Note the parenthesis () at the end. The reason I noticed that was because I was going to say that if you need to access the menu length later on, instead of using the root variable as you are at the moment you could just refer to stage.numChildren;.

    Adding children directly to the stage is a bad idea. The root clip is a child of the stage so you should add top level children to that:

    trace(this, stage, root, root.parent);

    Personally, with AS3, I never refer to root directly and always go with this as in most cases this makes it easier to move things around without worrying about the reference paths. So taking that into account, my last bit of advice would be changed to this.numChildren;.

    To save you some headaches later on you might want to create a container clip for your menu items i.e.
    Code:
    var menu_items:Sprite = new Sprite();
    this.addChild(menu_items);
    Then inside your loop you would use this instead of your current addChild line:

    this.menu_items.addChild(menuText);

    Then you can always find the number of menu items by this.menu_items.numChildren;. The reason I suggest this is because if you add any items to the stage at design time such as a logo or additional graphics then the number of children reported by root would be greater than the number of menu items.

    Finally, you don't need to maintain an array of the menu items as you currently do. You can always access them later on via:

    this.menu_items.getChildAt(0) as menu_btn_mc

    That gets a reference to the first menu item, that is returned as a DisplayObject so we then cast it to be a menu_btn_mc instance. You only need that last bit (as menu_btn_mc) if you are planning on calling any functions specific to that class. If you just want the x/y/scale etc then you can leave that bit off i.e.

    this.menu_items.getChildAt(0).x

    There is also getChildByName which works the same as above but instead you pass in the instance name of the clip you are after. For that to work you either need to create the asset at design time and give it an instance name via the properties panel or if you create things via code at runtime then you give them a name:
    Code:
    var cat:Sprite = new Sprite();
    cat.name = "Kitty";
    cat.graphics.beginFill(0xcc0000);
    cat.graphics.drawRect(0, 0, 100, 100);
    this.addChild(cat);
    
    // Later on...
    
    this.getChildByName("Kitty").x = 200;
    Sorry that all of the above is a bit of a ramble, I'm hungry and in a rush so I just wrote it as I saw it. Hope it helps.
    Quis custodiet ipsos custodes?
  4. #3
  5. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    For your text getting in the way problem, there are actually two problems. First, you should add menuText as a child of menu_btn_holder. At the moment you are adding them both as children of Stage. Remember to check out what I said in the last post about kids of Stage.
    Code:
    menuText.selectable = false;
    
    // Stop the text being mouse interactive.
    menuText.mouseEnabled = false;
    
    // Replaces - stage.addChild(menuText);
    menu_btn_holder.addChild(menuText);
    Quis custodiet ipsos custodes?
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Sep 2006
    Posts
    660
    Rep Power
    31
    Thanks for the response Tann!!
    I am going through it slowly, just trying to understand the basic logic and then applying what you are saying. Though most of it, I am still trying to grasp.

    Can't seem to get the addChild inside a child working.
    menu_btn_holder.addChild(menuText);

    It ends up only displaying "home'

    I am trying to create functions and eventlisteners inside my for loop code.
    Code:
    //addEventListeners
    		menu_btns[i].addEventListener(MouseEvent.CLICK, menuClick[i]);
    		function menuClick[i](e:Event):void{
    		trace("hit"+[i]);
    		}
    How do you go about creating eventListeners and functions using the for loop?
  8. #5
  9. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    Hi, so I took the original code you posted, mixed in what I said in the previous posts I made and then added an event listener so you can get an idea of how it should all go. I haven't tested this as I just wrote it in the quick reply box ^_^
    Code:
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    
    // create an instance of the XML Loader class to load the XML file to
    var myLoader: URLLoader = new URLLoader();
    myLoader.load(new URLRequest("menu.xml"));
    myLoader.addEventListener(Event.COMPLETE, processXML);
    
    function processXML(e:Event):void
       {      
          XML.ignoreWhitespace = true;
          
          var myXML:XML = new XML(e.target.data); 
          var count:int = myXML.menu.length(); 
          this.menuNumber_txt.text = count.toString();
       
          // Holds all the menu items.
          var mainmenu:Sprite = new Sprite();
          this.addChild(mainmenu);
                
          // Since the textformat is the same for all the menu items we might as well just create it once and then reuse the same TextFormat instance.
          var menuTextFormat:TextFormat = new TextFormat();
          menuTextFormat.font = "Impact";
          menuTextFormat.size = 14;
          menuTextFormat.italic = true;
          
          // Declare variables we will reuse in the loop here so we don't keep recreating them.
          var menu_item:menu_btn_mc;
          var menu_text:TextField;
          
          for (var i:int = 0; i < count; i++)
             {		
                menu_item = new menu_btn_mc();
                mainmenu.addChild(menu_item);
                
                menu_item.x = i * stage.stageWidth / 4;
                menu_item.buttonMode = true;            
                menu_item.addEventListener(MouseEvent.CLICK, this.doClick);
    				
                menu_text = new TextField;
                menu_text.selectable = false;
                menu_text.mouseEnabled = false;
                menu_text.defaultTextFormat = menuTextFormat;                        
                menu_text.text = myXML.menu[i].toString();
                menu_text.x = Math.round(100 - (menu_text.width * 0.5));           
                menu_item.addChild(menuText);
             }
       }  
    
    function doClick(event:MouseEvent):void
       {
          trace((event.target.getChildAt(0) as TextField).text, "was clicked");
       }
    You don't want to create the event handler function inside the loop as it means you will be creating separate instances of the same function each time. By creating it outside the loop and outside the processXML function you can just refer to the same one.

    I think this line might boggle you a bit so I'll break it up and describe it some:

    menu_text.x = Math.round(100 - (menu_text.width * 0.5));

    Because menu_text is now a child of menu_item it automatically will be positioned at the same x/y as menu_item. It's a TextField within a clip, hope that helps make it a bit clearer.

    We do Math.round as you should always put TextFields at integer positions to avoid blurry text. This was definitely an issue with older Flash versions although I think it's actually unnecessary now. I still do it as a force of habit.

    The "100" bit is because you are trying to center the TextField within the menu_item clip. To do that we have to know the width of menu_item. I'm assuming that you've created an object in the library and linked it with your "menu_btn_mc" class. So you could swap 100 for menu_item.width * 0.5. Of course if you happen to know the width and you're sure it won't change then feel free to use a hard coded value. I just picked 100 for an example.

    I imagined you had a 200px wide block as the button, so I divided that by 2 and got 100. So then I take the width of the TextField (after the TextFormat is applied and the text is written) and then divide that by 2. Then I subtract that half width from the half width of the button rectangle. I just do *0.5 instead of /2 as it's faster to multiply than divide and in this case *0.5 gives the same result as /2 i.e. 50%.
    Quis custodiet ipsos custodes?
  10. #6
  11. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    In the above code, this line has been bummed up by the forum code parser:

    menu_text.text = myXML.menu.toString();

    It should read:

    menu_text.text = myXML.menu[i].toString();
    Quis custodiet ipsos custodes?
  12. #7
  13. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Sep 2006
    Posts
    660
    Rep Power
    31
    I can't seem to get my add child into child working.

    Let me get this straight. When the loop goes, each time it adds the menu_item to the mainmenu, it adds it one level up at a time in the parent child relationship. Therefore it would be mainmenu as the parent, then it be menu_item at level 1, menu_item at level 2, etc.

    I tried replicating it again so I can get a better grasp of it
    Code:
    var stageWidth=this.stage.width;
    trace(stageWidth);
    
    var myXML:XML;
    var myLoader:URLLoader = new URLLoader();
    myLoader.load(new URLRequest("menu.xml"));
    
    myLoader.addEventListener(Event.COMPLETE, processXML);
    
    // create variable to store text field class
    var menuHead:Sprite = new Sprite();
    this.addChild(menuHead);
    
    function processXML(e:Event):void{
    	XML.ignoreWhitespace = true;
    	myXML = new XML(e.target.data);
    	for(var i:uint=0; i<myXML.menu.length(); i++){
    		var menuText:TextField = new TextField();
    		menuText.text=String(myXML.menu[i]);
    		menuText.x=i*stage.stageWidth/myXML.menu.length();
    		menuText.y=50;
    		this.menuHead.addChild(menuText);
    	}
    }
    
    trace(this.menuHead.numChildren); // find out how many children are in menuHead
    this.menuHead.getChildAt(2).y = 300;
    When I trace menuHead.numChildren, I get 0?
    When I try to move one of the menuHead, I get an error that my children reference is "out of bounds"
  14. #8
  15. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    That's because you are tracing it before the function has been called so no children have been created yet. Now we're moving into explaining event handlers.

    So you create "myLoader" earlier on which is used to load in the "menu.XML" file. Loading external files is not an instant thing. It takes time to finish even though sometimes it appears instant. Even just a gap of a millisecond means alot in programming terms. Here's what that could look like:
    • Start loading external XML file.
    • Try using the data - error - because the data has not finished loading.
    • Now data finishes loading.

    The problem is that you tried to use the data before it had finished loading. To make things easier you can use event handlers which only fire off once certain events have taken place. You have already created one:

    myLoader.addEventListener(Event.COMPLETE, processXML);

    That means that the "processXML" function only gets called once the Event.COMPLETE event has fired. That only happens once the data has finished loading. That in turn means that anything inside processXML will only occur once the data has finished loading.

    So from a slightly different perspective, anything and I mean anything that relies on the external data should not happen until the processXML function has been called. So in the code you posted, the trace and .y assignment should really be moved inside processXML, after the for loop. Using that same example from before:
    • Add an event listener to the XML loader.
    • Start loading external XML file.
    • Event.COMPLETE gets fired once the data has finished loading.
    • Try using the data - success - because the data has finished loading.
    • Cheer for joy!

    Event handlers can be confusing because although they can be written at any point of your code you may assume they get called in that order but that is not the case as hopefully I have managed to explain.
    Quis custodiet ipsos custodes?
  16. #9
  17. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Sep 2006
    Posts
    660
    Rep Power
    31

    Thumbs up


    Thanks Tann!! Well explained.

    I see what you are saying. I thought that the for loop would have slowed things down before following to the last 2 lines of code but I didn't realise that it was relying on the function to be called first.

    Next question in Tann XML lesson 101

    Is there any possible way to have more than 1 root node in an XML file and access it?
    eg.
    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <menu_list>
    <menu title="1">Home</menu>
    <menu title="2">About Us</menu>
    <menu title="3">Services</menu>
    <menu title="4">Contact Us</menu>
    </menu_list>
    <gallery>
    <image title="harry">image01.jpg</image>
    <image title="sally">image02.jpg</image>
    <image title="tom">image03.jpg</image>
    <image title="kelly">image04.jpg</image>
    </gallery>
    and access it like
    trace(myXML.gallery.image[1]);

    or do I have to put it all one main root node like the following?
    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <XMLContainer>
    <menu_list>
    <menu title="1">Home</menu>
    <menu title="2">About Us</menu>
    <menu title="3">Services</menu>
    <menu title="4">Contact Us</menu>
    </menu_list>
    <gallery>
    <image title="harry">image01.jpg</image>
    <image title="sally">image02.jpg</image>
    <image title="tom">image03.jpg</image>
    <image title="kelly">image04.jpg</image>
    </gallery>
    </XMLContainer>
    and access it like
    trace(myXML.xmlContainer.gallery.image[1]);

    I can't seem to get this working? I have seen XML files written with different tags to build a whole website in one XML file. Like they have one for the gallery, menu, content, etc.
    Last edited by rePete; September 16th, 2011 at 05:43 PM.
  18. #10
  19. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    I'm not sure, my hunch is that it only works with a single root node. I always do it like you have in your second example.
    Quis custodiet ipsos custodes?
  20. #11
  21. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    Using your second XML block, your trace would be:

    trace(myXML.gallery.image[1].toString());

    To access the image titles would be:

    trace(myXML.gallery.image[1].@title);
    Last edited by Tann San; September 16th, 2011 at 05:33 PM.
    Quis custodiet ipsos custodes?
  22. #12
  23. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    Sep 2006
    Posts
    660
    Rep Power
    31
    OK. got it. Should have followed properly what you clearly wrote. What I don't understand is why it will not work once you refer to the root node.

    I was including the root node in the code
    eg.
    trace(myXML.xmlContainer.gallery.image[0].toString());

    It seems you can only write it with reference to the child node and not the root node.... hmm
    Last edited by rePete; September 16th, 2011 at 07:37 PM.
  24. #13
  25. No Profile Picture
    Gotta get to the next screen..
    Devshed Supreme Being (6500+ posts)

    Join Date
    Nov 2003
    Location
    Legion of Dynamic Discord
    Posts
    6,675
    Rep Power
    3164
    Because when it loads the actual XML, the XML instance myXML becomes the root node. The XML class requires that you have a single root node. In your example, once the data has loaded myXML is a reference to the root node xmlContainer.

    It is possible to have more than one root node but then you would be using an XMLList. The "children()" method of the XML class actually returns an XMLList of the child nodes.

    I wouldn't lose too much sleep over it but if you really want to give yourself a headache then I recommend you read Senoculars XML article which covers everything in maximum gory detail ^_^
    Quis custodiet ipsos custodes?

IMN logo majestic logo threadwatch logo seochat tools logo