1. Trang chủ
  2. » Công Nghệ Thông Tin

Tài liệu Flash Builder 4 and Flex 4 Bible- P12 doc

50 307 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Working with Pop-up Windows and Custom Pop-up Windows
Trường học University of New South Wales
Chuyên ngành Flex Application Development
Thể loại Giáo trình
Năm xuất bản 2023
Thành phố Sydney
Định dạng
Số trang 50
Dung lượng 613,58 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Chapter 17: Working with Pop-up WindowsWorking with Custom Pop-up Windows You can create custom pop-up windows in a Flex application for many purposes: l Presenting detailed information

Trang 1

Chapter 17: Working with Pop-up Windows

Working with Custom Pop-up Windows

You can create custom pop-up windows in a Flex application for many purposes:

l Presenting detailed information to the user that’s too complex to easily fit into an Alert

dialog box

l Collecting configuration and preference information before executing an operation

l Providing a pop-up window that can be reused as a custom component

l Collecting data through a data entry form wrapped in a pop-up window

Tip

A custom pop-up window component must be extended from a class that implements the IFlexDisplayObject interface This interface is implemented by the UIComponent class, which in turn is in the inheri- tance hierarchy of all MX containers and controls This essentially means that any component can be used as a custom pop-up window If you want to create a custom pop-up window based on a Spark component, though, you should base your custom pop-up window on the Spark TitleWindow component n

Defining a custom pop-up window

Custom pop-up windows can be defined as custom MXML components If you want to create a window that looks like a dialog box, you can use either the Panel or TitleWindow container

While either component has the appearance of a dialog box, the Spark Panel component can’t be dragged around the screen by the user If you want full dialog box functionality, create your cus-tom pop-up window components as subclasses of the TitleWindow component

Creating the component

The steps for creating an MXML component that will be used as a pop-up window are the same as for any other MXML component:

1 Create a new MXML component based on spark.components.TitleWindow.

2 Save the new component in your project as a file with the mxml file extension.

The following code defines an MXML component designed to collect login information, and it might be saved as a file named LoginWindow.mxml:

Trang 2

Part II: Designing Flex Applications

Sharing data with events

The custom component that will be used as a pop-up window should share information with the rest of the application using custom events The LoginWindow component described in the pre-ceding code sample would share events for logging in and for canceling the operation In order to share the login information, you need to create a custom event class to contain the login data

Listing 17.5 is a custom event class with public properties for the user name and password values that will be collected by the custom component

LISTING 17.5

A custom event class designed for use with a custom Login component

package events {

import flash.events.Event;

public class LoginEvent extends Event {

public var username:String;

public var password:String;

public function LoginEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) {

super(type, bubbles, cancelable);

} override public function clone():Event {

var ev:LoginEvent = new LoginEvent(this.type);

ev.username = this.username;

ev.password = this.password;

return ev;

} } }

Trang 3

Chapter 17: Working with Pop-up Windows

When the user clicks the custom component’s Log In button, the component shares data with the application by constructing and dispatching a custom event object:

var event:LoginEvent = new LoginEvent(“login”);

userInput.setFocus();

} ]]>

</fx:Script>

<mx:Form>

<mx:FormItem label=”User Name:”>

continued

Trang 4

Part II: Designing Flex Applications

The code in Listing 17.6 is available in the Web site files as LoginTitleWindow.mxml in the chapter17

project’s src/popups folder n

Managing custom pop-up windows with the PopUpManager class

The PopUpManager is a singleton class with static methods that you use to manage custom

pop-up windows at runtime It has two methods that you can use to present a pop-pop-up window:

l addPopUp() Adds a new top-level window using a component that’s already been instantiated and is ready to use

l createPopUp() Creates a new instance of a component, presents the component as a

pop-up window, and returns a reference

Of these two methods, the addPopUp() method is more useful, because it enables you to struct and preconfigure a visual object prior to presenting it as a pop-up window

con-The PopUpManager also has these methods that you use to manipulate the position and order of pop-up windows:

l bringToFront() Gives top-level presentation and focus to a particular window

l centerPopUp() Positions a pop-up window in the horizontal and vertical center of its parent window

