Author Topic: Direct ActionScript interaction  (Read 1081 times)

BoppreH

  • Newbie
  • *
  • Posts: 13
Direct ActionScript interaction
« on: February 18, 2010, 02:03:58 PM »
I'm using the MegaZine API in a bigger project, and countless hours were wasted trying to make it work. It's gorgeous and has many incredibly useful features, but it's making me pull my hairs one by one. The main problem is that the API configuration is mostly based on XML files, but the rest of my application is entirely in AS (that requires lots of interactions with dynamically generated pages). Any attempts to circumvent this resulted in lost features and even more headaches.

My suggestion is enabling a more close-knitted integration between the API and the rest of the AS code. For example, at one point I had to get a reference to a loaded .swf page, and this is the code I had to use:
Code: (actionscript)
  1. var currentPageSide:IPageSide
  2. var pageTimeline:MovieClip = currentPageSide.elementProxies[0].element.interactiveObject.getChildAt(0).getChildAt(0)
And this returned me the swf timeline. I searched for hours in the API docs, forums and wiki, but this was the only way I found and took me quite some time to develop it.

Couldn't it be just
Code: (actionscript)
  1. var currentPageSide:IPageSide
  2. var pageTimeline:MovieClip = currentPageSide.getContent()

And for the book settings, why not
Code: (actionscript)
  1. megazine.autoDrag = false
instead of setting it via XML?

Or, if it's not a good idea to change settings during runtime, make a load method that accepts a "MegaZineConfiguration" object...

And why does the "megazine.addPage" method requires an XML object and not a class name or generator function?

I know there is the plugin layer, but it only works as a temporary solution, lost as soon as the page needs to be unloaded.


All this interactions are probably already there, but covered by a thick layer of bureaucracy. The following is a mockup code of what I really needed from this API:
Code: (actionscript)
  1. var configuration:MegaZineConfiguration = new MegaZineConfiguration()
  2. configuration.autoDrag = false
  3. configuration.maxLoaded = 25
  4. var megazine:MegaZine = new MegaZine(configuration) // or via a tweenlite-like object: { autoDrag: false, maxLoaded: 10 }
  5. addChild(megazine)
  6. megazine.load() // loads all that visual assets required, such as asul files and cursor icons
  7.  
  8. var pageGenerator(pageNumber:int):MyPage {
  9.    var page:MyPage = new MyPage(pageNumber) // MyPage can implement an IPage interface or be wrapped by a Page instance when added to the book
  10.    return page
  11. }
  12.  
  13. megazine.addPage(MyCover) // MyCover is a class that would be instantiated every time this page had to be loaded
  14.  
  15. megazine.addManyPages(pageGenerator, 40) // the function is called every time a new page has to be loaded, and the "pageNumber" parameter is passed as in the array.map method

And then you have a megazine instance with a cover and 40 dynamically generated pages, all using only ActionScript.


If there is already a way to do the above, please let me know. I'm still pulling hairs here.
« Last Edit: February 18, 2010, 02:20:18 PM by BoppreH »

Florian Nücke

  • κρύα πόδια
  • Administrator
  • Hero Member
  • *****
  • Posts: 1342
  • MegaZine3 Developer
    • MegaZine3
Re: Direct ActionScript interaction
« Reply #1 on: February 18, 2010, 06:29:55 PM »
My suggestion is enabling a more close-knitted integration between the API and the rest of the AS code. For example, at one point I had to get a reference to a loaded .swf page, and this is the code I had to use:
Code: (actionscript)
  1. var currentPageSide:IPageSide
  2. var pageTimeline:MovieClip = currentPageSide.elementProxies[0].element.interactiveObject.getChildAt(0).getChildAt(0)
And this returned me the swf timeline. I searched for hours in the API docs, forums and wiki, but this was the only way I found and took me quite some time to develop it.

Couldn't it be just
Code: (actionscript)
  1. var currentPageSide:IPageSide
  2. var pageTimeline:MovieClip = currentPageSide.getContent()

