Large scale application development and MVP – Part II

The sample project, referenced throughout this tutorial, can be downloaded at Tutorial-Contacts2.zip.

With the foundation of our MVP-based app laid out, you may be asking yourself, “Hey, why can’t I use that fancy UiBinder feature that was previously discussed?” This answer is, you can with a little bit of tweaking within the Views and Presenters. On top of that, you may find that the code flows more smoothly and the techniques we put in place will dovetail nicely when we address the topic of implementing more complex UIs, optimized UIs, and code splitting.

Doing things the UiBinder way

To start things out, let’s take a look at the code that constructs our main ContactList view. Previously we programmatically setup the UI within the ContactsView constructor:

public class ContactsView extends Composite implements ContactsPresenter.Display {  ...  public ContactsView() {    DecoratorPanel contentTableDecorator = new DecoratorPanel();    initWidget(contentTableDecorator);    contentTableDecorator.setWidth("100%");    contentTableDecorator.setWidth("18em");    contentTable = new FlexTable();    contentTable.setWidth("100%");    contentTable.getCellFormatter().addStyleName(0, 0, "contacts-ListContainer");    contentTable.getCellFormatter().setWidth(0, 0, "100%");    contentTable.getFlexCellFormatter().setVerticalAlignment(0, 0, DockPanel.ALIGN_TOP);

    // Create the menu    //    HorizontalPanel hPanel = new HorizontalPanel();    hPanel.setBorderWidth(0);    hPanel.setSpacing(0);    hPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);    addButton = new Button("Add");    hPanel.add(addButton);    deleteButton = new Button("Delete");    hPanel.add(deleteButton);    contentTable.getCellFormatter().addStyleName(0, 0, "contacts-ListMenu");    contentTable.setWidget(0, 0, hPanel);

    // Create the contacts list    //    contactsTable = new FlexTable();    contactsTable.setCellSpacing(0);    contactsTable.setCellPadding(0);    contactsTable.setWidth("100%");    contactsTable.addStyleName("contacts-ListContents");    contactsTable.getColumnFormatter().setWidth(0, "15px");    contentTable.setWidget(1, 0, contactsTable);    contentTableDecorator.add(contentTable);  }  ...}

The first step towards a UiBinder-way of doing things is to move this code into a Contacts.ui.xml file and perform the associated transformations. As mentioned in previous chapters, constructing UiBinder-based UIs allows you to do so in a declarative way that resembles HTML more than straight Java code. To that extent, the result is the following:

ContactsView.ui.xml

<ui:UiBinder
  xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui">

  <ui:style>
    .contactsViewButtonHPanel {
      margin: 5px 0px 0x 5px;
    }
    .contactsViewContactsFlexTable {
      margin: 5px 0px 5px 0px;
    }

  </ui:style>

  <g:DecoratorPanel>
    <g:VerticalPanel>
      <g:HorizontalPanel addStyleNames="{style.contactsViewButtonHPanel}">
        <g:Button ui:field="addButton">Add</g:Button>
        <g:Button ui:field="deleteButton">Delete</g:Button>
      </g:HorizontalPanel>
      <g:FlexTable ui:field="contactsTable" addStyleNames="{style.contactsViewContactsFlexTable}"/>
    </g:VerticalPanel>
  </g:DecoratorPanel>
</ui:UiBinder>

Here we’ve laid out a VerticalPanel that wraps our Add/Delete buttons and FlexTable that contains the list of contacts. This is then wrapped by a DecoratorPanel for a bit of style. The <ui:style> element declares a small amount of margin so that things aren’t placed too close together. The ContactsView constructor and members are then reduced to the following:

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  @UiTemplate("ContactsView.ui.xml")  interface ContactsViewUiBinder extends UiBinder {}  private static ContactsViewUiBinder uiBinder =    GWT.create(ContactsViewUiBinder.class);

  @UiField FlexTable contactsTable;  @UiField Button addButton;  @UiField Button deleteButton;

  public ContactsViewImpl() {    initWidget(uiBinder.createAndBindUi(this));  }  ...}</WIDGET,>

We’ll get to why it’s templatized, why it’s an “impl” class, and why we use the UiTemplate annotation later on when we discuss “Complex UIs – Dumb Views”. For now it’s important to note that a) we’ll access the underlying widgets via the UiField annotations, and b) the constructor code is significantly smaller. And by “smaller”, I mean a single line. UiBinder does a good job of removing the boilerplate code necessary to setup UIs, and allows you to further segment the code that declares the UI from the code that drives the UI.

Now that we have the UI constructed, we need to hook up the associated UI events, namely Add/Delete button clicks, and interaction with the contact list FlexTable. This is where we’ll start to notice significant changes in the overall layout of our application design. Mainly due to the fact that we want to link methods within the view to UI interactions via the UiHandler annotation. The first major change is that we want our ContactsPresenter to implement a Presenter interface that allows our ContactsView to callback into the presenter when it receives a click, select or other event. The Presenter interface defines the following:

  public interface Presenter<T> {    void onAddButtonClicked();    void onDeleteButtonClicked();    void onItemClicked(T clickedItem);    void onItemSelected(T selectedItem);  }

Again, templatizing the interface will be covered in the next section, but with this interface in place you can start to see how the ContactsView is going to communicate with the ContactsPresenter. The first part of wiring everything up is to have our ContactsPresenter implement the Presenter interface, and then register itself with the underlying view. To register itself, we’ll need our ContactsView to expose a setPresenter() method:

  private Presenter<T> presenter;  public void setPresenter(Presenter<T> presenter) {    this.presenter = presenter;  }

Now we can take a look at how we’ll wire up the UI interactions within the ContactsView via the UiHandler annotation:

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  @UiHandler("addButton")  void onAddButtonClicked(ClickEvent event) {    if (presenter != null) {      presenter.onAddButtonClicked();    }  }

  @UiHandler("deleteButton")  void onDeleteButtonClicked(ClickEvent event) {    if (presenter != null) {      presenter.onDeleteButtonClicked();    }  }

  @UiHandler("contactsTable")  void onTableClicked(ClickEvent event) {    if (presenter != null) {      HTMLTable.Cell cell = contactsTable.getCellForEvent(event);

      if (cell != null) {        if (shouldFireClickEvent(cell)) {          presenter.onItemClicked(rowData.get(cell.getRowIndex()));        }

        if (shouldFireSelectEvent(cell)) {          presenter.onItemSelected(rowData.get(cell.getRowIndex()));

        }      }    }  }  ...}

Using this technique, we’ve provided the UiBinder generator the corresponding methods that should be called when a Widget has a “ui:field” attribute set to “addButton”, “deleteButton”, and “contactsTable”. On the ContactsPresenter side of the fence we end up with the following:

public class ContactsPresenter implements Presenter {  ...  public void onAddButtonClicked() {    eventBus.fireEvent(new AddContactEvent());  }

  public void onDeleteButtonClicked() {    deleteSelectedContacts();  }

  public void onItemClicked(ContactDetails contactDetails) {    eventBus.fireEvent(new EditContactEvent(contactDetails.getId()));  }

  public void onItemSelected(ContactDetails contactDetails) {    if (selectionModel.isSelected(contactDetails)) {      selectionModel.removeSelection(contactDetails);    }    else {      selectionModel.addSelection(contactDetails);    }  }  ...}

The resulting method implementations are the same as the non-UiBinder sample, with the exception of the onItemClicked() and onItemSelected() methods. And while these methods may seem straightforward, we need to dive into how they came about, and explain what this “SelectionModel” is in the first place. This and much, much more is explained in the next section.

Complex UIs – Dumb Views

Our current solution is to have our presenters pass a dumbed down version of the model to our views. In the case of our ContactsView, the presenter takes a list of DTOs (Data Transfer Objects) and constructs a list of Strings that it then passes to the view.

public ContactsPresenter implements Presenter {  ...  public void onSuccess(ArrayList<ContactDetails> result) {    contactDetails = result;    sortContactDetails();    List<String> data = new ArrayList<String>();    for (int i = 0; i < result.size(); ++i) {      data.add(contactDetails.get(i).getDisplayName());    }

    display.setData(data);  }  ...}

The “data” object that is passed to the view is a very (and I mean very) simplistic ViewModel — basically a representation of a more complex data model using primitives. This is fine for a simplistic view, but as soon as you start to do something more complex, you quickly realize that something has to give. Either the presenter needs to know more about the view (making it hard to swap out views for other platforms), or the view needs to know more about the data model (ultimately making your view smarter, thus requiring more GwtTestCases). The solution is to use generics along with a third party that abstracts any knowledge of a cell’s data type, as well as how that data type is rendered.

First, we’re going to rely on the fact that data types are typically homogeneous within column borders. Doing so allows us to define a ColumnDefinition abstract class that houses the any type-specific code (this is the third party mentioned above).

  public abstract class ColumnDefinition<T> {    public abstract Widget render(T t);

    public boolean isClickable() {      return false;    }

    public boolean isSelectable() {      return false;    }  }

By stringing together a list of these classes, and providing the necessary render() implementations and isClickable()/isSelectable() overrides, you can start see how we would define our layout. Let’s take a look at how we would make this work with our Contacts sample.

  public class ContactsViewColumnDefinitions<ContactDetails> {    List<ColumnDefinition<ContactDetails>> columnDefinitions =      new ArrayList<ColumnDefinition<ContactDetails>>();

    private ContactsViewColumnDefinitions() {      columnDefinitions.add(new ColumnDefinition<ContactDetails>() {        public Widget render(ContactDetails c) {          return new CheckBox();        }

        public boolean isSelectable() {          return true;        }      });

      columnDefinitions.add(new ColumnDefinition<ContactDetails>() {        public Widget render(ContactDetails c) {          return new HTML(c.getDisplayName());        }

        public boolean isClickable() {          return true;        }      });    }

    public List<ColumnDefinition<ContactDetails>> getColumnDefnitions() {      return columnDefinitions;    }  }

These ColumnDefinition(s) would be created outside of the presenter so that we can reuse its logic regardless of what view we’ve attached ourself to (be it an iPhone, Android, desktop, etc… view). This can be done using a platform-specific ContactsViewColumnDefinitions class that is loaded (or injected using GIN) on a per-permutation basis. Regardless of the technique, we’ll need to update our views such that we can set their ColumnDefinition(s).

  public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {    ...    private List<ColumnDefinition<T>> columnDefinitions;    public void setColumnDefinitions(        List<ColumnDefinition<T>> columnDefinitions) {      this.columnDefinitions = columnDefinitions;    }    ...  }

Note that our ContactsView is now ContactsViewImpl<T> and implements ContactsView<T>. This is so that we can pass in a mocked ContactsView instance when testing our ContactsPresenter. Now in our AppController, when we create the ContactsView, we can initialize it with the necessary ColumnDefinition(s).

  public class AppController implements Presenter, ValueChangeHandler<String> {    ...    public void onValueChange(ValueChangeEvent<String> event) {      String token = event.getValue();      if (token != null) {        Presenter presenter = null;        if (token.equals("list")) {          // lazily initialize our views, and keep them around to be reused          //          if (contactsView == null) {            contactsView = new ContactsViewImpl<ContactDetails>();            if (contactsViewColumnDefinitions == null) {              contcactsViewColumnDefinitions = new ContactsViewColumnDefinitions().getColumnDefinitions();            }            contactsView.setColumnDefiniions(contactsViewColumnDefinitions);         }        }        presenter = new ContactsPresenter(rpcService, eventBus, contactsView);      }      ...    }    ...  }

With the ColumnDefinition(s) in place, we will start to see the fruits of our labor. Mainly in the way we pass model data to the view. As mentioned above we were previously dumbing down the model into a list of Strings. With our ColumnDefinition(s) we can pass the model untouched.

  public class ContactsPresenter implements Presenter,    ...    private void fetchContactDetails() {      rpcService.getContactDetails(new AsyncCallback<ArrayList<ContactDetails>>() {        public void onSuccess(ArrayList<ContactDetails> result) {            contactDetails = result;            sortContactDetails();            view.setRowData(contactDetails);        }        ...      });    }    ...  }

And our ContactsViewImpl has the following setRowData() implementation:

  public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {    ...    public void setRowData(List<T> rowData) {      contactsTable.removeAllRows();      this.rowData = rowData;

      for (int i = 0; i < rowData.size(); ++i) {        T t = rowData.get(i);        for (int j = 0; j < columnDefinitions.size(); ++j) {          ColumnDefinition<T> columnDefinition = columnDefinitions.get(j);          contactsTable.setWidget(i, j, columnDefinition.render(t));        }      }    }    ...  }

A definite improvement; the presenter can pass the model untouched and the view has no rendering code that we would otherwise need to test. And the fun doesn’t stop there. Remember the isClickable() and isSelectable() methods? Well, let’s take a look at how they work in conjunction with ClickEvents that are received within the view.

  public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {    ...    @UiHandler("contactsTable")    void onTableClicked(ClickEvent event) {      if (presenter != null) {        HTMLTable.Cell cell = contactsTable.getCellForEvent(event);

        if (cell != null) {          if (shouldFireClickEvent(cell)) {            presenter.onItemClicked(rowData.get(cell.getRowIndex()));          }

          if (shouldFireSelectEvent(cell)) {            presenter.onItemSelected(rowData.get(cell.getRowIndex()));          }        }      }    }

    private boolean shouldFireClickEvent(HTMLTable.Cell cell) {      boolean shouldFireClickEvent = false;

      if (cell != null) {        ColumnDefinition<T> columnDefinition =          columnDefinitions.get(cell.getCellIndex());

        if (columnDefinition != null) {          shouldFireClickEvent = columnDefinition.isClickable();        }      }

      return shouldFireClickEvent;    }

    private boolean shouldFireSelectEvent(HTMLTable.Cell cell) {      boolean shouldFireSelectEvent = false;

      if (cell != null) {        ColumnDefinition<T> columnDefinition =          columnDefinitions.get(cell.getCellIndex());

        if (columnDefinition != null) {          shouldFireSelectEvent = columnDefinition.isSelectable();        }      }

      return shouldFireSelectEvent;    }    ...  }

The notion here is that you’ll want to respond to user interaction in different ways based upon the cell type that was clicked. Given that our ColumnDefinition(s) are intertwined with the cell type, we’re not only able to use them for rendering purposes, but for defining how to interpret user interactions.

To take this one final step further, we’re going to remove any application state from the ContactsView. To do this, we’ll replace the view’s getSelectedRows() with a SelectionModel that the presenter holds on to. The SelectionModel is nothing more than a wrapper around a list of model objects.

  public class SelectionModel<T> {    List<T> selectedItems = new ArrayList<T>();

    public List<T> getSelectedItems() {      return selectedItems;    }

    public void addSelection(T item) {      selectedItems.add(item);    }

    public void removeSelection(T item) {      selectedItems.remove(item);    }

    public boolean isSelected(T item) {      return selectedItems.contains(item);    }  }

The ContactsPresenter holds on to an instance of this class and updates it accordingly, based on calls to onItemSelected().

  public class ContactsPresenter implements Presenter,    ...    public void onItemSelected(ContactDetails contactDetails) {      if (selectionModel.isSelected(contactDetails)) {        selectionModel.removeSelection(contactDetails);      }

      else {        selectionModel.addSelection(contactDetails);      }    }    ...  }

When it needs to grab the list of selected items, for example when the user clicks the “Delete” button, it has them right at its disposal.

  public class ContactsPresenter implements Presenter,    ...

    public void onDeleteButtonClicked() {      deleteSelectedContacts();    }

    private void deleteSelectedContacts() {      List<ContactDetails> selectedContacts = selectionModel.getSelectedItems();      ArrayList<String> ids = new ArrayList<String>();

      for (int i = 0; i < selectedContacts.size(); ++i) {        ids.add(selectedContacts.get(i).getId());      }

      rpcService.deleteContacts(ids, new AsyncCallback<ArrayList<ContactDetails>>() {        public void onSuccess(ArrayList<ContactDetails> result) {           ...        }        ...      });    }    ...  }

Alright, so that was a fair amount to digest, and describing it in code snippets might lead to some being “lost in translation”. If that’s the case, be sure to check out the full source that is available here.

Optimized UIs – Dumb Views

We’ve figured out how to create the foundation for complex UIs while sticking to our requirement that the view remain as dumb (and minimally testable) as possible, but that’s no reason to stop. While functionality is decoupled, there is still room for optimization. Having the ColumnDefinition create a new widget for each cell is too heavy, and can quickly lead to performance degradation as your application grows. The two leading factors of this degradation are:

  • Inefficiencies related to inserting new elements via DOM manipulation
  • Overhead associated with sinking events per Widget

To overcome this we will update our application to do the following (in respective order):

  • Replace our FlexTable implementation with an HTML widget that we’ll populate by calling setHTML(), effectively batching all DOM manipulation into a single call.
  • Reduce the event overhead by sinking events on the HTML widget, rather than the individual cells.

The changes are encompassed within our ContactsView.ui.xml file, as well as our setRowData() and onTableClicked() methods. First we’ll need to update our ContactsView.ui.xml file to use a HTML widget rather than a FlexTable widget.

<ui:UiBinder>
  ...
  <g:DecoratorPanel>
    <g:VerticalPanel>
      <g:HorizontalPanel addStyleNames="{style.contactsViewButtonHPanel}">
        <g:Button ui:field="addButton">Add</g:Button>
        <g:Button ui:field="deleteButton">Delete</g:Button>
      </g:HorizontalPanel>
      <g:HTML ui:field="contactsTable"></g:HTML>
    </g:VerticalPanel>
  </g:DecoratorPanel>
</ui:UiBinder>

We’ll also need to change the widget that we reference within our ContactsViewImpl class.

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  @UiField HTML contactsTable;  ...

Next we’ll make the necessary changes to our setRowData() method.

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  public void setRowData(List<T> rowData) {    this.rowData = rowData;

    TableElement table = Document.get().createTableElement();    TableSectionElement tbody = Document.get().createTBodyElement();    table.appendChild(tbody);

    for (int i = 0; i < rowData.size(); ++i) {      TableRowElement row = tbody.insertRow(-1);      T t = rowData.get(i);

      for (int j = 0; j < columnDefinitions.size(); ++j) {        TableCellElement cell = row.insertCell(-1);        StringBuilder sb = new StringBuilder();        columnDefinitions.get(j).render(t, sb);        cell.setInnerHTML(sb.toString());

        // TODO: Really total hack! There's gotta be a better way...        Element child = cell.getFirstChildElement();        if (child != null) {          Event.sinkEvents(child, Event.ONFOCUS | Event.ONBLUR);        }      }    }

    contactsTable.setHTML(table.getInnerHTML());  }  ...}

The above code is similar to our original setRowData() method, we iterate through the rowData and for each item ask our column definitions to render accordingly. The main differences being that a) we’re expecting each column definition to render itself into the StringBuilder rather than passing back a full-on widget, and b) we’re calling setHTML on a HTML widget rather than calling setWidget on a FlexTable. This will decrease your load time, especially as your tables start to grow.