Finally, PopUpManager has a removePopUp() method to remove top-level windows from the display when they’re no longer needed, though they will still exist in application memory

Adding a pop-up window to the display

To add a new pop-up window to the application at runtime using the addPopUp() method, first declare an instance of the custom component you want to present This declaration will likely be outside of any functions so the pop-up window reference persists between function calls:

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 5

Chapter 17: Working with Pop-up Windows

private var popup:LoginWindow;

Within a function that you call to display the pop-up window, instantiate the component and ate any required event listeners with accompanying event handler functions The LoginWindow

cre-component in this example dispatches events named login and cancel, so it requires two

addEventListener() calls:

popup = new LoginWindow();

popup.addEventListener(“login”, loginHandler);

popup.addEventListener(“cancel”, cancelHandler);

To present the window on-screen, call PopUpManager.addPopUp() with these arguments:

l childList:String The display child list in which you’re adding the pop-up window

Possible values include PopUpManagerChildList.APPLICATION,

PopUpManagerChildList.POPUP, and PopUpManagerChildList.PARENT (the default)

l modal:Boolean This argument determines whether the custom pop-up window is modal If not passed in, it defaults to false

l parent:DisplayObject The parent window over which the pop-up window is displayed

l window:IFlexDisplayObject The component reference you just instantiated

After adding the pop-up window to the application interface, you can center the window over its parent window with a call to PopUpManager.centerPopUp() If necessary, you can ensure that the new window has top-level focus with a call to PopUpManager.bringToFront().This makes a call to PopUpManager.addPopup() to present the LoginWindow custom com-ponent as a modal pop-up window and then centers it on the parent component:

PopUpManager.addPopUp(popup, this, true);

Removing a pop-up window

To remove a pop-up window, use the PopUpManager class’s static removePopUp() method

The method takes a single argument that references the pop-up window instance:

PopUpManager.removePopUp(popup);

Trang 6

Part II: Designing Flex Applications

The application in Listing 17.7 uses the LoginWindow component as a pop-up window In each

of its custom event handler functions, it explicitly closes the pop-up window with a call to

private function showLoginWindow():void {

popup = new LoginTitleWindow();

popup.addEventListener(Event.CLOSE, closeHandler);

popup.addEventListener(“login”, loginHandler);

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 7

Chapter 17: Working with Pop-up Windows

Alert.show(“You logged in as “ + event.username + “ with a password of “ + event.password, “Login Successful”);

PopUpManager.removePopUp(popup);

} private function closeHandler(event:Event):void {

Alert.show(“You cancelled the login operation”, “Login Cancelled”);

PopUpManager.removePopUp(popup);

} ]]>

The TitleWindow container is a subclass of Panel, so it shares all of that container’s features:

It contains a title bar, a caption, a border, and a content area, and like the Panel, it can host a

controlBarContent area with wizard-like buttons at the bottom

The TitleWindow displays a close button in its upper-right corner, creating a common visual interface for pop-up windows

The close button doesn’t actually close the pop-up window Instead, it dispatches a close event with an event object typed as mx.events.CloseEvent Upon instantiating the custom compo-nent (and prior to adding it as a pop-up window), create a listener for the close event:

popup.addEventListener(CloseEvent.CLOSE, closeHandler);

Then, in the event handler function, call PopUpManager.removePopUp() to remove the

pop-up window from the application interface:

private function closeHandler(event:CloseEvent):void{

Alert.show(“You canceled the login operation”, “Login Canceled”);

PopUpManager.removePopUp(popup);

}

Trang 8

Part II: Designing Flex Applications

528

If you don’t want to display the close button in the custom pop-up window, just create a custom skin for your pop-up and modify it as needed To do this, follow these steps:

1 Open the custom pop-up window file in Flash Builder’s Design mode.

2 Right-click anywhere on the design area and select Create Skin.

3 As shown in Figure 17.12, enter the package and name of the new skin you want to

create As with all custom components, you should place the skin component in a subfolder of the project’s source-code root.

FIGURE 17.12

Creating a new custom skin based on the TitleWindow component’s default skin

