
Login: added ajax dialog form
@eee6252c2221305217ed9a6e26c72de21204328f
--- app/assets/stylesheets/less/_mixins.less
+++ app/assets/stylesheets/less/_mixins.less
... | ... | @@ -19,9 +19,9 @@ |
19 | 19 |
|
20 | 20 |
// Drop shadows |
21 | 21 |
.box-shadow(@shadow: 0 1px 2px rgba(0, 0, 0, .25)){ |
22 |
- -webkit-box-shadow: @shadow; |
|
23 |
- -moz-box-shadow: @shadow; |
|
24 |
- box-shadow: @shadow; |
|
22 |
+ -webkit-box-shadow: @shadow !important; |
|
23 |
+ -moz-box-shadow: @shadow !important; |
|
24 |
+ box-shadow: @shadow !important; |
|
25 | 25 |
} |
26 | 26 |
|
27 | 27 |
// Transitions |
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -2957,6 +2957,8 @@ |
2957 | 2957 |
} |
2958 | 2958 |
.disabled { |
2959 | 2959 |
resize:none; |
2960 |
+ height:80px; |
|
2961 |
+ background:#f3f3f3; |
|
2960 | 2962 |
} |
2961 | 2963 |
input[type="radio"]{ |
2962 | 2964 |
display: none; |
... | ... | @@ -2996,9 +2998,6 @@ |
2996 | 2998 |
margin-right: 5px; |
2997 | 2999 |
} |
2998 | 3000 |
} |
2999 |
- /*a { |
|
3000 |
- margin-left: 5px; |
|
3001 |
- }*/ |
|
3002 | 3001 |
} |
3003 | 3002 |
.board-actrow { |
3004 | 3003 |
margin:20px 0; |
... | ... | @@ -5665,3 +5664,21 @@ |
5665 | 5664 |
} |
5666 | 5665 |
} |
5667 | 5666 |
|
5667 |
+.loginDialog { |
|
5668 |
+ width:460px; margin-left:-230px; |
|
5669 |
+ input { .box-shadow(none); } |
|
5670 |
+ .login-form-wrap { margin:20px auto !important; } |
|
5671 |
+ .error { |
|
5672 |
+ display:none; |
|
5673 |
+ margin-bottom:20px; |
|
5674 |
+ color:#E74C3C; |
|
5675 |
+ .yobicon-error { vertical-align: middle; margin-right:5px; font-size:13px; } |
|
5676 |
+ } |
|
5677 |
+ .act-row { |
|
5678 |
+ margin-bottom:20px; |
|
5679 |
+ input.checkbox { margin-top:4px !important; } |
|
5680 |
+ label.bg-checkbox { display:inline-block; } |
|
5681 |
+ } |
|
5682 |
+ .btns-row { width:auto; } |
|
5683 |
+ .fullsize { width:100%; } |
|
5684 |
+} |
--- app/controllers/UserApp.java
+++ app/controllers/UserApp.java
... | ... | @@ -149,42 +149,66 @@ |
149 | 149 |
|
150 | 150 |
/** |
151 | 151 |
* 로그인 처리 |
152 |
- * 시스템 설정에서 가입승인 기능이 활성화 되어 있고 사용자 상태가 잠금상태(미승인?)라면 계정이 잠겼다는 메시지를 노출하고 로그인 폼으로 돌아감 |
|
153 |
- * 시스템 설정에서 가입승인 기능이 활성화 되어 있지 않다면, 사용자 상태가 잠금상태라도 로그인이 가능하다 (스펙확인 필요) |
|
154 |
- * 요청의 정보로 사용자 인증에 성공하면 로그인쿠키를 생성하고 로그인유지하기가 선택되었다면, 로그인유지를 위한 쿠키를 별도로 생성한다 |
|
155 |
- * 인증에 실패하면 관련된 메시지를 노출하고 로그인 폼으로 돌아간다 |
|
156 | 152 |
* |
157 | 153 |
* @return |
158 | 154 |
*/ |
155 |
+ |
|
159 | 156 |
public static Result login() { |
157 |
+ if (HttpUtil.isJSONPreferred(request())) { |
|
158 |
+ return loginByAjaxRequest(); |
|
159 |
+ } else { |
|
160 |
+ return loginByFormRequest(); |
|
161 |
+ } |
|
162 |
+ } |
|
163 |
+ |
|
164 |
+ /** |
|
165 |
+ * Process login in general case of request. |
|
166 |
+ * |
|
167 |
+ * Returns: |
|
168 |
+ * - If "signup.require.confirm = true" has enabled in application.conf, |
|
169 |
+ * the user in state of locked(or unconfirmed) cannot be logged in. |
|
170 |
+ * and page will be redirected to login form with message "user.locked". |
|
171 |
+ * |
|
172 |
+ * - If "signup.require.confirm" is disabled(as default), |
|
173 |
+ * the user in state of locked can be logged in. (TODO: check this in feature specification). |
|
174 |
+ * |
|
175 |
+ * - If failed to authentication, redirect to login form with error message. |
|
176 |
+ * |
|
177 |
+ * Cookie for login will be created |
|
178 |
+ * if success to authenticate with request. |
|
179 |
+ * |
|
180 |
+ * If "rememberMe" included in request, |
|
181 |
+ * Cookie for "rememberMe" (which means "Stay logged in") will be create |
|
182 |
+ * separate from login cookie. |
|
183 |
+ * |
|
184 |
+ * @return |
|
185 |
+ */ |
|
186 |
+ private static Result loginByFormRequest(){ |
|
160 | 187 |
Form<User> userForm = form(User.class).bindFromRequest(); |
188 |
+ |
|
161 | 189 |
if(userForm.hasErrors()) { |
162 | 190 |
return badRequest(login.render("title.login", userForm, null)); |
163 | 191 |
} |
192 |
+ |
|
164 | 193 |
User sourceUser = form(User.class).bindFromRequest().get(); |
165 |
- |
|
166 |
- Map<String, String[]> params = request().body().asFormUrlEncoded(); |
|
167 |
- String redirectUrl = HttpUtil.getFirstValueFromQuery(params, "redirectUrl"); |
|
168 |
- |
|
169 |
- String loginFormUrl = routes.UserApp.loginForm().url(); |
|
170 |
- loginFormUrl += "?redirectUrl=" + redirectUrl; |
|
171 | 194 |
|
172 | 195 |
if (isUseSignUpConfirm()) { |
173 | 196 |
if (User.findByLoginId(sourceUser.loginId).state == UserState.LOCKED) { |
174 | 197 |
flash(Constants.WARNING, "user.locked"); |
175 |
- return redirect(loginFormUrl); |
|
198 |
+ return redirect(getLoginFormURLWithRedirectURL()); |
|
176 | 199 |
} |
177 | 200 |
} |
178 | 201 |
|
179 | 202 |
if (User.findByLoginId(sourceUser.loginId).state == UserState.DELETED) { |
180 | 203 |
flash(Constants.WARNING, "user.deleted"); |
181 |
- return redirect(loginFormUrl); |
|
204 |
+ return redirect(getLoginFormURLWithRedirectURL()); |
|
182 | 205 |
} |
183 | 206 |
|
184 | 207 |
User authenticate = authenticateWithPlainPassword(sourceUser.loginId, sourceUser.password); |
185 | 208 |
|
186 | 209 |
if (!authenticate.isAnonymous()) { |
187 | 210 |
addUserInfoToSession(authenticate); |
211 |
+ |
|
188 | 212 |
if (sourceUser.rememberMe) { |
189 | 213 |
setupRememberMe(authenticate); |
190 | 214 |
} |
... | ... | @@ -192,7 +216,9 @@ |
192 | 216 |
authenticate.lang = play.mvc.Http.Context.current().lang().code(); |
193 | 217 |
authenticate.update(); |
194 | 218 |
|
195 |
- if (StringUtils.isEmpty(redirectUrl)) { |
|
219 |
+ String redirectUrl = getRedirectURLFromParams(); |
|
220 |
+ |
|
221 |
+ if(StringUtils.isEmpty(redirectUrl)){ |
|
196 | 222 |
return redirect(routes.Application.index()); |
197 | 223 |
} else { |
198 | 224 |
return redirect(redirectUrl); |
... | ... | @@ -204,6 +230,89 @@ |
204 | 230 |
} |
205 | 231 |
|
206 | 232 |
/** |
233 |
+ * Process login request by AJAX |
|
234 |
+ * |
|
235 |
+ * Almost same with loginByFormRequest |
|
236 |
+ * except part of handle with "redirectUrl" has excluded. |
|
237 |
+ * |
|
238 |
+ * Returns: |
|
239 |
+ * - In case of success: empty JSON string {} |
|
240 |
+ * - In case of failed: error message as JSON string in form of {"message":"cause"}. |
|
241 |
+ * |
|
242 |
+ * @return |
|
243 |
+ */ |
|
244 |
+ private static Result loginByAjaxRequest(){ |
|
245 |
+ Form<User> userForm = form(User.class).bindFromRequest(); |
|
246 |
+ |
|
247 |
+ if(userForm.hasErrors()) { |
|
248 |
+ return badRequest(getObjectNodeWithMessage("validation.required")); |
|
249 |
+ } |
|
250 |
+ |
|
251 |
+ User sourceUser = form(User.class).bindFromRequest().get(); |
|
252 |
+ |
|
253 |
+ if (isUseSignUpConfirm()) { |
|
254 |
+ if (User.findByLoginId(sourceUser.loginId).state == UserState.LOCKED) { |
|
255 |
+ return forbidden(getObjectNodeWithMessage("user.locked")); |
|
256 |
+ } |
|
257 |
+ } |
|
258 |
+ |
|
259 |
+ if (User.findByLoginId(sourceUser.loginId).state == UserState.DELETED) { |
|
260 |
+ return notFound(getObjectNodeWithMessage("user.deleted")); |
|
261 |
+ } |
|
262 |
+ |
|
263 |
+ User authenticate = authenticateWithPlainPassword(sourceUser.loginId, sourceUser.password); |
|
264 |
+ |
|
265 |
+ if (!authenticate.isAnonymous()) { |
|
266 |
+ addUserInfoToSession(authenticate); |
|
267 |
+ |
|
268 |
+ if (sourceUser.rememberMe) { |
|
269 |
+ setupRememberMe(authenticate); |
|
270 |
+ } |
|
271 |
+ |
|
272 |
+ authenticate.lang = play.mvc.Http.Context.current().lang().code(); |
|
273 |
+ authenticate.update(); |
|
274 |
+ |
|
275 |
+ return ok("{}"); |
|
276 |
+ } |
|
277 |
+ |
|
278 |
+ return forbidden(getObjectNodeWithMessage("user.login.failed")); |
|
279 |
+ } |
|
280 |
+ |
|
281 |
+ /** |
|
282 |
+ * Get value of "redirectUrl" from query |
|
283 |
+ * @return |
|
284 |
+ */ |
|
285 |
+ private static String getRedirectURLFromParams(){ |
|
286 |
+ Map<String, String[]> params = request().body().asFormUrlEncoded(); |
|
287 |
+ return HttpUtil.getFirstValueFromQuery(params, "redirectUrl"); |
|
288 |
+ } |
|
289 |
+ |
|
290 |
+ /** |
|
291 |
+ * Get login form URL string with "redirectUrl" parameter in query |
|
292 |
+ * @return |
|
293 |
+ */ |
|
294 |
+ private static String getLoginFormURLWithRedirectURL(){ |
|
295 |
+ String redirectUrl = getRedirectURLFromParams(); |
|
296 |
+ String loginFormUrl = routes.UserApp.loginForm().url(); |
|
297 |
+ loginFormUrl = loginFormUrl + "?redirectUrl=" + redirectUrl; |
|
298 |
+ |
|
299 |
+ return loginFormUrl; |
|
300 |
+ } |
|
301 |
+ |
|
302 |
+ /** |
|
303 |
+ * Returns ObjectNode which has "message" node filled with {@code message} |
|
304 |
+ * loginByAjaxRequest() uses this to return result as JSON string |
|
305 |
+ * |
|
306 |
+ * @param message |
|
307 |
+ * @return |
|
308 |
+ */ |
|
309 |
+ private static ObjectNode getObjectNodeWithMessage(String message){ |
|
310 |
+ ObjectNode result = Json.newObject(); |
|
311 |
+ result.put("message", message); |
|
312 |
+ return result; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ /** |
|
207 | 316 |
* loginId 와 hash 값을 이용해서 사용자 인증. |
208 | 317 |
* 인증에 성공하면 DB 에서 조회된 사용자 정보를 리턴 |
209 | 318 |
* 인증에 실패하면 {@code User.anonymous} 리턴 |
+++ app/views/common/loginDialog.scala.html
... | ... | @@ -0,0 +1,68 @@ |
1 | +@** | |
2 | +* Yobi, Project Hosting SW | |
3 | +* | |
4 | +* Copyright 2014 NAVER Corp. | |
5 | +* http://yobi.io | |
6 | +* | |
7 | +* @Author Jihan Kim | |
8 | +* | |
9 | +* Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | +* you may not use this file except in compliance with the License. | |
11 | +* You may obtain a copy of the License at | |
12 | +* | |
13 | +* http://www.apache.org/licenses/LICENSE-2.0 | |
14 | +* | |
15 | +* Unless required by applicable law or agreed to in writing, software | |
16 | +* distributed under the License is distributed on an "AS IS" BASIS, | |
17 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | +* See the License for the specific language governing permissions and | |
19 | +* limitations under the License. | |
20 | +**@ | |
21 | +@() | |
22 | + | |
23 | +<div id="loginDialog" class="modal hide loginDialog" tabindex="-1" role="dialog"> | |
24 | + <div class="modal-body"> | |
25 | + <div class="pull-right"> | |
26 | + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" class="mr10">×</button> | |
27 | + </div> | |
28 | + <form action="@routes.UserApp.login()" method="post" class="frm-wrap login-form-wrap"> | |
29 | + <dl> | |
30 | + <dt> | |
31 | + <label for="loginIdD">@Messages("user.loginId")</label> | |
32 | + </dt> | |
33 | + <dd> | |
34 | + <input id="loginIdD" name="loginId" type="text" class="text email" autocomplete="off"> | |
35 | + </dd> | |
36 | + | |
37 | + <dt> | |
38 | + <label for="passwordD">@Messages("user.password")</label> | |
39 | + </dt> | |
40 | + <dd> | |
41 | + <input id="passwordD" name="password" type="password" class="text password" autocomplete="off"> | |
42 | + </dd> | |
43 | + </dl> | |
44 | + | |
45 | + <div class="error"> | |
46 | + <i class="yobicon-error"></i> | |
47 | + <span class="error-message">@Messages("user.login.failed")</span> | |
48 | + </div> | |
49 | + | |
50 | + <div class="btns-row nm"> | |
51 | + <button type="submit" class="ybtn ybtn-primary fullsize">@Messages("button.login")</button> | |
52 | + </div> | |
53 | + | |
54 | + <div class="act-row right-txt mt20"> | |
55 | + <div class="pull-left"> | |
56 | + <input id="remember-meD" type="checkbox" name="rememberMe" class="checkbox"> | |
57 | + <label for="remember-meD" class="bg-checkbox">@Messages("title.rememberMe")</label> | |
58 | + </div> | |
59 | + | |
60 | + <a href="@routes.PasswordResetApp.lostPassword()">@Messages("title.resetPassword")</a> | |
61 | + <span class="gray-txt ml10 mr10">|</span> | |
62 | + <a href="@routes.UserApp.signupForm">@Messages("title.signup")</a> | |
63 | + </div> | |
64 | + </form> | |
65 | + </div> | |
66 | +</div> | |
67 | +<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery-ui-1.10.4.custom.min.js")"></script> | |
68 | +<script type="text/javascript" src="@routes.Assets.at("javascripts/common/yobi.LoginDialog.js")"></script> |
--- app/views/common/scripts.scala.html
+++ app/views/common/scripts.scala.html
... | ... | @@ -55,6 +55,8 @@ |
55 | 55 |
</div> |
56 | 56 |
@**<!-- //yobi.ui.Spinner -->**@ |
57 | 57 |
|
58 |
+@common.loginDialog() |
|
59 |
+ |
|
58 | 60 |
<script type="text/javascript" src="@routes.Application.jsMessages()"></script> |
59 | 61 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery.tmpl.js")"></script> |
60 | 62 |
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/jquery/jquery.form.js")"></script> |
+++ public/javascripts/common/yobi.LoginDialog.js
... | ... | @@ -0,0 +1,98 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2014 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @author Jihan Kim | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | +$(function(){ | |
22 | + "use strict"; | |
23 | + | |
24 | + var htElement = {}; | |
25 | + | |
26 | + function _init(){ | |
27 | + // do this except in loginForm, signUpForm | |
28 | + if(location.pathname.substr(1).split('/')[0] === "users"){ | |
29 | + return; | |
30 | + } | |
31 | + | |
32 | + _initElement(); | |
33 | + _attachEvent(); | |
34 | + } | |
35 | + | |
36 | + function _initElement(){ | |
37 | + htElement.welDialog = $("#loginDialog"); | |
38 | + htElement.welForm = htElement.welDialog.find("form"); | |
39 | + htElement.welInputId = htElement.welDialog.find("input[name='loginId']"); | |
40 | + htElement.welInputPw = htElement.welDialog.find("input[name='password']"); | |
41 | + htElement.welLoginError = htElement.welDialog.find(".error"); | |
42 | + htElement.welLoginErrorMsg = htElement.welLoginError.find(".error-message"); | |
43 | + } | |
44 | + | |
45 | + function _attachEvent(){ | |
46 | + $(document.body).on('click', '[login-required]', _showDialog); | |
47 | + htElement.welForm.on('submit', _onSubmitForm); | |
48 | + } | |
49 | + | |
50 | + function _showDialog(weEvt){ | |
51 | + if(_isInputElement(weEvt.target)){ | |
52 | + $(weEvt.target).blur(); | |
53 | + } | |
54 | + | |
55 | + htElement.welLoginError.hide(); | |
56 | + htElement.welInputPw.val(""); | |
57 | + htElement.welInputId.val(""); | |
58 | + | |
59 | + htElement.welDialog.modal("show"); | |
60 | + htElement.welInputId.focus(); | |
61 | + | |
62 | + weEvt.preventDefault(); | |
63 | + weEvt.stopPropagation(); | |
64 | + return false; | |
65 | + } | |
66 | + | |
67 | + function _isInputElement(el){ | |
68 | + return (["INPUT", "TEXTAREA"].indexOf(el.tagName.toUpperCase()) > -1); | |
69 | + } | |
70 | + | |
71 | + function _onSubmitForm(weEvt){ | |
72 | + $.ajax(htElement.welForm.attr("action"), { | |
73 | + "type": "post", | |
74 | + "dataType": "json", | |
75 | + "data": { | |
76 | + "loginId" : htElement.welInputId.val(), | |
77 | + "password": htElement.welInputPw.val() | |
78 | + } | |
79 | + }).done(function(){ | |
80 | + document.location.reload(); | |
81 | + }).fail(function(htResult){ | |
82 | + _showDialogError(Messages(htResult.message)); | |
83 | + }); | |
84 | + | |
85 | + weEvt.preventDefault(); | |
86 | + weEvt.stopPropagation(); | |
87 | + return false; | |
88 | + } | |
89 | + | |
90 | + function _showDialogError(sMessage){ | |
91 | + htElement.welLoginErrorMsg.html(sMessage); | |
92 | + htElement.welLoginError.show(); | |
93 | + htElement.welDialog.effect("shake", {"distance": 2}, 200); | |
94 | + htElement.welInputId.focus(); | |
95 | + } | |
96 | + | |
97 | + _init(); | |
98 | +}); |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?