I don't want to tempt users to meddling with the actual elements on the page directly, as this could
easily break stuff. So that's why I won't expose the content directly. Still, it's not that complicated,
really. What you're doing implies you have a SWF loaded onto the page. To get access to those, do
something like this:
Code: (actionscript3)
  1. var yourSwf:DisplayObject = IImg(pageSide.elementProxies[0].element).swf;
Yes, you still need to go through the proxies, but that's necessary because elements can be unloaded.
Secondly, yes you'll need the index, because, after all, there can be any number of elements on a page
(which is why a simple getContent isnt' really doable, either -- well, it could always just return the first
element on the page, but that's kinda...).

Note that that only is true for elements defined via the XML, though. I added a possibility to add elements
to pagesides directly in the last commit (will be in 2.0.6), so you could just add your element to a page side
and work with those, then. To that "layer" you'll have direct access (via treating a pageside like a normal
DisplayObjectContainer). More on that below.

And for the book settings, why not
Code: (actionscript)
  1. megazine.autoDrag = false
instead of setting it via XML?

Or, if it's not a good idea to change settings during runtime, make a load method that accepts a "MegaZineConfiguration" object...

That's actually one of the parameters that could be changed afterward. Reason it's not changable via the API is because I didn't really think
it to be necessary, because normally you'd not change that during runtime (but once, in the XML). As for that config object, see below.

And why does the "megazine.addPage" method requires an XML object and not a class name or generator function?

I know there is the plugin layer, but it only works as a temporary solution, lost as soon as the page needs to be unloaded.

I get the feeling you don't like the idea of defining things in XML? ;)
Elements that should be unloaded / reloaded with the page must be defined in the XML (mainly so that they can be reloaded
once removed). And that's the default way of constructing a book. To add content manually, see below, I just added this, as
there seems to be some demand for this. Note that displayobjects added to a page this way cannot be unloaded / reloaded
by the engine, though.

All this interactions are probably already there, but covered by a thick layer of bureaucracy. The following is a mockup code of what I really needed from this API:
Code: (actionscript)
  1. var configuration:MegaZineConfiguration = new MegaZineConfiguration()
  2. configuration.autoDrag = false
  3. configuration.maxLoaded = 25
  4. var megazine:MegaZine = new MegaZine(configuration) // or via a tweenlite-like object: { autoDrag: false, maxLoaded: 10 }
  5. addChild(megazine)
  6. megazine.load() // loads all that visual assets required, such as asul files and cursor icons
  7.  
  8. var pageGenerator(pageNumber:int):MyPage {
  9.    var page:MyPage = new MyPage(pageNumber) // MyPage can implement an IPage interface or be wrapped by a Page instance when added to the book
  10.    return page
  11. }
  12.  
  13. megazine.addPage(MyCover) // MyCover is a class that would be instantiated every time this page had to be loaded
  14.  
  15. megazine.addManyPages(pageGenerator, 40) // the function is called every time a new page has to be loaded, and the "pageNumber" parameter is passed as in the array.map method

And then you have a megazine instance with a cover and 40 dynamically generated pages, all using only ActionScript.


If there is already a way to do the above, please let me know. I'm still pulling hairs here.

When using the latest revision in the trunk, you could do this:
Code: (actionscript3)
  1. var megazine:IMegaZine = new MegaZine();
  2. addChild(megazine);
  3. megazine.addEventListener(Event.COMPLETE, handleMZComplete, false, 0, true);
  4. megazine.loadXML(null, null, { autodrag : false, maxloaded : 25 }); // < new in the latest revision, allows passing
  5.                                                                     // parameters that override definitions in the XML
  6. function handleMZComplete(e:Event):void {
  7.    for (var i:int = 0; i < 40; i += 2) {
  8.        var page:IPage = megazine.addPage();
  9.        DisplayObjectContainer(page.even).addChild(yourContentGenerator(i));    // Also new in the latest revision, adding like this
  10.        DisplayObjectContainer(page.odd).addChild(yourContentGenerator(i + 1)); // will now add to a special layer where the content
  11.                                                                                // will not be removed on unloading a page.
  12.    }
  13. }
  14.  
  15. function yourContentGenerator(pageNumber:int):DisplayObject {
  16.    // returns some displayobject for page 'pageNumber'
  17. }