4 Set Host component to spark.components.TitleWindow.

5 Select Create a copy of and use the default value spark.skins.spark.

TitleWindowSkin (this is the default skin for the Spark TitleWindow component).

6 Click Finish to create the new custom skin.

7 When the new custom skin file appears in Flash Builder’s Design mode, click the

close icon in the skin’s upper-right corner as shown in Figure 17.13.

8 Press Delete to remove the button from the skin Alternatively, you can use Source

mode and comment out the <s:Button> tag

9 Save your changes.

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 9

Chapter 17: Working with Pop-up Windows

10 Open the custom pop-up window file in Source mode and look at the

<s:TitleWindow> starting tag It now includes the skinClass attribute that assigns your new custom skin:

If you want to use the custom skin with all dialog boxes in your application, you can set the

skinClass style in an embedded or external style sheet For example, this CSS code assigns the new skin for all components that are based on the Spark TitleWindow component:

<fx:Style>

@namespace s “library://ns.adobe.com/flex/spark”;

s|TitleWindow { skinClass: ClassReference(“skins.CustomTitleWindowSkin”);

} </fx:Style>

Trang 10

Part II: Designing Flex Applications

530

Summary

In this chapter, I described how to create pop-up windows as part of a Flex application interface

You learned the following:

l Pop-up windows are typically used to present and collect information in a windowing style application

l You use the Alert class to present simple informational messages and to enable a user to confirm or decline an operation

l The PopUpMenuButton control combines a Button and single-level Menu that’s similar

in presentation to a ComboBox

l You use the PopUpButton control to present any visual container or control as a pop-up window

l Custom pop-up windows are defined in the same way as any custom component

l The Spark TitleWindow is designed to be used as a custom pop-up window and enables dragging of the resulting window by the user

l You can remove the close button from the TitleWindow interface by creating a custom skin

l You use the PopUpManager class’s static methods to add and remove custom pop-up windows at runtime

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 12

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 13

C H A P T E R

Modeling and Managing Data

IN THIS CHAPTER

Using the <fx:Model> tag

to model data items Embedding data with

<fx:Model>

Creating value object classes

in ActionScript Storing data sets in client memory with the

ArrayList and

ArrayCollection classes Filtering and sorting data with the ArrayCollection

class Traversing, searching, and bookmarking data objects with the IViewCursor

interface

Flex applications are stateful; that is, they have the capability to

remem-ber data persistently for the duration of the user’s session in a way that classic Web applications usually don’t One of the most common tasks you must accomplish as an application developer is to create a frame-work for storing data that the application can use at runtime

The content of an application’s data can come from many sources: XML files, databases or other server-side resources, or remote functions wrapped by and exposed as SOAP-style or Representational State Transfer (REST)-style Web services Regardless of how the data comes to an application, though, a Flex application stores the data in exactly the same way: as a data model

In this chapter, I describe common techniques for modeling data in Flex applications I start with creating single-object data models: ActionScript classes designed to hold one instance of a data entity at a time (A data instance might represent a row in a database table or a single element in an XML file.) You can represent such data instances with the <fx:Model> tag,

a generic data object, or, more commonly, you create your own custom

ActionScript classes, known variously by the design pattern names Value

Object and Transfer Object.

In the second part of the chapter, I describe the use of data collections:

ordered collections of data instances managed by the ArrayList and

ArrayCollection classes I describe how and where to declare these classes and then describe how to use the powerful ArrayCollection class

to filter, sort, bookmark, and traverse data in client application memory

On the Web

To use the sample code for this chapter, import the chapter18.fxp Flex project archive from the Web site files into your Flex Builder workspace n

Trang 14

Part III: Working with Data

534

Creating a Data Model

A data model is a way of representing data (information) in a client application It’s a truism of database applications that you can’t do much without knowing your data structure Take an appli-cation that represents the personal information of your contact list Whether you store this data in

an e-mail client or a complex server-side database application such as SQL Server or MySQL, the software that manages the data has to know its structure

