Call JS function from Flash

No Comments

งานอย่างนึงที่ต้องทำผ่านมาคือเรียกโปรแกรมที่เขียนด้วย Adobe AIR จากเว็บ แต่เรียกอย่างเดียวมันธรรมดาไป ต้องมีการรับค่าจากฟอร์มในเว็บด้วย! ตอนแรกก็คิดว่าไม่ยาก ที่ไหนได้เจออิทธิฤทธ์ของ IE6 เข้าไปถึงกับงงเลยทีเดียว แต่จะเขียนแล้วก็เขียนถึงวิธีเรียกทั้งหมดเลยละกัน

ใน Flex framework มีคลาสสำหรับติดต่อกับ Javascript จะใช้เรียกฟังก์ชั่น หรือจะให้ฟังก์ชั่น Javascript มาเรียกใน Flash คือ flash.external.ExternalInterface เวลาเรียกฟังก์ชั่น Javascript จาก Flash ก็ง่ายๆ ด้วยคำสั่ง

ExternalInterface.call("javascriptFunctionName", "argument1", "argument2", ... , "argumentN")

แต่ความยากไม่ได้อยู่ที่ตรงนี้ สิ่งที่ทำพลาดจนทำให้ต้องจดคือสิ่งที่อยู่ใน HTML เพราะบราวเซอร์แต่ละตัวเห็น tag object embed ต่างกัน สุดท้ายแล้วทางที่ดีคือคัดลอกเอาจากใน Flex API Document มานั่นแหละ ปลอดภัยที่สุด

     <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
             id="ExternalInterfaceExample" width="40" height="22"
             codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab">
         <param name="movie" value="movie.swf" />
         <param name="quality" value="high" />
         <param name="bgcolor" value="#869ca7" />
         <param name="allowScriptAccess" value="sameDomain" />
         <embed src="movie.swf" quality="high" bgcolor="#869ca7"
             width="40" height="22" name="ExternalInterfaceExample" align="middle"
             play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
             type="application/x-shockwave-flash"
             pluginspage="http://www.macromedia.com/go/getflashplayer">
         </embed>
     </object>

ลองมาดูของจริงเลยละกัน Javascript ในหน้านี้หน้าตาเป็นอย่างด้านล่าง

<script type="text/javascript">
function hello() {
     return 'Hello, World'
}
</script>
เมื่อกดปุ่มด้านล่าง จะเรียก function hello เพื่อเอามาแสดงใน Alert



ไม่ยากเท่าไหร่แต่เรื่องนี้สอนให้รู้ว่า จงทำตามเอกสารมันทุกอย่าง อย่าไปลองอะไรจากเว็บอื่นให้ปวดหัวอีก

Function declaration style and variable scope problem

3 Comments

เป็นปัญหาที่ยังไม่สามารถทำให้เกิดขึ้นอีกในโค้ดแบบอื่นได้ แต่เอามาเขียนไว้ก่อนเพราะมันสร้างความปวดหัวให้กับคนเขียน Javascript มาก่อนมากมาย และไม่รู้จะเจอมันอีกทีเหมื่อไหร่ แต่ไม่สามารถเอาโค้ดต้นฉบับทั้งก้อนมาใส่ในนี้ได้ เลยขอแค่เล่าแล้วเอา stacktrace มาแปะใส่ไว้ละกัน

เหตุการณ์เริ่มต้นเกิดจาก @pitiphong_p เจอบั๊กหนึ่งที่ไม่รู็จะแก้อย่างไรเข้าเพราะใน Flash Builder Debugger บอกว่าตัวแปรต่างๆ มีค่าครบสมบุรณ์หมด แต่ error บอกไม่สามารถหาตัวแปรนั้นได้

[Fault] exception, information=TypeError: Error #1010: A term is undefined and has no properties.
Fault, CheckboxListRenderer.as:21
 21            data.selected = checkbox.selected
(fdb) bt
#0   this = [Object 32588233, class='global'].<anonymous>(event=[Object 659079665, class='flash.events::Event']) at CheckboxListRenderer.as:21
#1   EventDispatcher/dispatchEventFunction() at <null>:0
#2   this = [Object 655130785, class='mx.controls::CheckBox'].EventDispatcher/dispatchEvent(_arg1=[Object 659079665, class='flash.events::Event']) at <null>:0
#3   this = [Object 655130785, class='mx.controls::CheckBox'].UIComponent/dispatchEvent(event=[Object 659079665, class='flash.events::Event']) at UIComponent.as:9440
#4   this = [Object 655130785, class='mx.controls::CheckBox'].Button/http://www.adobe.com/2006/flex/mx/internal::setSelected(value=true, isProgrammatic=false) at Button.as:1204
#5   this = [Object 655130785, class='mx.controls::CheckBox'].Button/clickHandler(event=[Object 591642521, class='flash.events::MouseEvent']) at Button.as:2798