Note that elements this way will always be on top of elements defined in the book's XML, and below the pluginLayer.
They will not be unloaded if the page is unloaded.
For the Snark was a Boojum, you see.

Before you ask a question
          After you get an answer
  • please document your problem with the answer in the Project Wiki. (e.g. in the FAQs)
  • help others out if you can, by answering their questions on the forum.

BoppreH

  • Newbie
  • *
  • Posts: 13
Re: Direct ActionScript interaction
« Reply #2 on: February 18, 2010, 07:15:34 PM »
The XML parameter overriding is really cool, thanks.

But about
Quote
DisplayObjectContainer(page.even).addChild(yourContentGenerator(i));

Note that elements this way will always be on top of elements defined in the book's XML, and below the pluginLayer.
They will not be unloaded if the page is unloaded.
Not unloading them is actually bad for me, for performance reasons. That's the whole point of passing a generator or class as parameter, so the megazine can create new instances when needed.

I'll try to describe what I'm trying to do, so you have a user case:
It's a stamps album app that, at a certain part, has an actual album. A MegaZine instance. This book has over 100 pages that, aside from a few ones at the beginning, are all standardized. New pages can be easily created with a "new Page(pageNumber)" call.

Now, the MegaZine API requires me to define all pages in XML. I already have all the page code and symbols I need in the main swf, but because of this requirement I had to create a "page.swf" file that accepts the page number as URL parameter, and the XML ended up like this:
Code: (actionscript)
  1. <page>
  2. <img src="page.swf?number=0"/>
  3. </page>
  4.  
  5. <page>
  6. <img src="page.swf?number=1"/>
  7. </page>
  8.  
  9. <page>
  10. <img src="page.swf?number=2"/>
  11. </page>
And so on. Now, not only there is an overheat to load those swfs, but I'm obligated to put all the dist files in a webserver in order to test it (the ?parameter=x thing only works when loading from the web), AND there are all those security limitations that Flash imposes. On top of that, XML has to be declared as string, and thus have no code completion or compile-time checks. And that is why I don't to use XML in this case.

All that I needed was a function to tell the book "look, take this function/class and register it as page X. If you need an instance, just call it and pass the page number. And don't keep more than ~25 pages loaded at a time".

tl;dr: I need too many dynamically generated pages.

Florian Nücke

  • κρύα πόδια
  • Administrator
  • Hero Member
  • *****
  • Posts: 1342
  • MegaZine3 Developer
    • MegaZine3