In classic relational databases, data is stored in tables Each table has columns that represent the bits of data that are created for each row in the table A database table representing contact infor-mation might have any number of columns Each column has a name and a data type For exam-ple, a contacts table might have the data structure shown in Table 18.1

TABLE 18.1

A Simple Database Table Structure

When data is returned to a Flex application in this structure, you need a way to store it The goal is

to create an object that can serve as a container for this data and can share this data structure to the best of the Flex framework’s capability

Figure 18.1 shows a UML diagram describing the structure of an object that would be able to hold this data

You can create a data model to store the data in two ways: by using the <fx:Model> tag to declare a generic untyped data object and by creating a custom ActionScript class Of these approaches, the custom ActionScript version is significantly more powerful and flexible The

<fx:Model> approach is fast and easy to code, and it might be used during early prototyping of

an application, but an application that’s built for durability and easy long-term maintenance ally requires custom ActionScript classes to represent data in Flex application memory

gener-Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 15

Chapter 18: Modeling and Managing Data

Using the <fx:Model> element

The <fx:Model> element compiles XML markup into a generic ActionScript Object New Feature

The <fx:Model> element was used with the mx prefix in previous versions of the Flex SDK As with all sual elements, it must be declared within an <fx:Declarations> element in a Flex 4 application n

nonvi-You could implement the data structure described in the UML diagram in Figure 18.1 as a data object with this code:

Trang 16

Part III: Working with Data

At runtime, as the user interacts with form controls, the controls’ values are passed to the Model

object through the binding expressions

Trang 17

Chapter 18: Modeling and Managing Data

</data>

</fx:Model>

</fx:Declarations>

<s:VGroup top=”20” horizontalCenter=”0”>

<s:Label text=”{contact.firstName} {contact.lastName}”/>

l The object and all its properties are automatically bindable You don’t have to include

the [Bindable] metadata tag, and you can refer to any of the object’s properties with binding expressions, as in:

<s:Label text=”{contact.firstName} {contact.lastName}”/>

l The <fx:Model> tag uses simple XML syntax to declare its property names

After a data object has been declared with the <fx:Model> tag, you refer to its data using dot syntax The object’s id, assigned in the <fx:Model> start tag, actually refers to the model’s root element if there is a sole root element In the preceding example, this element is named <data>, but its name isn’t important; you refer to the root by the model object’s id and then to its named properties as child objects of the model:

l You can declare only a single instance of an object Unlike strongly typed ActionScript

classes, which are designed to be instantiated as many times as necessary, if you want another data object you have to declare it explicitly

l Because <fx:Model> is a compiler tag that doesn’t represent an ActionScript class,

it has no methods or properties.

Trang 18

Part III: Working with Data

538

Importing data with <fx:Model>

The <fx:Model> tag does have one very useful capability: It can be used to compile data into an application This technique is useful only when two circumstances are true:

l It is a relatively small amount of data Large amounts of embedded data result in an

increase in the size of the compiled application For applications that are deployed over the Web, embedding data results in a slower download and longer delay before the appli-cation starts for the first time On the positive side, the data is instantly available to the application without having to be downloaded at runtime

l The data is completely static If any of the data under consideration might change

dur-ing the lifetime of the application, you should load the data at runtime usdur-ing the

HTTPService component or another runtime loading mechanism

To embed data with the <fx:Model> tag, first save it as an XML file The names of the XML file’s data elements can be anything you like; the only requirements are that the XML file be well formed and have a single root element The following XML structure is suitable for use with the

<fx:Model id=”bookData” source=”data/books.xml”/>

As with hard-coded data, the Model element’s id points to the XML structure’s root element

From there, the data typing of each element depends on the number of elements with a particular name If the preceding structure contains two or more <book> elements, the expression book-Data.book returns an Array If the XML structure’s root element contains only a single child

<book> element, the expression bookData.book instead returns an ActionScript Object

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 19

Chapter 18: Modeling and Managing Data

Tip

To ensure that you always have an Array to work with, you can use the ArrayUtil.toArray() method wrapped around an expression that might return an Object due to the number of elements in the XML data structure At application startup, declare a separate Array variable and fill it as shown here:

import mx.utils.ArrayUtil;

[Bindable]

