1 /* 2 * AppNameTextbox.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.studio.client.rsconnect.ui; 16 17 import org.rstudio.core.client.CommandWithArg; 18 import org.rstudio.core.client.StringUtil; 19 import org.rstudio.core.client.dom.DomUtils; 20 import org.rstudio.studio.client.rsconnect.model.RSConnectAppName; 21 22 import com.google.gwt.core.client.GWT; 23 import com.google.gwt.event.dom.client.KeyUpEvent; 24 import com.google.gwt.event.dom.client.KeyUpHandler; 25 import com.google.gwt.regexp.shared.RegExp; 26 import com.google.gwt.uibinder.client.UiBinder; 27 import com.google.gwt.uibinder.client.UiField; 28 import com.google.gwt.user.client.Command; 29 import com.google.gwt.user.client.ui.Composite; 30 import com.google.gwt.user.client.ui.HTMLPanel; 31 import com.google.gwt.user.client.ui.Label; 32 import com.google.gwt.user.client.ui.TextBox; 33 import com.google.gwt.user.client.ui.Widget; 34 35 public class AppNameTextbox extends Composite 36 { 37 38 private static AppNameTextboxUiBinder uiBinder = GWT 39 .create(AppNameTextboxUiBinder.class); 40 41 interface AppNameTextboxUiBinder extends UiBinder<Widget, AppNameTextbox> 42 { 43 } 44 45 interface Host 46 { supportsTitle()47 boolean supportsTitle(); generateAppName(String title, CommandWithArg<RSConnectAppName> result)48 void generateAppName(String title, 49 CommandWithArg<RSConnectAppName> result); 50 } 51 AppNameTextbox(Host host)52 public AppNameTextbox(Host host) 53 { 54 host_ = host; 55 initWidget(uiBinder.createAndBindUi(this)); 56 57 // Validate the application name on every keystroke 58 appTitle_.addKeyUpHandler(new KeyUpHandler() 59 { 60 @Override 61 public void onKeyUp(KeyUpEvent event) 62 { 63 validateAppName(); 64 } 65 }); 66 } 67 setOnNameIsValid(Command cmd)68 public void setOnNameIsValid(Command cmd) 69 { 70 onNameIsValid_ = cmd; 71 } 72 setOnNameIsInvalid(Command cmd)73 public void setOnNameIsInvalid(Command cmd) 74 { 75 onNameIsInvalidTitle_ = cmd; 76 } 77 setTitle(String text)78 public void setTitle(String text) 79 { 80 appTitle_.setText(text); 81 } 82 getTitle()83 public String getTitle() 84 { 85 return appTitle_.getText().trim(); 86 } 87 getTextBox()88 public TextBox getTextBox() 89 { 90 return appTitle_; 91 } 92 getName()93 public String getName() 94 { 95 // return current title if no generated name is available 96 if (StringUtil.isNullOrEmpty(name_)) 97 return getTitle(); 98 return name_; 99 } 100 setFocus(boolean focused)101 public void setFocus(boolean focused) 102 { 103 appTitle_.setFocus(focused); 104 } 105 validateAppName()106 public void validateAppName() 107 { 108 if (!host_.supportsTitle()) 109 { 110 String app = appTitle_.getText(); 111 RegExp validReg = RegExp.compile("^[A-Za-z0-9_-]{4,63}$"); 112 validTitle_ = validReg.test(app); 113 setAppNameValid(validTitle_); 114 if (validTitle_) 115 name_ = app; 116 else 117 error_.setText("The title must contain 4 - 64 alphanumeric " + 118 "characters."); 119 return; 120 } 121 122 // if we don't have enough characters, bail out early 123 final String title = appTitle_.getText().trim(); 124 if (title.length() < 3) 125 { 126 validTitle_ = false; 127 // if we also don't have focus in the box, show an error 128 if (DomUtils.getActiveElement() != appTitle_.getElement()) 129 { 130 setAppNameValid(false); 131 error_.setText("The title must contain at least 3 characters."); 132 } 133 return; 134 } 135 136 host_.generateAppName(title, 137 new CommandWithArg<RSConnectAppName>() 138 { 139 @Override 140 public void execute(RSConnectAppName arg) 141 { 142 name_ = arg.name(); 143 validTitle_ = arg.valid(); 144 error_.setText(arg.error()); 145 setAppNameValid(arg.valid()); 146 } 147 }); 148 } 149 150 @Override setStyleName(String styleName)151 public void setStyleName(String styleName) 152 { 153 appTitle_.setStyleName(styleName); 154 } 155 setDetails(String title, String name)156 public void setDetails(String title, String name) 157 { 158 if (StringUtil.isNullOrEmpty(title)) 159 title = name; 160 appTitle_.setTitle(title); 161 name_ = name; 162 } 163 isValid()164 public boolean isValid() 165 { 166 return !appTitle_.getText().trim().isEmpty() && validTitle_; 167 } 168 169 // Private methods --------------------------------------------------------- 170 setAppNameValid(boolean isValid)171 private void setAppNameValid(boolean isValid) 172 { 173 nameValidatePanel_.setVisible(!isValid); 174 if (isValid && onNameIsValid_ != null) 175 onNameIsValid_.execute(); 176 else if (!isValid && onNameIsInvalidTitle_ != null) 177 onNameIsInvalidTitle_.execute(); 178 } 179 180 private final Host host_; 181 private Command onNameIsValid_; 182 private Command onNameIsInvalidTitle_; 183 private String name_; 184 private boolean validTitle_ = true; 185 186 @UiField TextBox appTitle_; 187 @UiField HTMLPanel nameValidatePanel_; 188 @UiField Label error_; 189 } 190