Re: Direct ActionScript interaction
« Reply #3 on: February 18, 2010, 08:02:35 PM »
Hmm, well, you could take of the unloading / reloading by registering your page
content with the PageSide's state change event. Something like this, perhaps:
Code: (actionscript3)
  1. var megazine:IMegaZine = new MegaZine();
  2. addChild(megazine);
  3. megazine.addEventListener(Event.COMPLETE, handleMZComplete);
  4. megazine.load(null, null, {...});
  5. function handleMZComplete(e:Event):void {
  6.    for (var i:int = 0; i < 40; i += 2) {
  7.        var page:IPage = megazine.addPage();
  8.        new ContentManager(page.even);
  9.        new ContentManager(page.odd);
  10.    }
  11. }
  12.  
  13. // Where the ContentManager class would take care of adding / removing depending on the
  14. // load state of the page side, something like this
  15. class ContentManager {
  16.    // Store reference to pageside the manager is responsible for (as doc so we don't have to cast all the time later on).
  17.    private var pageSide:DisplayObjectContainer;
  18.    // Setup by registering event handler.
  19.    public function ContentManager(pageSide:IPageSide) {
  20.        pageSide.addEventListener(StateChangeEvent.STATE_CHANGE, handleStateChange);
  21.        this.pageSide = DisplayObjectContainer(pageSide);
  22.        if (pageSide.state == Constants.PAGE_SIDE_STATE_LOADED) {
  23.            load();
  24.        }
  25.    }
  26.    // On state change, check if now unloaded or loaded.
  27.    private function handleStateChange(e:StateChangeEvent):void {
  28.        if (e.newState == Constants.PAGE_SIDE_STATE_UNLOADED) {
  29.            // Unloaded, remove content.
  30.            unload();
  31.        } else if (e.newState == Constants.PAGE_SIDE_STATE_LOADED) {
  32.            // Loaded, add content.
  33.            load();
  34.        }
  35.    }
  36.    private function load():void {
  37.        pageSide.addChild(...); // wherever the content comes from
  38.    }
  39.    private function unload():void {
  40.        pageSide.removeChild(...); // and possibly more cleanup
  41.    }
  42. }

The reason the engine can't do that itself is that it doesn't know from where to get the data to add to the page.
For the Snark was a Boojum, you see.

Before you ask a question
          After you get an answer
  • please document your problem with the answer in the Project Wiki. (e.g. in the FAQs)
  • help others out if you can, by answering their questions on the forum.

BoppreH

  • Newbie
  • *
  • Posts: 13
Re: Direct ActionScript interaction
« Reply #4 on: February 18, 2010, 08:13:01 PM »
That's quite an interesting piece of code, I haven't thought about that before. Tried to look for MegaZine "page loaded" events, but not in the pages themselves. I'll try this way later.

Quote
The reason the engine can't do that itself is that it doesn't know from where to get the data to add to the page.
Well, that's exactly the reason there would be a content generator. The generator returns a DisplayObject and this object alone is added to the page. The only problem is see is the parameters passed to the generator, but I guess a array.map-like order would be acceptable.

I guess I can achieve this functionality with your code above. When I'm finished with this project (one or two days max) I'll try to automatize this (plugin? directly added to the megazine code?).

Florian Nücke

  • κρύα πόδια
  • Administrator
  • Hero Member
  • *****
  • Posts: 1342
  • MegaZine3 Developer
    • MegaZine3
Re: Direct ActionScript interaction
« Reply #5 on: February 18, 2010, 08:26:26 PM »
Hmm, so basically that would be like an alternative ElementProxy, no? Not loading
an engine element but a normal DisplayObject (and removing it again).

Realizing this via a plugin would be difficult, I imagine, as it's rather lowlevel. If you
can patch it into the core I'd definitely be interested in it!
For the Snark was a Boojum, you see.

Before you ask a question
          After you get an answer
  • please document your problem with the answer in the Project Wiki. (e.g. in the FAQs)
  • help others out if you can, by answering their questions on the forum.

mamru

  • Newbie
  • *
  • Posts: 1