private var bookArray:Array;

private function initApp():void{

bookArray = ArrayUtil.toArray(bookData.book);

} n

Using Value Objects

A value object, also known variously as a transfer object, a data transfer object, and a bean, is a class

designed to hold data for a single instance of a data entity The design pattern is named Transfer Object in the world of Java Enterprise Edition (JEE) application server development, where it’s implemented as a Java class

Web Resource

The Transfer Object design pattern is described in the J2EE design pattern catalog at http://java.sun

com/blueprints/corej2eepatterns/Patterns/TransferObject.html In the most recent sion on the Sun Web site, the graphics still refer to the design pattern as Value Object, its old name Don’t be confused; it’s really the same pattern! n

ver-Value object classes have these advantages over the <fx:Model> tag:

l Class properties can be strongly datatyped Each property is declared with standard able declaration syntax and typically has a data type declared after the colon:

vari-public var myDateProperty:Date;

l Class properties can have default values As when declaring a variable inside or outside a function, you can declare default values by appending the value after an = assignment operator This code declares a Date property with the default set to the current date:

public var myDateProperty:Date = new Date();

l You can use implicit setter and getter accessor methods Accessor methods enable plex logic and authentication when setting or getting data and creating read-only proper-ties (properties that can be read from the application but can only be set internally within the value object class)

