HTMLLoader in AIR

来源:互联网 发布:中国网络诗歌网站 编辑:程序博客网 时间:2024/05/22 03:38

Why I need this? Not important for now. Here is the Adobe online doc about this class HTMLLoader. It plays a role like browser.


Explore Basis


Now let's set up our programs for this experiment. I need the output HTML is generated by a currently popular Javascript rich text editor, such as Tiny_MCE. Of course, since it only gives a piece of HTML markups in body, I create a simple HTML template, it is a kind of "shell".


HTML template:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Tiny Output</title><style>html, body {background-color: transparent;}</style></head><body><!-- page content --><body></html>


I create a CSS rule to specify the page's background is transparent, you will understand later why is that. After I insert my content generated byTiny_MCE, it is:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Tiny Output</title><style>html, body {background-color: transparent;}</style></head><body><h1>Welcome to the TinyMCE editor demo!</h1><p>Feel free to try out the different features that are provided, please note that the MCImageManager and</p><p><img style="float: right;" title="Laughing" src="emoticonBig.gif" alt="Laughing" border="0" /></p><p>MCFileManager specific functionality is part of our commercial offering. The demo is to show the integration.</p><p>We really recommend Firefox as the primary browser for the best editing experience, but of course, TinyMCE is compatible with all major browsers.</p><h2>Got questions or need help?</h2><p>If you have questions or need help, feel free to visit our community forum! We also offer Enterprise support solutions. Also do not miss out on the documentation, its a great resource wiki for understanding how TinyMCE works and integrates.</p><h2>Found a bug?</h2><p>If you think you have found a bug, you can use the Tracker to report bugs to the developers.</p><p>And here is a simple table for you to play with.</p><table style="border-color: #cc3399; border-width: 1px; width: 136px; height: 61px; border-style: solid;" border="1"><tbody><tr><td><strong>Product</strong></td><td><strong>Cost</strong></td><td><strong>Really?</strong></td></tr><tr><td>TinyMCE</td><td>Free</td><td>YES!</td></tr><tr><td>Plupload</td><td>Free</td><td>YES!</td></tr></tbody></table><p>Enjoy our software and create great content!</p><p><img src="115933554.jpg" alt="" width="353" height="470" /></p><p> </p><p>Oh, and by the way, don't forget to check out our other product called Plupload, your ultimate upload solution with HTML5 upload support!</p><body></html>


In my Chrome, it would be rendered like:

tiny-mce-output-browser


The next up is my Flash program. I create a fla file with 1000px by 1500px stage, in addition, give it another color rather than white. Some buttons might be helpful.


After reading the online spec, I created the document class file as:

package  {import flash.display.Sprite;import flash.html.HTMLLoader;import flash.net.URLRequest;import flash.events.Event;import flash.events.MouseEvent;public class basicHTMLLoader extends Sprite{private var _html:HTMLLoader;public function basicHTMLLoader() {this._html = new HTMLLoader();this._html.width = 1000;this._html.height = 600;this._html.paintsDefaultBackground = false;this._html.y = 80;addChild(this._html);this.addEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler);}private function addedToStageHandler($e:Event):void{navBtn.addEventListener(MouseEvent.CLICK, this.navButtonClicked);//this._html.addEventListener(Event.HTML_RENDER, this.htmlRenderedHandler);this._html.addEventListener(Event.HTML_BOUNDS_CHANGE, this.boundsChangedHandler);this._html.addEventListener(Event.COMPLETE, this.completedHandler);heiBtn.addEventListener(MouseEvent.CLICK, this.heiButtonClicked);}private function navButtonClicked($e:MouseEvent):void{var $urlReq:URLRequest = new URLRequest("http://192.168.0.123/lab/richTxTedtotpt/tiny.html");this._html.load($urlReq); }private function downButtonClicked($e:MouseEvent):void{this._html.y -= 40;}private function heiButtonClicked($e:MouseEvent):void{//this._html.height += 40;this._html.height = this._html.contentHeight;}private function completedHandler($e:Event):void{trace("completed.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);}private function boundsChangedHandler($e:Event):void{trace("bounds changed.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);}private function htmlRenderedHandler($e:Event):void{trace("render html up to date.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);}}}


The main purpose of this sample program is to explore the events flow around HTMLLoader, and I output the dimensions as well, because you will found that is the difficulty when dealing with HTMLLoader. Nothing would disturb you as much as that does.

sample-program-blank


And the output of console is:

bounds changed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 600render html up to date.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 600


So, that is to say, to assign a value to the width and height of HTMLLoader instance, would cause it dispatch event of HTML_BOUNDS_CHANGE and HTML_RENDER.


Now, click navigate button, see what happen:

with-content-loaded


The console output more lines:

bounds changed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 0HTML content height: 0completed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 1159bounds changed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 1159render html up to date.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 1159


So, it seems that the load of HTML content will cause these events be dispatched again. And along with COMPLETE event. Besides, we also get some information, the HTML content's dimension: 1000px by 1159px.


And now, click the height button to assign the HTML content height to the HTMLLoader instance.

html-content-reheight


The console output:

render html up to date.HTML Loader width: 1000HTML Loader height: 1159HTML content width: 1000HTML content height: 1159


And if we move the assignment of height operation inside heiButtonClicked() method into completedHandler(), we get the HTML page displayed correctly.


The Problem with Drag & Drop


What's the problem now? It is simple to explain it in action, let's add some handling methods:

private function htmlLoaderMouseDownHandler($e:MouseEvent):void{trace("down");this._html.addEventListener(MouseEvent.MOUSE_MOVE, htmlLoaderMouseMoveHandler);this._html.addEventListener(MouseEvent.MOUSE_UP, htmlLoaderMouseUpHandler);}private function htmlLoaderMouseMoveHandler($e:MouseEvent):void{trace("move");}private function htmlLoaderMouseUpHandler($e:MouseEvent):void{trace("up");this._html.removeEventListener(MouseEvent.MOUSE_MOVE, htmlLoaderMouseMoveHandler);this._html.removeEventListener(MouseEvent.MOUSE_UP, htmlLoaderMouseUpHandler);}


This is the most used method to make some element in Flash draggable. And set it up in completedHandler():

this._html.addEventListener(MouseEvent.MOUSE_DOWN, htmlLoaderMouseDownHandler);


And the result comes to prove that the HTMLLoader instance never dispatch MOUSE_DOWN event, according to the console output, the htmlLoaderMouseDownHandler() never be executed.


And, you know, that is a big trouble.


Potential Solution


I have an alternative, that is create a Bitmap instance, copy the pixels from the HTMLLoader instance. Let's do it:

package  {import flash.display.Sprite;import flash.display.BitmapData;import flash.display.Bitmap;import flash.html.HTMLLoader;    import flash.net.URLRequest;import flash.events.Event;import flash.events.MouseEvent;public class cloneHTMLLoader extends Sprite{private var _html:HTMLLoader;private var _copy:Sprite;private var _canvas:Bitmap;private var _downPos:Number;private var _bmPos:Number;public function cloneHTMLLoader() {this._html = new HTMLLoader();this._html.width = 1000;this._html.height = 600;this._html.paintsDefaultBackground = false;this._html.y = 80;this.addEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler);}private function addedToStageHandler($e:Event):void{            navBtn.addEventListener(MouseEvent.CLICK, this.navButtonClicked);this._html.addEventListener(Event.HTML_RENDER, this.htmlRenderedHandler);this._html.addEventListener(Event.COMPLETE, this.completedHandler);}private function navButtonClicked($e:MouseEvent):void{var $urlReq:URLRequest = new URLRequest("http://192.168.0.123/lab/richTxTedtotpt/tiny.html");this._html.load($urlReq); }private function completedHandler($e:Event):void{trace("completed.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);this._html.height = this._html.contentHeight;var $bd:BitmapData=new BitmapData(this._html.width, this._html.height, true, 0x00FFFFFF);$bd.draw(this._html);this._canvas = new Bitmap($bd);this._canvas.y = 6;this._copy = new Sprite();this._copy.y = 90;this._copy.addChild(this._canvas);trace("copy width: " + this._copy.width);trace("copy height: " + this._copy.height);this.addChild(this._copy);stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);}private function htmlRenderedHandler($e:Event):void{trace("render html up to date.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);}private function stageMouseDownHandler($e:MouseEvent):void{_downPos = $e.stageY;_bmPos = this._copy.y;stage.removeEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);}private function stageMouseMoveHandler($e:MouseEvent):void{var $curPos:Number = $e.stageY;this._copy.y = _bmPos + ($curPos - _downPos);}private function stageMouseUpHandler($e:MouseEvent):void{_downPos = 0;_bmPos = 0;stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);}}}