Re: Direct ActionScript interaction
« Reply #6 on: April 23, 2010, 11:16:52 AM »
Code: (actionscript3)
  1. var megazine:IMegaZine = new MegaZine();
  2. addChild(megazine);
  3. megazine.addEventListener(Event.COMPLETE, handleMZComplete);
  4. megazine.load(null, null, {...});
  5. function handleMZComplete(e:Event):void {
  6.    for (var i:int = 0; i < 40; i += 2) {
  7.        var page:IPage = megazine.addPage();
  8.        new ContentManager(page.even);
  9.        new ContentManager(page.odd);
  10.    }
  11. }
  12.  
  13. // Where the ContentManager class would take care of adding / removing depending on the
  14. // load state of the page side, something like this
  15. class ContentManager {
  16.    // Store reference to pageside the manager is responsible for (as doc so we don't have to cast all the time later on).
  17.    private var pageSide:DisplayObjectContainer;
  18.    // Setup by registering event handler.
  19.    public function ContentManager(pageSide:IPageSide) {
  20.        pageSide.addEventListener(StateChangeEvent.STATE_CHANGE, handleStateChange);
  21.        this.pageSide = DisplayObjectContainer(pageSide);
  22.        if (pageSide.state == Constants.PAGE_SIDE_STATE_LOADED) {
  23.            load();
  24.        }
  25.    }
  26.    // On state change, check if now unloaded or loaded.
  27.    private function handleStateChange(e:StateChangeEvent):void {
  28.        if (e.newState == Constants.PAGE_SIDE_STATE_UNLOADED) {
  29.            // Unloaded, remove content.
  30.            unload();
  31.        } else if (e.newState == Constants.PAGE_SIDE_STATE_LOADED) {
  32.            // Loaded, add content.
  33.            load();
  34.        }
  35.    }
  36.    private function load():void {
  37.        pageSide.addChild(...); // wherever the content comes from
  38.    }
  39.    private function unload():void {
  40.        pageSide.removeChild(...); // and possibly more cleanup
  41.    }
  42. }


Thank you- that example helped me a lot,
but how would you make spreadpages with this method?
I've tried to set the isSpread property, but it can't be set.

Florian Nücke

  • κρύα πόδια
  • Administrator
  • Hero Member
  • *****
  • Posts: 1342
  • MegaZine3 Developer
    • MegaZine3
Re: Direct ActionScript interaction
« Reply #7 on: April 25, 2010, 04:07:02 PM »
For spread pages, just use addPage(<spreadpage/>) instead of the normal addPage() call.
For the Snark was a Boojum, you see.

Before you ask a question
          After you get an answer
  • please document your problem with the answer in the Project Wiki. (e.g. in the FAQs)
  • help others out if you can, by answering their questions on the forum.

landed

  • Newbie
  • *
  • Posts: 19
Re: Direct ActionScript interaction
« Reply #8 on: July 02, 2010, 02:38:45 PM »
I'm getting this far great ! but I am working with flex4 and the object gets added to the list but isn't showing in the page !

DisplayObjectContainer(homepage.even).addChild(bttn);

no errors just doesnt show up...but If I just add it like addElement in the class scope it shows so I know the "bttn" is good to display.

Florian Nücke

  • κρύα πόδια
  • Administrator
  • Hero Member
  • *****
  • Posts: 1342
  • MegaZine3 Developer
    • MegaZine3
Re: Direct ActionScript interaction
« Reply #9 on: July 02, 2010, 06:10:17 PM »
Of what type is bttn? If it's a flex object, this will most likely not work, because as far as I know flex components only work when added to other flex components (which is the reason you can't load flex SWFs into pages - well, at least it didn't work when I tried it once).
For the Snark was a Boojum, you see.

Before you ask a question
          After you get an answer
  • please document your problem with the answer in the Project Wiki. (e.g. in the FAQs)
  • help others out if you can, by answering their questions on the forum.

landed

  • Newbie
  • *
  • Posts: 19
Re: Direct ActionScript interaction
« Reply #10 on: July 02, 2010, 07:19:54 PM »
yes you have to do addElement then it works for me, but this is also peculiar to flex4 so I'm back on things again. I'm quite happy because your code base is quite a lot and I was thinking I might have to build my thing in flex from scratch, we still have to see.

My next problem is something like the following
Code: [Select]
private function doneFlipBook(e:Event):void
{
var pf:PassportFlip=passportView as PassportFlip;
var zine:MegaZine=pf.getBook();
var hp1:HomePage1=zine.getPage(0).even as HomePage1;
trace("hp1 ",HomePage1(zine.getPage(0).even));
//hp1.populateUserDetails(configData);
}

as soon as I try to cast it i loose access to populateUserDetails ... I am thinking I might need to just fool the compiler...

Thanks for making such a great piece of code open source.