前言
本文将優化 SPA 前端頁面邏輯,
- 登入成功後,Login 菜單轉變為 Logout;
- 點選 Logout 後,Member 頁面也要退出,還原登入前的頁面内容
優化前端頁面
之前的文章為了友善用了靜态方式編寫,為了項目日後複雜度提升的時候,能有更好的相容和實施效率,優化代碼讓項目更有實施彈性。
頭部菜單以 Unordered List 實作
首先,修改 index.html <header><nav>。增加 nav id 及把原先的<div>内容更換成 <ul></ul>
<header>
<nav id="headerNavigationMenu">
<ul></ul>
</nav>
</header>
把原來 index.css 關于 #headerNavigation div 相關的 css 全部删掉,換成如下:
#headerNavigationMenu ul{
float: right;
margin: 0; padding: 0;
margin-right: 20px;
cursor: pointer;
font-weight:700;
color:#555555ee;
}
#headerNavigationMenu ul li, #headerNavigationMenu ul span{
list-style: none;
margin: 0; padding: 0;
float: left;
}
#headerNavigationMenu ul li.selected{
font-weight: 700;
color:#f36c01ee;
}
#headerNavigationMenu ul li.onHover{
font-weight: 700;
color:#000000;
}
最後在 index.js 裡面作一下改動。因為修改的代碼部分有點多,這裡直接上整份 index.js 内容。可以自行比對一下:
$(function() {
// 定義預設内容頁面及菜單
const defaultPage = "Login"
// 定義目前標明的菜單ID
let activeNavId = defaultPage
var navIds = {}
navIds.navHomeLink = "Home"
navIds.navAboutUsLink = "AboutUs"
navIds.navLoginLink = "Login"
navIds.navMemberLink = "Member"
// build a navigation menu link array
// to filter out false click events from $(document).click(function(e) {})
// if user not clicking on navigation menu, no change from displaying page
let navItems = []
for (let key in navIds) {
navItems.push(key)
}
for(let key in navIds){
$('#headerNavigationMenu ul').append("<li id='"+key+"'>"+navIds[key]+"</li>")
}
$('#headerNavigationMenu ul li').each(function() {
_this = $(this)
console.log("_this: "+_this.attr('id'))
for (let key in navIds) {
if (_this.attr('id') == key) {
_this.attr('id', key)
if(_this.attr('id') != $('#headerNavigationMenu ul li:last-child').attr('id')) {
_this.after("<span> | </span>")
}
if(_this.attr('id') == "navMemberLink") {
console.log("true")
_this.append(" <i class='bi bi-person-circle'></i>")
}
}
}
// console.log("$('#headerNavigationMenu ul li:last-child') - attr('id'): "+$('#headerNavigationMenu ul li:last-child').attr('id'))
})
// 初始化頁面
updateObjectDisplayStatus(navIds,defaultPage)
$(document).click(function(e) {
let clickedElementId = $(e.target).attr('id')
// console.log('clickedELementId: '+clickedElementId)
// console.log('navItems.includes(clickedElementId: '+navItems.includes(clickedElementId))
headerNavigationHover()
// menu only response when user clicked on the header navigation bar AND
// if the menu items is not being selected already
if (navItems.includes(clickedElementId) && (navIds[clickedElementId] != activeNavId)) {
contentTransition(navIds[clickedElementId], activeNavId)
updateObjectDisplayStatus(navIds,navIds[clickedElementId])
activeNavId = navIds[clickedElementId]
}
})
function updateObjectDisplayStatus(navIds, contentId) {
for (let key in navIds) {
//console.log(key+": "+navIds[key])
if (navIds[key] == contentId) {
console.log("clicked "+contentId)
$('#'+contentId).css("display","block")
$('#'+key).removeClass().addClass('selected')
} else {
$('#'+navIds[key]).css("display","none")
$('#'+key).removeClass()
}
}
}
function contentTransition(fadeInComponent, fadeOutComponent) {
$('#'+fadeInComponent).fadeIn()
$('#'+fadeOutComponent).fadeOut()
}
function headerNavigationHover() {
for (let key in navIds) {
$('#'+key).hover(function() {
if (activeNavId != navIds[key]) {
$("#"+key).addClass('onHover')
}
}, function() {
if (activeNavId != navIds[key]) {
$("#"+key).removeClass()
}
})
}
}
// ***********************************
// Submit Login Form
// ***********************************
$('#ssoLoginForm').submit(function(e) {
e.preventDefault()
e.stopPropagation()
$("#loginResultMessage").css("display","none")
let ssoLoginForm = $('#ssoLoginForm')
let ssoLoginFormObject = {}
$('#ssoLoginForm :input').each(function() {
if(this.id === "submitButton") {
// continue
} else {
ssoLoginFormObject[this.id] = this.value
}
})
console.log(JSON.stringify(ssoLoginFormObject))
$.ajax({
type: ssoLoginForm.attr('method'),
url: ssoLoginForm.attr('action'),
contentType: "application/json",
data: JSON.stringify(ssoLoginFormObject),
success: function(data) {
if (data.code === 200) {
console.log("data to F/E: "+ JSON.stringify(data))
console.log("login status: "+data.objectToFE.status)
contentTransition("Member", activeNavId)
updateObjectDisplayStatus(navIds, "Member")
activeNavId = "Member"
$('#Member h1').html(data.objectToFE.status)
} else if (data.code === 401 || data.code === 404) {
$('#loginResultMessage').fadeIn();
$('#loginResultMessage').html(data.description);
$('#loginResultMessage').css("display","block");
}
console.log("JSON.stringify(data): "+JSON.stringify(data))
},
error: function(e) {
console.log("FAILED: JSON.stringify(e): "+JSON.stringify(e));
}
})
})
})
新的 index.js 重複代碼的問題都解決了,實施彈性比之前高很多。
後端部分代碼不用改。啟動後端應用和確定 Nginx 反向代理運作正常後,重新測試一下 SPA 看看修改後的樣子和功能有沒有問題。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLzMTZ0YzY5kDMhZTZ4QzM5M2M0QTZwYmN5IWM4UWMihzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
測試全部通過,完成優化。
後續更新内容
SPA 加入登入狀态 (session id)及相關更新。
區分登入前後狀态
登入狀态由兩個部分組成:
- 後端發送過來的 session id
- 後端發送 session id 到前端 的 timestamp
源代碼
關注我并發表不少于10字評論可以擷取本文源代碼。
碼農經典語錄
Linus Torvalds
Talk is cheap, show me the code.
蜂窩碼農
- DRY Principle (Don’t Repeat Yourself) 是做技術的最大笑話, DRY Principle應該改成 DORY Principle (Do Repeat Yourself)才對
- 沒有中國參與的标準,不能稱為世界标準*。
俗語
好讀書、好記性不如爛筆頭