/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.appengine.demos.taskengine.client;
import com.google.appengine.demos.taskengine.client.TaskList.TaskRow;
import com.google.appengine.demos.taskengine.shared.Task;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.StyleInjector;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* Entry point classes define <code>onModuleLoad()</code>.
*
* 'Task Engine' is a mobile sample App that demonstrates using GWT at a lower
* level and closer to the underlying DOM.
*
* If your Application is simple enough, you may be able to get by without
* leveraging GWT's built in widgets and just use the core DOM API. For smaller
* Apps, this may buy you some savings in code size. In the end however, there
* is a trade off with regards to code complexity and managability. The more
* complex the App is, the more you may find yourself re-inventing stuff that
* GWT already has built in. Applications like those more often than not will
* benefit all around (size and development effort) from using GWT's built in
* Widgets.
*/
public class Tasks implements EntryPoint {
/**
* This class provides Application level controls like page switching, and
* exposes the RPC service. We pass an instance of this object around so that
* the various components of our App can have access to the functionality.
*/
public class Controller {
/**
* Adds a new task to the task list UI and persist it to the server.
*
* @param task
*/
public void addNewTask(Task task) {
persistTask(taskList.addTaskToUi(task));
goToTaskList();
}
/**
* Deletes all tasks from the server that have been checked off in the UI as
* completed.
*/
public void deleteCompletedTasks() {
String[] tasksToDelete = taskList.getCompletedTaskIdsAndMoveToPending();
if (tasksToDelete.length > 0) {
boolean shouldDelete = DomUtils.getWindow().confirm(
"Delete Completed Tasks?");
if (shouldDelete) {
api.deleteTasks(tasksToDelete, new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
// This does not change the UI. So the user should still see the
// row as unpersisted.
taskList.movePendingTasksBackToCompleted();
}
public void onSuccess(String result) {
if (result == null) {
onFailure(null);
}
taskList.confirmDeletion();
}
});
}
}
}
/**
* Transitions to the {@link TaskDetails} page.
*/
public void goToTaskDetails() {
uiPages.doPageTransition(taskDetails.getPageIndex());
}
/**
* Transitions to the {@link TaskList} page.
*/
public void goToTaskList() {
uiPages.doPageTransition(taskList.getPageIndex());
}
/**
* Loads a task in the TaskDetails page.
*
* @param task the {@link Task} for the task to be loaded.
*/
public void loadTask(Task task) {
taskDetails.view(task);
}
/**
* Saves a {@link Task} for a given {@link TaskRow} to the server. This is
* used to both update existing tasks, and to save new ones. It updates the
* UI for the TaskRow after getting a response from the server.
*
* @param row the {@link TaskRow} for the task we want to save.
*/
public void persistTask(final TaskRow row) {
api.persistTask(row.getTaskData(), new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
// Let the user know that persistence failed.
// try again later or something
DomUtils.getWindow().alert(
"Failed to save task to server. Try so re-save it.");
}
public void onSuccess(String result) {
if (result == null) {
onFailure(null);
} else {
// mark the Task row as persisted
row.setRowAsPersisted(result);
}
}
});
}
/**
* Updates the task information on the server and transitions back to the
* {@link TaskList}.
*
* @param task the {@link Task} for the updated task
* @param oldPriority the old value for the priority of the task
*/
public void updateTask(Task task, int oldPriority) {
persistTask(taskList.updateTask(task, oldPriority));
goToTaskList();
}
}
/**
* Our resources used in the sample.
*
* {@link ControlBar.Resources} is an {@link ImmutableResourceBundle} (IRB).
* IRB allows us to have a programmatic interface with static resources used
* in this sample, like CSS styles and Images. Images specified here (or in
* the inheritance chain) are automatically combined into a single sprite,
* with corresponding CSS automatically generated to display each individual
* image piece through cropping.
*
*/
public interface Resources extends TaskList.Resources, TaskDetails.Resources {
}
private final TasksApiAsync api = GWT.create(TasksApi.class);;
private final Resources resources = GWT.create(Resources.class);
private TaskDetails taskDetails;
private TaskList taskList;
private PageTransitionPanel uiPages;
/**
* This is the entry point method.
*/
public void onModuleLoad() {
// Inject styles into the page
StyleInjector.injectAtEnd(resources.taskDetailsCss().getText()
+ resources.taskListCss().getText()
+ resources.controlBarCss().getText()
+ resources.labelMatrixCss().getText());
// Our main Widget and container of our UI pages
uiPages = new PageTransitionPanel(Document.get().getBody());
// The object passed around that controls the main functionality of the app.
Controller controller = new Controller();
// Make UI Pages
ControlBar.Controls taskListControls = TaskList.createControls(controller,
resources);
taskList = new TaskList(uiPages, taskListControls, controller, resources);
ControlBar.Controls taskDetailsControls = TaskDetails.createControls(
controller, resources);
taskDetails = new TaskDetails(uiPages, taskDetailsControls, controller,
resources);
// Do initial load of tasks wait
loadEntireTaskList();
// Do our initial page sizing.
uiPages.doResize();
// We queue up a deferred resize as a hack to work around the fact that
// certain mobile browsers fail to repaint after the above initial resize
// happens and leave you with a blank screen. If we only do a deferred
// resize, we see the screen flicker, so we do both the synchronous resize
// above, and this deferred one.
DeferredCommand.defer(new DeferredCommand() {
@Override
public void onExecute() {
uiPages.doResize();
}
}, 100);
}
/**
* Fetches all tasks from the server.
*/
private void loadEntireTaskList() {
// Set a timeout in case the call to the backend takes an unbounded amount
// of time (e.g. network failure). We simply ask them to try signing in
// again.
DeferredCommand.defer(new DeferredCommand() {
@Override
public void onExecute() {
if (!taskList.isLoggedIn()) {
taskList.notifyNotLoggedIn(null);
}
}
}, 5000);
// Fetch the list of tasks
api.getTaskList(new AsyncCallback<Task[]>() {
public void onFailure(Throwable caught) {
if (caught == null) {
// RPC may have returned success, but the user isn't logged in!
// So we Fetch SignIn URL.
api.getLoginUrl(new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
taskList.notifyNotLoggedIn(null);
}
public void onSuccess(String loginUrl) {
taskList.notifyNotLoggedIn(loginUrl);
}
});
} else {
// We had a serialized exception. User need not be aware of this.
// For debugging purposes we may want to alert this.
// DomUtils.getWindow().alert(caught.getMessage());
taskList.notifyNotLoggedIn(null);
}
}
public void onSuccess(Task[] tasks) {
if (tasks == null) {
onFailure(null);
} else {
// The 0th index contains a meta task that is just a container for the
// user's email address and logout URL
Task metaTask = tasks[0];
String userEmail = metaTask.getEmail();
String logoutUrl = metaTask.getDetails();
taskList.setUserLoggedIn(userEmail, logoutUrl);
for (int i = 1, n = tasks.length; i < n; i++) {
Task task = tasks[i];
taskList.addTaskToUi(task).setRowAsPersisted(task.getId());
}
}
}
});
}
}