/* * 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.ControlBar.Controls; import com.google.appengine.demos.taskengine.client.DomUtils.EventRemover; import com.google.appengine.demos.taskengine.client.Tasks.Controller; import com.google.appengine.demos.taskengine.shared.Label; import com.google.appengine.demos.taskengine.shared.Task; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * The main UI for our Application. This is the list of tasks. */ public class TaskList extends Page { /** * Styles for this Widget. CssResource styles are compiled, minified and * injected into the compiled output for this application. Fewer round trips * since everything is included in the JavaScript :)! */ public interface Css extends CssResource { String checkBoxContainer(); String checked(); String garbage(); String plus(); String taskRow(); String taskRowPersisted(); String title(); String unChecked(); String user(); } /** * Resources for this Widget. * */ public interface Resources extends ControlBar.Resources { @Source("resources/checkBox.png") ImageResource checkBox(); @Source("resources/check.png") ImageResource checkMark(); @Source("resources/garbage.png") ImageResource garbage(); @Source("resources/plus.png") ImageResource plus(); @Source("resources/TaskList.css") TaskList.Css taskListCss(); } /** * This class wraps TaskData and its associated DOM elements. This class * controls the rendering of Task Data to the UI. */ public class TaskRow extends Widget { private final DivElement checkMark; private final Task data; private List<EventRemover> removers = new ArrayList<EventRemover>(); private final DivElement titleElem; public TaskRow(Element parentElem, Task data) { super(parentElem); this.data = data; Element myElem = getElement(); TaskList.Css css = resources.taskListCss(); myElem.setClassName(css.taskRow()); titleElem = Document.get().createDivElement(); titleElem.setClassName(css.title()); DivElement rightMask = Document.get().createDivElement(); rightMask.setClassName(css.checkBoxContainer()); checkMark = Document.get().createDivElement(); rightMask.appendChild(checkMark); myElem.appendChild(titleElem); myElem.appendChild(rightMask); renderTask(); hookEventListeners(); } public Task getTaskData() { return data; } public void removeFromList() { // unhook event handlers for (int i = 0, n = removers.size(); i < n; i++) { removers.get(i).remove(); } removers.clear(); taskRowMap.remove(data.getId()); getTaskListContainerElement(data.getLabelPriority()).removeChild( getElement()); } public void renderTask() { titleElem.getStyle().setProperty("borderColor", Label.chooseColor(data.getLabelPriority())); titleElem.setInnerText(data.getTitle()); if (data.isFinished()) { checkMark.setClassName(resources.taskListCss().checked()); } else { checkMark.setClassName(resources.taskListCss().unChecked()); } } public void setRowAsNotPersisted() { getElement().setClassName(resources.taskListCss().taskRow()); } public void setRowAsPersisted(String id) { getElement().setClassName( resources.taskListCss().taskRow() + " " + resources.taskListCss().taskRowPersisted()); taskRowMap.remove(data.getId()); data.setId(id); taskRowMap.put(id, this); } private void hookEventListeners() { // This is what happens when we click on the task label DomUtils.addEventListener("click", titleElem, new EventListener() { public void onBrowserEvent(Event event) { controller.loadTask(data); controller.goToTaskDetails(); } }); // This is what happens when we click the checkbox DomUtils.addEventListener("click", checkMark, new EventListener() { public void onBrowserEvent(Event event) { if (data.isFinished()) { data.setFinished(false); completedTasks.remove(TaskRow.this); } else { data.setFinished(true); completedTasks.add(TaskRow.this); } renderTask(); controller.persistTask(TaskRow.this); event.stopPropagation(); } }); } } /** * Creates the controls to be added to a TaskList. */ public static Controls createControls(final Controller controller, TaskList.Resources resources) { TaskList.Css css = resources.taskListCss(); // Setup the controls that will be added to the top bar the TaskList screen Controls controls = new Controls(resources); controls.addControl(css.plus(), new EventListener() { public void onBrowserEvent(Event event) { controller.loadTask(null); controller.goToTaskDetails(); } }); controls.addControl(css.garbage(), new EventListener() { public void onBrowserEvent(Event event) { controller.deleteCompletedTasks(); } }); return controls; } private final List<TaskRow> completedTasks = new ArrayList<TaskRow>(); private final Controller controller; private boolean isLoggedIn = false; private final AnchorElement logoutLink; private final DivElement notUrgentImportantTasks; private final DivElement notUrgentNotImportantTasks; private final TaskList.Resources resources; private final HashMap<String, TaskRow> taskRowMap = new HashMap<String, TaskRow>(); private final List<TaskRow> tasksPendingDeleteConfirmation = new ArrayList<TaskRow>(); private final DivElement urgentImportantTasks; private final DivElement urgentNotImportantTasks; private final DivElement userEmail; protected TaskList(PageTransitionPanel parent, Controls controls, Controller controller, TaskList.Resources resources) { super(parent, controls, resources); this.controller = controller; this.resources = resources; urgentNotImportantTasks = Document.get().createDivElement(); urgentImportantTasks = Document.get().createDivElement(); notUrgentNotImportantTasks = Document.get().createDivElement(); notUrgentImportantTasks = Document.get().createDivElement(); Element container = getContentContainer(); container.appendChild(urgentImportantTasks); container.appendChild(notUrgentImportantTasks); container.appendChild(urgentNotImportantTasks); container.appendChild(notUrgentNotImportantTasks); // Login stuff userEmail = Document.get().createDivElement(); userEmail.getStyle().setProperty("display", "inline-block"); userEmail.setInnerText("Loading..."); logoutLink = Document.get().createAnchorElement(); DivElement userInfoContainer = Document.get().createDivElement(); userInfoContainer.appendChild(userEmail); userInfoContainer.appendChild(logoutLink); userInfoContainer.setClassName(resources.taskListCss().user()); container.appendChild(userInfoContainer); } /** * Adds a task to our TaskList UI. * * @param task the task to be added * @return returns the {@link TaskRow} that was attached to the UI */ public TaskRow addTaskToUi(Task task) { Element container = getTaskListContainerElement(task.getLabelPriority()); TaskRow row = new TaskRow(container, task); // For tasks that we have read from storage that are completed. if (row.getTaskData().isFinished()) { completedTasks.add(row); } return row; } /** * Removes the tasks pending deletion. */ public void confirmDeletion() { for (int i = 0, n = tasksPendingDeleteConfirmation.size(); i < n; i++) { TaskRow row = tasksPendingDeleteConfirmation.get(i); row.removeFromList(); } tasksPendingDeleteConfirmation.clear(); } /** * Gets the tasks currently marked for completion and moves them over to * pending delete confirmation. * * @return the tasks currently marked for completion */ public String[] getCompletedTaskIdsAndMoveToPending() { String[] tasks = new String[completedTasks.size()]; int i = 0; while (!completedTasks.isEmpty()) { TaskRow row = completedTasks.remove(0); row.setRowAsNotPersisted(); tasks[i] = row.getTaskData().getId(); tasksPendingDeleteConfirmation.add(row); i++; } return tasks; } public boolean isLoggedIn() { return isLoggedIn; } /** * Moves tasks that were pending deletion back over to pending. This is called * in the case where we fail to complete a delete due to * network/authentication issues. */ public void movePendingTasksBackToCompleted() { int i = 0; while (!tasksPendingDeleteConfirmation.isEmpty()) { TaskRow row = tasksPendingDeleteConfirmation.remove(0); completedTasks.add(row); i++; } } /** * Method invoked if our RPC times out, returns an error, or if we are not * logged in. * * @param loginUrl server generated sign in url, or <code>null</code> if we * time out or have an RPC error. */ public void notifyNotLoggedIn(String loginUrl) { getControlBar().disableControls(); if (loginUrl != null) { // We are definitely not signed in userEmail.setInnerText("Please "); logoutLink.setHref(loginUrl); logoutLink.setInnerText(" signin."); } else { // We had an RPC error or a timeout userEmail.setInnerText("Network slow :(, please wait or"); logoutLink.setHref("javascript:location.reload(true);"); logoutLink.setInnerText(" Try Refresh"); } } /** * Sets the logout link and displays the currently signed in user. * * @param userEmailStr the email address for the currently signed in user. * @param logoutUrl the logout url. */ public void setUserLoggedIn(String userEmailStr, String logoutUrl) { isLoggedIn = true; userEmail.setInnerText(userEmailStr + " | "); logoutLink.setHref(logoutUrl); logoutLink.setInnerText(" logout"); // In case we get a login response after already disabling controls getControlBar().enableControls(); } /** * Updates the UI to reflect that the underlying task has been updated. * * @param task the {@link Task} that has been updated. * @param oldPriority the old priority level of the task. * @return returns the updated {@link TaskRow}. */ public TaskRow updateTask(Task task, int oldPriority) { TaskRow row = taskRowMap.get(task.getId()); assert (row != null); if (oldPriority != task.getLabelPriority()) { Element container = getTaskListContainerElement(oldPriority); container.removeChild(row.getElement()); container = getTaskListContainerElement(task.getLabelPriority()); container.appendChild(row.getElement()); } row.setRowAsNotPersisted(); row.renderTask(); return row; } private Element getTaskListContainerElement(int priorityLevel) { switch (priorityLevel) { case Label.NOT_URGENT_IMPORTANT: return notUrgentImportantTasks; case Label.NOT_URGENT_NOT_IMPORTANT: return notUrgentNotImportantTasks; case Label.URGENT_IMPORTANT: return urgentImportantTasks; case Label.URGENT_NOT_IMPORTANT: return urgentNotImportantTasks; default: return urgentImportantTasks; } } }