Now let’s take a look at the code used to sink events on the table.

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  @UiHandler("contactsTable")  void onTableClicked(ClickEvent event) {    if (presenter != null) {      EventTarget target = event.getNativeEvent().getEventTarget();      Node node = Node.as(target);      TableCellElement cell = findNearestParentCell(node);      if (cell == null) {        return;      }

      TableRowElement tr = TableRowElement.as(cell.getParentElement());      int row = tr.getSectionRowIndex();

      if (cell != null) {        if (shouldFireClickEvent(cell)) {          presenter.onItemClicked(rowData.get(row));        }        if (shouldFireSelectEvent(cell)) {          presenter.onItemSelected(rowData.get(row));        }      }  ...}

Here our onTableClicked() code gets a bit more complicated, but nothing that would raise a red flag when compared to the rest of our application. To reiterate, we’re reducing the overhead of sinking events on per-cell widgets, and instead sinking on a single container, our HTML widget. The ClickEvents are still wired up via our UiHandler annotations, but with this approach, we’re going to get the Element that was clicked on and walk the DOM until we find a parent TableCellElement. From there we can determine the row, and thus the corresponding rowData.

The other tweak we need to make is to update our shouldFirdClickEvent() and shouldFireSelectEvent() to take as a parameter a TableCellElement rather than a HTMLTable.Cell. The implementation remains the same, as you can see below.

public class ContactsViewImpl<T> extends Composite implements ContactsView<T> {  ...  private boolean shouldFireClickEvent(TableCellElement cell) {    boolean shouldFireClickEvent = false;

    if (cell != null) {      ColumnDefinition<T> columnDefinition =        columnDefinitions.get(cell.getCellIndex());

      if (columnDefinition != null) {        shouldFireClickEvent = columnDefinition.isClickable();      }    }

    return shouldFireClickEvent;  }

  private boolean shouldFireSelectEvent(TableCellElement cell) {    boolean shouldFireSelectEvent = false;

    if (cell != null) {      ColumnDefinition<T> columnDefinition =        columnDefinitions.get(cell.getCellIndex());

      if (columnDefinition != null) {        shouldFireSelectEvent = columnDefinition.isSelectable();      }    }

    return shouldFireSelectEvent;  }  ... }

Code Splitting – Only the relevant parts please

Up to this point we’ve discussed how code maintainability and testing are benefits of an MVP-based application. One other benefit that may go overlooked is faster startup times via Code Splitting. I know, you’re probably wondering what in the world an MVP architecture has to do with code splitting, but the same techniques that make your app more maintainable, and testable, make it easier to split with runAsync() points. Let’s back up for a quick recap first. Code Splitting is the act of wrapping segmented pieces of your application into “split” points by declaring them within a runAsync() call. As long as the split portion of your code is purely segmented, and not referenced by other parts of the app, it will be downloaded and executed at the point that it needs to run.

So take for example the code we wrote in the previous section. It was rather lengthy, right? Now assume that our application has a login screen, and as usual once the user logs in they’re taken to the main application screen (in this case their list of Contacts). Do we really want to download all of that code before the user even logs in? Not really. It would be nice if we could simply grab the login code, and leave the rest for when we actually need it (e.g. after the user has logged in). Well we can, and here’s how.

  public void onValueChange(ValueChangeEvent<String> event) {    String token = event.getValue();

    if (token != null) {      if (token.equals("list")) {        GWT.runAsync(new RunAsyncCallback() {          ...          public void onSuccess() {            // lazily initialize our views, and keep them around to be reused            //            if (contactsView == null) {              contactsView = new ContactsViewImpl<ContactDetails>();            }            new ContactsPresenter(rpcService, eventBus, contactsView).go(container);          }        });      }      ...   }

Here, all we’ve done is wrap the code that creates the ContactsView and ContactsPresenter in a runAsync() call, and as a result it won’t be downloaded until the first time we go to show the Contact list. After that, subsequent calls will realize that the code has already been downloaded, and will use it in lieu of re-downloading.

A bit anti climatic isn’t it? Well, that’s not always a bad thing. The amount of boilerplate code necessary to get an MVP-based app up and running is generally large. But the time spent is not wasted, as optimizations such as this one become easier and easier to implement.

Share
Posted in uncategorized | Leave a comment

Large scale application development and MVP

Building any large scale application has its hurdles, and GWT apps are no exception. Multiple developers working simultaneously on the same code base, while maintaining legacy features and functionality, can quickly turn into messy code. To help sort things out we introduce design patterns to create compartmentalized areas of responsibility within our project.

There are various design patterns to choose from; Presentation-abstraction-control, Model-view-controller, Model-view-presenter, etc… And while each pattern has its benefits, we have found that a Model-view-presenter (MVP) architecture works best when developing GWT apps for two main reasons. First the MVP model, much like other design patterns, decouples development in a way that allows multiple developers to work simultaneously. Secondly, this model allows us to minimize our use of GWTTestCase, which relies on the presence of a browser, and, for the bulk of our code, write lightweight (and fast) JRE tests (which don’t require a browser).

At the heart of this pattern is the separation of functionality into components that logically make sense, but in the case of GWT there is a clear focus on making the view as simple as possible in order to minimize our reliance on GWTTestCase and reduce the overall time spent running tests.

Building an MVP-based application can be straightforward and easy once you understand the fundamentals behind this design pattern. To help explain these concepts we will use a simple Contacts application as an example. This application will allow users to view, edit, and add contacts to a list of contacts that are stored on the server.

To begin we will break our application up into the following components:

We’ll then look into how each of these components interact by digging into:

Sample Project

The sample project, referenced throughout this tutorial, can be found at Tutorial-Contacts.zip.

Model

A model encompasses business objects, and in the case of our Contacts application we have:

  • Contact: A representation of a contact within the contact list. For simplicity, this object contains a first name, last name, and email address. In a more complex application, this object would have more fields.
  • ContactDetails: A light version of the Contact that contains only the unique identifier and display name. This “light” version of the Contact object will make the Contact list retrieval more efficient, as there will be fewer bits to serialize and transport across the wire. In the case of our example application, this optimization has less impact than it would in a more complex app where Contact objects have substantially more fields. The initial RPC will return a list of ContactDetails, and we’ve added a display name field so that there is some amount of data that can be displayed (within the ContactsView) without having to making subsequent RPCs.

View

A view contains all of the UI components that make up our application. This includes any tables, labels, buttons, textboxes, etc… Views are responsible for the layout of the UI components and have no notion of the model. That is to say a view doesn’t know that it is displaying a Contact, it simply knows that it has, for example, 3 labels, 3 textboxes, and 2 buttons that are organized in a vertical fashion. Switching between views is tied to the history management within the presentation layer.

The views in our Contacts application are:

  • ContactsView
  • EditContactView

The EditContactView is used to add new contacts as well as editing existing contacts.

Presenter

A presenter contains all of the logic for our Contacts application, including history management, view transition and data sync via RPCs back to the server. As a general rule, for every view you’ll want a presenter to drive the view and handle events that are sourced from the UI widgets within the view.

For our sample we have the following Presenters:

  • ContactsPresenter
  • EditContactPresenter

Just as with the view, the EditContactPresenter adds new contacts as well as edits existing contacts.

AppController

To handle logic that is not specific to any presenter and instead resides at the application layer, we’ll introduce the AppController component. This component contains the history management and view transition logic. View transition is directly tied to the history management and is discussed in greater length below.

At this point the overall hierarchy of our sample project should look like the following:

screenshot

With our component structure in place, and before we jump into wiring it all up, we’ll want to take a look at the process that starts everything up. The general flow, as shown in the following code, is:

  1. GWT’s bootstrap process calls onModuleLoad()
  2. onModuleLoad() creates the RPC service, Event Bus, and the AppController
  3. The AppController is passed the RootPanel instance and takes over
  4. From then on the AppController is in control of creating specific presenters and supplying a view that the presenter will drive.
public class Contacts implements EntryPoint {

  public void onModuleLoad() {    ContactsServiceAsync rpcService = GWT.create(ContactsService.class);    HandlerManager eventBus = new HandlerManager(null);    AppController appViewer = new AppController(rpcService, eventBus);    appViewer.go(RootPanel.get());  }}

Binding presenters and views

In order to bind the presenter and associated view together we’ll rely on Display interfaces that are defined within the presenter. Take for example the ContactsView:

screenshot

This view has 3 widgets: a table and two buttons. In order for the app to do something meaningful, the presenter is going to need to:

  • Respond to button clicks
  • Populate the list
  • Respond to a user clicking on a contact in the list
  • Query the view for selected contacts

In the case of our ContactsPresenter, we define the Display interface as such:

public class ContactsPresenter implements Presenter {  ...  public interface Display extends HasValue<List<String>> {    HasClickHandlers getAddButton();    HasClickHandlers getDeleteButton();    HasClickHandlers getList();    void setData(List<String> data);    int getClickedRow(ClickEvent event);    List<Integer> getSelectedRows();    Widget asWidget();  }}

While the ContactsView implements the above interface using Buttons and a FlexTable, the ContactsPresenter is none the wiser. In addition, if we wanted to run this application within a mobile browser we could switch out the views without having to change any of the surrounding application code. To be transparent, with methods such as getClickedRow() and getSelectedRows() the presenter is making the assumption that a view is going to display the data in the form of a list. That said, it’s at a high enough level that a view is still able to switch out the specific implementation of the list without any side effects. Method setData() is a simple way of getting model data into the view without the view having intrinsic knowledge of the model itself. The data being displayed is directly tied to the complexity of our model. A more complex model may lead to more data being displayed in a view. The beauty of using setData() is that changes to the model can be made without updating the view code.

To show you how this works, let’s look at the code that is executed upon receiving the list of Contacts from the server:

public class ContactsPresenter implements Presenter {  ...  private void fetchContactDetails() {    rpcService.getContactDetails(new AsyncCallback<ArrayList<ContactDetails>>() {      public void onSuccess(ArrayList<ContactDetails> result) {          contacts = result;          List<String> data = new ArrayList<String>();

          for (int i = 0; i < result.size(); ++i) {            data.add(contacts.get(i).getDisplayName());          }

          display.setData(data);      }

      public void onFailure(Throwable caught) {        ...      }    });  }}

To listen for UI events we have the following:

public class ContactsPresenter implements Presenter {  ...  public void bind() {    display.getAddButton().addClickHandler(new ClickHandler() {      public void onClick(ClickEvent event) {        eventBus.fireEvent(new AddContactEvent());      }    });

    display.getDeleteButton().addClickHandler(new ClickHandler() {      public void onClick(ClickEvent event) {        deleteSelectedContacts();      }    });

    display.getList().addClickHandler(new ClickHandler() {      public void onClick(ClickEvent event) {        int selectedRow = display.getClickedRow(event);

        if (selectedRow >= 0) {          String id = contacts.get(selectedRow).getId();          eventBus.fireEvent(new EditContactEvent(id));        }      }    });  }}

To respond to UI events, such as the user deleting a list of selected contacts, we have the following:

public class ContactsPresenter implements Presenter {  ...  private void deleteSelectedContacts() {    List<Integer> selectedRows = display.getSelectedRows();    ArrayList<String> ids = new ArrayList<String>();        for (int i = 0; i < selectedRows.size(); ++i) {      ids.add(contactDetails.get(selectedRows.get(i)).getId());    }        rpcService.deleteContacts(ids, new AsyncCallback<ArrayList<ContactDetails>>() {      public void onSuccess(ArrayList<ContactDetails> result) {        contactDetails = result;        List<String> data = new ArrayList<String>();

        for (int i = 0; i < result.size(); ++i) {          data.add(contactDetails.get(i).getDisplayName());        }                display.setData(data);      }            public void onFailure(Throwable caught) {        ...      }    });  }}

Again, in order to reap the benefits of the MVP model, the presenter should have no knowledge of any widget-based code. So long as we wrap a view in a display interface that can be mocked and our JRE tests never call asWidget(), all is grand. That’s how you can have your cake and eat it too: minimize the GWT ties to allow a non-GWTTestCase to be useful, but still have the ability to slap a Display instance into a panel.

Events and the Event Bus

Once you have presenters sinking events that are sourced by widgets within views, you’ll want to take some action on these events. To do so, you’ll want to rely on an Event Bus that is built on top of GWT’s HandlerManager. The Event Bus is a mechanism for a) passing events and b) registering to be notified of some subset of these events.

It’s important to keep in mind that not all events should be placed on the Event Bus. Blindly dumping all of the possible events within your app on the Event Bus can lead to chatty applications that get bogged down in event handling. Not to mention, you’ll find yourself writing a fair amount of boilerplate code to define, source, sink, and act upon these events.

App-wide events are really the only events that you want to be passing around on the Event Bus. The app is uninterested in events such as “the user clicked enter” or “an RPC is about to be made”. Instead (at least in our example app), we pass around events such as a contact being updated, the user switching to the edit view, or an RPC that deleted a user has successfully returned from the server.

Below is a list of the events that we have defined.

  • AddContactEvent
  • ContactDeletedEvent
  • ContactUpdatedEvent
  • EditContactCancelledEvent
  • EditContactEvent

Each of these events will extend GwtEvent and override dispatch() and getAssociatedType(). Method dispatch() takes a single param of type EventHandler, and for our application we have defined handler interfaces for each of our events.

  • AddContactEventHandler
  • ContactDeletedEventHandler
  • ContactUpdatedEventHandler
  • EditContactCancelledEventHandler
  • EditContactEventHandler

To demonstrate how these pieces fit together let’s look at what takes place when a user chooses to edit a contact. First we’ll need the AppController to register for the EditContactEvent. To do so, we call HandlerManager.addHandler() and pass in the GwtEvent.Type as well as the handler that should be called when the event is fired. The code below shows how the AppController registers to receive EditContactEvents.

public class AppController implements ValueChangeHandler {  ...  eventBus.addHandler(EditContactEvent.TYPE,      new EditContactEventHandler() {        public void onEditContact(EditContactEvent event) {          doEditContact(event.getId());        }      });  ...}

Here the AppController has an instance of the HandlerManager, called eventBus, and is registering a new EditContactEventHandler. This handler will grab the id of the contact to be edited, and pass it to the doEditContact() method whenever an event of EditContactEvent.getAssociatedType() is fired. Multiple components can be listening for a single event, so when an event is fired using the HandlerManager.fireEvent(), the HandlerManager looks for any component that has added a handler for event.getAssociatedType(). For each component that has a handler, the HandlerManager calls event.dispatch() with that component’s EventHandler interface.

To see how an event is fired, let’s take a look at the code that sources the EditContactEvent. As mentioned above, we’ve added ourselves as a click handler on the ListContactView’s list. Now when a user clicks on the contacts list, we’ll notify the rest of the app by calling the HandlerManager.fireEvent() method with a EditContactEvent() class that is initialized with the id of the contacts to be edited.

public class ContactsPresenter {  ...  display.getList().addClickHandler(new ClickHandler() {    public void onClick(ClickEvent event) {      int selectedRow = display.getClickedRow(event);              if (selectedRow >= 0) {        String id = contactDetails.get(selectedRow).getId();        eventBus.fireEvent(new EditContactEvent(id));      }    }  });  ...}

View transition is a proactive event flow, where the event source lets the rest of the app know, “Hey a view transition is about to take place, so if you have some last minute cleanup work to do, I’d suggest you do it now”. With RPC it’s a bit different. The event is fired when the RPC returns rather than before the RPC is made. The reason being is that the app is really only concerned when some state has changed (contact values changed, contact deleted, etc…), which is the case after the RPC has returned.

Below is an example of the event that is fired upon successfully updating a contact.

public class EditContactPresenter {  ...  private void doSave() {    contact.setFirstName(display.getFirstName().getValue());    contact.setLastName(display.getLastName().getValue());    contact.setEmailAddress(display.getEmailAddress().getValue());        rpcService.updateContact(contact, new AsyncCallback<Contact>() {        public void onSuccess(Contact result) {          eventBus.fireEvent(new ContactUpdatedEvent(result));        }        public void onFailure(Throwable caught) {           ...         }    });  }  ...}

History and view transitions

One of the more integral parts of any web app is the handling of history events. History events are token strings that represent some new state within your application. Think of them as “markers” for where you are in the application. Take, for example, a user navigating from the “Contact List” view to the “Add Contact” view and then clicking the “Back” button. The resulting action should land the user back on the “Contact List” view, and to do so you would push the initial state (the “Contact List” view) onto the history stack followed by a push of the “Add Contact” view. Thus when they click the back button the “Add Contact” token will be popped off of the stack and the current history token will be the “Contact List” view.

Now that we have the flow straight, we need to decide where to put the code. Given that history is not specific to a particular view, it makes sense to to add it to the AppController class.

To start with, we’ll need to have the AppController implement ValueChangeHandler and declare its own onValueChange() method. The interface and parameter are of type String because the History events are simply the tokens that are pushed onto the stack.

public class AppController implements ValueChangeHandler<String> {  ...  public void onValueChange(ValueChangeEvent<String> event) {    String token = event.getValue();    ...  }}

Next we’ll need to register to receive History events, much like we registered for events coming off of the EventBus.

public class AppController implements ValueChangeHandler<String> {  ...  private void bind() {    History.addValueChangeHandler(this);    ...  }}

In the example above, where the user navigates from the “Contact List” view to the “Add Contact” view, we mentioned setting an initial state. This is important because it not only gives us some starting point, but it’s also the piece of code that will check for an existing history token (for example, if a user bookmarked a specific state within your app) and route the user to the appropriate view. The AppController’s go() method, which is called after everything has been wired up, is where we’ll add this logic.

public class AppController implements ValueChangeHandler<String> {  ...  public void go(final HasWidgets container) {    this.container = container;

    if ("".equals(History.getToken())) {      History.newItem("list");    }    else {      History.fireCurrentHistoryState();    }  }}

With the above plumbing in place, we need to do something meaningful within the onValueChange() method that is called whenever the user clicks the “Back” or “Forward” button. Using the getValue() of the event, we’ll decide which view to show next.

public class AppController implements ValueChangeHandler<String> {  ...  public void onValueChange(ValueChangeEvent<String> event) {    String token = event.getValue();

    if (token != null) {      Presenter presenter = null;

      if (token.equals("list")) {        presenter = new ContactsPresenter(rpcService, eventBus, new ContactView());      }      else if (token.equals("add")) {        presenter = new EditContactPresenter(rpcService, eventBus, new EditContactView());      }      else if (token.equals("edit")) {        presenter = new EditContactPresenter(rpcService, eventBus, new EditContactView());      }

      if (presenter != null) {        presenter.go(container);      }    }}

Now when the user clicks the back button from the “Add Contact” view, GWT’s History mechanism will call the onValueChange() method with the previous history token. In our example the previous view was the “Contact List” view and the previous history token (which was set in the go() method) is “list”.

Handling history events in this manner isn’t limited to just “Back” and “Forward” handling, they can used for all view transitions. Going back to the AppController’s go() method, you’ll notice that we call fireCurrentHistoryState() if the current History token is not null. Thus if user specifies http://myapp.com/contacts.html#add, the initial history token will be “add” and fireCurrentHistoryState() will in turn call the onValueChange() with this token. This isn’t specific to just setting up the initial view with the app; other user interactions that result in a view transition can call History.newItem(), which will push a new history token onto the stack which will in turn trigger a call to onValueChange().

Below is an example of how you can hook up the ContactsPresenter to the “Add Contact” button, fire the associated event upon receiving the click, and transition to the “Add Contact” view as a result.

public class ContactsPresenter implements Presenter {  ...  public void bind() {    display.getAddButton().addClickHandler(new ClickHandler() {      public void onClick(ClickEvent event) {        eventBus.fireEvent(new AddContactEvent());      }    });  }}
public class AppController implements ValueChangeHandler<String> {  ...  private void bind() {    ...    eventBus.addHandler(AddContactEvent.TYPE,      new AddContactEventHandler() {        public void onAddContact(AddContactEvent event) {          doAddNewContact();        }    });  }

  private void doAddNewContact() {    History.newItem("add");  }}

Since the view transition logic is already built into the onValueChange() method, this provides a centralized, reusable way of navigating within the app.

Testing

The MVP model takes the pain out of unit testing a GWT app. That’s not to say that you can’t write unit tests without using the MVP model. In fact you can, but they will often times be slower than the average JRE-based JUnit test. Why? Well, in short, applications that don’t utilize the MVP model will require test cases that test components that rely on a DOM being present, a Javascript engine, etc… Essentially these test cases need to run in a browser.

GWT’s GWTTestCase makes this possible, as it will launch a “headless” browser to run each of the tests. The launching of the browser coupled with the actual execution of the test case is why these tests typically take longer than standard JRE tests. Within the MVP model, we strive to make a view, the component that encompasses the bulk of our code that relies on the DOM and Javascript engine, as small and as simplistic as possible. Less code equals less testing which means less time that it takes to actually run your tests. If the bulk of the code within your app is encompassed within a presenter, and that presenter relies strictly on JRE-based components, you can build the majority of your test cases as efficient, vanilla JUnit tests.

To demonstrate the benefits of using the MVP model to drive JRE-based unit tests, rather than those based on GWTTestCase, we’ve added the following tests to our Contacts app.

screenshot

Each example is set up to test adding a list of ContactDetails, sorting those ContactDetails, and then verifying that the sorted list is correct. Taking a look at the ExampleJRETest, we have the following code.

public class ExampleJRETest extends TestCase {  private ContactsPresenter contactsPresenter;  private ContactsServiceAsync mockRpcService;  private HandlerManager eventBus;  private ContactsPresenter.Display mockDisplay;    protected void setUp() {    mockRpcService = createStrictMock(ContactsServiceAsync.class);    eventBus = new HandlerManager(null);    mockDisplay = createStrictMock(ContactsPresenter.Display.class);    contactsPresenter = new ContactsPresenter(mockRpcService, eventBus, mockDisplay);  }   public void testContactSort(){    List<ContactDetails> contactDetails = new ArrayList<ContactDetails>();    contactDetails.add(new ContactDetails("0", "c_contact"));    contactDetails.add(new ContactDetails("1", "b_contact"));    contactDetails.add(new ContactDetails("2", "a_contact"));    contactsPresenter.setContactDetails(contactDetails);    contactsPresenter.sortContactDetails();    assertTrue(contactsPresenter.getContactDetail(0).getDisplayName().equals("a_contact"));    assertTrue(contactsPresenter.getContactDetail(1).getDisplayName().equals("b_contact"));    assertTrue(contactsPresenter.getContactDetail(2).getDisplayName().equals("c_contact"));  }}

Because we have structured the view behind a Display interface, we’re able to mock it out (using EasyMock in this example), remove the need for access to the browser’s resources (DOM, Javascript engine, etc…), and avoid having to base our tests on GWTTestCase.

We then created the same test using GWTTestCase.

public class ExampleGWTTest extends GWTTestCase {  private ContactsPresenter contactsPresenter;  private ContactsServiceAsync rpcService;  private HandlerManager eventBus;  private ContactsPresenter.Display display;

  public String getModuleName() {    return "com.google.gwt.sample.contacts.Contacts";  }

  public void gwtSetUp() {    rpcService = GWT.create(ContactsService.class);    eventBus = new HandlerManager(null);    display = new ContactsView();    contactsPresenter = new ContactsPresenter(rpcService, eventBus, display);  }

  public void testContactSort(){    List<ContactDetails> contactDetails = new ArrayList<ContactDetails>();    contactDetails.add(new ContactDetails("0", "c_contact"));    contactDetails.add(new ContactDetails("1", "b_contact"));    contactDetails.add(new ContactDetails("2", "a_contact"));    contactsPresenter.setContactDetails(contactDetails);    contactsPresenter.sortContactDetails();    assertTrue(contactsPresenter.getContactDetail(0).getDisplayName().equals("a_contact"));    assertTrue(contactsPresenter.getContactDetail(1).getDisplayName().equals("b_contact"));    assertTrue(contactsPresenter.getContactDetail(2).getDisplayName().equals("c_contact"));  }}

Given that our app was designed using the MVP model, it realistically makes no sense to structure your tests like this. But that’s not the point. The point is that ExampleGWTTest takes 15.23 secs to run, whereas the lightweight ExampleJRETest takes 0.01 secs. If you can manage to decouple app logic from widget-based code, your unit tests will be much more efficient. Imagine these numbers applied across the board to the hundreds of automated tests that are run on each build.

For more information on testing and the MVP model, checkout the article, Testing Methodologies Using Google Web Toolkit.

Follow-up topics

The MVP architecture is expansive; in future articles we look to discuss the following concepts:

Share
Posted in IT | Leave a comment

GWT Development with Activities and Places

GWT 2.1 introduced a built-in framework for browser history management. The Activities and Places framework allows you to create bookmarkable URLs within your application, thus allowing the browser’s back button and bookmarks to work as users expect. It builds on GWT’s history mechanism and may be used in conjunction with MVP development, though not required.

Strictly speaking, MVP architecture is not concerned with browser history management, but Activities and Places may be used with MVP development as shown in this article. If you’re not familiar with MVP, you may want to read these articles first:

Definitions

An activity simply represents something the user is doing. An Activity contains no Widgets or UI code. Activities typically restore state (“wake up”), perform initialization (“set up”), and load a corresponding UI (“show up”). Activities are started and stopped by an ActivityManager associated with a container Widget. An Activity can automatically display a warning confirmation when the Activity is about to be stopped (such as when the user navigates to a new Place). In addition, the ActivityManager warns the user before the window is about to be closed.

A place is a Java object representing a particular state of the UI. A Place can be converted to and from a URL history token (see GWT’s History mechanism) by defining a PlaceTokenizer for each Place, and GWT’s PlaceHistoryHandler automatically updates the browser URL corresponding to each Place in your app.

You can download all of the code referenced here in this sample app. The sample app is a simple “Hello, World!” example demonstrating the use of Activities and Places with MVP.

Let’s take a look at each of the moving parts in a GWT 2.1 app using Places and Activities.

  1. Views
  2. ClientFactory
  3. Activities
  4. Places
  5. PlaceHistoryMapper
  6. ActivityMapper

Then we’ll take at how you wire it all together and how it works.

  1. Putting it all together
  2. How it all works
  3. How to navigate
  4. Related resources

Views

A view is simply the part of the UI associated with an Activity. In MVP development, a view is defined by an interface, which allows multiple view implementations based on client characteristics (such as mobile vs. desktop) and also facilitates lightweight unit testing by avoiding the time-consuming GWTTestCase. There is no View interface or class in GWT which views must implement or extend; however, GWT 2.1 introduces an IsWidget interface that is implemented by most Widgets as well as Composite. It is useful for views to extend IsWidget if they do in fact provide a Widget. Here is a simple view from our sample app.

public interface GoodbyeView extends IsWidget {    void setName(String goodbyeName);}

The corresponding view implementation extends Composite, which keeps dependencies on a particular Widget from leaking out.

public class GoodbyeViewImpl extends Composite implements GoodbyeView {    SimplePanel viewPanel = new SimplePanel();    Element nameSpan = DOM.createSpan();

    public GoodbyeViewImpl() {        viewPanel.getElement().appendChild(nameSpan);        initWidget(viewPanel);    }

    @Override    public void setName(String name) {        nameSpan.setInnerText("Good-bye, " + name);    }}

Here is a slightly more complicated view that additionally defines an interface for its corresponding presenter (activity).

public interface HelloView extends IsWidget {    void setName(String helloName);    void setPresenter(Presenter presenter);

    public interface Presenter {        void goTo(Place place);    }}

The Presenter interface and setPresenter method allow for bi-directional communication between view and presenter, which simplifies interactions involving repeating Widgets and also allows view implementations to use UiBinder with @UiHandler methods that delegate to the presenter interface.

The HelloView implementation uses UiBinder and a template.

public class HelloViewImpl extends Composite implements HelloView {        private static HelloViewImplUiBinder uiBinder = GWT                        .create(HelloViewImplUiBinder.class);

        interface HelloViewImplUiBinder extends UiBinder {        }

        @UiField        SpanElement nameSpan;        @UiField        Anchor goodbyeLink;        private Presenter presenter;        private String name;

        public HelloViewImpl() {                initWidget(uiBinder.createAndBindUi(this));        }

        @Override        public void setName(String name) {                this.name = name;                nameSpan.setInnerText(name);        }

        @UiHandler("goodbyeLink")        void onClickGoodbye(ClickEvent e) {                presenter.goTo(new GoodbyePlace(name));        }

        @Override        public void setPresenter(Presenter presenter) {                this.presenter = presenter;        }}</WIDGET,>

Note the use of @UiHandler that delegates to the presenter. Here is the corresponding template:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"><ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"                         xmlns:g="urn:import:com.google.gwt.user.client.ui">        <ui:style>                .important {                        font-weight: bold;                }        </ui:style>        <g:HTMLPanel>                Hello,                <span class="{style.important}" ui:field="nameSpan" />                <g:Anchor ui:field="goodbyeLink" text="Say good-bye"></g:Anchor>        </g:HTMLPanel></ui:UiBinder>

Because Widget creation involves DOM operations, views are relatively expensive to create. It is therefore good practice to make them reusable, and a relatively easy way to do this is via a view factory, which might be part of a larger ClientFactory.

ClientFactory

A ClientFactory is not required to use Activities and Places; however, it is helpful to use a factory or dependency injection framework like GIN to obtain references to objects needed throughout your application like the event bus. Our example uses a ClientFactory to provide an EventBus, GWT PlaceController, and view implementations.

public interface ClientFactory {    EventBus getEventBus();    PlaceController getPlaceController();    HelloView getHelloView();    GoodbyeView getGoodbyeView();}

Another advantage of using a ClientFactory is that you can use it with GWT deferred binding to use different implementation classes based on user.agent or other properties. For example, you might use a MobileClientFactory to provide different view implementations than the default DesktopClientFactory. To do this, instantiate your ClientFactory with GWT.create in onModuleLoad(), like this:

    ClientFactory clientFactory = GWT.create(ClientFactory.class);

Specify the implementation class in .gwt.xml:

    <!-- Use ClientFactoryImpl by default -->    <replace-with class="com.hellomvp.client.ClientFactoryImpl">    <when-type-is class="com.hellomvp.client.ClientFactory"/>    </replace-with>

You can use <when-property-is> to specify different implementations based on user.agent, locale, or other properties you define. The mobilewebapp sample application defines a “formfactor” property used to select a different view implementations for mobile, tablet, and desktop devices.

Here is a default implementation of ClientFactory for the sample app:

public class ClientFactoryImpl implements ClientFactory {    private final EventBus eventBus = new SimpleEventBus();    private final PlaceController placeController = new PlaceController(eventBus);    private final HelloView helloView = new HelloViewImpl();    private final GoodbyeView goodbyeView = new GoodbyeViewImpl();

    @Override    public EventBus getEventBus() {        return eventBus;    }    ...}

Activities

Activity classes implement com.google.gwt.activity.shared.Activity. For convenience, you can extend AbstractActivity, which provides default (null) implementations of all required methods. Here is a HelloActivity, which simply says hello to a named user:

public class HelloActivity extends AbstractActivity implements HelloView.Presenter {    // Used to obtain views, eventBus, placeController    // Alternatively, could be injected via GIN    private ClientFactory clientFactory;    // Name that will be appended to "Hello,"    private String name;

    public HelloActivity(HelloPlace place, ClientFactory clientFactory) {        this.name = place.getHelloName();        this.clientFactory = clientFactory;    }

    /**     * Invoked by the ActivityManager to start a new Activity     */    @Override    public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {        HelloView helloView = clientFactory.getHelloView();        helloView.setName(name);        helloView.setPresenter(this);        containerWidget.setWidget(helloView.asWidget());    }

    /**     * Ask user before stopping this activity     */    @Override    public String mayStop() {        return "Please hold on. This activity is stopping.";    }

    /**     * Navigate to a new Place in the browser     */    public void goTo(Place place) {        clientFactory.getPlaceController().goTo(place);    }}

The first thing to notice is that HelloActivity makes reference to HelloView, which is a view interface, not an implementation. One style of MVP coding defines the view interface in the presenter. This is perfectly legitimate; however, there is no fundamental reason why an Activity and it’s corresponding view interface have to be tightly bound together. Note that HelloActivity also implements the view’s Presenter interface. This is used to allow the view to call methods on the Activity, which facilitates the use of UiBinder as we saw above.

The HelloActivity constructor takes two arguments: a HelloPlace and the ClientFactory. Neither is strictly required for an Activity. The HelloPlace simply makes it easy for HelloActivity to obtain properties of the state represented by HelloPlace (in this case, the name of the user we are greeting). Accepting an instance of a HelloPlace in the constructor implies that a new HelloActivity will be created for each HelloPlace. You could instead obtain an activity from a factory, but it’s typically cleaner to use a newly constructed Activity so you don’t have to clean up any prior state. Activities are designed to be disposable, whereas views, which are more expensive to create due to the DOM calls required, should be reusable. In keeping with this idea, ClientFactory is used by HelloActivity to obtain a reference to the HelloView as well as the EventBus and PlaceController.

The start method is invoked by the ActivityManager and sets things in motion. It updates the view and then swaps the view back into the Activity’s container widget by calling setWidget.

The non-null mayStop() method provides a warning that will be shown to the user when the Activity is about to be stopped due to window closing or navigation to another Place. If it returns null, no such warning will be shown.

Finally, the goTo() method invokes the PlaceController to navigate to a new Place. PlaceController in turn notifies the ActivityManager to stop the current Activity, find and start the Activity associated with the new Place, and update the URL in PlaceHistoryHandler.

Places

In order to be accessible via a URL, an Activity needs a corresponding Place. A Place extends com.google.gwt.place.shared.Place and must have an associated PlaceTokenizer which knows how to serialize the Place’s state to a URL token. By default, the URL consists of the Place’s simple class name (like “HelloPlace”) followed by a colon (:) and the token returned by the PlaceTokenizer.

public class HelloPlace extends Place {    private String helloName;

    public HelloPlace(String token) {        this.helloName = token;    }

    public String getHelloName() {        return helloName;    }

    public static class Tokenizer implements PlaceTokenizer<HelloPlace> {        @Override        public String getToken(HelloPlace place) {            return place.getHelloName();        }

        @Override        public HelloPlace getPlace(String token) {            return new HelloPlace(token);        }    }}

It is convenient (though not required) to declare the PlaceTokenizer as a static class inside the corresponding Place. However, you need not have a PlaceTokenizer for each Place. Many Places in your app might not save any state to the URL, so they could just extend a BasicPlace which declares a PlaceTokenizer that returns a null token.

PlaceHistoryMapper

PlaceHistoryMapper declares all the Places available in your app. You create an interface that extends PlaceHistoryMapper and uses the annotation @WithTokenizers to list each of your tokenizer classes. Here is the PlaceHistoryMapper in our sample:

@WithTokenizers({HelloPlace.Tokenizer.class, GoodbyePlace.Tokenizer.class})public interface AppPlaceHistoryMapper extends PlaceHistoryMapper{}

At GWT compile time, GWT generates (see PlaceHistoryMapperGenerator) a class based on your interface that extends AbstractPlaceHistoryMapper. PlaceHistoryMapper is the link between your PlaceTokenizers and GWT’s PlaceHistoryHandler that synchronizes the browser URL with each Place.

For more control of the PlaceHistoryMapper, you can use the @Prefix annotation on a PlaceTokenizer to change the first part of the URL associated with the Place. For even more control, you can instead implement PlaceHistoryMapperWithFactory and provide a TokenizerFactory that, in turn, provides individual PlaceTokenizers.

ActivityMapper

Finally, your app’s ActivityMapper maps each Place to its corresponding Activity. It must implement ActivityMapper, and will likely have lots of code like “if (place instanceof SomePlace) return new SomeActivity(place)”. Here is the ActivityMapper for our sample app:

public class AppActivityMapper implements ActivityMapper {    private ClientFactory clientFactory;

    public AppActivityMapper(ClientFactory clientFactory) {        super();        this.clientFactory = clientFactory;    }

    @Override    public Activity getActivity(Place place) {        if (place instanceof HelloPlace)            return new HelloActivity((HelloPlace) place, clientFactory);        else if (place instanceof GoodbyePlace)            return new GoodbyeActivity((GoodbyePlace) place, clientFactory);        return null;    }}

Note that our ActivityMapper must know about the ClientFactory so it can provide it to activities as needed.

Putting it all together

Here’s how all the pieces come together in onModuleLoad():

public class HelloMVP implements EntryPoint {    private Place defaultPlace = new HelloPlace("World!");    private SimplePanel appWidget = new SimplePanel();

    public void onModuleLoad() {        ClientFactory clientFactory = GWT.create(ClientFactory.class);        EventBus eventBus = clientFactory.getEventBus();        PlaceController placeController = clientFactory.getPlaceController();

        // Start ActivityManager for the main widget with our ActivityMapper        ActivityMapper activityMapper = new AppActivityMapper(clientFactory);        ActivityManager activityManager = new ActivityManager(activityMapper, eventBus);        activityManager.setDisplay(appWidget);

        // Start PlaceHistoryHandler with our PlaceHistoryMapper        AppPlaceHistoryMapper historyMapper= GWT.create(AppPlaceHistoryMapper.class);        PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);        historyHandler.register(placeController, eventBus, defaultPlace);

        RootPanel.get().add(appWidget);        // Goes to the place represented on URL else default place        historyHandler.handleCurrentHistory();    }}

How it all works

The ActivityManager keeps track of all Activities running within the context of one container widget. It listens for PlaceChangeRequestEvents and notifies the current activity when a new Place has been requested. If the current Activity allows the Place change (Activity.onMayStop() returns null) or the user allows it (by clicking OK in the confirmation dialog), the ActivityManager discards the current Activity and starts the new one. In order to find the new one, it uses your app’s ActivityMapper to obtain the Activity associated with the requested Place.

Along with the ActivityManager, two other GWT classes work to keep track of Places in your app. PlaceController initiates navigation to a new Place and is responsible for warning the user before doing so. PlaceHistoryHandler provides bi-directional mapping between Places and the URL. Whenever your app navigates to a new Place, the URL will be updated with the new token representing the Place so it can be bookmarked and saved in browser history. Likewise, when the user clicks the back button or pulls up a bookmark, PlaceHistoryHandler ensures that your application loads the corresponding Place.

How to navigate

To navigate to a new Place in your application, call the goTo() method on your PlaceController. This is illustrated above in the goTo() method of HelloActivity. PlaceController warns the current Activity that it may be stopping (via a PlaceChangeRequest event) and once allowed, fires a PlaceChangeEvent with the new Place. The PlaceHistoryHandler listens for PlaceChangeEvents and updates the URL history token accordingly. The ActivityManager also listens for PlaceChangeEvents and uses your app’s ActivityMapper to start the Activity associated with the new Place.

Rather than using PlaceController.goTo(), you can also create a Hyperlink containing the history token for the new Place obtained by calling your PlaceHistoryMapper.getToken(). When the user navigates to a new URL (via hyperlink, back button, or bookmark), PlaceHistoryHandler catches the ValueChangeEvent from the History object and calls your app’s PlaceHistoryMapper to turn the history token into its corresponding Place. It then calls PlaceController.goTo() with the new Place.

What about apps with multiple panels in the same window whose state should all be saved together in a single URL? You can accomplish this by using an ActivityManager and ActivityMapper for each panel. Using this technique, a Place can be mapped to multiple activities. For further examples, see the resources below.

Share
Posted in IT | Leave a comment

股价提醒网站www.stock-reminder.com

使用Google App Engine平台做了个网站www.stock-reminder.com,功能就是服务器轮询当前股价,越过警戒就会向手机邮箱发送邮件,前台还有个web界面做配置。通过这个网站学到了不少新东西。

首先是学会了java语言,我会用的编程语言现在包括了C、C++、C#、Delphi、VB、php,现在又加上java,当前的主流语言都碰了。Java让用多了C风格语言的我感觉很怪异,它没什么阻塞的操作,使用addListener添加监听,程序里面有很多new出来的Listener。

其次就是GWT,我还使用了EXT-GWT,通过强语言编写网页比用HTML写网页感觉容易一些,用HTML写的话会很快迷失在一大堆HTML标签里面,还有CSS和布局,有时候元素直接差几个像素就是去不掉,我毕竟不是专业写网页的。通过GWT直接使用Layout布局(当然最后还是会编译成HTML和javascript),还用了EXT-GWT的MVC模式,感觉很不错,准备用GWT的MVP模式重构一下。

还有就是GAE提供DataStore,任务队列,用作服务器端的保存配置和轮询股价,发邮件则是我自己写的一个API调用我的空间上的一个mail程序,GAE的发邮件配额每天只有100封。

Java程序感觉对设计模式要求很高,这个网站的结构我不大满意调整了好几次,感觉还是有可以优化的地方。

Share
Posted in IT | Leave a comment

GWT工程运行出错

    创建一个使用AppEngine的GWT工程,出现如下错误:

Exception in thread “main” java.lang.NoSuchMethodError: org.mortbay.thread.Timeout.<init>(Ljava/lang/Object;)V
    at org.mortbay.io.nio.SelectorManager$SelectSet.<init>(SelectorManager.java:306)
    at org.mortbay.io.nio.SelectorManager.doStart(SelectorManager.java:223)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    at org.mortbay.jetty.nio.SelectChannelConnector.doStart(SelectChannelConnector.java:303)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    at org.mortbay.jetty.Server.doStart(Server.java:233)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
    at com.google.gwt.dev.shell.jetty.JettyLauncher.start(JettyLauncher.java:667)
    at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:500)
    at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1055)
    at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:804)
    at com.google.gwt.dev.DevMode.main(DevMode.java:309)

解决方法:在项目的build path里面,把GWT SDK移到App Engine SDK前面。

Share
Posted in IT | Leave a comment

ChinaDaily早报6.28

【Bonus Oriens】
Youth is not a time of life; it is a state of mind; it is not a matter of rosy cheeks, red lips and supple knees; it is a matter of the will, a quality of the imagination, a vigor of the emotions; it is the freshness of the deep springs of life.
青春不是年华,而是心境;青春不是桃面、丹唇、柔膝,而是深沉的意志,恢宏的想象,炙热的感情;青春是生命的深泉在涌流。
Youth means a temperamental
predominance of courage over timidity, of the appetite for adventure over the love of ease. This often exists in a man of sixty more than a boy of twenty. Nobody grows old merely by a number of years. We grow old by deserting our ideals. (Samuel Ullman, American businessman and poet, 1840-1924)
青春气贯长虹,勇锐盖过怯懦,进取压倒苟安。如此锐气,二十后生有之,六旬男子则更多见。年岁有加,并非垂老;理想丢弃,方堕暮年。(塞缪尔·厄尔曼,美国商人、诗人,1840-1924)
【Markets】
Dow Jones
12,043.56 +108.98 +0.91%
Nasdaq
2,688.28 +35.39 +1.33%
【Highlights】
>UK, China seal trade deals
中英签23亿美元贸易协议
>Arrest warrant for Gaddafi
卡扎菲正式遭国际通缉
>Philip Morris may sue Aussie
澳统一香烟包装或被诉
>Peng loses at Wimbledon
彭帅温网惨败无缘八强
>Star helps poor on final journey
缅甸影星发展’殡葬慈善’
>Bugarach: refuge of 2012
法小村成2012末日圣地

【Cover Story】
>China deficit hits RMB800B
中央公共财政赤字8千亿
China’s Central Public Fiscal Deficit in 2010 hit RMB800b. Xinhuanet.com reported the deficit was RMB50b less than planned. Chairman of the Ministry of Finance Xie Xuren said public fiscal revenue in 2010 hit RMB4.2 trillion, or 111.6% of budget, up 18.3% from 2009. Last year, public fiscal expenditures totaled RMB4.8 trillion, or 103.6% of budget, up 10.3% from 2009.
据新华网报道,财政部部长谢旭人27日介绍,2010年中央公共财政收支总量相抵,赤字达8000亿元,比预算减少500亿元。2010年,中央公共财政收入4.2万亿元,完成预算的111.6%,比2009年增长18.3%。中央公共财政支出4.8万亿元,完成预算的103.6%,较2009年度增长10.3%。
【Top News】
>UK, China seal trade deals
中英签署巨额贸易协议
Britain and China unveiled a series of deals worth $2.3b during a Monday visit from Chinese Premier Wen Jiabao. Reuters reported the deals include a new agreement between energy group BG Group and Bank of China to help BG expand in the UK. “Our target is a hundred billion dollars of bilateral trade by 2015, something we discussed and agreed again this morning,” said UK Prime Minister David Cameron.
据路透社报道,当地时间27日,正在英国访问的中国国务院总理温家宝宣布与英国签署价值23亿美元的系列商业协议。其中包括中国银行与英国天然气集团的一项新合作协议,以帮助该集团在英国扩张。英国首相卡梅伦表示,中英希望在2015年使双边贸易额达到1000亿美元。他表示当日上午已经与温家宝总理讨论此问题,并就此达成一致。

>Arrest warrant for Gaddafi
卡扎菲正式遭国际通缉
The International Criminal Court (ICC) issued an arrest warrant for Libyan leader Muammar Gaddafi Monday. BBC reported the court accused him of crimes against humanity and of ordering attacks on civilians after an uprising against him began in mid-February. The Hague-based court also issued warrants for 2 of Col Gaddafi’s top aides – his son Saif al-Islam and intelligence chief Abdullah al-Sanussi.
据英国广播公司报道,位于荷兰海牙的国际刑事法院27日宣布,正式对利比亚领导人卡扎菲发出国际逮捕令。国际刑事法院指控卡扎菲犯有反人类罪,认为他自2月中旬国内爆发动乱后曾下令袭击平民。国际刑院同时对卡扎菲的儿子赛义夫·伊斯拉姆·卡扎菲,以及利比亚情报部门最高负责人阿卜杜拉·阿尔·塞努希发布逮捕令。
>Helicopter sales to grow
民用直升机潜力难估量
Demand for civilian helicopters in China is expected to climb, making China a potentially large market. Wang Bin, president of AVIC Helicopter Co, said the number of civilian helicopters in service is expected to soar from the current 170 to more than 1,500 over the next decade. Interest in helicopters has been growing since the Wenchuan earthquake in 2008 when helicopters played a crucial role in air rescue service. Industries such as offshore
drilling and high-rise construction sites have started using helicopters to increase production efficiency.
中国将迎来中国民用直升机市场井喷时代,市场潜力巨大。中航直升机有限公司总裁王斌近日透露,未来10年,中国的注册民用直升机数量预计将从现在的170架增长至1500余架。直升机在2008年的汶川大地震航空救援中发挥了重要作用,引发了中国社会对直升机的持续关注。目前,海底钻探、高楼施工等领域均开始使用直升机提高生产效率。

Did You Know?
你知道吗?
–The Beijing Municipal Public Security Bureau used 2 helicopters to patrol for fires during the Spring Festival for the first time this year.
今年春节期间,北京公安已经首次利用2架直升机监控防火。
>Philip Morris may sue Aussie
澳统一香烟包装或被诉
Tobacco giant Philip Morris has threatened to sue the Australian government over its plan to introduce plain, brandless packing for cigarettes. The BBC reported Monday Australia’s government has proposed to ban logos and branding on tobacco packaging. The company said it had sent a legal notice to the Australian government setting a mandatory 3-month period for the 2 sides to negotiate on the issue. It warned that if no agreement was reached in that time, it would seek financial compensation. “We estimate it may be in the billions (of dollars) but ultimately it will be up to a panel operating under the United Nations International Trade rules to decide,” said Philip Morris Asia spokeswoman Anne Edwards.
据英国广播公司27日报道,由于澳大利亚政府计划推行香烟统一包装制度,厂商不得在烟盒上印刷企业品牌和商标,烟草巨头菲利普·莫里斯公司威胁要起诉澳洲政府。菲利普·莫里斯公司称,已向澳洲政府发送法律公告,要求双方在3个月内就此事进行协商。该公司警告称,若在此期间未达成协议,将索取经济补偿。该公司亚洲发言人安妮·爱德华表示:”我们估计索赔金额将达数十亿美元,但最终数额将由联合国国际贸易法委员会小组审议决定。”

【In Brief】
>Reuters reported floodwater in North Dakota, the US has swallowed 3,000 homes and displaced more than 12,000 local residents.
据路透社报道,美国北达科塔州近日爆发的洪水已吞没3000处民宅,导致逾1.2万当地居民流离失所。
>The National Bureau of Statistics announced profits for China’s industrial businesses rose 27.9% year-on-year in the first 5 months of this year, hitting RMB1.92 trillion.
国家统计局发布报告,1至5月,全国规模以上工业企业实现利润19200亿元,同比增长27.9%。
>Reuters reported a survey showed 60% of Japanese voters want Prime Minister Naoto Kan to resign by the end of August.
据路透社报道,27日公布的民调显示,60%的日本选民希望首相菅直人今年8月底前辞职。
>AFP reported Peng Shuai lost (4-6, 2-6) to Maria Sharapova in the 4th round at Wimbledon Monday. (See photo)
据法新社报道,中国金花彭帅在27日的温网第4轮中以4-6、2-6连丢两局,不敌沙拉波娃,无缘8强。(见图)

【Newsmaker】
>Star helps poor on final journey
缅甸影星发展’殡葬慈善’
He’s the star of hundreds of films in Myanmar, but these days Kyaw Thu is more likely to be found delivering coffins for the poor. In the last decade, he has helped more than 100,000 families pay their last respects to late relatives, without charging a single kyat. “I want someone’s final journey to be good enough,” said the 51-year-old. He founded the Free Funeral Services Society organization to fill the void left by an underfunded public sector and relatively low foreign aid. Myanmar is one of the world’s least developed countries with nearly a third of the population living below the poverty line.
他叫觉都,是曾出演过数百部电影的缅甸明星;而如今,他奔波在给穷人送棺木的路上。过去10年里,他帮助超过10万个家庭安葬了去世的亲友,没有收取任何费用。这位51岁的”免费殡葬协会”创始人表示:”我希望一个人的最后一程走得体面些。”他的协会试图弥补缅甸公共部门资金不足、外国援助相对较少的弊端。缅甸是全球最不发达国家之一,近1/3人口生活在贫困线以下。

【Kaleidoscope】
>Bugarach: refuge of 2012
法小村成2012末日圣地
Residents of a tiny French town are growing concerned because of Doomsday believers. Reuters reported these believers think the southern French town of Bugarach is the only place that will survive judgment day in 2012. After natural disasters like the earthquake in Japan and hurricanes in the US, the number of people flocking to Bugarach has risen sharply. The town of 194 people has received around 20,000 visitors since the start of the year, more than double last year’s figures.
据路透社报道,Bugarach是法国南部的一个小村庄,当地人口仅有194人。但近日,大量”末世信徒”涌入该村,因为他们深信,这里是唯一能逃过2012末日审判活下来的地方。发生在日本的地震和美国的飓风等自然灾害让人们更加相信末日即将来临,使得涌入Bugarach的人数爆增:年初至今游客数量已达2万人,是去年人数的2倍多。
>Obedient Wives: ‘a setback’
‘听话妻子俱乐部’惹争议
The new Obedient Wives Club has caused controversy in Indonesia’s Jakarta. The club encourages women to be totally obedient to their husbands, especially sexually. The club says it can cure social ills such as prostitution and divorce by teaching women to be a “whore in bed” for their husbands. The women’s empowerment minister of Indonesia said it was “a huge setback for both women and men because asking wives to be whores is really not the right thing to do. Marriage is about commitment and responsibility. It’s so much more than just sex.”
印尼雅加达日前开设”听话妻子俱乐部”,鼓励妇女对丈夫百依百顺,特别是在性方面,在当地引发争议。该俱乐部声称,教导妻子”象妓女一样”满足丈夫的性需要,可以帮助减少卖淫、离婚等社会问题。印尼妇女事务部长称,”不管对男人还是对女人而言,要求妻子在丈夫面前扮演妓女角色,都是一个巨大的倒退,这是完全错误的。婚姻的意义远高于性,它意味着承诺与责任。”

【Talk Show】
日常口语看似十分简单的句子,也容易犯Chinglish的错误。本期我们看看中国人容易说错的句子到底应该如何表达。
>我的舞也跳得不好。
误:I don’t dance well too.
正:I am not a very good dancer either.
注:当我们说不擅长做什么事情的时候,英语里面通常用not good at something,英语的思维甚至直接跳跃到:我不是一个好的舞者。

【TOEIC Test Daily】
Please bring your ________ application and a list of references to the interview on Tuesday.
Key: (C) completed
[答案解析]根据句意,application前需要一个形容词来修饰,表示”填写完整的”,四个选项中只有C符合条件。

Share
Posted in English | Leave a comment

ChinaDaily早报6.27

【Bonus Oriens】
The world is a dangerous place. Not because of the people who are evil; but because of the people who don’t do anything about it. (Albert Einstein, German-born American physicist, 1879-1955)
世界是个危险的地方。不是因为有恶人,而是因为有人无动于衷。(阿尔伯特·爱因斯坦,德裔美籍物理学家,1879-1955)
【Highlights】
>Wen Jiabao visits UK
温家宝抵英启’商贸之旅’
>US bridges made in China
美国桥梁偏爱’中国制造’
>iPad leads tablet traffic
iPad引领平板电脑流量
>Wozniacki silent on crush
利物浦球星暗恋网坛1姐?
>Man with 150 pellets in body
男子身中百弹竟无恙(图)
【Notice】
《爱·分享》周一有约
【Cover Story】
>Back saving methods
美媒支招如何’拯救’腰背
Everyone wants to avoid back trouble, but surprisingly few of us manage to escape it. If the spine is not balanced, you will inevitably have problems in back, neck, shoulders and joints. Back pain can often be prevented by developing proper posture. The New York Times gives some advice to help you stave off back problems.
每个人都想避免背部问题,但令人吃惊的是,很少有人成功摆脱背痛的纠缠。如果脊椎受力不平衡,那么背部、脖子、肩膀甚至关节都不可避免地会出问题。保持良好的坐姿通常可以避免背痛。《纽约时报》给出了一些可以帮你远离背部问题的建议。

–Stand up and lift your chin slightly; align your ears over your shoulders and your shoulders over your hips. Place your hands on your hips and pitch forward about 5 cm. There should be a slight inward curve in your lower back, an outward curve in your upper back, and another inward curve at your neck. Maintain this posture and sit down.
站起来,稍微扬起你的下巴,让双耳与双肩、双肩与臀部处于同一条线上。双手放在臀部,身体前倾约5厘米。这时你下背部应该是一条略微内凹的曲线,上背部是一条略微外凸的曲线,脖子则是另一条内凹的曲线。保持这个姿势,坐下。
–When you are sitting or driving for long periods of time, place a cushion or rolled-up towel between the curve of your lower spine and the back of your seat.
当你需要坐很久或者开车很久时,应该用垫子或卷起的毛巾垫在脊柱尾部曲线和座椅靠背的空隙之间。
–If you sit at a desk all day, the center of your computer screen should be at eye level and the desk height should allow your forearms to rest comfortably at a 90-degree angle. Work with your feet flat on the floor and your back against the chair.
如果你需要一整天在办公桌前工作,电脑显示器的中心应该和你的眼睛在同一高度,办公桌高度应该保证你的前臂能以一个直角舒服地放在桌面。工作时你的脚应该平放在地板上,而背部要靠在椅子上。
–Whether you work in an office or at home, get up and stretch every 30 to 60 minutes. Stand up and place your hands on your lower back as if you were sliding them into your back pockets. Gently push your hips forward and slightly arch your back. Sit back down and circle your shoulders backward with your chin tucked about 10 times.
无论你在公司还是在家工作,每30到60分钟就应该起身做伸展运动。站起来,把手放在下背部,感觉就像你要把手放进背后的裤袋里。轻轻向前推臀部,背部略微后仰。再坐下来,肩膀依次向后转,收下巴,重复上述动作约10次。

【Top News】
>Wen Jiabao visits UK
温家宝抵英启’商贸之旅’
Chinese Premier Wen Jiabao has arrived in the UK for a visit focused on boosting economic ties. On Sunday, the life-long Shakespeare admirer took a tour of the historic town of Stratford-upon-Avon, the birthplace of Shakespeare. He also visited the Longbridge car factory, a plant now owned by China’s Shanghai Automotive Industry Corporation. On Monday, Wen will take part in an annual UK-China Summit attended by UK Prime Minister David Cameron. BBC said business deals are expected to be announced during the 3-day UK tour. The UK stop is the 2nd leg of a 3-nation tour (Hungary, Britain and Germany) in Europe.
中国总理温家宝目前已经抵达英国,开启欧洲3国之旅(匈牙利、英国、德国)的第2站,以期推进中英双方经济联系。当地时间26日,”莎翁终身崇拜者”温家宝探访了莎士比亚出生地——英国历史名城埃文河畔斯特拉特福。温家宝还访问了上汽集团的英国长桥工厂。当地时间27日,温家宝和英国首相卡梅伦将出席中英领导人年度峰会。据英国广播公司报道,温家宝访英3天期间或将宣布一系列商贸协定。
>US bridges made in China
美国桥梁偏爱’中国制造’
Hundreds of Chinese laborers are now completing work on the San Francisco-Oakland Bay Bridge. Next month, the giant steel modules will be transported to Oakland. California officials said the state saved hundreds of millions of dollars by turning to China. “They’ve produced a pretty impressive bridge for us,” said a program manager at the California Department of Transportation. According to the New York Times, the labor is taking place at a sprawling manufacturing complex in Shanghai.
据《纽约时报》报道,在上海某大型建筑工地上,数百名中国工人在忙碌着,他们的工作是建设美国旧金山-奥克兰海湾大桥的一部分。下个月,这些巨大的钢模被完成后,将被运至加州的奥克兰市。加州官员表示,把项目包给中国建筑商让他们省下了数亿美元。而且,据加州交通部一项目经理说:”中国公司建造的桥让我们非常满意。”

[中国项目海外不'豆腐']
–Chinese companies have built a reputation for doing good work on large projects. Projects like Beijing’s Olympic-size airport terminal and the hydroelectric Three Gorges Dam have led to Chinese companies being hired to build copper mines in the Congo, high-speed rail lines in Brazil and huge apartment complexes in Saudi Arabia.
中国在建造这样的大工程上可谓名声在外了,比如规模宏大的北京机场航站楼,庞大的水利系统三峡大坝。国外方面,中国公司还承建过刚果的铜矿、巴西的高铁线路以及沙特的摩天公寓建筑。
–In New York City, Chinese companies have won contracts to help renovate the subway system, refurbish the Alexander Hamilton Bridge over the Harlem River and build a new Metro-North train platform near Yankee Stadium.
就纽约市一处而言,中国公司也是合约不断,比如翻新地铁系统,重修哈莱姆河上的亚历山大·汉密尔顿桥,以及新建洋基球场附近的北部铁路站台等。
>iPad leads tablet traffic
iPad引领平板电脑流量
A new study has found that 89% of the world’s Internet traffic on tablets comes from Apple’s iPad. The iPad makes up the highest percentage of total non-computer traffic in Canada with 33.5%. Brazil is 2nd with 31.8%. Though Android tablets are far behind the iPad in terms of traffic, Android smartphones topped the iPhone with 35.6% of smartphone traffic, while the iPhone had 23.5%. The data also suggested iOS users are more likely to access the Internet over a Wi-Fi network than are Android users.
最新发布的一份研究报告显示,iPad贡献了全球平板电脑互联网流量的89%。报告显示,在加拿大,非传统计算机互联网流量中有33.5%来自iPad,这一比例在该报告涉及的所有国家中排名第1。排名第2的是巴西,该比例为31.8%。尽管安卓平板电脑的互联网流量不及iPad,但安卓手机在智能手机互联网流量中占35.6%,高于iPhone的23.5%。数据还显示,相对于安卓用户,iOS用户更多地通过WiFi网络连接互联网。

【In Brief】
>The China Earthquake Networks Center said an earthquake measuring 5.2 on the Richter scale hit Yushu in Qinghai at 3:48 pm Sunday.
中国地震台网中心监测显示,26日下午3时48分,青海省玉树发生里氏5.2级地震。
>Xinhua reported 7 migrant workers were killed when a building under construction collapsed in Xi’an Saturday afternoon. Local authorities in Shaanxi confirmed the deaths Sunday.
据新华社报道,陕西当地官员26日证实,25日下午,西安一在建民房倒塌致7名民工死亡。
>People in Beijing spend 38 minutes to get to work on average under normal traffic conditions. The Chinese Academy of Sciences reported it is the longest among 50 mainland cities. Shanghai and Guangzhou ranked 2nd with 36 minutes of commuting time each.
中科院日前发布的最新调查显示,在不堵车的情况下,大陆50个城市中,北京市上班平均花费的时间最长,为38分钟;上海和广州以36分钟紧随其后。

【Celebrity】
>Wozniacki silent on crush
利物浦球星暗恋网坛1姐?
World No 1 tennis star Caroline Wozniacki breezed through her latest match with a typically powerful display at Wimbledon. The Daily Mail reported she received some secret good wishes from a mystery Liverpool footballer as well. Wozniacki refused to name her Anfield admirer for fear of being romantically linked with him. “I don’t know if I’m supposed to be mentioning name,” Wozniacki said. “You know, all of them are well known. The last 3 weeks I’ve been in the spotlight, supposedly dating every single guy I’ve been to dinner with. If I say a name here, I’m sure it will make a big headline. So I just prefer to be quiet about it.”
女子网坛现世界排名第一的沃兹尼亚奇日前依靠一贯强势的发挥,毫无悬念地晋级温网下一轮,而《每日邮报》26日披露了一则花絮,丹麦甜姐在赛前收到了某位利物浦球星的神秘祝福。由于害怕惹上绯闻,沃兹尼亚奇拒绝透露这位球星的名字,她说:”我不知道我是否该说出他的名字。要知道,他们所有人都是大名鼎鼎。过去的3周里,我一直都是媒体关注的焦点,只要我一和单身男士吃饭,媒体就会报道我在和这个人约会。如果我在这里说出他的名字,我敢肯定又会是一个头条新闻。所以,我更想对此保持沉默。”
Did You Know?
你知道吗?
–Caroline Wozniacki admits she’s a huge Liverpool fan. She wore a Liverpool shirt signed by footballer Steven Gerrard on the court in the 2011 Qatar Ladies Open.
沃兹尼亚奇曾披露她是利物浦的忠实拥趸,今年的卡塔尔女子网球公开赛上,她身穿一件杰拉德签名的利物浦球衣惊艳亮相。

【Kaleidoscope】
>Man with 150 pellets in body
男子身中百弹竟无恙(图)
An X-ray shows the horrific wounds of a British man peppered with more than 150 shotgun pellets (see photo). The Daily Mail of London reported 33-year-old Joe Clarke survived the attack, but doctors could not remove any of the pellets because some had penetrated his vital organs. The horrific injuries will reduce his life expectancy by at least 25 years. In December 2007, 2 men brandishing sawn-off shotguns [枪管锯短的猎枪] opened fire on Clarke when he answered the door of his home. The attack is alleged to have stemmed from a rift with his former girlfriend.
据英国《每日邮报》报道,英国一男子日前遭散弹枪扫射,X光显示,其身上有令人触目惊心的150多颗弹丸(见图)。33岁的乔·克拉克虽然最终活了下来,但医生表示,因为部分弹丸已深入其体内关键器官,无法移除,克拉克将至少减寿25年。悲剧发生于2007年12月份,克拉克在家中应声开门后,门外两名持短管散弹枪的男子朝其开枪。据悉,此次袭击与克拉克与前女友的感情纠葛有关。

【Talk Show】
本期我们继续纠正中式英语。
>我想我不行。
误:I think I can’t.
正:I don’t think I can.
注:汉语里说”我想我不会”的时候,英语里面总是说”我不认为我会”。
【爱·分享】
自”爱·分享”栏目上线以来,我们的编辑在编稿之余,每天都会抽出时间来阅读大家心中的最爱,并尽量在版面允许的范围内,对分享内容进行最大限度的扩容。希望这些”爱”能给您带来一周的好心情,也希望您随时和CD读友分享那些感动您的、激励您的好作品(请发送”Love+推荐内容”并附上推荐理由至106580007835(免信息费),我们每周一早上不见不散。

[书籍类]
北京1521***7008
推荐奥地利作家茨威格(Stefan Zweig)的《一个陌生女人的来信》。作者用细腻的笔触将一个女人对花心男子一生的爱慕之情描绘得淋漓尽致。她对他的爱倾其一生,但她在他的眼中却永远只是个陌生的女人而已。看着花瓶中凋零的花朵,他似有所悔……
(编辑附语):温馨提示:1、本小说氛围寒冷潮湿,女性朋友自觉预备纸巾;2、对于妙龄的孤独的孩子,本小说如一副解药;而对于年稍长的读者,本小说是一首”相信爱的年纪,未能唱给你的歌曲”。3、合上书不妨思考:这个描写卑微女性心路历程的故事,究竟是作家茨威格做为男性自高自大的微妙心理影射,还是对弱者和众生的悲悯和同情?4、据说,李健创作《传奇》这首歌(王菲翻唱)的灵感,就是来自他对这本小说的感触。”只是因为在人群中多看了你一眼”。有时间了,不妨找来原唱李健的《传奇》一听。

湖北黄石1387***0277
这是一个真实,勇敢而略带些残酷的故事。不知在这个世界上又有多少人能如此直面死亡的恐惧。于娟,Being alive is a gift,你是个勇士,即使直面惨淡的人生,即使正视淋漓的鲜血,苍天却未必给了你一个完美的人生。她曾经说:哪怕就让我那般痛,痛得不能动,每日像个瘫子污衣垢面趴在国泰路政立路的十字路口上,任千人唾骂万人践踏,只要能看着爸妈牵着土豆(编者注:于娟2岁的幼子)的手蹦蹦跳跳去幼儿园去上学,我也是愿意的。这是多么渴望生存的人声嘶力竭的呼喊。这一刻,您是伟大的,任何的名利都那么不值一提……在此,我推荐《此生未完成》。
(编辑附语):于娟,32岁,上海复旦大学教师。2009年12月确诊患乳腺癌。在最后的日子里,于娟开设了名为”活着就是王道”的新浪博客,写下了79篇抗癌日记,文风鲜活幽默。2011年4月19日,于娟辞世。她的博文、日记汇总后,就有了这本《此生未完成》。
在这个故事里,很多读者看到的不是于娟,而是自己。
于娟博客地址:http://blog.sina.com.cn/u/1904273792

山东青岛1595***0657
推荐《年轮》,作者梁晓声。讲述了祖国变迁在几个兄弟之间留下的岁月痕迹。他们在那个时代留下了开拓者深刻的足迹,彰显了卓越的人性之光,纵然时代扭曲而此精神不可亵渎,纵然岁月异常而此精神不可轻薄,因为它乃是从祖先至我们,以人类的名义所肯定的奋勇!上下跨度三十年,很感动!
上海1370***1934
我要推荐丰子恺先生的《护生画集》,作者寥寥数笔就勾画出了丰富而有趣的生命场景,并配以诗句来表达对生命的理解和感悟。流浪猫,待宰杀的鸡,微小的虫蚁,枝头小鸟,耕牛,都是人类需要保护的生灵。经常看到新闻里人们虐待小动物和不珍惜自己生命的举动,我想,这本画集无疑能够帮助我们明白珍惜各种生命形式的重要,用一颗宁静、感恩和温暖的心灵去关爱自己和这个世界。

[影视类]
山东烟台1876***2821
《荒岛余生》(Cast Away)讲述的是一名极其注重时间观念的联邦快递员因一次飞机失事,流落到了荒岛上,由此展开了一段现代的鲁宾逊的生活。刚看完这部电影的那段时间,正好去海边露营,晚上在海边听着涛声,看着潮水一次又一次不停歇地冲刷着沙滩,突然明白人和自然是一体的。自然哺育着地球的子民,又严厉地磨砺着人们的意志。汤姆·汉克斯,这个不算帅的男人,在这部影片中一如既往地用自己的精湛演技,向人们诠释着自然与人生。
内蒙古呼和浩特1347***0046
《音恋》是部日本电影,由冈田准一和麻生久美子演绎一段唯美浪漫的纯爱故事。这部电影让人喜爱的地方在于它的质朴和可爱,既不矫情不做作,也没有刻意卖弄纯爱片的风骚。两个近三十的男女——摄影师聪和花艺师七绪,邻居了几年,隔着薄薄的一堵墙,每天都在聆听对方的生活,但这样的两人竟素未谋面,甚至连擦肩而过都没有,唯一的交集就是会哼同一首歌。挺刻意的设定不是吗,但好在影片没有像《向左走,向右走》那样过分渲染那些”命中注定”或”缘分”之类过多的偶然性东西。其实生活中哪儿有那么多偶然呀,只因我们不伫足留意而错过了很多本可以发现的美好,不是吗……

江西南昌1517***8733
推荐一个很经典的电影《黑天鹅》。讲述的是一个追求完美的舞者对自己苛刻的要求,最后因为幻想而误杀自己的故事。在现实生活中,我们有时会迷惑自己为什么老是活得那么累,其实不是因为别的,而是自己从心底里放不过自己……从容一些,坦然一点,自己的幸福真的只有自己能体会……
重庆1599***8147
推荐《初恋这件小事》,这部电影以清新自然的方式诠释了一段美好的初恋,情节虽然老套,但相信所有看过这部电影的同学都会重温初恋的美好,值得一看!
[音乐类]
上海1478***2466
推荐朴树的歌《Colorful Days》,朴树沙沙的却饱含穿透力的声音:Imagination, never lose my passion. It’s on my way, it’s on my way. All the colorful days。每句词都唱到心里,充满张力。

海南海口1524***9302
暴风雨后的夜晚,倾听《一首简单的歌》,写一首简单的歌,让你的心情快乐,爱情就像一条河,难免会碰到波折,这一首简单的歌,并没有什么独特,好像我那么的平凡却又深刻!所谓爱,就是把你的快乐当作我的幸福。人生苦短,缘来缘去,宠辱不惊,好好爱,享受爱!
浙江宁波1875***5227
张震岳的歌《小宇》。好玩的名字+淡淡的声音+经典的歌词,很容易令人引发”同桌的你”般淡淡的伤感。让我这整日窝图书馆的人,忽然想在毕业前谈场《毕业那天我们一起失恋》般的恋爱。不是不爱,只是无法许你任何有关未来的山盟海誓;不是不真,纵然它稍纵即逝,也不会把它当游戏。”不管未来会怎么样,至少我们现在很开心。不管结局会怎样,至少想念的人是你”。
上海1582***2962
推荐《May It Be》(祈愿)。在我还在上高三的时候,我偶尔发现了这首来自恩雅的歌曲,它仿佛是一股清凉的泉水,舒缓着我的疲惫。恩雅的歌声如此沉静,在简单的配乐下将一个人的心路历程娓娓道来。曾经有一位老师说过,如果你听了这首歌后真的有所感触,那你真的是长大了。
【Subscribe】
Send text “CD” to 10658000.
Twice a day, 5 yuan a month.
发短信CD到10658000
订阅ChinaDaily手机报
每日两期,5元/月
客服短信:106580007835(免信息费)
合作邮箱:mobile@chinadaily.com.cn

Share
Posted in English | Leave a comment

ChinaDaily晚报6.27

【Weather June 28】
Shanghai: rainy 25~30℃
【BOC Exchange Rates】
100 USD = 647.5 CNY
100 GBP = 1031.08 CNY
100 EUR = 913.56 CNY
100 HKD = 83.12 CNY
100 JPY = 8.0151 CNY
(以上基准价来源于中国银行网站。)
【Markets】
Shanghai Stock
2758.23 +12.02 +0.44%
HangSeng Stock
22041.77 -130.18 -0.59%
【Highlights】
>Tax law amendment reviewed
个税二审仍3000元起征
>Best employers in China
中移动中行成’最佳雇主’
>Weibo to be China’s Facebook
新浪微博打造Facebook?
>What the rich are reading
富豪今夏’必读书单’出炉
>River Plate relegated
河床降级引发球迷骚乱
>Bayberry sold for RMB1,980
浙江’超级杨梅’一颗1980元

【Cover Story】
>PKU: top billionaire producer
北大造富能力内地居首
A total of 79 Peking University graduates have become billionaires in the past decade. Peking University President Zhou Qifeng said the university has ranked at the top in producing China’s richest for 3 consecutive years. The Beijing News reported PKU alumni on the Global Chinese Billionaire List released April by Forbes include, Robin Li, CEO of Baidu.com and the mainland’s richest man, Wang Zhidong, initiator of Sina.com, and Li Ning, president of Lining Company Ltd.
据《新京报》报道,北京大学校长周其凤称,10多年来,北大校友中诞生了79位亿万富豪,连续3年高居内地高校首位。据悉,在今年4月公布的福布斯全球华人富豪榜上,中国大陆地区新首富、百度CEO李彦宏就是北大校友,此外还包括新浪网的创办人王志东、李宁体育用品有限公司董事长李宁等。
【Top News】
>Tax law amendment reviewed
个税二审仍3000元起征
China’s top legislature started its 2nd reading of an amendment to the country’s individual income tax law Monday. Xinhua reported the amendment would keep the monthly tax exemption income threshold at RMB3,000, the same threshold specified during the first reading. The new draft also proposes reducing the current minimum tax rate from 5% to 3%, which will further reduce the tax burden on low-income earners.
据新华社报道,全国人大常委会27日二次审议个税法修正案草案。二审草案维持3000元的起征点不变,将超额累进税率中的第一级税率由5%降到3%,以进一步降低较低收入人群的税负。

>Best employers in China
中移动中行成’最佳雇主’
Chinahr.com released a list of the 50 Best Employers for College Students Sunday. Foreign enterprises turned the tables this year, with 10 moving up in the 2011 rankings compared to the only 3 firms last year. China’s state-owned companies continued strong performance in the rankings, with China Mobile and the Bank of China in the top 2 places. Chinese e-commerce giant Alibaba took 3rd, followed by Microsoft and Commercial Bank of China.
26日,中华英才网发布2011年中国大学生最佳雇主榜单,外企今年打了个”翻身仗”,跻身前50的数量由去年的3家增至10家。国企仍处霸主地位,中移动、中国银行、阿里巴巴、微软和中国工商银行分居最佳雇主前5名。
>Weibo to be China’s Facebook
新浪微博打造Facebook?
Charles Chao has built Sina Corp into a Chinese Twitter. Now he wants it to be a Chinese Facebook too. In an interview with the Wall Street Journal Sina’s chief executive laid out a series of changes he is making to Weibo to broaden its offerings and attract more users. Chao said Weibo won’t be turning into Facebook, but will have more Facebook-like features to allow for “stronger social relationships based on our new applications.”
曹国伟已将新浪打造成中国的Twitter,现在他还想让它成为中国的Facebook。日前,新浪首席执行长曹国伟接受美国《华尔街日报》采访时,列出了对新浪微博的一系列调整计划,以期拓宽微博功能,吸引更多”围脖”用户。曹国伟说,微博不会变成Facebook,但会加设更多与Facebook类似的功能,这样就能以新应用软件为依托建立更紧密的社交联系。
[新版功能]
A new version of the site will change its look to add sections recommending users of interest and offering games and other applications. It will become easier for users to define their relationships with other users by labeling those who are real friends, as opposed to those who are just “fans”.
据介绍,正在测试的新版本拥有与以往不同的页面设计,它将在突出位置推荐值得关注的用户、提供游戏和其它应用程序。曹国伟正设法让用户能够更加方便地定义他们与其他用户的关系,比如把那些真正的朋友、而不仅仅是”粉丝”的用户标注出来。

>What the rich are reading
富豪今夏’必读书单’出炉
Every June, J.P. Morgan Private Bank sends its rich clients its official “Summer Reading List”. The Wall Street Journal reported the list is a collection of 10 books specifically chosen to appeal to the tastes and preoccupations of the wealthy. The reading list has become hugely popular with J.P. Morgan clients looking for ideas. If you want to know what the rich will be reading this summer and chatting about at cocktail parties, here is the full list.
据《华尔街日报》报道,每年6月,摩根大通私人银行都会给其富豪客户寄送一份”暑期阅读书单”,挑选10本迎合富豪口味和关注焦点的书籍。这些书极受摩根大通客户们的欢迎,他们从中寻求灵感。有钱人今年夏天读什么?他们在鸡尾酒会上谈论的话题有哪些?想知道的话请看完整书单。
1. The Corner Office: Indispensable and Unexpected Lessons from CEOs on How to Lead and Succeed, by Adam Bryant.
《角落办公室——来自CEO意外且必要的教训:谈领导艺术和成功秘诀》,作者:亚当·布莱恩
2. The Startup Game: Inside the Partnership Between Venture Capitalists and Entrepreneurs, by William Draper III.
《创业游戏:探秘风险投资商和创业者的合伙关系》,作者:威廉·德雷珀三世
3.America’s Medicis: The Rockefellers and Their Cultural Legacy, by Susan Loebl.
《美国的梅迪奇:洛克菲勒家族及其令人惊叹的文化遗产》,作者:苏珊·勒布尔
4. On China, by Henry Kissinger
《论中国》,作者:亨利·基辛格
5. Feast for the Senses: A Musical Odyssey in Umbria, by Lin Arison, Diana Stoll and Neil Folberg
《感官的盛宴:在翁布里亚的一次音乐之旅》,作者:林·阿里森、戴安娜·斯托尔和尼尔·福尔伯格
6. Little Bets: How Breakthrough Ideas Emerge from Small Discoveries, by Peter Sim
《小赌注:小小发现如何酝酿开创性思想》,作者:皮特·辛
7. She Walks in Beauty: A Woman’s Journey Through Poems, by Caroline Kennedy
《她在美中徜徉:一个女人的诗歌探索之旅》,作者:卡罗琳·肯尼迪
8. The Longevity Project: Surprising Discoveries for Health and Long Life, by Howard S. Friedman and Leslie R. Martin
《长寿项目:关于健康和长寿的惊人发现》,作者:霍华德·S·弗里德曼和莱斯利·R·马丁
9. Cleopatra: A Life, by Stacy Schiff
《克利奥帕特拉:埃及艳后的一生》,作者:史黛西·希夫
10.The Hare with Amber Eyes: A Family’s Century of Art and Loss, by Edmund De Waal
《有着琥珀色双眼的野兔:一个家族的世纪收藏和损失》,作者:埃德蒙·德·瓦尔

>River Plate relegated
河床降级引发球迷骚乱
Legendary Argentine football giant River Plate was relegated from the country’s top division for the first time in their 110-year history. The relegation came after a 1-1 draw with Belgrano Sunday in the second leg of a demotion play-off. Belgrano won the first leg 2-0. The relegation sparked ugly riots between police and fans as violence broke out a minute before the match was over. Angry fans pelted players with objects from the stands and police replied with high-powered fire hoses. Violence later spread to streets outside the stadium. AFP reported the clashes left 68 people injured, 50 arrested and 15 vehicles destroyed. The River Plate team is Argentina’s most successful club with 33 championships to their name.
阿根廷足坛最成功的球队、曾33次夺得阿根廷甲级联赛冠军的河床队降级了!河床队在26日进行的保级赛第二回合比赛中和贝尔格拉诺队以1:1战平,从而以1:3的总比分负于对手,惨遭降级。这是河床队建队110年来首次从甲级球队降级到乙级球队。比赛结束前一分钟,河床队球迷爆发了。愤怒的球迷向场内球员大量投掷杂物,更有部分球迷试图攀越铁丝网护栏冲入场内。警察忙着用高压水管冲散球迷。骚乱后来蔓延到体育场外的大街上。据法新社报道,这场冲突共造成68人受伤,50人被捕,以及15辆车被毁。

【In Brief】
>China’s top auditor said Monday the debts of local governments at provincial, city and county levels exceeded RMB10 trillion by the end of 2010. Only 54 county governments out of more than 2,800 had zero debt.
国家审计署27日发布的审计公报显示,截至2010年底,全国省、市、县3级政府债务余额超过10万亿元人民币。据了解,2800多个县级政府中,仅54个是零负债。
>Xinhua reported China’s top legislature Monday started reviewing an amendment to the country’s conscription law aiming to recruit more college students.
据新华社报道,全国人大常委会27日开始审议兵役法修正案草案,草案中增加了吸引高校学生入伍的内容。
>Statistics released Monday by the Hong Kong Tourism Board showed Hong Kong received 3.23m mainland visitors in May, an 14.7% increase from the same period last year.
根据香港旅游发展局27日公布的数据,今年5月份,香港接待各地旅客超过323万人次,较去年同期增长14.7%。
>Qianjiang Evening News reported a bayberry weighing 37 grams was auctioned for RMB1,980 in Lanxi, Zhejiang.
据《钱江晚报》报道,浙江省兰溪市日前以1980元拍出一颗重达37克的超级杨梅。

【Quotable Quote】
最近一个月,”锋芝婚变”风波在娱乐圈闹得沸沸扬扬。26日,谢霆锋在《财神客栈》北京首映会现场,首次面对媒体,就该事件发表声明。谢霆锋表示,二人依然相爱,只是不知道怎么继续走下去。最后谢霆锋以一段类似古惑仔电影台词的语言结束了讲话:
“We are both grown-ups. About our relationship, we can manage it, no matter if we love or hate, even fight and hurt each other. Others better not speculate.”
“我们两个已经是成年人了,所以我们两个的关系,以后是爱是恨是打是杀,我们自己会分配,不用大家去猜忌。”

【Celebrity】
>Obama signature autopenned?
美’总统自动笔’首签法案
For decades, US presidents have let an autopen do some of the heavy lifting when it came to scrawling their signatures. But the machine had never been used when signing a bill into law. But AP reported while traveling in Europe last month, Obama directed his staff in Washington to use an autopen to sign into law an extension of Patriot Act to fight terrorism. It is believed to be the first time a president has used an autopen to sign legislation.(See photo)
据美联社报道,几十年来,美国总统经常使用自动笔代替他们完成一些繁重的签名任务,但该机器从未用在签署法案上。然而,上月奥巴马访欧期间,他指示白宫工作人员用这一机器签署了为打击恐怖主义的《爱国者法案》某些条款的延长决定。这是历史上第一次总统用自动笔签署法案。(见图)
[总统仿真签名的类别]
There are varying levels of “fake” presidential signatures. There are preapproved form letters with digital signatures. There are preprinted cards for birthdays and other special events. Autopen signatures are generally reserved for more personalized correspondence that doesn’t need a real signature.
总统仿真签名还分不同等级。预先认可的套用信函上是数码签名。生日或者其它特别场合,卡片上的签名也是预先印好的。自动笔一般用在那些更加个人化却又不必真迹签名的函件上。

【Kaleidoscope】
>Old veteran lands on Forbes
老兵捐光积蓄登福布斯
An elderly veteran in Taiwan has been listed in Forbes magazine as one of Asia’s “Heroes of Philanthropy”. Hung Chung-hai donated his savings and pension collected over 60 years – totaling NT$6m (RMB1.34m) – to the Hualien services office of the Veterans Affairs Commission. The money is used to provide for the widows and orphans of veterans. The 82-year-old lives an austere life. He can subsist for a week on one cabbage, some canned fish and steamed buns. He spends less than NT$1,000 (RMB223) per month on living expenses. He said he hopes he lives longer so he can donate more.
台湾一退役老兵因捐出毕生积蓄登上《福布斯》杂志2011年度”亚洲慈善英雄榜”。82岁的洪中海把60多年的积蓄和养老金,总共600万元新台币(人民币134万),悉数捐给花莲退役军人事务委员会,用于帮助退伍军人遗眷。老人自己生活节俭,可以每周吃一个卷心菜、一些罐头鱼和馒头过活,每月生活费不超过1000元新台币(人民币223元)。他说,希望自己活久一点,再捐更多钱。

【China Daily Radio】
千金大小姐私密日记
#更正#
上周在回答问题环节中,有位读者曾提问关于continual和continuous区别的问题,经过反复核实和确认,我们对上次回答”区别很小,几乎没有区别”做出补充如下:
A: 如果一件事情重复发生,但是中间有间隔,为continual。如果某一件事情一直不停持续下去,即为continuous,同”永恒”。举例: the earth’s orbit around the sun is continuous(地球围绕太阳公转是永恒的). My friend’s habit of stopping at the store on Mondays, Thursdays and Saturdays is continual(朋友周一,周四和周六固定去购物,但是中间有时间间隔). 在这里,我们感谢来自上海1512***0674的读者给我们的建议,小编感激不尽!
好了,来看今天大小姐要给大家介绍什么平民美食呢? 美剧《生活大爆炸》相信很多人看过吧! 里面提到最多的中国菜相信就是宫保鸡丁了! 可是这宫保鸡丁漂洋过海就变了模样,先来熟悉单词!
Kong Po chicken: 宫保鸡丁
marinated: 卤,用酱汁腌制
stir-fried: 爆炒

It is quite tempting to have what your favorite drama character is having, especially when they order Chinese Kong Po chicken. Westernized versions commonly consist of diced marinated chicken stir-fried with skinless roasted peanuts, chopped red bell peppers, sherry wine, hoisin sauce, oyster sauce, and chili peppers. Shrimp, scallops, beef or pork are sometimes used instead of chicken. It can also be prepared with tofu instead of meat.
跟自己最爱的美剧主角吃一样的东西是不是一件很酷的事情? 是啦! 尤其他们点了经典中餐下饭菜-宫保鸡丁! 宫保鸡丁漂洋过海后变成这般模样:酱汁腌过的鸡丁加去皮花生米,将柿子椒切丁,混合雪利酒,海鲜沙司,蚝油和辣椒的美味! 甚至可以宫保虾,宫保扇贝,宫保各种猪牛肉。什么? 还有宫保豆腐?

【Word Power】
公铁行
BMW(Bus, Metro, Walk)
例句:
Xavier: Hey dude, you’re coming over tonight?
Dennis: Yeah, I’m taking my BMW. I’ll be there in a few hours.
泽维尔:唉,我说你今晚会出现是吧?
丹尼斯:没错儿,但是我抛弃了汽车,在路上就得花几个小时呢。
(本期英文内容由美籍编辑Clark Cahill润色。)

【TOEIC Test Daily】
[你知道吗]
Do you have a TOEIC score? If you know English, you will need one. Sign up to take the TOEIC test today!
Please bring your ______ application and a list of references to the interview on Tuesday.
(A) completes
(B) completing
(C) completed
(D) completely
请于今晚24点前,通过短信发送正确答案字母到106580007835(免信息费)。幸运答对者将获赠10元话费。本期答案见明日早报。
【Subscribe】
Send text “CD” to 10658000.
Twice a day, 5 yuan a month.
发短信CD到10658000
订阅ChinaDaily手机报
每日两期,5元/月
客服短信:106580007835(免信息费)
合作邮箱:mobile@chinadaily.com.cn

Share
Posted in English | Leave a comment

ChinaDaily晚报6.26

【Weather June 27】
Shanghai: rainy 24~28℃
【Highlights】
>Brands likely disappear
明年消失的品牌,你熟悉
>Race to marry millionaires
富豪相亲女宾泳装上阵
>’Got Talent’ graces Beijing
‘达人秀’京巡演杨澜加盟
>Cameron blasts new EU HQ
欧盟砸巨款建’子宫’总部
>Latest ‘Bond’ marries
‘邦德’闪婚娇妻有小金人
>Fashion tycoon
bribed cops
英富豪’有钱难使鬼推磨’
>China Daily Radio
音频:小心别当’电灯泡’!
【Cover Story】
>Meari packs rain and wind
风暴’米雷’擦过山东半岛
Tropical storm Meari is shaving off the eastern coast of Shandong Peninsula Sunday afternoon. Under the influence, the storm will bring heavy rains or rainstorm to Shandong Peninsula, eastern Liaoning, eastern Jilin and Shanghai. The National Meteorological Center (NMC) of China Meteorological Administration also predicted wind forces between 6 and 8 will hit coastal Jiangsu and Zhejiang in the next 24 hours.
今天下午开始,强热带风暴”米雷”正擦过山东半岛东部沿海。受其影响,山东半岛、辽宁东部、吉林东部、上海等地有大雨或暴雨。此外,未来24小时,江苏沿海和浙江沿海将有6-8级大风。

【Top News】
>Brands likely disappear
明年消失的品牌,你熟悉
US-based 24/7 Wall St created a new list of the top 10 brands that will disappear in 2012. The brands include Nokia, Sony Ericsson, Saab and MySpace. The article said potential buyers of Nokia would start with HTC. The other 3 likely bidders are Microsoft, Samsung and LG Electronics. MySpace, once the world’s largest social network, died a long time ago and it will be buried soon, according to 24/7 Wall St. Its total users in the US are currently estimated to be less than 20m.
美国财经网站24/7 Wall St近日评出2012年将会消失的十大品牌,诺基亚、索尼爱立信、萨博和MySpace等赫然在列。对于诺基亚的潜在买家,文章称,宏达电(HTC)可能性最大,其它如微软、三星和LG电子也不可小觑。对于曾经的社交网老大MySpace,24/7 Wall St认为,MySpace已经寿终正寝多时,很快就将下葬。目前该网站美国注册用户预计不足2000万人。
>Race to marry millionaires
富豪相亲女宾泳装上阵
A blind date exclusive to millionaires and beauties was held Saturday on the Donghu Lake beach in Wuhan, the Chutian Metropolis Daily reported. The potential husbands had to have personal assets of at least RMB30m or an annual salary of RMB1m. They also had to buy an admission ticket worth RMB99,999. The women were able to sign up for the prospective wife competition free of charge. They wore swimsuits to attract the bachelors.
据《楚天都市报》报道,25日,武汉东湖沙滩上演富豪相亲会。所有参与的男性须拥有个人资产3000万元以上或年收入100万元,且要花99999元买一张相亲会门票。女性免报名费,着泳装亮相。

>France bans British firm’s seeds
英法就疫情起源打嘴仗
France halted the sale of fenugreek from a British company after 8 people were hospitalized following an E. coli outbreak. AP reported French investigators found 2 of them were sickened after consuming sprouts from the 3 seed types in a suburb of Bordeaux. The fenugreek, mustard and arugula sprout seeds, came from seed and plant company Thompson & Morgan. In a statement, the company said the link being made by French officials was unsubstantiated. It added it believed “something local in the Bordeaux area, or the way the product has been grown, is responsible for the incident rather than our seeds.”
据美联社报道,法国日前封杀了苦豆、芥菜种和芝麻花3种产自英国汤普森&摩根公司的菜种。法国调查机构称,在此之前,有8人因感染大肠杆菌而住院,其中2人都在波尔多郊区食用过这3类种子培育成的菜苗。汤普森&摩根公司则在一项声明中称,法国的这一联系是毫无根据的,同时表示,”是波尔多本地的一些东西,或是他们种植的方式,而不是我们的种子,导致了这一事故。”
[英官方回应]
A spokesman for Britain’s Food Safety Agency said that officials “don’t have definitive evidence that the company is the source of the contamination.” So far, no food poisoning cases have been reported in the UK and investigations are ongoing.
英国食品安全部门发言人称,官方没有确切证据能证明该公司的菜种是感染源,而且英国并未发现类似食品中毒事件。调查仍在继续进行。
>Cameron blasts new EU HQ
欧盟砸巨款建’子宫’总部
EU chiefs are ecstatic over plans for a new luxury EUR300m headquarters at a summit in Brussels. The Guardian reported EU president Herman Van Rompuy has described his “Europa building” as a “jewel box”. But Britain’s Prime Minister David Cameron has been less enthusiastic
dubbing it a “gilded cage”. The EU HQ is a glass cube housing a central womb-like structure (see photo) leading to its “Brussels belly” and “EU-terus” ( uterus)nicknames. Cameron said he was in “immense
frustration” at the lavish spending of the Brussels elite.
据英国《卫报》报道,在布鲁塞尔欧盟峰会上,欧盟官员高度赞扬了一个造价高达3亿欧元的欧盟新总部计划。欧理会主席范龙佩将其命名为”欧罗巴”,并称其为”珠宝盒”,但英首相卡梅伦却狠批该建筑是”镀金雀笼”。据了解,新总部以玻璃立方结构建造,中央是一个状似子宫的建筑(见图),因此也被戏称为”布鲁塞尔大肚子”和”欧子宫”。卡梅伦直言不讳地表示他对于布鲁塞尔精英们的奢华浪费感到”极为受挫”。

【In Brief】
>Over the past 3 decades the number of adults with diabetes has more than doubled, jumping from 153m in 1980 to 347m in 2008, the USA Today reported Sunday.
据《今日美国》26日报道,在过去30年里,全球成年糖尿病患者人数翻番,由1980年的1.53亿人增加到2008年的3.47亿。
>Japan’s Prime Minister Naoto Kan could resign before August, said Katsuya Okada, the No 2 of Japan’s ruling party, Sunday.
日本民主党2号人物冈田克也26日表示,日本首相菅直人有可能8月份之前辞职。
>”China’s Got Talent”, a Shanghai-based talent show, will be held in the Great Hall of the People in Beijing at 9 pm Sunday. Yang Lan, a Chinese TV celebrity, will replace Huang Shujun to join the judge panel.
今天晚上9点,《中国达人秀》从上海转战北京人民大会堂。杨澜将取代黄舒骏,成为评委席上的一张新面容。
>Daniel Craig and Rachel Weisz, who play husband and wife in an upcoming film “Dream House”, have married quietly, AP reported Sunday. Craig is the latest James Bond and Weisz won an Academy Award for “The Constant Gardener.”
据美联社26日报道,新《007》扮演者丹尼尔·克雷格已秘密完婚,妻子瑞切尔·薇兹曾凭借《永恒的园丁》获奥斯卡奖。两人将在即将上映的影片《梦宅》中饰演夫妻。

【Kaleidoscope】
>Fashion tycoon bribed cops
英富豪’有钱难使鬼推磨’
The fashion tycoon and founder of Next offered 2 police officers his £100,000 sports car in an attempt to have a drunk-driving case against him dropped. The Daily Telegraph of London reported the 69-year-old George Davies was caught at the wheel of a Maserati Quattroporte. After he was arrested for driving over the limit, Davies was initially upset but later became “chatty” in the police car. He then offered police the keys to his Maserati Quattroporte to try to persuade them to turn a blind eye.
据英国《每日电讯报》报道,英国时尚界大亨、Next品牌创立人乔治·戴维斯酒驾被抓后,企图以10万英镑的豪华跑车对两名警察行贿,让其销案。据了解,这名69岁的亿万富翁当时驾着一辆玛莎拉蒂总裁跑车,因酒精含量超标被逮捕。他起初很不安,但坐进警车后变得健谈,甚至提出如果他们能”睁一只眼闭一只眼”,他可以留下豪华跑车的钥匙。

【China Daily Radio】
两个轮子的自行车大家再熟悉不过了。如果多给你一个轮胎,你可能会嫌它累赘、多余。就像一对情侣在约会时,旁边还有一个朋友作陪的话,这个朋友就会显得有点不合时宜了。Third wheel即我们所说的”电灯泡”。
A third wheel is a person or thing that serves no useful purpose, or an unwanted third party accompanying two people on a date. Such a person is as useful as a third wheel on a bicycle.
Third wheel指不发挥任何作用的人和事物,或是在两人约会时陪伴在旁的一个多余的人。之所以叫third wheel是因为这样的人就跟自行车上放第三个轮子一样,一无是处。
For example:
Larry and Jenny asked me to go out for dinner yesterday, but I said no because I didn’t want to be a third wheel.
赖瑞和詹妮昨天叫我跟他们一起出去吃饭,但是我没去,我可不想当电灯泡。

【Language Tips】
‘Take a leaf out of sb’s book’
———-
Take a leaf out of sb’s book直译是”从人家的书里撕下一页”。这句习语,本来是指学生抄袭同学笔记或作业、作者剽窃人家著作等不诚实行为,现在一般用来表示”模仿人家”。
请看例句:
Seeing my method worked, he took a leaf out of my book and acted in the same way.
他见我的方法有效,就依样画葫芦,也这样做了。

Share
Posted in English | Leave a comment

ChinaDaily早报6.26

【Bonus Oriens】
We are always getting ready to live, but never living. (Ralph Waldo Emerson, American poet and essayist, 1803-1882)
我们总是在为生活做准备,却从来没有真正生活。(拉尔夫·沃尔多·爱默生,美国诗人、散文家,1803-1882)
【Highlights】
>China to buy Hungary’s debt
中国称愿买匈牙利国债
>E China’s Meari strengthens
‘米雷’挺进东部沿海地区
>More invest in securities
面对通胀国人选择投资
>Libya footballers defect
利比亚众国脚叛逃反对派
>’Mad Men’ goes retail
时尚搭载美剧共同’抢钱’

【Cover Story】
>Drug abuse hot topic online
网民关注明星涉毒事件
June 26 is International Day against Drug Abuse and Illicit
Trafficking. Drug-related topics are heating up in China’s cyberspace. Among them, the top 3 concerns are celebrity drug scandals, synthetic or “new” drugs, and rehabilitation, xinhuanet.com reported.
据新华网报道,随着6月26日国际禁毒日来临,关于禁毒的讨论近期在网上持续升温。明星涉毒、新型毒品和戒毒康复,成为网民热议的三大”禁毒话题”。
[聚焦一]
A post depicting the horrendous changes that using drugs has caused to 20 celebrities’ once flawless face went viral online. Internet users expressed shock and disappointment after a spate of celebrity drug use scandals, blaming them for misleading others as public figures.
近日,一篇”曝光20位明星吸毒前后恐怖对比照”的帖子在网上广泛流传。作为公众人物的明星涉毒接连曝光,令网友大感失望和痛心。
[聚焦二]
Cyber users also voiced concern about the rising use of synthetic or “new” drugs in recent years. What’s worse, young people are increasingly falling prey.
近年来,合成毒品即”新型毒品”呈现快速蔓延的势头,同时吸食人员呈现出明显的低龄化特征,许多网友对此深表忧虑。
[聚焦三]
How to eliminate drug addiction is still an unsolved problem for the whole world. There’s a high rate of people who resume drug abuse after walking out of rehab. Meanwhile, it’s hard for them to get their lives back on track.
如何彻底戒除毒瘾,至今仍然是一个世界性难题。戒毒人员复吸率高、重新融入社会困难等问题有待破解。

【Top News】
>China to buy Hungary’s debt
中国称愿买匈牙利国债
China and Hungary pledged to further enhance bilateral ties Saturday, with China also promising to buy some of Hungary’s national debt, Xinhua reported. China had confidence in Hungary’s economy and would buy some its national debt, Chinese Premier Wen Jiabao said during his visit in Hungary. “China is a long-term investor in the European sovereign debt market, and will continue to support Europe and the euro,” Wen said.
据新华社报道,中国25日承诺将买入一定数量的匈牙利国债,并加强两国的双边关系。中国国务院总理温家宝在匈牙利访问时表示,中国对匈牙利的经济有信心,并且将买入部分匈牙利国债。温家宝总理还表示:”中国是欧洲主权债券市场的长期投资者,今后将一如既往地支持欧洲和欧元。”
>E China’s Meari strengthens
‘米雷’挺进东部沿海地区
East China is bracing for high gales and torrential rains as tropical storm Meari swirls north, battering the country’s coastal areas, Xinhua reported. The full force of Meari, still gaining in strength and likely to become a typhoon, was felt in Zhejiang province Saturday evening, according to an alert from the meteorological station of Zhejiang province. Meari is forecast to hit Shanghai soon after, according to the Shanghai municipal meteorological station. Further north, the storm will unleash heavy rains on coastal cities of Shandong province Sunday.
据新华社报道,强热带风暴”米雷”目前正向北移动,受其影响,我国东部将迎来强降雨大风天气。浙江省气象台发布警报称,米雷强度仍在持续增长,有可能发展成为台风。它于25日晚与浙江省擦肩而过。上海气象台预计,其后,米雷将袭击上海。随后,米雷将继续北上,逐渐逼近山东省。受其影响,26日期间,山东沿海城市将遭遇暴雨。

>More invest in securities
面对通胀国人选择投资
Faced with rising inflation, more Chinese are investing in securities and fund products despite the recent poor performance of the country’s stock markets, Xinhua reported Saturday. Investors opened about 203,400 securities accounts for trading on Shanghai and Shenzhen stock exchanges while opening 64,200 accounts for fund products last week, according to the China Securities Depository and Clearing Corp. By the end of last week, the number of securities accounts totaled 160.1m in China.
据新华社25日报道,虽然近期国内股市表现欠佳,但面对不断上涨的通胀压力,更多国人还是选择将资产投入证券、基金理财产品。中国证券登记结算有限责任公司数据显示,上周,上海证券交易所和深圳证券交易所新开证券类账户达20.34万个,新开基金产品账户达6.42万个。截至上周末,中国国内证券账户数量已达1.601亿个。

【In Brief】
>June 25 marked the second anniversary of Michael Jackson’s death. Fans across the globe paid tribute to the legendary singer with various activities.
6月25日是迈克尔·杰克逊逝世两周年纪念日,世界各地的粉丝通过各种各样的活动来纪念偶像。
>London 2012 organizers say 17 Olympics sports sold out of tickets after a frenetic opening to the 2nd round of ticket sales, British website Sportinglife.com reported. Athletics is especially popular.
据英国网站Sportinglife.com报道,伦敦奥组委称,2012年伦敦奥运17个项目的门票在第二轮售票开始后不久后,就被抢购一空。其中,田径项目尤为抢手。
>New research shows Finland’s consumers have the distinction of being some of the slowest in the world to buy new cellphones, while Americans are the quickest, Reuters reported.
据路透社报道,最新研究显示,芬兰人是世界上最不经常换新手机的消费者,而美国人是最常换手机的人群。

【Newsmaker】
>Libya footballers defect
利比亚众国脚叛逃反对派
Seventeen of Libya’s top football figures, including national team goalkeeper Juma Gtat, defected to rebels battling to oust the country’s leader, Muammar Gadhafi, BBC reported Saturday. The coach of Tripoli’s top club al-Ahly, Adel bin Issa, also defected, in what BBC described as “a propaganda blow” for Gadhafi in “football-mad North Africa.” Issa said he wanted “to send a message that Libya should be unified and free,” adding that he hoped “to wake up one morning to find that Gadhafi is no longer there.”
据英国广播公司(BBC)25日报道,包括国家队门将吉塔特在内的17名利比亚顶级足球运动员已于近日投奔反对派,以图推翻利比亚总统卡扎菲的统治。据悉,连利比亚豪门俱乐部阿尔-阿赫里队主教练宾-伊萨也加入了叛逃的行列,BBC称,在因足球而疯狂的北非,这是”对卡扎菲舆论宣传工作的沉重打击”。伊萨表示,他这么做是为了”向外界传达一个明确的信息,即利比亚应该获得统一和自由”,他希望”有一天早晨醒来时,发现卡扎菲终于不在位了。”
>On NY’s gay marriage law
名人如何看同性恋婚姻
New York on Friday night became the sixth and largest state in America to legalize gay marriage when senators voted to allow same-sex unions after protracted negotiations. Many celebrities have been posting their reactions on Twitter. Here are some quotes.
当地时间24日晚,纽约州正式投票通过有关同性恋婚姻合法化的法案,这使得纽约州成为全美第6个、也是最大的允许同性恋合法结婚的州。针对此喜讯,许多社会名流都在社交网站推特上发表了自己的感言,以下是部分摘录:
“I can’t stop crying. We did it, kids.” — Lady Gaga
“我禁不住泪流满面。孩子们,我们终于办到了!” —Lady Gaga(当红流行歌手)
“Time to celebrate!!! Marriage Equality for NYers! It’s about… love!” — Ricky Martin
“这是庆祝的时刻!!!纽约人终获婚姻平等!这一切都关乎于……爱!” —瑞奇·马丁(拉丁流行歌王,于去年3月通过个人官网站公布自己的同性恋身份)
“I’m thrilled about the news from NY. Marriage equality! Every day we get a little closer. What an amazing feeling.” — Ellen DeGeneres
“从纽约传来的消息太令人激动了。婚姻平等!每一天我们彼此的距离都被拉得更近,这是种多么令人迷醉的感觉。” —艾伦·德杰尼勒斯(美国当红脱口秀主持人,同性恋身份)
“Proud to be FROM NY!” — Lindsay Lohan
“我很自豪自己是纽约人!”— 琳赛·罗韩

【Kaleidoscope】
>’Mad Men’ goes retail
时尚搭载美剧共同’抢钱’
“Mad Men” fans and fashionistas of 1960s style have every reason to rejoice now that a new partnership between Banana Republic and the “Mad Men” franchise is on the verge of being launched, style website becomegorgeous.com reported. The new limited edition collection is set to debut this fall and will include at least 65 pieces for both men and women. “Mad Men” costume designer Janie Bryant teamed with Banana Republic’s creative director Simon Kneen for the collection, which will recapture 1960s Madison Avenue chic.(See photo)
美剧《广告狂人》的粉丝和钟情于60年代复古风的时尚达人们有福了。据时尚网站becomegorgeous.com报道,时尚品牌Banana Republic将于今秋推出以剧中人物造型为灵感的至少65款限量版服饰,涵盖男装和女装。该服装系列由剧集的服装造型师珍妮·布莱恩特和Banana Republic的创意总监西蒙·尼恩共同操刀,力求重现片中60年代纽约麦迪逊大街的风情。

【Language Tips】
‘Sockpuppet’
———-
“马甲”(sockpuppet)是网络社区中为了隐藏身份而注册的ID。时常在网络论坛里转悠的人估计注册帐号都不会少于两个,而且每个帐号的语言特点和发言风格可能都大不相同。有的褒奖,有的批判,还有的搅浑水。大家管这些不同的虚拟身份叫做”马甲”,换个”马甲”就等于换了个身份。英文里则管它叫sockpuppet。
请看例句:
A sockpuppet is a fake online identity.
“马甲”是网络上的假身份。

【Talk Show】
本期我们继续纠正中式英语。
我没有英文名。
误:I haven’t English name.
正:I don’t have an English name.
注:Have在这里是实义动词,而并不是在现在完成时里面那个没有意义的助动词。所以,这句话由肯定句变成否定句要加助动词。又如:我没有钱:I don’t have any money.我没有兄弟姐妹:I don’t have any brothers or sisters.我没有车:I don’t have a car.

Share
Posted in English | Leave a comment