But, after I click navigate button, I got nothing:

sample-program-blank


And the output of console is:

render html up to date.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 600completed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 1159copy width: 1000copy height: 1159render html up to date.HTML Loader width: 1000HTML Loader height: 1159HTML content width: 1000HTML content height: 1159


What we can see is that the _copy Sprite has dimension of 1000*1159, but it seems that this could not be an evidance, to prove that we did everything right, since we still got nothing showed. I put everything inside the COMPLETE event handler, but it seems that when this event dispatched, it is not really completed.

Then I change the logic of this program, I add a button, and attach an listener to it for clicking, I do the copying inside the handler:

package  {import flash.display.Sprite;import flash.display.BitmapData;import flash.display.Bitmap;import flash.html.HTMLLoader;    import flash.net.URLRequest;import flash.events.Event;import flash.events.MouseEvent;public class cloneHTMLLoader extends Sprite{private var _html:HTMLLoader;private var _copy:Sprite;private var _canvas:Bitmap;private var _downPos:Number;private var _bmPos:Number;public function cloneHTMLLoader() {this._html = new HTMLLoader();this._html.width = 1000;this._html.height = 600;this._html.paintsDefaultBackground = false;this._html.y = 80;this.addEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler);}private function addedToStageHandler($e:Event):void{            navBtn.addEventListener(MouseEvent.CLICK, this.navButtonClicked);this._html.addEventListener(Event.HTML_RENDER, this.htmlRenderedHandler);this._html.addEventListener(Event.COMPLETE, this.completedHandler);cloneBtn.addEventListener(MouseEvent.CLICK, this.cloneButtonClicked);}private function navButtonClicked($e:MouseEvent):void{var $urlReq:URLRequest = new URLRequest("http://192.168.0.123/lab/richTxTedtotpt/tiny.html");this._html.load($urlReq); }private function cloneButtonClicked($e:MouseEvent):void{var $bd:BitmapData=new BitmapData(this._html.width, this._html.height, true, 0x00FFFFFF);$bd.draw(this._html);this._canvas = new Bitmap($bd);this._canvas.y = 6;this._copy = new Sprite();this._copy.y = 90;this._copy.addChild(this._canvas);trace("copy width: " + this._copy.width);trace("copy height: " + this._copy.height);this.addChild(this._copy);stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);}private function completedHandler($e:Event):void{trace("completed.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);this._html.height = this._html.contentHeight;}private function htmlRenderedHandler($e:Event):void{trace("render html up to date.");trace("HTML Loader width: " + this._html.width);trace("HTML Loader height: " + this._html.height);trace("HTML content width: " + this._html.contentWidth);trace("HTML content height: " + this._html.contentHeight);}private function stageMouseDownHandler($e:MouseEvent):void{_downPos = $e.stageY;_bmPos = this._copy.y;stage.removeEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);}private function stageMouseMoveHandler($e:MouseEvent):void{var $curPos:Number = $e.stageY;this._copy.y = _bmPos + ($curPos - _downPos);}private function stageMouseUpHandler($e:MouseEvent):void{_downPos = 0;_bmPos = 0;stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);}}}


This time, we click navigate button, and then click clone button, and we got what we expect:

html-content-reheight


Although this looks the same, it can be dragged vertically, and with the output:

render html up to date.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 600completed.HTML Loader width: 1000HTML Loader height: 600HTML content width: 1000HTML content height: 1159render html up to date.HTML Loader width: 1000HTML Loader height: 1159HTML content width: 1000HTML content height: 1159copy width: 1000copy height: 1159



So the problem can be boiled down to the event flow.


The Mechanism Behind the Scene


We come to one point, that is: we receive the HTML_RENDER events twice, because we change the height of HTMLLoader instance twice! One time is inside the contructor:this._html.height = 100, the other time is our COMPLETE event handler: this._html.height = this._html.contentHeight.

And that means, we should do the cloning on the second time we receive HTML_RENDER event. At first, I was working on how to distinguish the two times of receiving it. But why I listen to it at the very beginning and bother with how to differentiate it? I just attach the listener when we need it, so let's splite the flow up, I then came to this:


package  {

import flash.display.Sprite;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.html.HTMLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.events.MouseEvent;

public class cloneOnHTMLRender extends Sprite
{
private var _html:HTMLLoader;
private var _copy:Sprite;
private var _canvas:Bitmap;

private var _downPos:Number;
private var _bmPos:Number;

public function cloneOnHTMLRender() 
{
this._html = new HTMLLoader();
this._html.width = 1000;
this._html.height = 100;
this._html.paintsDefaultBackground = false;
this.addEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler);
}



private function addedToStageHandler($e:Event):void
{
navBtn.addEventListener(MouseEvent.CLICK, this.navButtonClicked);

this._html.addEventListener(Event.COMPLETE, this.completedHandler);
}



private function navButtonClicked($e:MouseEvent):void
{
var $urlReq:URLRequest = new URLRequest("http://192.168.0.123/lab/richTxTedtotpt/tiny.html");
this._html.load($urlReq); 
}



private function completedHandler($e:Event):void
{
trace("completed.");
trace("HTML Loader width: " + this._html.width);
trace("HTML Loader height: " + this._html.height);
trace("HTML content width: " + this._html.contentWidth);
trace("HTML content height: " + this._html.contentHeight);

this._html.addEventListener(Event.HTML_RENDER, this.htmlRenderedHandler);
this._html.height = this._html.contentHeight;

}



private function htmlRenderedHandler($e:Event):void
{
trace("render html up to date.");
trace("HTML Loader width: " + this._html.width);
trace("HTML Loader height: " + this._html.height);
trace("HTML content width: " + this._html.contentWidth);
trace("HTML content height: " + this._html.contentHeight);

this._html.removeEventListener(Event.HTML_RENDER, this.htmlRenderedHandler);
clone();

}



private function stageMouseDownHandler($e:MouseEvent):void
{
_downPos = $e.stageY;
_bmPos = this._copy.y;

stage.removeEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);
}



private function stageMouseMoveHandler($e:MouseEvent):void
{
var $curPos:Number = $e.stageY;
this._copy.y = _bmPos + ($curPos - _downPos);
}



private function stageMouseUpHandler($e:MouseEvent):void
{
_downPos = 0;
_bmPos = 0;

stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, stageMouseMoveHandler);
stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseUpHandler);
}




private function clone():void
{
var $bd:BitmapData=new BitmapData(this._html.width, this._html.height, true, 0x00FFFFFF);
$bd.draw(this._html);
this._canvas = new Bitmap($bd);
this._canvas.y = 6;
this._copy = new Sprite();
this._copy.y = 90;
this._copy.addChild(this._canvas);

trace("copy width: " + this._copy.width);
trace("copy height: " + this._copy.height);

this.addChild(this._copy);

stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseDownHandler);
}


}

}


I highlight the change. I add the listener to listen to HTML_RENDER when got COMPLETE event, and inside the handler of HTML_RENDER, I remove its listener, and do the cloning.


It is done now.