https://developer.mozilla.org/ko/docs/Web/API/Document/visibilityState#%EB%AA%85%EC%84%B8%EC%84%9C

 

Document.visibilityState - Web API | MDN

Document.visibilityState 읽기 전용 property로, 이 element가 현재 표시된 컨텍스트를 나타내는 document의 가시성을 반환합니다. document가 background 또는 보이지 않는 탭(다른 탭)에 있는지, 또는 pre-rendering을

developer.mozilla.org

document.addEventListener("visibilitychange", () => {
  console.log(document.visibilityState);
  // 기타 동작...
});

위 이벤트를 이용해서 브라우저가 "hidden" or "visible" 되었는지 확인 가능 (상태가 변경될때마다 감지된다)

 

포커스로 확인하는 코드는 아래와 같다.

window.addEventListener('focus', function() {
    console.log('사용자가 웹페이지에 돌아왔습니다.')
}, false);​

 

ajax 처리를 위해 코드를 작성하였는데 아래와 같은 에러 코드가 발생하였다.

$.ajax({
    url:"/login/login",
    type:"POST",
    data:JSON.stringify(params),
    contentType: "application/json",
    success: function(result) {
        if (result) {
            alert("저장되었습니다.");
        } else {
            alert("잠시 후에 시도해주세요.");
        }
    },
    error: function() {
        alert("에러 발생");
    }
})

 

 

원인: jquery 선언을 위에 안해주었기 때문에 발생

 

jquery cdn을 검색하여 코드에 추가해준다.

https://releases.jquery.com/

 

jQuery CDN

The integrity and crossorigin attributes are used for Subresource Integrity (SRI) checking. This allows browsers to ensure that resources hosted on third-party servers have not been tampered with. Use of SRI is recommended as a best-practice, whenever libr

releases.jquery.com

추가 후 에러가 발생하지 않는다.

입력란 생성

<td class="last">
<span class="td_type06"><input type="text" name="from_date" value="${baseMap.from_date }" class="date" onkeyup="enterkey()"/></span>
~
<span class="td_type06"><input type="text" name="to_date" value="${baseMap.to_date }" class="date" onkeyup="enterkey()"/></span>
</td>

 

초기화 버튼 및  한글 표시, 해당 input type 속성을 readonly로 설정하기 위해 브라우저를 실행하자 마자 아래 코드 동작