ตอนแรกก็พยายามคาดเดาปัญหาไปต่าง ๆ อาจเกิดจากการ bind ตัวแปรผิดที่ หรือตอนกำหนดค่าผิด แต่ถ้าอย่างนั้น ทำไมตอน debug ถึงสามารถเอาค่าออกมาดูได้หละ ลองดูโค้ดเจ้าปัญหาซักนิดก่อนละกัน

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package sample
{
  import mx.binding.utils.BindingUtils
  import mx.containers.HBox
  import mx.controls.Alert
  import mx.controls.CheckBox
  import mx.core.IFactory
 
  import flash.events.Event
 
  public class CheckboxListRenderer extends HBox {
 
    [Bindable]
    public var listRenderer:IFactory
 
    public var checkbox:CheckBox = new CheckBox()
 
    private var instance:*
 
    private var checkboxChange = function(event:Event):void {
      if (data.hasOwnProperty('selected')) {
        data.selected = checkbox.selected
      }
    }
 
    public override function set data(item:Object):void {
      super.data = item
      if (item && item.hasOwnProperty('selected')) {
        checkbox.selected = item.selected
      }
    }
 
    protected override function createChildren():void {
      super.createChildren()
      addChild(checkbox)
 
      if (listRenderer != null) {
        instance = listRenderer.newInstance()
        addChild(instance)
      }
      trace("Checkbox list renderer created children")
    }
 
    protected override function commitProperties():void {
      super.commitProperties()
      horizontalScrollPolicy = "off"
      verticalScrollPolicy = "off"
 
      // Handle change in targeting phase for changing data selected flag.
      checkbox.addEventListener(Event.CHANGE, checkboxChange)
 
      BindingUtils.bindProperty(instance, "data", this, "data")
    }
 
  }
}

รับรองได้เลยว่าถ้าใครเอาโค้ดด้านบนไปลองเล่นดูเองจะไม่เจอปัญหาแน่นอน ซึ่งถึงวันนี้ยังไม่เข้าใจเหมือนกันว่าทำยังไงถึงจะเกิดปัญหาได้ แต่วิธีแก้นั้นง่ายมากคือ เปลี่ยนวิธีประกาศฟังก์ชั่นจาก

20
21
22
23
24
    private var checkboxChange = function(event:Event):void {
      if (data.hasOwnProperty('selected')) {
        data.selected = checkbox.selected
      }
    }

เป็น

20
21
22
23
24
    private function checkboxChange(event:Event):void {
      if (data.hasOwnProperty('selected')) {
        data.selected = checkbox.selected
      }
    }

ปัญหาทุกอย่างก็หายไปอย่างปริศนา ทิ้งไว้แต่เครื่องหมายคำถามว่าทำไมขอบเขตของฟังก์ชั่นถึงต่างกันเพียงแค่เปลี่ยนรูปแบบการประกาศฟังก์ชั่นเท่านั้น และไม่สามารถทดลองซ้ำกับการเขียนโค้ดแบบง่าย ๆ ได้ แต่หลังจากนี้รู้แล้วว่า จะไม่ประกาศฟังก์ชั่นในรูปแบบตัวแปรอีก เพราะไม่อยากเจอปัญหาแปลกๆ แบบนี้ให้นั่งปวดหัวหาวิธีแก้อีกแล้ว

Flex Event Propagation

1 Comment

ปล่อยเรื่องนี้ค้างคาใจมานานเหลือเกิน เพราะไม่ได้ใช้ Event ครบทุก Phase อย่างจริงจัง วันนี้ต้องทำให้ Checkbox List ทำงานเร็วขึ้นแต่ไม่อยากแก้ List ของ Adobe ตรงๆ หรือทำไรเพิ่มเติมก็ต้องเล่นกับ Event แทนนี่แหละ อ่านคร่าวๆ ขั้นตอนการส่งต่อ Event ของ Flex มีแค่สามขั้นคือ Capturing Phase, Targeting Phase, Bubbling Phase ลองดูภาพด้านล่างเผื่อเข้าใจง่ายขึ้น


ภาพด้านบนแสดงการซ้อนกันของ Component ต่างๆ ที่สร้างขึ้น มี Application เป็น Component ที่อยู่นอกสุด ตามด้วย HBox และ Button ถ้าเขียนเป็น mxml ก็จะได้อย่างด้านล่าง

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Box>
<mx:Button />
</mx:Box>
</mx:Application>

เมื่อมีคนกดปุ่มสิ่งที่เกิดขึ้นคือ component ที่อยู่นอกสุดซึ่งก็คือ Application จะดักจับ event ได้ก่อน จากนั้นจะส่งต่อไปที่ Box และ Box จะส่งต่อให้ Button อีกที การส่งต่อ Event ในช่วงนี้เรียกว่า Capturing Phase เมื่อถึง Button ซึ่งเป็นปุ่มที่เรากดแล้ว ช่วงที่อยู่ตรงปุ่มเรียกว่า Targeting Phase จากนั้น Button จะส่ง Event กลับไปยัง Box และ Box ส่งต่อให้ Application อีกที เรียกว่า Bubbling Phase
เอาหละ มาดูผลลัพธ์กันดีกว่าว่าใช้พวกนี้ช่วยแล้วเป็นยังไง หรือจะ ดาวโหลด ไปดูเต็มๆ ก็ได้




ของแถม อันนี้ทำเพื่อทดสอบว่า Event มันส่งต่อยังไงบ้าง


เกี่ยวข้อง:

  1. Event Propagation in Adobe help

Older Entries