/*******************************************************************************
*
* 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 org.spiffyui.spiffyforms.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.spiffyui.client.JSUtil;
import org.spiffyui.client.MainFooter;
import org.spiffyui.client.MainHeader;
import org.spiffyui.client.MessageUtil;
import org.spiffyui.client.rest.RESTException;
import org.spiffyui.client.rest.RESTObjectCallBack;
import org.spiffyui.client.widgets.DatePickerTextBox;
import org.spiffyui.client.widgets.FormFeedback;
import org.spiffyui.client.widgets.button.FancyButton;
import org.spiffyui.client.widgets.button.FancySaveButton;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.Widget;
/**
* Index is the main entry point of our application. It is also the UI and handles all
* of the user interaction, widgets, and application workflow.
*
* This UI has a list on top and a form at the bottom. The list manages the users and
* the form edits the details of the specific users.
*/
public class Index implements EntryPoint, ClickHandler, KeyUpHandler
{
private static final String WIDE_TEXT_FIELD = "wideTextField";
private static final SpiffyUiHtml STRINGS = (SpiffyUiHtml) GWT.create(SpiffyUiHtml.class);
private HTMLPanel m_panel;
private TextBox m_userId;
private FormFeedback m_userIdFeedback;
private TextBox m_firstName;
private FormFeedback m_firstNameFeedback;
private TextBox m_lastName;
private FormFeedback m_lastNameFeedback;
private TextBox m_email;
private FormFeedback m_emailFeedback;
private TextBox m_password;
private FormFeedback m_passwordFeedback;
private TextBox m_passwordRepeat;
private FormFeedback m_passwordRepeatFeedback;
private DatePickerTextBox m_bDay;
private FormFeedback m_bDayFeedback;
private RadioButton m_male;
private RadioButton m_female;
private TextArea m_userDesc;
private FormFeedback m_userDescFeedback;
private FancyButton m_save;
private FancyButton m_del;
private Timer m_timer;
private List<FormFeedback> m_feedbacks = new ArrayList<FormFeedback>();
private Map<String, Anchor> m_anchors = new HashMap<String, Anchor>();
private User m_currentUser;
/**
* The Index page constructor
*/
public Index()
{
}
@Override
public void onModuleLoad()
{
/*
This is where we load our module and create our dynamic controls. The MainHeader
displays our title bar at the top of our page.
*/
MainHeader header = new MainHeader();
header.setHeaderTitle("SpiffyForms Sample App");
/*
The main footer shows our message at the bottom of the page.
*/
MainFooter footer = new MainFooter();
footer.setFooterString("SpiffyForms was built with the <a href=\"http://www.spiffyui.org\">Spiffy UI Framework</a>");
getUsers();
buildFormUI();
Anchor newUser = new Anchor("add user", "#");
newUser.getElement().setId("newUserLink");
newUser.addClickHandler(new ClickHandler()
{
@Override
public void onClick(ClickEvent event)
{
event.preventDefault();
showUser(new User());
m_userId.setFocus(true);
}
});
m_panel.add(newUser, "userListTitle");
}
private void buildFormUI()
{
/*
This HTMLPanel holds most of our content.
MainPanel_html was built in the HTMLProps task from MainPanel.html, which allows you to use large passages of html
without having to string escape them.
*/
m_panel = new HTMLPanel(STRINGS.MainPanel_html());
RootPanel.get("mainContent").add(m_panel);
/*
User ID
*/
m_userId = new TextBox();
m_userId.addKeyUpHandler(this);
m_userId.getElement().setId("userIdTxt");
m_userId.getElement().addClassName(WIDE_TEXT_FIELD);
m_panel.add(m_userId, "userId");
m_userIdFeedback = new FormFeedback();
m_feedbacks.add(m_userIdFeedback);
m_panel.add(m_userIdFeedback, "userIdRow");
/*
First name
*/
m_firstName = new TextBox();
m_firstName.addKeyUpHandler(this);
m_firstName.getElement().setId("firstNameTxt");
m_firstName.getElement().addClassName(WIDE_TEXT_FIELD);
m_firstName.getElement().setAttribute("autofocus", "true");
m_panel.add(m_firstName, "firstName");
m_firstNameFeedback = new FormFeedback();
m_feedbacks.add(m_firstNameFeedback);
m_panel.add(m_firstNameFeedback, "firstNameRow");
/*
Last name
*/
m_lastName = new TextBox();
m_lastName.addKeyUpHandler(this);
m_lastName.getElement().setId("lastNameTxt");
m_lastName.getElement().addClassName(WIDE_TEXT_FIELD);
m_panel.add(m_lastName, "lastName");
m_lastNameFeedback = new FormFeedback();
m_feedbacks.add(m_lastNameFeedback);
m_panel.add(m_lastNameFeedback, "lastNameRow");
/*
email
*/
m_email = new TextBox();
m_email.addKeyUpHandler(this);
m_email.getElement().setId("emailTxt");
m_email.getElement().addClassName(WIDE_TEXT_FIELD);
m_panel.add(m_email, "email");
m_emailFeedback = new FormFeedback();
m_feedbacks.add(m_emailFeedback);
m_panel.add(m_emailFeedback, "emailRow");
/*
User's birthdate
*/
m_bDay = new DatePickerTextBox("userBdayTxt");
m_bDay.setMaximumDate(new Date()); //user cannot be born tomorrow
m_bDay.addKeyUpHandler(this);
m_bDay.getElement().addClassName("slimTextField");
m_panel.add(m_bDay, "userBday");
m_bDayFeedback = new FormFeedback();
m_panel.add(m_bDayFeedback, "userBdayRow");
/*
User's gender
*/
m_female = new RadioButton("userGender", "Female");
m_panel.add(m_female, "userGender");
m_male = new RadioButton("userGender", "Male");
m_male.addStyleName("radioOption");
m_male.setValue(true);
m_male.getElement().setId("userMale");
m_panel.add(m_male, "userGender");
/*
User description
*/
m_userDesc = new TextArea();
m_userDesc.addKeyUpHandler(this);
m_userDesc.getElement().setId("userDescTxt");
m_userDesc.getElement().addClassName(WIDE_TEXT_FIELD);
m_userDesc.getElement().setAttribute("placeholder", "Tell us a little about yourself.");
m_panel.add(m_userDesc, "userDesc");
m_userDescFeedback = new FormFeedback();
m_feedbacks.add(m_userDescFeedback);
m_panel.add(m_userDescFeedback, "userDescRow");
/*
Password
*/
m_password = new PasswordTextBox();
m_password.addKeyUpHandler(this);
m_password.getElement().setId("passwordTxt");
m_password.getElement().addClassName("slimTextField");
m_panel.add(m_password, "password");
m_passwordFeedback = new FormFeedback();
m_feedbacks.add(m_passwordFeedback);
m_panel.add(m_passwordFeedback, "passwordRow");
/*
Password repeat
*/
m_passwordRepeat = new PasswordTextBox();
m_passwordRepeat.addKeyUpHandler(this);
m_passwordRepeat.getElement().setId("passwordRepeatTxt");
m_passwordRepeat.getElement().addClassName("slimTextField");
m_panel.add(m_passwordRepeat, "passwordRepeat");
m_passwordRepeatFeedback = new FormFeedback();
m_feedbacks.add(m_passwordRepeatFeedback);
m_panel.add(m_passwordRepeatFeedback, "passwordRepeatRow");
/*
The big save button
*/
m_save = new FancySaveButton("Save");
m_save.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event)
{
save();
}
});
m_panel.add(m_save, "buttons");
/*
The delete button
*/
m_del = new DeleteButton("Delete");
m_del.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event)
{
delete();
}
});
m_panel.add(m_del, "buttons");
updateFormStatus(null);
}
/**
* This is the first method which calls REST and we call it when the application
* first loads. We make a GET request to the server to get the list of Users from
* the server.
*/
private void getUsers()
{
User.getUsers(new RESTObjectCallBack<User[]>() {
public void success(User[] users)
{
showUsers(users);
}
public void error(String message)
{
MessageUtil.showFatalError(message);
}
public void error(RESTException e)
{
MessageUtil.showFatalError(e.getReason());
}
});
}
/**
* Shows the list of users.
*
* We're building our list of users as a simple set of DIVs with style to make them easy
* to work with in our simple table. We could use a table or another GWT widget for this,
* but DIV tags are fine for our simple example.
*
* @param users the users to display
*/
private void showUsers(User users[])
{
if (m_currentUser != null) {
showUser(m_currentUser);
} else if (users.length > 0) {
showUser(users[0]);
} else {
showUser(new User());
}
m_userId.setFocus(true);
for (String id : m_anchors.keySet()) {
m_anchors.get(id).removeFromParent();
}
m_anchors.clear();
StringBuffer userHTML = new StringBuffer();
userHTML.append("<div class=\"gridlist\">");
for (int i = 0; i < users.length; i++) {
User u = users[i];
if (i % 2 == 0) {
userHTML.append("<div class=\"gridlistitem evenrow\">");
} else {
userHTML.append("<div class=\"gridlistitem oddrow\">");
}
String id = HTMLPanel.createUniqueId();
/*
The user id
*/
userHTML.append("<div id=\"" + id + "\" class=\"useridcol\"></div>");
/*
The user's name
*/
userHTML.append("<div class=\"userfullnamecol\">" + u.getFirstName() + " " + u.getLastName() + "</div>");
/*
The email address
*/
userHTML.append("<div class=\"useremailcol\">" + u.getEmail() + "</div>");
userHTML.append("</div>");
Anchor a = new Anchor(u.getUserId(), "#");
a.getElement().setPropertyObject("user", u);
a.addClickHandler(this);
m_anchors.put(id, a);
}
m_panel.getElementById("userListGrid").setInnerHTML(userHTML.toString());
/*
Now that we've added the elements to the DOM we can add the
anchors
*/
for (String id : m_anchors.keySet()) {
m_panel.add(m_anchors.get(id), id);
}
}
@Override
public void onClick(ClickEvent event)
{
event.preventDefault();
if (event.getSource() instanceof Anchor) {
showUser((User) ((Anchor) event.getSource()).getElement().getPropertyObject("user"));
}
}
/**
* Show the specified user.
*
* This method clears out the user form, resets the validation widgets, and populates
* the form with the data about the specified user.
*
* @param user the user to show
*/
private void showUser(User user)
{
m_currentUser = user;
m_userId.setText(user.getUserId());
m_firstName.setText(user.getFirstName());
m_lastName.setText(user.getLastName());
m_email.setText(user.getEmail());
m_password.setText(user.getPassword());
m_passwordRepeat.setText(user.getPassword());
if (user.getBirthday() != null) {
m_bDay.setDateValue(user.getBirthday());
} else {
m_bDay.setText("");
}
m_userDesc.setText(user.getUserDesc());
m_male.setValue(user.getGender().equals("male"));
m_female.setValue(user.getGender().equals("female"));
for (FormFeedback f : m_feedbacks) {
f.setText("");
if (m_currentUser.isNew()) {
f.setStatus(FormFeedback.NONE);
} else {
f.setStatus(FormFeedback.VALID);
}
}
updateFormStatus(null);
m_del.setEnabled(!m_currentUser.isNew());
m_userId.setEnabled(m_currentUser.isNew());
if (m_currentUser.isNew()) {
m_panel.getElementById("userDetailsTitle").setInnerText("User Details - New User");
} else {
m_panel.getElementById("userDetailsTitle").setInnerText("User Details - " + user.getUserId());
}
}
@Override
public void onKeyUp(KeyUpEvent event)
{
if (event.getNativeKeyCode() != KeyCodes.KEY_TAB) {
updateFormStatus((Widget) event.getSource());
}
}
/**
* This method saves the details about a specific user. We call this one method to
* create a new user or save an existing user.
*/
private void save()
{
if (m_currentUser == null) {
MessageUtil.showWarning("No user selected to save.", false);
return;
}
m_save.setInProgress(true);
/*
Now we take the data out of our controls and save it into the REST bean
*/
m_currentUser.setUserId(m_userId.getText());
m_currentUser.setFirstName(m_firstName.getText());
m_currentUser.setLastName(m_lastName.getText());
m_currentUser.setEmail(m_email.getText());
m_currentUser.setPassword(m_password.getText());
m_currentUser.setBirthday(m_bDay.getDateValue());
m_currentUser.setUserDesc(m_userDesc.getText());
if (m_male.getValue()) {
m_currentUser.setGender("male");
} else {
m_currentUser.setGender("female");
}
/*
Then we call the bean to save the data to the server
*/
m_currentUser.save(new RESTObjectCallBack<Boolean>() {
public void success(Boolean b)
{
MessageUtil.showMessage(m_currentUser.getUserId() + " was saved successfully");
getUsers();
m_save.setInProgress(false);
}
public void error(String message)
{
MessageUtil.showFatalError(message);
m_save.setInProgress(false);
}
public void error(RESTException e)
{
MessageUtil.showFatalError(e.getReason());
m_save.setInProgress(false);
}
});
}
/**
* This method deletes the currently selected user. In a real application we would
* probably want to prompt first with an "are you sure" style dialog, but this is
* just a sample.
*/
private void delete()
{
if (m_currentUser == null) {
MessageUtil.showWarning("No user selected to delete.", false);
return;
}
m_del.setInProgress(true);
m_currentUser.delete(new RESTObjectCallBack<Boolean>() {
public void success(Boolean b)
{
MessageUtil.showMessage("Finished deleting " + m_currentUser.getUserId());
// clear out m_currentUser
m_currentUser = null;
getUsers();
m_del.setInProgress(false);
}
public void error(String message)
{
MessageUtil.showFatalError(message);
m_del.setInProgress(false);
}
public void error(RESTException e)
{
MessageUtil.showFatalError(e.getReason());
m_del.setInProgress(false);
}
});
}
/**
* Validate that the specified field is filled in and valid.
*
* @param tb the field to validate
* @param minLength the minimum character length of the field
* @param feedback the feedback control for this field
* @param error the error to show in the feedback if the field isn't valid
*/
private void validateField(TextBoxBase tb, int minLength, FormFeedback feedback, String error)
{
if (tb.getText().length() > minLength) {
feedback.setStatus(FormFeedback.VALID);
feedback.setTitle("");
} else {
feedback.setStatus(FormFeedback.WARNING);
feedback.setTitle(error);
}
}
/**
* Validate the username in this form. This method uses a timer and makes a REST call
* to the server to validate the username after the user has stopped typing for one
* second.
*/
private void validateUsername()
{
if (m_timer != null) {
m_timer.cancel();
}
if (m_userId.getText().length() < 2) {
m_userIdFeedback.setStatus(FormFeedback.WARNING);
m_userIdFeedback.setTitle("Username must be more than two characters");
return;
}
m_userIdFeedback.setText("");
m_userIdFeedback.setStatus(FormFeedback.LOADING);
m_timer = new Timer()
{
@Override
public void run()
{
runUserValidation();
}
};
m_timer.schedule(1000);
}
/**
* Call the usernames REST endpoint and verify that this username is available.
*/
private void runUserValidation()
{
m_userId.setEnabled(false);
User.isUsernameInUse(new RESTObjectCallBack<Boolean>() {
public void success(Boolean b)
{
if (b.booleanValue()) {
m_userIdFeedback.setStatus(FormFeedback.ERROR);
m_userIdFeedback.setText("This username is already in use");
m_userId.setTitle("This username is already in use");
m_save.setEnabled(false);
} else {
m_userIdFeedback.setStatus(FormFeedback.VALID);
m_userIdFeedback.setText("This username is available");
m_userIdFeedback.setText("This username is available");
m_userId.setTitle("");
}
m_userId.setEnabled(true);
}
public void error(String message)
{
MessageUtil.showFatalError(message);
m_userIdFeedback.setStatus(FormFeedback.ERROR);
m_save.setEnabled(false);
m_userId.setEnabled(true);
}
public void error(RESTException e)
{
MessageUtil.showFatalError(e.getReason());
m_userIdFeedback.setStatus(FormFeedback.ERROR);
m_userId.setEnabled(true);
m_save.setEnabled(false);
}
}, m_userId.getText());
}
/**
* Enable or disable the save button based on the state of the fields.
*/
private void enableSaveButton()
{
/*
* We only want to enable the save button if every field is valid
*/
for (FormFeedback feedback : m_feedbacks) {
if (feedback.getStatus() != FormFeedback.VALID) {
m_save.setEnabled(false);
return;
}
}
m_save.setEnabled(true);
}
/**
* Validate the second password field.
*/
private void validatePasswordRepeat()
{
validateField(m_passwordRepeat, 2, m_passwordRepeatFeedback, "Your passwords don't match");
if (m_passwordRepeat.getText().equals(m_password.getText())) {
m_passwordRepeatFeedback.setStatus(FormFeedback.VALID);
m_passwordRepeatFeedback.setText("");
m_passwordRepeatFeedback.setTitle("");
} else {
m_passwordRepeatFeedback.setStatus(FormFeedback.ERROR);
m_passwordRepeatFeedback.setText("Your passwords don't match");
m_passwordRepeatFeedback.setTitle("Your passwords don't match");
}
}
/**
* Update the status of our form. This method handles field validation and enabling
* the save button. This method is called when the user makes any change to the form.
*
* When the user types in the first field we want to validate that field, but we don't
* want to validate the rest of them since the rest aren't filled in yet and we don't
* want to show invalid messages for fields they haven't edited yet. This method takes
* the widget that was edited as an argument so it can validate just that field.
*
* @param w the widget that's being changed
*/
private void updateFormStatus(Widget w)
{
if (w == m_userId) {
validateUsername();
} else if (w == m_firstName) {
validateField(m_firstName, 1, m_firstNameFeedback, "First name must be more than two characters");
} else if (w == m_lastName) {
validateField(m_lastName, 1, m_lastNameFeedback, "Last name must be more than two characters");
} else if (w == m_email) {
validateEmail();
} else if (w == m_password) {
validateField(m_password, 2, m_passwordFeedback, "Password name must be more than two characters");
} else if (w == m_bDay) {
//validateBirthday();
} else if (w == m_passwordRepeat) {
validatePasswordRepeat();
} else if (w == m_userDesc) {
validateField(m_userDesc, 8, m_userDescFeedback, "The user description must be more than two characters");
}
enableSaveButton();
}
/**
* Validate that the email field is filled in with a valid email address.
*/
private void validateEmail()
{
if (JSUtil.validateEmail(m_email.getText())) {
m_emailFeedback.setStatus(FormFeedback.VALID);
m_emailFeedback.setTitle("");
} else {
m_emailFeedback.setStatus(FormFeedback.ERROR);
m_emailFeedback.setTitle("Invalid email address");
}
}
}
/**
* This is a little class to handle our delete button. It mostly just applies styling.
*/
class DeleteButton extends FancyButton
{
public DeleteButton(String s)
{
super(s);
getElement().setClassName("spiffy-del-button");
getElement().addClassName("spiffy-fancy-button");
}
}