$(function() {

	window.onload = function cleanDatepicker(){
		//alert('aaaa');
        // clear 버튼 생성
	     var old_fn = $.datepicker._updateDatepicker;

		 $.datepicker._updateDatepicker = function(inst) {
		   old_fn.call(this, inst);

		   var buttonPane = $(this).datepicker("widget").find(".ui-datepicker-buttonpane");

		   $("<button type='button' class='ui-datepicker-clean ui-state-default ui-priority-primary ui-corner-all'>clear</button>").appendTo(buttonPane).click(function(ev) {
		    $.datepicker._clearDate(inst.input);
		   }) ;
		}
		
        // 한글 표시
		$.datepicker.setDefaults({
		        dateFormat: 'yymmdd',
		        prevText: '이전 달',
		        nextText: '다음 달',
		        monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
		        monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
		        dayNames: ['일', '월', '화', '수', '목', '금', '토'],
		        dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
		        dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'],
		        showMonthAfterYear: true,
		        yearSuffix: '년'
		});
		
        // 읽기 전용으로 변환
		$(".date").attr("readonly",true);

	}
    
    // input에 datepicker 적용
    $(".date").datepicker({
		dateFormat: 'yy-mm-dd',
		showButtonPanel: true
	});
}

 

HTML의 각 태그 데이터를 선택자를 이용하여 변수에 담은 후 배열에 저장한다.

그 후 ajax를 통해 배열 데이터를 전송

function order(flag) {
		var isAdmin = <c:out value="${isMasterAdmin }" />;
		var url = "/dit/apps/array123.nr";
		var num_check=/^[0-9]*$/;
		var data = new Object();
		var rowData = new Array(); 
	    var tdArr = new Array();
	    var checkbox = $("input[name=select]:checked");
	    
	    // 인수확인버튼 클릭할때에만 메일번호 생성
	   /* if(flag == "a2"){
	    	var mailNum = get_MailNum();
	    }*/
	    
	    //alert('메일번호: ' + mailNum)
//	    alert('checkbox: ' + checkbox);
	 // 체크된 체크박스 값을 가져온다
	    checkbox.each(function(i) {
	    	//alert('aaa: ' + i);
		    // checkbox.parent() : checkbox의 부모는 <td>이다.
		    // checkbox.parent().parent() : <td>의 부모이므로 <tr>이다.
		    var tr = checkbox.parent().parent().eq(i);
		    var td = tr.children();
		                
		    // 체크된 row의 모든 값을 배열에 담는다.
		    rowData.push(tr.text());
		                
		    // td.eq(0)은 체크박스 이므로  td.eq(1)의 값부터 가져온다.
		    var req_id = td.eq(1).text();
		    var vendor_id = td.eq(2).text();
		    var company_reg_no = td.eq(3).text();		    
		    var req_num = td.eq(4).text();
		    var company_name = td.eq(5).text();
		    var item_id = td.eq(6).text();
		    var item_desc = td.eq(7).text();
		    var req_quantity = td.eq(8).text();
		    var ship_method = td.eq(9).find('input[type="text"]').val();
		    var ship_quantity = td.eq(10).find('input[type="number"]').val();
		    var take_quantity = td.eq(11).find('input[type="number"]').val();
		    var req_date = td.eq(12).text();
		    var take_check = td.eq(13).text();
		    var status_ori = td.eq(14).text();
		    var status = td.eq(14).text();
		    
		    if (status == '작성중'){
		    	status = "01";	
		    } else if (status == '주문'){
		    	status = "02";	
		    } else if (status == '발송'){
		    	status = "03";	
		    } else if (status == '인수확인'){
		    	status = "04";	
		    } else if (status == '취소'){
		    	status = "05";	
		    } else if (status == '최종확인'){
		    	status = "06";	
		    }
		    
		    if (flag == "a0"){
		    	status = "02";	
		    } else if(flag == "a1"){
		    	status = "03";	
		    } else if (flag == "a2") {
		    	take_check = "Y";	
		    	status = "04";	
		    } else if (flag == "a3") {
		    	take_check = "C";	
		    	status = "05";	
		    } else if (flag == "a4") {
		    	take_check = "Y";	
		    	status = "06";	
		    }
		    
		    
		                
		    // 가져온 값을 배열에 담는다.
		    var allData = {"req_num": req_num, "company_name" : company_name, "item_id" : item_id
		    		      ,"item_desc" : item_desc, "req_quantity" : req_quantity, "ship_method" : ship_method
		    		      ,"ship_quantity" : ship_quantity, "take_quantity" : take_quantity, "req_date" : req_date
		    		      ,"take_check" : take_check, "status" : status, "req_id": req_id, "vendor_id": vendor_id, "company_reg_no": company_reg_no
		    		      ,"attribute2": '11'};
		    

		    if(td.eq(1).text() != '요청번호'){
			    // 관리자일 경우 어떤상태에서든지 이벤트 가능
			    if(isAdmin){
			    	
					$.ajax({
						 type: 'post'
					    ,data: allData
					    ,url: url
					    ,dataType: "json"
						,async: false
						,success: function(data, status){
						   num = data;
						}
					});
			    // 관리자가 아니면 발송버튼은 주문상태에서만, 확인버튼은 발송상태에서만 가능
			    } else {
			    	// 주문버튼 -> 주문
			    	if(flag == "a0"){
					    if(status_ori == "작성중"){
					    		
						    $.ajax({
								type: 'post'
							   ,data: allData
							   ,url: url
							   ,dataType: "json"
							   ,async: false
							   ,success: function(data, status){
								   num = data;
							   }
							});
					    } else {
					    	alert("요청번호: " + req_num + "은 진행상태가 작성중상태가 아닙니다.");
					    }
					 // 발송버튼 -> 발송    
					}else if(flag == "a1"){
					    if(status_ori == "주문"){
					    		
						    $.ajax({
								type: 'post'
							   ,data: allData
							   ,url: url
							   ,dataType: "json"
							   ,async: false
							   ,success: function(data, status){
								   num = data;
							   }
							});
					    } else {
					    	alert("요청번호: " + req_num + "은 진행상태가 주문상태가 아닙니다.");
					    }
					 // 인수버튼 -> 인수확인    
					}else if(flag == "a2"){
					   	if(status_ori == "발송"){
					    		
							 $.ajax({
								type: 'post'
							   ,data: allData
							   ,url: url
							   ,dataType: "json"
							   ,async: false
							   ,success: function(data, status){
								   num = data;
							   }
							});
					    }else{
					    	alert("요청번호: " + req_num + "은 진행상태가 발송상태가 아닙니다.");
					    }
					 // 취소버튼 -> 취소
					}else if(flag == "a3"){
					   	
				    	// 취소상태는 언제든 취소가능	
						$.ajax({
							type: 'post'
						   ,data: allData
						   ,url: url
						   ,dataType: "json"
						   ,async: false
						   ,success: function(data, status){
						       num = data;
						   }
					    });
					// 발송버튼 -> 최종확인  
			       }else if(flag == "a4"){
					   	if(status_ori == "인수확인"){
				    		
							 $.ajax({
								type: 'post'
							   ,data: allData
							   ,url: url
							   ,dataType: "json"
							   ,async: false
							   ,success: function(data, status){
								   num = data;
							   }
							});
					    }else{
					    	alert("요청번호: " + req_num + "은 진행상태가 인수확인상태가 아닙니다.");
					    }
			       }

		    }
	    
		    }
		    
	    });
	 /*
	   if(flag == "a2") {
		   url = "/dit/apps/ReqMat/mail.nr";
		   var TakeMailData = {"key": mailNum, "flag": flag};
		   $.ajax({
				 type: 'post'
			    ,data: TakeMailData
			    ,url: url
			    ,dataType: "json"
				,async: false
				,success: function(data, status){
				   num = data;
				}
			});   
	   } 
	 */
	   movePage(); /* 재조회 */
	 
	}

 

controller에서 VO객체를 이용하여 배열로 전송된 데이터를 받는다.

@SuppressWarnings({ "rawtypes", "unchecked" })
		@RequestMapping(value = "/dit/apps/array123.nr")
		@ResponseBody
		public void ProductMail(HttpServletRequest request, Model model, ReqMaterialVO vo) throws Exception {
			logger.debug("## 체크리스트");
			LoginoutUserEntity userInfo = SessionManager.getLoginoutUser(request);
			logger.debug(userInfo.getUserLoginID());
			//QueryCondition qc = new QueryCondition(param);
			//logger.debug((String) param.get("allData"));
			//logger.debug("no: " + no);
			logger.debug(vo.getReq_id());
			logger.debug(vo.getReq_num());
			logger.debug(vo.getItem_desc());
			logger.debug(vo.getShip_method());
			logger.debug(vo.getShip_quantity());
			logger.debug(vo.getAttribute2()); // 메일 묶음 키 값
			logger.debug(vo.toString());
			vo.setCreated_by(userInfo.getUserLoginID());
			vo.setLast_updated_by(userInfo.getUserLoginID());
			logger.debug("최종: " + vo.toString());
			//vo.setStatus("03"); // 발송상태로 변경
			int result = 0;
			result = materialService.reqMaterialUpdate(vo);
			
			// 메일 전송을 위해 데이터 저장
			materialService.reqMaterialMail(vo);
			
			//logger.debug("arrayParams: " + arrayParams);
			//Object a = param.get("data");
			//System.out.println("aaa: " + a.toString());
			//List<Map<String, Object>> list = new ArrayList();
			//list.addAll("data",(String)param.get("data"));
			//logger.debug(list.get(0).toString());
			
		}

 

참고 사이트

https://itkjspo56.tistory.com/181

 

[Java] Array 배열 Controller VO로 받기 파싱

자바를 이용하면서 MVC패턴을 통해 Controller에서 데이터를 주고받는일이 있다. 기존 JSP에서는 제목 ${testVO.title} 내용 ${testVO.content} 이름 ${testVO.name} 번호 ${testVO.bno} 이러한 형태로 데이터를..

itkjspo56.tistory.com

 

VO방식 말고도 map으로 받는 방법도 있는것으로 보인다.

https://yulfsong.tistory.com/76

 

Ajax data를 Controller에서 받는 두 가지 방법 : Vo / Map

Ajax의 data를 Controller에서 받아야할 때가 자주 있는데 Vo를 만들어서 받는 방법이 있고 Map을 이용하는 방법이 있다. 1. Vo로 받는 방법 var memberId = $("#memberId").val(); var memberPass = $("#memberPa..

yulfsong.tistory.com

 

<c:if test="${empty dataList.SHIP_QUANTITY}"> <!-- 초기값 설정 -->
  <td><input name="SHIP_QUANTITY" value="0" type="number" 
	  <c:if test="${! isEmployee || dataList.STATUS != '주문'}">readonly </c:if>>
  </td>  
</c:if>

 

input 태그 안에 <c:if> 문을 중첩하여 사용하면 input 태그 안에 readonly 속성을 상황에 맞게 조절 할 수 있다.

 

이와 같이 select 태그도 설정 가능하다 select 태그는 readonly 속성이 없기 때문에 disabled 속성을 사용한다.

<select name="ship_method" style="width: 200px; height: 30px;" 
   <c:if test="${! isEmployee || dataList.STATUS != '주문'}"> disabled="disabled" </c:if>>
		<option value=""></option>									     
		<option value="직송" <c:if test= "${dataList.SHIP_METHOD == '직송' }">selected</c:if>>직송</option>
		<option value="안양" <c:if test= "${dataList.SHIP_METHOD == '안양' }">selected</c:if>>안양</option>
</select>

store -> movie.js 파일 테스트

 

관련 공식문서

https://jestjs.io/docs/mock-function-api

 

Mock Functions · Jest

Mock functions are also known as "spies", because they let you spy on the behavior of a function that is called indirectly by some other code, rather than only testing the output. You can create a mock function with jest.fn(). If no implementation is given

jestjs.io

 

테스트를 진행할 코드

import axios from 'axios'
//  중복제거을 위해서 uniqBy를 사용
import _uniqBy from 'lodash/uniqBy'

const _defaultMessage = 'Search for the movie title!'

export default {
  // module!
  namespaced: true,
  //data!
  state: () => ({
    movies: [],
    message: _defaultMessage,
    loading: false,
    theMovie: {}
  }),
  // computed!
  getters: {
    // 실제 데이터를 계산해서 새로운 데이터 형식으로 반영할때 사용
    
  },
  // methods!
  // 변이: 관리하는 데이터를 변경시켜줄수 있다. 다른 메소드에서 변경할 수 없다.
  mutations: {
    // ['movies', 'message', 'loading']
    updateState(state, payload) {
      Object.keys(payload).forEach(key => {
        state[key] = payload[key]
        
      })
    },
    // assignMovies (state, Search){
    //   state.movies = Search
    // },
    // 화면 이동시 초기화 설정
    resetMovies(state) {
      state.movies = []
      state.message = _defaultMessage
      state.loading = false
    }
  },
  // 비동기로 동작한다.
  actions: {
    async searchMovies({state, commit}, payload) {

      // 영화검색을 동시에 동작하는 것을 방지
      if(state.loading) {
        return
      }

      commit('updateState', {
        message: '',
        loading: true
        // 검색된 imdbID 데이터의 중복을 제거
        // movies: _uniqBy(Search,'imdbID')
        // message: 'Hello world',
        // loading: true
      })
      try {
                // const { title, type, number, year } = payload
        // const OMDB_API_KEY = '7035c60c'
        // http -> https 로 변경
        //const res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`);
        const res = await _fetchMovie({
          // 전개연산자 사용
          ...payload,
          page: 1
        })
         console.log(res);
        const { Search, totalResults } = res.data
        commit('updateState', {
          // 검색된 imdbID 데이터의 중복을 제거
          movies: _uniqBy(Search,'imdbID')
          // message: 'Hello world',
          // loading: true
        })
        
        console.log(totalResults) // 261
        console.log(typeof totalResults) // string

        const total = parseInt(totalResults, 10)
        const pageLength = Math.ceil(total / 10)

        // 추가 요청!
        if (pageLength > 1) {
          for (let page = 2; page <= pageLength; page += 1) {
            if (page > payload.number / 10) {
              // 반복문 종료
              // 사용자가 지정한 갯수만큼 보여주도록 설정
              break;
            }
            //const res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`);
            const res = await _fetchMovie({
              ...payload,
              page: page
            })
            const {Search} = res.data
            commit('updateState', {
              // _uniqBy 를 이용해서 중복 제거
              movies: [
                ...state.movies,
                ..._uniqBy(Search, 'imdbID')
              ]
            })
          }
        }
      } catch ({message}) {
        commit('updateState', {
          // 초기화
          movies: [],
          // 메세지 출력
          message: message
        })
      } finally {
        commit('updateState', {
          loading: false
        })
      }
    },
    async searchMovieWithId({state, commit}, payload) {
      if(state.loading) return 

      commit('updateState', {
        theMovie: {},
        loading: true
      })
      //const {id} = payload
      try {
        const res = await _fetchMovie(payload)
        commit('updateState',{
          theMovie: res.data
          // id: id
        })
        console.log(res)
      } catch(error) {
        commit('updateState', {
          theMovie: {}
        })
      } finally {
        commit('updateState', {
          loading: false
        })
      }
    }
  }
}

async function _fetchMovie(payload) {
  // 서버리스 함수로 처리
  return await axios.post('/.netlify/functions/movie', payload)

  // const { title, type, year, page, id } = payload;
  // const OMDB_API_KEY = '7035c60c';
  // const url = id 
  //   ? `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${id}` 
  //   : `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`;
  // // const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}`;

  // return new Promise((resolve, reject) => {
  //   axios.get(url)
  //     .then((res) => {
  //       // console.log(res)
  //       if (res.data.Error) {
  //         reject(res.data.Error)
  //       }
  //       resolve(res)
  //     })
  //     .catch((err) => {
  //       reject(err.message)
  //     })
  // })
}

 

테스트 코드

import movieStore from '~/store/movie.js'
import _cloneDeep from 'lodash/cloneDeep'
import axios from 'axios'

describe('store/movie.js', () => {
  let store

  beforeEach(() => {
    // 원본 훼손을 방지하기 위해 복사 기능 이용
    store = _cloneDeep(movieStore)
    store.state = store.state()
    // this.$store.state.movies
    store.commit = (name, payload) => {
      store.mutations[name](store.state, payload)
      
    }
    
     store.dispatch = (name, payload) => {
       const context = {
         state: store.state,
         commit: store.commit,
         dispatch: store.dispatch
       }
       return store.actions[name](context, payload)
     }
    
  })

  test('영화 데이터를 초기화합니다.', () => {
    store.commit('updateState', {
      movies: [{imdbId: '1'}],
      message: 'Hello world',
      loading: true
    })
    store.commit('resetMovies')    
    expect(store.state.movies).toEqual([])
    expect(store.state.message).toBe('Search for the movie title!')
    expect(store.state.loading).toBe(false)
    //store.mutations.updateState(store.state, {})
    //store.dispatch('searchMovies', {})
  })

  test('영화 목록을 잘 가져온 경우 데이터를 확인합니다.', async () => {
    const res = {
      data: {
        totalResults: '1',
        Search: [
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          }
        ]
      }
    }
    // 모의함수 및 가짜 데이터 설정
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovies')
    expect(store.state.movies).toEqual(res.data.Search)
  })

  test('영화 목록을 가져오지 못한 경우 에러 메시지를 확인합니다.', async () => {
    const errorMessage = 'Network Error'
    axios.post = jest.fn().mockRejectedValue(new Error(errorMessage))
    await store.dispatch('searchMovies')
    expect(store.state.message).toBe(errorMessage)
  })

  test('영화 아이템이 중복된 경우 고유하게 처리합니다.', async () => {
    const res = {
      data: {
        totalResults: '1',
        Search: [
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          },
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          },
          {
            imdbID: '1',
            Title: 'Hello',
            Poster: 'hello.jpg',
            Year: '2021'
          }
        ]
      }
    }
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovies')
    expect(store.state.movies.length).toBe(1)
  })

  test('단일 영화의 상세 정보를 잘 가져온 경우 데이터를 확인합니다.', async () => {
    const res = {
      data: {
        imdbID: '1',
        Title: 'Frozen',
        Poster: 'frozen.jpg',
        Year: '2021'
      }
    }
    axios.post = jest.fn().mockResolvedValue(res)
    await store.dispatch('searchMovieWithId')
    expect(store.state.theMovie).toEqual(res.data)
  })
})

 

+ Recent posts