/******************************************************************************* * * 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"); } }