Flash Builder 4 and Flex 4 Bible- P4
lượt xem 25
download
Flash Builder 4 and Flex 4 Bible- P4: When Macromedia first released Flash MX in 2002, the product was branded as the new way to build Rich Internet Applications (known by the acronym RIA). The term was invented at Macromedia to describe a new class of applications that would offer the benefits of being connected to the Internet, including access to various types of Web-based services, but would solve many of the nagging issues that had been inherent in browser-based applications since the mid-1990s....
Bình luận(0) Đăng nhập để gửi bình luận!
Nội dung Text: Flash Builder 4 and Flex 4 Bible- P4
- Chapter 4: Understanding the Anatomy of a Flex Application Any code in the external file is compiled as part of the MXML file and the ActionScript class it rep- resents but is executed after objects declared in MXML are instantiated so you can access these objects in your ActionScript code. Because the external file isn’t in XML format, you don’t need to embed the element or the CDATA block inside the file to protect the code. Follow these steps to create an external ActionScript file: 1. Choose File ➪ New ➪ ActionScript File from the Flash Builder menu. (Don’t select ActionScript Class — that’s a different sort of file I’ll describe in a later chapter.) 2. In the New ActionScript File dialog box, select the folder in which you want to cre- ate the file. External ActionScript files can go anywhere in the project source-code root folder, because you’ll explicitly refer to the file’s location when you link to it from an MXML file. I usually place the file in the same folder as the MXML file it’s linked to. 3. Enter the name of the file. It should have a file extension of .as, but the rest of the file- name is up to you. For an application named HelloWorld.mxml, the matching exter- nal ActionScript file would be helloWorld.as. 4. Click Finish to create the file. Note Notice that in this usage, the external ActionScript filename starts with a lowercase character. This doesn’t have any technical effect on the code, but it’s a way of indicating that it’s a simple file containing ActionScript code, as opposed to an ActionScript class (which, by object-oriented programming conventions, has an initial uppercase character). n After the file has been created, you link to it from the MXML file with the element and add a source property pointing to the external file. The application in Listing 4.2 embeds its ActionScript code in an tag set. Caution Any particular element can contain nested ActionScript or use the source property to link to an external ActionScript file, but it cannot do both at the same time. You can, however, have as many declarations in a single MXML file as you need. n LISTING 4.2 An MXML application with nested ActionScript
- Part I: Flex Fundamentals LISTING 4.2 (continued) [Bindable] private var currentResult:Number=0; [Bindable] private var currentInput:String=””; private function calculateHandler(event:Event):void { currentResult += Number(currentInput); currentInput=””; } private function selectHandler(event:TextEvent):void { currentInput += event.text; } ]]> On the Web The code in Listing 4.2 is available in the Web site files in the chapter04 project folder as CalculatorWithScript.mxml. n Listing 4.3 shows the same application after the ActionScript has been moved to an external file. LISTING 4.3 MXML application Calculator.mxml with linked ActionScript file
- Chapter 4: Understanding the Anatomy of a Flex Application xmlns:s=”library://ns.adobe.com/flex/spark” xmlns:mx=”library://ns.adobe.com/flex/mx” xmlns:components=”components.*”> On the Web The code in Listing 4.3 is available in the Web site files in the chapter04 project folder as Calculator. mxml. n You have just as much code to manage, but the XML markup is cleaner and easier to read. And, as shown in Listing 4.4, the ActionScript file now contains only the scripting code: LISTING 4.4 External ActionScript file calculator.as //ActionScript code for Calculator.mxml [Bindable] private var currentResult:Number=0; [Bindable] private var currentInput:String=””; private function calculateHandler(event:Event):void { currentResult += Number(currentInput); currentInput=””; } private function selectHandler(event:TextEvent):void { currentInput += event.text; } 123
- Part I: Flex Fundamentals On the Web The code in Listing 4.4 is available in the Web site files in the chapter04 project’s src folder as calculator.as. n Managing ActionScript code with Flash Builder Whether you’re working with MXML or ActionScript, Flash Builder’s Outline view enables you to easily find function and variable declarations within the source code. The Outline view appears in the lower-right corner of Flash Builder in the default Flex Development perspective. Using the Outline view with ActionScript When working with MXML, the default Outline view displays a tree of MXML elements. As shown in Figure 4.5, the element shows up as a single selectable object. New Feature In Flash Builder 4, MXML elements that represent compiler instructions always appear at the bottom of the Outline view, regardless of where they’re placed in the actual code. All visual objects appear at the top of the outline. n FIGURE 4.5 Flash Builder’s Outline view with the MXML editor To navigate to a specific function or variable declaration using the Outline view, click the Show class view icon at the top of the view. As shown in Figure 4.6, you’re now able to click a declara- tion and jump to that bit of code. When using the outline’s Class view, you can change the display with these other options that are accessed from buttons at the top of the Outline view: l Sort. Displays variables and functions in alphabetical order. l Hide Static Functions and Variables. Hides variables and functions that are marked with the static modifier. l Hide Non-Public Members. Hides variables and functions that aren’t marked with the public access modifier. 124
- Chapter 4: Understanding the Anatomy of a Flex Application FIGURE 4.6 Outline view and the Class view buttons Hide static Sort Class view Hide non-public Note You need to click an item only once in the Outline view to jump to the matching code. n Tip From any object reference in the ActionScript file, hold down Ctrl (Windows) or Ô (Mac) and click the refer- ence to jump to that object’s declaration. This works whether the declaration is in the ActionScript file or an external MXML file and for both custom classes and Flex library classes whose source code has been provided by Adobe. n Flash Builder 4 adds a new documentation feature that enables you to quickly get information about an ActionScript class from the Flex SDK. Move the cursor over a component declaration and wait about one second. As shown in Figure 4.7, a documentation window appears. If the available documentation for that component exceeds the height of the window, you can then press F2 to give the window focus. You can then resize or scroll through the window to see all the available documentation. Managing code in the ActionScript editor When you store ActionScript code in an external file, Flash Builder provides some additional code management tools. Code folding Code folding refers to the capability of the Flash Builder editor to fold, or collapse, certain sections of code and hide them from view. In an MXML editor, code folding is based on the source file’s MXML elements. As shown in Figure 4.8, an MXML file displays code folding icons at the begin- ning of each MXML element. You’ll see a folding icon for the tag that enables you to collapse that section of code to a single line. 125
- Part I: Flex Fundamentals FIGURE 4.7 Opening API documentation quickly within Flash Builder FIGURE 4.8 Code folding icons in an MXML file Code folding icons Clicking the icon reduces the MXML element at that line to a single line of displayed code. Then, when you move the cursor over the folding icon, you see a pop-up window showing the first of code in the collapsed section. Clicking the icon again expands it to full display. In an ActionScript file, because you are using Flash Builder’s ActionScript editor, code folding collapses function declarations instead of MXML elements. As shown in Figure 4.9, you can click any function’s folding icon and reduce it to a single line of code. You also can collapse all functions in a file to single-line display: 1. Right-click in the column of line numbers. 2. Choose Folding ➪ Collapse Functions to reduce all functions to single-line displays. 126
- Chapter 4: Understanding the Anatomy of a Flex Application FIGURE 4.9 Code folding icons in an ActionScript file Code folding icons Now all functions are displayed as single lines of code. And finally, moving the cursor over a folded icon that is in a collapsed state displays the contents of the folded function. Organizing import statements An import statement informs the compiler about the location of ActionScript classes it needs to compile in an application or component. Most ActionScript classes must be explicitly imported to be recognized by the compiler. This import statement makes a class named ArrayCollection available to the compiler: import mx.collections.ArrayCollection; In Flash Builder 2, the development environment helped you build an import list by creating import statements for classes you referred to as you typed. But later, if you removed a class refer- ence from the body of the code, the import statement would be left in the file. This doesn’t cause any harm to the application (import statements on their own don’t add size or functionality to a compiled application), but it could be confusing later when you opened the file and saw import statements that had nothing to do with the code’s functionality. In Flex Builder 3, the ActionScript editor added the capability to organize an ActionScript file’s import statements with a simple menu selection or keyboard shortcut. When you organize imports, unused import statements are removed and the ones you need are left in alphabetical order, grouped by package. Consider this list of import statements: import mx.controls.Alert; import flash.net.FileFilter; import flash.net.URLRequest; import mx.collections.ArrayCollection; import mx.validators.Validator; import flash.net.FileReference; 127
- Part I: Flex Fundamentals To organize this list, choose Source ➪ Organize Imports from the Flash Builder menu. (Or press the keyboard shortcut Ctrl+Shift+O.) After organization, the list now looks like this: import flash.net.FileFilter; import flash.net.FileReference; import flash.net.URLRequest; import mx.controls.Alert; import mx.validators.Validator; The import statement for the unused class is removed, and the remaining statements are alpha- betized and grouped by package. In Flash Builder 4, this feature is now available in MXML files as well. Just as with ActionScript files, you can now use the Organize Imports feature to alphabetize and group import statements and remove unused imports in the sections of your MXML documents. Using the Application Component The Application component is always declared as the root element of an MXML application file. It represents the top level of the application’s containership hierarchy. New Feature The Flex 4 Application component is part of the Spark component set. It replaces the Application con- tainer that was used in previous versions of Flex, now known as the MX Application component. You can still use the MX version if you prefer, but only the new version implements Flex 4’s advanced layout architec- ture and declarative skinning and is compatible with Flex projects created in Flash Catalyst. n The Flex 4 version of the Application component is defined as an ActionScript class with the fully qualified name spark.components.Application. The Application class supports important properties and styles that are not part of other Flex 4 containers. Table 4.2 shows these properties and styles. TABLE 4.2 Application Properties and Styles Property Purpose Example pageTitle A value that’s passed through to in place of the ${title} placeholder. backgroundColor A color value stated as a 128
- Chapter 4: Understanding the Anatomy of a Flex Application Property Purpose Example controlBarContent An array of visual objects that are laid out in a skin part named controlBarGroup and typed ... navigation elements... as a Group. The default skin “docks” the control bar at the top of the screen. frameRate The number of frames per second at which changes are reflected in Flash Player. The default in Flex is 24 frames/second. This prop- erty must be set in MXML. url A read-only property returning var currentURL:String = this. the URL from which the applica- url; tion SWF file was opened. Tip You can make typing appear to be smoother in a Flex application by increasing the frameRate. For example, if the cursor is in a TextArea or TextInput control and you hold down a key at 24 frames/second, the effect can be a bit “jumpy.” That is, the characters may not appear at an even rate. Setting the frameRate to 60 or 90 frames/second may noticeably improve this “animation.” In theory, this could have a negative effect on CPU usage on the client system, but in testing on a modern computer, it’s difficult to see a difference. n New Feature The MX version of the Application component had a static property named application that refer- enced the application itself. The commonly used expression was Application.application. In Flex 4, this functionality has been moved to a static property named topLevelApplication, in a new class named FlexGlobals. So, the new version of this expression is FlexGlobals.topLevelApplication. As with the Flex 3 version, this expression is defined in the API to return an Object, but the native type matches the application’s actual name. So, to create a variable that references an application named HelloWorld, use this syntax: private var myApp:HelloWorld = FlexGlobals.topLevelApplication as HelloWorld; n Note The url property refers to the URL from which the application SWF file was loaded. For example, when run- ning the URLDemo.mxml application from the local disk, the browser’s URL is displayed as: file:///C:/flex4bible/chapter04/bin-debug/URLDemo.html The url property returns this value: file:///C:/flex4bible/chapter04/bin-debug/URLDemo.swf n 129
- Part I: Flex Fundamentals Passing application parameters You pass parameters to the application from the browser using a special Flash Player variable named flashvars. If you’re using the HTML wrapper file that’s generated during compilation by Flash Builder 4, the flashvars variable is declared as a JavaScript object in the HTML wrapper file JavaScript code. You can add your own parameters by declaring named properties of the flashvars object: var flashvars = {}; flashvars.state=”New”; The flashvars object is then passed to Flash Player in the call to swfobject.embedSWF(): swfobject.embedSWF( “${swf}.swf”, “flashContent”, “${width}”, “${height}”, swfVersionStr, xiSwfUrlStr, flashvars, params, attributes); To retrieve these values at runtime, use the Application object’s parameters property. The parameters property is a dynamic object that enables you to address its named properties with dot notation, as in: currentState=this.parameters.state; Note If you’re hosting a Web-based Flex application on a dynamic application server such as ColdFusion or PHP, you can generate the flashvars variable dynamically. n Controlling application dimensions The default values for the Application component’s width and height are both 100 percent. These values are passed to Flash Player through the HTML wrapper file that’s generated by Flash Builder. For example, this code: results in these values being passed to Flash Player in the generated HTML wrapper page: swfobject.embedSWF( “URLDemo.swf”, “flashContent”, “300”, “200”, swfVersionStr, xiSwfUrlStr, flashvars, params, attributes); 130
- Chapter 4: Understanding the Anatomy of a Flex Application These dimension properties are then passed to the application by Flash Player. In contrast the minWidth and minHeight properties that are set automatically on new applications affect only the Flex application itself, and not the HTML wrapper file. Setting the layout property The Application component’s layout property controls how its nested visual objects are laid out on the screen. New Feature The new Application component is extended from SkinnableContainer, and uses Flex 4’s new component layout architecture. In previous versions of Flex, the application’s layout property was a simple string with possible values of vertical, horizontal, and absolute. In Flex 4 components, the layout property is set to an instance of a complex object. n The layout property is set to an instance of a class that extends a superclass named LayoutBase. These classes and their built-in properties, styles, and methods allow for flexible and portable cus- tomization of layout details. The Flex 4 SDK includes these prebuilt layout classes: l BasicLayout. The default. Places objects in the container based on object’s positioning properties: x, y, top, bottom, left, right, horizonalCenter, and verticalCenter. l HorizontalLayout. Lays out objects in a single row from left to right. l VerticalLayout. Lays out objects in a single column from top to bottom. l TileLayout. Arranges objects in either rows or columns of equally sized cells. This is designed to replace Flex 3’s Tile container. You typically declare the application’s layout property with an MXML child element: ...add visual objects here... This creates an instance of the VerticalLayout class and assigns it to the application’s layout property. Vertical and horizontal layout Settings of vertical and horizontal cause the application to lay out its nested visual objects automatically. As shown in Figure 4.10, setting layout to a VerticalLayout object causes objects in the application’s display list to appear in a single column. 131
- Part I: Flex Fundamentals FIGURE 4.10 An application with vertical layout Note The new Application component doesn’t implement the old horizontalAlign and verticalAlign styles. These are now implemented as properties of the layout classes. For example, HorizontalLayout supports verticalAlign, and VerticalLayout supports horizontalAlign. Both of these layout classes support padding properties: paddingTop, paddingBottom, paddingLeft, and paddingRight. n Figure 4.11 shows what happens when you change the Application object’s layout property to an instance of HorizontalLayout. Objects in the display list are laid out in a row from left to right. FIGURE 4.11 An application with horizontal layout 132
- Chapter 4: Understanding the Anatomy of a Flex Application Note The HorizontalLayout and VerticalLayout classes require the application to calculate the quantity and size of the nested controls at runtime, and then in a second pass to place them on the screen. This calcula- tion has to be re-executed each time the application is resized (for example, if the user resizes the browser). On slower computers, this process can seem a bit sluggish. One solution to improve client-side performance in this situation is to switch to BasicLayout, because the application then doesn’t have to do this calculation. n Basic layout An application with basic layout enables each object to be placed in a specific position relative to the top-left corner of the application. New Feature In Flex 3, the scheme now known as basic layout was called absolute layout. They have the same meaning, although certain implementation details have changed. n As shown in Figure 4.12, basic layout has the additional advantage of being able to overlap objects. When objects have alpha settings that enable transparency, as is the case with default settings of the new Button component, you can make objects show through each other from back to front. Note The z-index, or relative depth, of overlapping visual objects is controlled by their order in the container’s dis- play list. When declared in MXML, the last declared object has the highest z-index and overlaps any other objects with which it shares screen space. n FIGURE 4.12 An application with absolute layout and overlapping 133
- Part I: Flex Fundamentals Besides Application, these Spark components support the layout property: l Panel l Group l BorderContainer l Window (used only in desktop applications deployed with Adobe AIR) l WindowedApplication (used only in desktop applications deployed with Adobe AIR) l NavigatorContent (used to “wrap” Spark components you want to manage in a ViewStack) l ItemRenderer (used to render repeated elements in List components) I describe them in detail in Chapter 9. Summary In this chapter, I described the basic anatomy of a Flex application. You learned the following: l MXML and ActionScript 3 are the two programming languages you use for Flex development. l ActionScript 3 is based on the ECMAScript 4th Edition recommendation. l ActionScript’s syntax is similar to Java, JavaScript, C, C++, and C#. l MXML is a “convenience” language that compiles into ActionScript. l MXML is a pure XML-based language. l FXG is an XML language for describing graphics rendering as implemented in Flash Player 10. l The Flex 4 SDK includes ActionScript classes that, when declared with MXML, mimic FXG syntax. l You can combine MXML and ActionScript in a number of ways. l The new Spark-based Application component is the root element in a Flex application designed for Web deployment. l The Application class’s layout property can be set to instances of classes that extend LayoutBase. 134
- CHAPTER Using Bindings and Components I n Chapter 1, I described the object-oriented concept of modularity and how dividing an application into small pieces can increase developer IN THIS CHAPTER productivity and improve long-term maintenance of an application. I Using binding expressions also described the concept of encapsulation that encourages developers to cre- ate application building blocks that hide the details of a feature’s implemen- Creating MXML components tation from the rest of the application, and only expose tools in the module’s Instantiating MXML public interface that are needed to set and get the module’s information and components execute its functions. Creating component In this chapter, I describe some of the basic building blocks of a Flex appli- properties and methods cation that can improve its modularity and make it easier to manage over time. I start with a look at binding expressions and describe how they help Creating and using component you easily move data around an application. A binding expression can move libraries data from one object to another at runtime without explicit event handling Creating Flash-based controls or ActionScript statements. I describe a couple of binding syntax styles and show when to use each. This chapter also includes a description of how to create and use custom MXML components in a Flex application. In the last section of this chapter, I describe how to package and manage multiple components and classes in a component library using a Flex Library Project. On the Web To use the sample code for this chapter, import the chapter05.fxp Flex project file from the Web site files into your Flash Builder workspace. n 135
- Part I: Flex Fundamentals Using Binding Expressions As I previously described, a binding expression enables you to move data from one object to another at runtime without having to handle complex events or write lots of ActionScript code. Tip Binding expressions represent only one possible approach to managing data within a Flex application. Because they generate automatic event broadcasters and listeners, they can create significant application activity when overused. Sometimes it’s best just to assign object properties using ActionScript code. n The purpose of a binding is to “listen” for changes to an expression and to “broadcast” the expres- sion’s value to an object’s property. The expression that returns the value is known in a binding as the source. The expression to which you pass the value when it changes is known as the destination. Look at this example of two controls: If you want the first control’s text property value to be displayed in the second control, you refer to the first as the source and the second as the destination. Flex supports three binding syntax styles: l A simple, shorthand MXML-based version that wraps a binding expression in an attribute of an MXML declaration l A longhand MXML-based version that uses the tag l A longhand ActionScript-based version that uses the mx.binding.utils. BindingUtils class Note The longhand ActionScript-based version of creating a binding has some limitations compared to MXML. While the BindingUtils class enables you to create a binding at runtime, it does not support the use of simple ActionScript or ECMAScript for XML (E4X) expressions, and it doesn’t have as good a set of error and warning detection as bindings declared in MXML. n Shorthand MXML binding expressions In the shorthand MXML version, you start by assigning an id, or unique identifier, to the source control. This becomes the instance name of your object for future reference: In the destination control’s declaration, you use an ActionScript expression that refers to the source control’s text property, wrapped in {} characters: 136
- Chapter 5: Using Bindings and Components At runtime, if the source control’s text property changes, the destination control is updated at the same time. New Feature Flex 4 adds new syntax to create two-way bindings between components. In Flex 3, you could implement this sort of two-way transfer of data with redundant coding: In Flex 4, you can accomplish the same thing with a single binding expression in just one of the controls. Add the @ character as a prefix to the binding expression, placed before the braces: n Using The longhand MXML binding syntax uses an tag with properties of source and destination to define the two expressions: The tag can be used when the destination object is declared in ActionScript code, rather than MXML. Because shorthand syntax works only in the context of an MXML declaration, it just doesn’t work for this case. In the following code, a value entered into a TextInput control is passed to a pre-declared vari- able named myVar whenever the user makes a change. That variable’s value is then passed to the Label control using a shorthand binding expression. Tip You might not use the tag in the simplest Flex applications, but the first time you need to pass a value to an object or expression that’s declared in ActionScript, you’ll find it a valuable tool. n Making expressions bindable Most object properties in the Flex framework’s library are bindable, meaning that if the property’s value changes at runtime, the new value is broadcast to the listening destination object. When you 137
- Part I: Flex Fundamentals declare your own variables in ActionScript, their values aren’t automatically bindable; you have to mark them with a [Bindable] metadata tag to indicate that they should share new values with the rest of the application. Consider this code: The variable myVar shares its value with the destinationLabel control at application startup, but because the variable isn’t marked as bindable, any changes at runtime won’t be passed to the control. In fact, the compiler notices this problem and generates a compiler warning, as shown in Figure 5.1. FIGURE 5.1 A compiler warning for a binding to a non-bindable expression To fix this and get rid of the compiler warning, add the [Bindable] metadata tag before the vari- able declaration: The compiler warning disappears, and if the source expression’s value changes at runtime, the Label control correctly displays the new value. Tip The [Bindable] metadata tag must be placed immediately before the variable it modifies. If you prefer, you can place both on a single line: [Bindable] private var myVar:String=”Hello World”; n 138
- Chapter 5: Using Bindings and Components The View in Model-View-Controller A single Flex application can have dozens or hundreds of “views” — that is, screens or visual represen- tations of data that execute particular functions, collect data, or present information to the user. If you try to implement all these views in a single source-code file, the result can be a mess. Similarly, the application may need to make calls to remote servers to get data, and implement object structures in application memory in which to hold that data at runtime. In classic model-view-control- ler application architecture, the parts of the application that make the calls, and the classes that con- tain, or represent, the data, are known as model components. The controller part of the architecture is responsible for receiving and interpreting user input. In Flex, the controller is frequently implemented as a mapping of application events to actions that the applica- tion is capable of executing. You can create view components with either MXML or ActionScript, but for most purposes an MXML component is the simplest approach. And after you create the components, you need a way to share data with them and make them do things. In this chapter, I describe how to build the Flex application’s views as MXML components and how to design the components to hold and receive data. Using MXML Components As I described in Chapter 1, modularity means that you break up an application into pieces that are focused on particular application tasks. A modular application tends to be more stable and main- tainable than one that mixes many types of functionality into a single source-code file. Flex supports the object-oriented concept of modularity through the use of custom MXML compo- nents and ActionScript classes. In this section, I describe how to create and incorporate MXML components in a Flex application. Creating MXML components Like the application itself, an MXML component is defined in a source-code file with an extension of .mxml. At compilation time, an MXML component is rewritten as an ActionScript class where the name of the class matches the first part of the component’s filename. For example, if you create a file named MyComponent.mxml, the resulting ActionScript class is named MyComponent. Tip I strongly recommend that you create components in subfolders of the project source root folder, rather than the source folder itself. This enables you to group components by purpose into packages. For example, you might have one folder for forms, another for DataGrid and List components (data-aware components), a third for navigational tools, and so on. The names of the folders are up to you but should follow ActionScript naming conventions: letters, numbers, and underscore characters, always starting with a lowercase alphabeti- cal letter. n 139
- Part I: Flex Fundamentals Tip Because an MXML component is really an ActionScript class, I recommend that you follow object-oriented naming conventions for class definitions. Specifically this means that component filenames usually start with an initial uppercase character and use mixed-case after that. This is a convention, not a technical requirement, but it’s one that most Flex developers follow. n Component inheritance Each MXML component extends, or is derived from, an existing ActionScript class. You indicate which class the current component extends with the MXML file’s root element. So a class named MyComponent.mxml that extends the VGroup container looks like this: ...place additional MXML tags here... Caution Notice that the MXML component file’s root element includes the same three namespace and prefix declara- tions as the main application file. The http://ns.adobe.com/mxml/2009 namespace is required in all Flex 4 MXML files, and the Spark and MX namespaces are required if you use any of those components. When you create an MXML component using the New MXML Component wizard, all three namespaces are added to the component’s root element automatically. n The preceding MXML code results in the inheritance relationship described by the Unified Modeling Language (UML) diagram in Figure 5.2. FIGURE 5.2 The inheritance relationship between VGroup, the superclass, and the custom component, the subclass VGroup MyComponent 140
CÓ THỂ BẠN MUỐN DOWNLOAD
Chịu trách nhiệm nội dung:
Nguyễn Công Hà - Giám đốc Công ty TNHH TÀI LIỆU TRỰC TUYẾN VI NA
LIÊN HỆ
Địa chỉ: P402, 54A Nơ Trang Long, Phường 14, Q.Bình Thạnh, TP.HCM
Hotline: 093 303 0098
Email: support@tailieu.vn