com-l When you integrate a Flex client application with an application server that supports data transfer with AMF (Action Message Format), such as ColdFusion, BlazeDS, LiveCycle Data Services, PHP, and others, client-side value object classes defined in ActionScript can be mapped to equivalent classes on the server (written in the server’s native language, such as Java, ColdFusion Markup Language, PHP, or C#) This enables you to pass data between the application tiers with minimal code in both tiers

Trang 20

Part III: Working with Data

540

Using the New ActionScript Class wizard

You can use Flex Builder’s ActionScript Class wizard to create a simple ActionScript class Follow these steps to create a new value object class to represent Book data:

1 Open the chapter18 project if it isn’t already open Notice that the project contains

a valueObjects package

2 Right-click on the valueObjects package and select New ➪ ActionScript Class.

3 Set the class name as Contact, as shown in Figure 18.2.

4 Click Finish to create the new ActionScript class.

FIGURE 18.2

The New ActionScript Class wizard

The completed ActionScript class is created in the file Contact.as in the valueObjects folder and should appear in the Source view editor as follows:

package valueObjects{

public class Contact {

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 21

Chapter 18: Modeling and Managing Data

public function Contact() {

} }}

The class is ready to fill in with properties and other functionality

Value object class syntax

Value objects are implemented in Flex as simple ActionScript classes, and their syntax is mined by basic ActionScript syntax requirements In this section, I describe each part of a value object class, its purpose, and some best practice recommendations

deter-Declaring a package

A package is a collection of related classes As in many other languages, including both Java and

ColdFusion, packages are tied to the folder structure of an application’s source code

In ActionScript 3.0, each public ActionScript class must be wrapped inside a package declaration that’s implemented as a code block The package declaration tells the compiler where the class is stored based on its package within the project’s source root folder or other locations in the proj-ect’s build path

As shown in Figure 18.3, the Contact value object class is stored in the valueObjects subfolder

of the project’s source root

FIGURE 18.3

The project structure, including the valueObjects subfolder

Trang 22

Part III: Working with Data

542

The package declaration looks like this:

package valueObjects{

public class declaration here

}

Caution

When you generate a new class file with the New ActionScript Class wizard, the package declaration is created for you However, if you move the class source code later, you’re responsible for manually updating the pack- age declaration in the class source code n

Declaring the public class

The class declaration for a public class is placed inside the package code block Value object classes are always declared as public, so they can be used by the rest of the application Also, value object classes typically don’t explicitly extend any other class, as they usually don’t have to inherit existing functionality

The name of an ActionScript public class must match the name of the source-code file in which it’s defined The name is case-sensitive, and by convention always has an initial uppercase character

The public class declaration looks like this:

package valueObjects{

public class Contact {

class members declared here

}}

As noted in the preceding example, members of the class, including properties, functions, and stants, are declared inside the class declaration’s code block

con-Tip

You can declare private classes in an ActionScript class source-code file These classes are available for use only by the public class in whose source-code file the private class is declared The private class doesn’t actu- ally have a private access modifier declaration, and it’s declared outside the package declaration:

package valueObjects{

public class Book {

public var page1:Page = new Page();

public function Book() {

}

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 23

Chapter 18: Modeling and Managing Data

}}class Page {

public var pageNumber:int;

public var text:String;

} n

Declaring ActionScript class properties

ActionScript class properties are declared as variables, using this syntax:

[access modifier] var [property name]:[data type];

The access modifiers at the beginning of a property declaration should be one of these keywords:

l internal Properties that can be set and read by the current class and by any classes in the same package

l private Properties that can only be set and read by instances of the class in which they’re declared

l protected Properties that can be set and read by the current class and by any of its subclasses

l public Properties that can be set and read by the rest of the application

Note

The default access modifier is internal; if you leave the access modifier off a property declaration, the erty is available only to the current class and any other classes in the same package You’ll also see a compiler warning indicating that you should include an explicit access modifier n

prop-The name of a property is subject to naming rules for all ActionScript identifiers: It can include alphabetical, numeric, and underscore characters, and it must start with an alphabetical character

or an underscore The following naming conventions are considered to be best practices by most developers:

l The initial character in a property name is lowercase

l Private properties have an initial underscore (_) character

Neither of these conventions is a technical requirement, but by following them you create code that makes sense to other developers

To add public properties representing the data structure in Table 18.1, follow these steps:

1 Open the Contact.as file you created in the previous exercise.

2 Place the cursor inside the class declaration’s code block, but before the constructor

method.

Trang 24

Part III: Working with Data

544

3 Declare each of the required properties with appropriate data types, as follows:

public var contactId:int=0;

public var firstName:String;

public var lastName:String;

public var dob:Date;

public var address:String;

public var city:String;

public var zipCode:String;

public var telephone:String;

4 Save the file to disk.

Making properties bindable

Value objects benefit from having their properties marked as bindable, so that as the property ues change at runtime, they can broadcast those changes to any objects with binding expressions

val-You can make individual properties bindable by adding the [Bindable] metadata tag before each property declaration This code makes the firstName and lastName properties bindable, but doesn’t do the same for the contactId property:

public var contactId:int=0;

[Bindable]

public var firstName:String;

[Bindable]

public var lastName:String;

Alternatively, you can add a single [Bindable] tag before the class declaration to make all its erties bindable:

prop-package valueObjects{

[Bindable]

public class Contact {

public var contactId:int=0;

public var firstName:String;

public var lastName:String;

remaining property declarations

}}

Follow these steps to make all the value object class’s properties bindable:

1 Open Contact.as

2 Create an empty line just before the class declaration, and add a [Bindable]

meta-data tag.

3 Save the changes to disk.

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Trang 25

Chapter 18: Modeling and Managing Data

Listing 18.2 shows the completed value object Contact class

LISTING 18.2

A completed value object class

package valueObjects {

[Bindable]

public class Contact {

public var contactId:int=0;

public var firstName:String;

public var lastName:String;

public var dob:Date;

public var address:String;

public var city:String;

public var zipCode:String;

public var telephone:String;

public function Contact() {

} } }

On the Web

A completed class that’s similar to Listing 18.2 is available in the Web site files as ContactComplete.as in the chapter18 project n

Using private properties and accessor methods

If you prefer, you can use private properties and set and get accessor methods This is a preferred syntax for some developers, because it follows the object-oriented practice of encapsulation and hiding data members from public usage

To declare a private property in an ActionScript class, replace the public access modifier with the keyword private If you like, you also can follow the practice of using an underscore (_) prefix

as the property name’s initial character:

private var _firstName:String;

To make the property accessible to the rest of the application, you then create set and get accessor methods In ActionScript 3, these methods use explicit set and get keywords to indicate that the functions should be accessed by the class consumer as though they were properties

Ngày đăng: 26/01/2014, 20:20

TỪ KHÓA LIÊN QUAN