✅ 상품등록
- 상품등록의 UI는 다음과 같이 구현하였다
위의 UI 부분에서 가장 어려웠던 사진등록 UI부분 + 카테고리 + 검색태그등록 UI + 가격 정규표현식
에 대해서만 작성해보겠다
<img src="<%=request.getContextPath()%>/images/productregist/imgregist.png"
class="upload" width="250px" height="250px">
<input type="file" id="inputFile" class="real-upload"
accept="image/*" required multiple style="display: none;">
<!-- multiple -> 사용자가 둘 이상의 값을 입력할 수 있음을 명시 -->
<!-- email, file 타입에만 적용 가능함 -->
<div class="cate">
<h4 class="h4Size">카테고리 *</h4>
<select class="mainCate" onchange="chageSubCate(this.value);">
<!-- this.value -> 선택된 option의 밸류값을 매개변수로 넣음 -->
<%
<!-- categorys는 getAttribute를 통해서 디비안의 값들을 갖고온다 -->
if (!categorys.isEmpty()) {
for (int i = 0; i < categorys.size(); i++) {
%>
<option value="<%=categorys.get(i).getCategoryId()%>"><%=categorys.get(i).getCategoryName()%></option>
<%
}
}
%>
</select>
<select class="middleCate" name="subCate">
<!-- 이안의 내용은 서브카테고리의 내용들이 출력된다 -->
</select>
// 카테고리 선택하는 작업
$(() => {
$(".mainCate").trigger("change", $(".mainCate:selected").val()); // 페이지로딩되었을때, 자동으로 change 함수 실행
// 대상값은 현재 그 select에 선택된 값
})
function chageSubCate(value) {
console.log(value);
$.ajax({
url: "findSubCate",
data: { "cateId": value },
success: function(result) {
const subCate = result.split(","); // 문자열로 넘어온 값들을 ,를 구분자로 배열을 만듬
$(".middleCate option").remove(); // 메인카테고리 선택할때마다 옵션들 다 삭제(초기화)
for (let i = 0; i < subCate.length; i++) { // 배열에 있는 서브카테고리들을 option value로 만들어줌
var option = $("<option value=" + subCate[i] + ">" + subCate[i] + "</option>");
$(".middleCate").append(option);
}
},
error: function() {
console.log("카테고리 선택 오류발생");
}
})
}
컨트롤러
// findSubCate 서블릿
String cateId = request.getParameter("cateId");
List<String> subCategorys = new ProductRegistService().selectSubCate(cateId); // 서브카테고리들이 리스트로 나옴
// 리스트안에 문자열 형식으로 받아야함
String result = subCategorys.stream().map(n->String.valueOf(n)).collect(Collectors.joining(","));
// 리스트를 -> 문자열로 만들어줌 (문자열로 만들어줄때 앞뒤로 공백을 없애고 ,로 구분해줌)
// [컴퓨터, 노트북, 스마트폰, 소프트웨어, 기타 주변기기] -> 컴퓨터,노트북,스마트폰,소프트웨어,기타 주변기기
Mapper
<select id="selectSubCate" resultType="string" parameterType="string">
SELECT SUBCATEGORY_NAME FROM SUBCATEGORY WHERE CATEGORY_ID = #{cateId}
</select> <!--반환타입 : 문자열, 매개변수 타입 : 문자열 -->
<!-- 타입을 정수형으로 할 경우 -> _int -->
,
로 숫자를 구별해줌<input type="text" id="priceId" oninput="inputNumberFormat(this);"
placeholder="숫자만 입력해주세요." name="price">
<!-- oninput 속성을 통해 정규표현식을 적용하였다 -->
// ==== 가격 입력했을 때, 숫자만입력되고, 3자리수마다 ,로 구분해주는 작업
function comma(str) {
str = String(str);
return str.replace(/(\d)(?=(?:\d{3})+(?!\d))/g, "$1,");
}
function uncomma(str) {
str = String(str);
return str.replace(/[^\d]+/g, "");
}
function inputNumberFormat(obj) {
obj.value = comma(uncomma(obj.value));
}
// 가격이 입력될때마다 예외처리
const priceValue = document.getElementById("priceId")
const spanPrice = $("#spanPrice");
priceValue.addEventListener("keyup", function() {
if (priceValue.value.length == 0) {
spanPrice.text("");
checkProductRegist.productPrice=false;
}else{
replacePrice = priceValue.value.replace(",","");
if(replacePrice <= 0){
spanPrice.text("0원보다 크게 입력하세요").css("color","red");
checkProductRegist.productPrice=false;
}else{
spanPrice.text("○").css("color","green");
checkProductRegist.productPrice=true;
}
}
});
<div class="relativeTag">
<h4 class="h4Size">
상품태그
</h4>
<input type="text" id="searchTag" placeholder="연관 태그를 입력해주세요" autocomplete="on">
<div id="relativeTagDiv">
<!-- 태그들이 등록되는 곳 -->
</div>
</div>
<div class="autocomplete"><!-- 자동완성 검색어들이 나오는 곳 --></div>
// 상품태그 검색 관련 js
const dataList = ["넣을값"];
let registTagList = [];
const $searchTag = document.querySelector("#searchTag");
const $autoComplete = document.querySelector(".autocomplete");
let nowIndex = 0;
let matchDataList;
$searchTag.onkeyup = (event) => {
// 검색어
const value = $searchTag.value.trim();
// 자동완성 필터링
matchDataList = value
? dataList.filter((label) => label.includes(value))
: [];
switch (event.keyCode) {
// UP KEY
case 38:
nowIndex = Math.max(nowIndex - 1, 0);
break;
// DOWN KEY
case 40:
nowIndex = Math.min(nowIndex + 1, matchDataList.length - 1);
// document.querySelector("#searchTag").value = matchDataList[nowIndex];
break;
// 관련검색어에 엔터키 눌렀을 때 (태그만들어지는 과정)
case 13:
document.querySelector("#searchTag").value = matchDataList[nowIndex] || "";
const key = document.getElementById("searchTag").value;
const aa = document.getElementById("registTag>li label");
if ((key != "") && (!registTagList.includes(key))) { // 검색키가 없거나, 중복값이 있으면 추가 x
if (registTagList.length >= 5) {
alert("태그는 최대 5개까지만 추가 가능합니다.");
document.querySelector("#searchTag").value = ""; // 연관검색창 닫기
break;
}
registTagList.push(key); // 태그가 5개미만일경우 추가
const $li = document.createElement("li");
document.getElementById("relativeTagDiv").appendChild($li);
const $button1 = document.createElement("label");
const $button2 = document.createElement("button");
const $img = document.createElement("img"); // img 태그 생성
$img.height = "15";
$img.width = "15";
$img.src = context + "/images/productregist/xbtn.png";
$img.addEventListener("click", e => { // 해당 이미지 클릭시
for (let i = 0; i < registTagList.length; i++) { // 저장해놓은 키워드배열에서 값 삭제하고 개수 줄임
if (registTagList[i] == e.target.parentElement.previousElementSibling.innerHTML) {
registTagList.splice(i, 1);
break;
}
}
$(e.target).parent().parent().remove(); // li밑 label+button 밑 img까지 삭제
});
$button2.appendChild($img);
$button1.innerHTML = key;
$li.appendChild($button1);
$li.appendChild($button2);
var input1 = document.createElement('input');
input1.setAttribute("type", "hidden");
input1.setAttribute("name", "data1");
input1.setAttribute("value", key);
$li.appendChild(input1);
}
// 초기화
nowIndex = 0;
matchDataList.length = 0;
document.querySelector("#searchTag").value = ""; // 연관검색창 닫기
break;
case 27: // esc 눌렀을때 입력창 초기화 및 관련검색어 창 닫기
document.querySelector("#searchTag").value = "";
matchDataList.length = 0;
// 그외 다시 초기화
default:
nowIndex = 0;
break;
}
// 리스트 보여주기
showList(matchDataList, value, nowIndex);
};
const showList = (data, value, nowIndex) => {
// 정규식으로 변환
const regex = new RegExp(`(${value})`, "g");
$autoComplete.innerHTML = data
.map(
(label, index) => `
<div class='${nowIndex === index ? "active" : ""}'>
${label.replace(regex, "<label>$1</label>")}
</div>
`
)
.join("");
};
$autoComplete.addEventListener("click", e => { // 관련검색어 클릭했을경우
let clickAnswer = e.target.innerHTML.trim();
for (let i = 0; i <= 10; i++) {
clickAnswer = clickAnswer.replace("<label>", "");
clickAnswer = clickAnswer.replace("</label>", "");
}
document.querySelector("#searchTag").value = clickAnswer;
const key = document.getElementById("searchTag").value;
const aa = document.getElementById("registTag>li label");
if (registTagList.length >= 5) {
alert("태그는 최대 5개까지만 추가 가능합니다.");
document.querySelector("#searchTag").value = ""; // 연관검색창 닫기
return;
}
if ((key != "") && (!registTagList.includes(key))) {
registTagList.push(key);
const $li = document.createElement("li");
document.getElementById("relativeTagDiv").appendChild($li);
const $button1 = document.createElement("label");
const $button2 = document.createElement("button");
/*const $button2 = $("<button>").css({"border":"none", "background-color":"transparent"});
*/
const $img = document.createElement("img");
$img.height = "15";
$img.width = "15";
$img.src = context + "/images/productregist/xbtn.png";
$button2.appendChild($img);
$button1.innerHTML = key;
$li.appendChild($button1);
$li.appendChild($button2);
var input1 = document.createElement('input');
input1.setAttribute("type", "hidden");
input1.setAttribute("name", "data1");
input1.setAttribute("value", key);
$li.appendChild(input1);
$img.addEventListener("click", e => { // 해당 이미지 클릭시
$(e.target).parent().parent().remove(); // li밑 label+button 밑 img까지 삭제
for (let i = 0; i < registTagList.length; i++) { // 저장해놓은 키워드배열에서 값 삭제하고 개수 줄임
if (registTagList[i] == e.target.parentElement.previousElementSibling.innerHTML) {
registTagList.splice(i, 1);
break;
}
}
});
document.querySelector("#searchTag").dispatchEvent(new KeyboardEvent("keyup", { keyCode: 13 }));
// 관련검색어 마우스로 클릭했을 때 엔터효과 한번 발생
$("#searchTag").val(""); // 그 후 input창 비워줌 document.querySelector("#searchTag").value ="";
$("#searchTag").focus(); // 그 input창에 focus 줌 document.getElementById("searchTag").focus();
}
})
/* 관련검색어에 #키 입력 못하도록 설정*/
$(document).ready(function() {
$("#searchTag").keypress(function(e) {
if (event.key == '#') {
e.preventDefault();
e.returnValue = false;
}
});
});
/*=============================*/
function productRegist() { // 상품등록 버튼 클릭됬을 때,
if(checkProductRegist.productTitle && checkProductRegist.productPrice && checkProductRegist.productExplan
&& checkProductRegist.productImg){ // 입력칸들이 다 true여야만 상품등록가능함
}else{
console.log("다 입력해라")
return;
}
const form = new FormData(); // form 객체에 입력한 값들을 먼저 다 추가함
form.append("title", $(".inputTitle").val());
form.append("subCate", $(".middleCate").val());
form.append("place", $("#sample6_address").val());
form.append("state", $("input[name=state]:checked").val());
form.append("price", $("#priceId").val())
form.append("explan", $("#explanId").val())
let tag="";
$("input[name=data1]").each((i,element)=>{
// jquery로 해당 선택자로 값을 가져옴 .each(i,elemnet) 번호와,
// -> 해당 데이터들의 인덱스 해당 값을 가져옴 (상품태그들)
if(i!=0) tag+=",";
tag+=element.value;
})
form.append("tag",tag);
const files= dataTransfer.files; // 올린 이미지 파일값들
$.each(files,(index,file)=>{
form.append("upfile"+index,file);
});
$.ajax({
url: "productRegistEnd.do", // 해당 서블릿으로 ajax로 요청
data: form, // 저정한 form 객체를 데이터로 보냄
processData:false, // 멀티파트폼으로 보내기위해서 설정
contentType:false, // 멀티파트폼으로 보내기위해서 설정
type:"post",
success: function(result) {
if(result==1) { // db는 결과값이 정수로 나옴 // 입력성공
alert("등록 성공");
location.replace("http://localhost:9090/semi-hifive/");
}else{
alert("등록 실패");
location.replace("http://localhost:9090/semi-hifive/"+"productRegist.do");
}
},
error: function() {
alert("오류발생");
location.replace("http://localhost:9090/semi-hifive/"+"productRegist.do");
}
})
}
HttpSession session = request.getSession();
String path=getServletContext().getRealPath("/upload/productRegist"); // -> /upload/productRegist 안에다 업로드되는 이미지 넣음
System.out.println(path);
MultipartRequest mr= new MultipartRequest(request, path, 1024*1024*60,"UTF-8",new DefaultFileRenamePolicy());
// MultipartRequest 객체 사용하려면, 이 서블릿을 요청시킨 form태그에 enctype="multipart/form-data" 를 넣어야함
String replacePrice = mr.getParameter("price");
replacePrice = replacePrice.replace(",",""); // ,있는 돈 문자열을 ,를 ""로 대체함
Member m = (Member) session.getAttribute("loginMember");// 세션에서 현재로그인한 정보 갖고옴
String productId = mr.getParameter("productId");
String userId = m.getUserId();
String title = mr.getParameter("title");
String subCate = mr.getParameter("subCate");
String place = mr.getParameter("place");
String state = mr.getParameter("state");
int price = Integer.parseInt(replacePrice); // 정수일경우 앞에 Integer.parseInt 로 형변환해야함
String explan = mr.getParameter("explan");
String tag = mr.getParameter("tag");
List<ProductFile> files= new ArrayList();
Enumeration<String> names=mr.getFileNames(); // 해당 파일객체들의 키값을 하나씩 출력 (Enumeration -> 열거객체 순환)
while(names.hasMoreElements()) { // 다중 파일들의 이미지를 접근 가능
String key=names.nextElement(); // key-> 해당 파일은 객체로 저장되잇음 각각 파일의 키를 저장함
// mr.getFilesystemName(key)); new 파일
// mr.getOriginalFileName(key)); ori파일
files.add(ProductFile.builder().imageName(mr.getFilesystemName(key)).build());
// ProductFile 객체 안에 멤버변수들을 builder를 통해서 각각 넣어줌 (파일이름, 등등)
// builder 함수 사용시 마지막에 .build() 해줘야함
}
Product p = Product.builder().title(title).productStatus(state).price(price).explanation(explan)
.keyword(tag).areaName(place).subCategoryName(subCate).files(files).build();
// Product 객체 안에 멤버변수들을 builder를 통해서 넣어줌 (마지막에는 files 멤버변수도 builder를 통해서 해당맞는 타입의 값을 넣어줌)
// Product 클래스안에 결국 ProudctFile 값들도 들어있는것임
// 그러기 때문에 Product만 객체만 넣어줘도 됨
int result = new ProductRegistService().insertProduct(p,userId); // 상품등록 및 상품이미지첨부파일 데이터 추가 하는 작업
response.getWriter().print(result); // 해당 반환되는 0 또는 1의 값을 다시 js로 반환됨 (ajax이기 때문에 해줘야함)-> js에서 정수 값을 통해 분기처리