[JavaScript] SelectBox를 사용하는 날짜 검색창 제작[JavaScript] SelectBox를 사용하는 날짜 검색창 제작

Posted at 2020. 7. 12. 20:31 | Posted in JavaScript & jQuery/JavaScript







■ 셀렉트 박스를 이용한 캘린더 제작







위와 같이 <SELECT> 태그를 통해 날짜값을 지정하여 검색하는 페이지를 제작하게 되었다.


jQuery UI의 DatePicker등을 사용하면 그냥 손쉽게 해결 할 수 있고,


보통은 다 이렇게 하지만, 또 막상 일을 하다보면 위와 같은 검색을 선호하는 클라이언트가 또 있을 수 있다라는 생각이들어


추가적으로 실제 업무에서 많이 쓰이게 되는 GET 방식을 통한 검색조건 전달의 경우를 포함하여


내용을 정리해보았다.





※ 예외 처리해야할 사항들


 ① 매월의 마지막 날의 날짜는 항상 일치하지 않기에, 그달의 마지막 날보다 큰 날짜는 자동적으로 선택할 수 없어야 한다.

 ② 처음 접속시 해당월의 첫째날( 01일 )과 마지막날( 28일, 29,일 30일, 31일 등 )이 자동 세팅되어야 한다.

 ③ GET방식을 통해 검색 시작일과 종룡이릐 날짜 데이터가 전달되는 경우의 수를 처리해야 한다.

 ④ 사용자가 날짜를 선택하는 과정에서 31일이 선택되어 있는 가운데 31일이 존재하지 않는 년, 월을 선택한 경우

     해당 월의 마지막 날로 자동 변경시켜주어야 한다.




위 경우를 상정하여 아래 코드를 작성하게 되었다.




# 소스코드

<html>

<head>

<title>:: JavaScript 매월 주차 구하기 ::</title>

<script type="text/javascript">


    document.addEventListener("DOMContentLoaded", function() {


        const stdDays = document.querySelector("span:nth-child(1)");     // @param 시작일 영역지정

        const endDays = document.querySelector("span:nth-child(2)");    // @param 종료일 영역지정


        let stdFirstDate = null;    // @param 시작일의 첫날

        let endFirstDate = null;   // @param 종료일의 첫날

        let stdLastDate = null;    // @param 시작일의 전체일수

        let endLastDate = null;   // @param 종료일의 전체일수



        // @param GET방식으로 전달된 시작일, 종료일의 지정값이 존재하지 않는경우 ( ※ 맨처음 시작시에 실행 )

        if(nullPointerException(getParameter("stdDate")) == false && nullPointerException(getParameter("endDate")) == false) {


            const toDate = new Date();


            stdFirstDate = new Date(toDate.getFullYear(), (toDate.getMonth() + 1));

            stdLastDate = new Date(stdFirstDate.getFullYear(), (stdFirstDate.getMonth() + 1), 1);  // @param 다음달의 첫째날을 지정

            stdLastDate.setDate(0);    // @param 다음달에서 하루를 빼서 원하는 월의 마지막 날로 맞춤


            endFirstDate = new Date(toDate.getFullYear(), (toDate.getMonth() + 1));

            endLastDate = new Date(endFirstDate.getFullYear(), (endFirstDate.getMonth() + 1), 1);  // @param 다음달의 첫째날을 지정

            endLastDate.setDate(0);    // @param 다음달에서 하루를 빼서 원하는 월의 마지막 날로 맞춤


        }


        // @param GET방식으로 전달된 시작일, 종료일의 지정값이 존재하는 경우

        else {


            const stdDate = getParameter("stdDate");

            stdFirstDate = new Date(

                  Number(stdDate.substring(0, 4))

                , Number(stdDate.substring(6, 8))

                , Number(stdDate.substring(4, 6))

            );

            stdLastDate = new Date(stdFirstDate.getFullYear(), stdFirstDate.getMonth(), 1);  // @param 다음달의 첫째날을 지정

            stdLastDate.setDate(0);    // @param 다음달에서 하루를 빼서 원하는 월의 마지막 날로 맞춤


            const endDate = getParameter("endDate");

            endFirstDate = new Date(

                  Number(endDate.substring(0, 4))

                , Number(endDate.substring(4, 6))

                , Number(endDate.substring(6, 8))

            );

            endLastDate = new Date(endFirstDate.getFullYear(), endFirstDate.getMonth(), 1);  // @param 다음달의 첫째날을 지정

            endLastDate.setDate(0);    // @param 다음달에서 하루를 빼서 원하는 월의 마지막 날로 맞춤


        }


        // @param 년도( Year ) - 선택

        for(let year = 0; stdDays.querySelector("select:nth-child(1)").length > year; year++) {

            if(stdDays.querySelector("select:nth-child(1)").options[year].value == stdFirstDate.getFullYear()) {

                stdDays.querySelector("select:nth-child(1)").options[year].selected = true;

            }


            if(endDays.querySelector("select:nth-child(1)").options[year].value == stdFirstDate.getFullYear()) {

                endDays.querySelector("select:nth-child(1)").options[year].selected = true;

            }

        }


        // @param 월( Month ) - 선택

        for(let month = 0; stdDays.querySelector("select:nth-child(2)").length > month; month++) {

            if(stdDays.querySelector("select:nth-child(2)").options[month].value == stdFirstDate.getMonth()) {

                stdDays.querySelector("select:nth-child(2)").options[month].selected = true;

            }


            if(endDays.querySelector("select:nth-child(2)").options[month].value == endFirstDate.getMonth()) {

                endDays.querySelector("select:nth-child(2)").options[month].selected = true;

            }

        }


        // @param 일( Day ) - 생성 및 선택

        // @details - 날짜는 매월 마지막일이 다르게 끝나기 때문에 직접 생성하고,

        //                선택한 날( 기본값 시작일 - 01일, 종료일은 - 말일 )을 자동으로 selected 한다.

        for(let date = 1; stdLastDate.getDate() >= date; date++) {

            if(stdFirstDate.getDate() == date) {

                stdDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "' selected>" + autoLeftPad(date, 2) + "</option>";

            } else {

                stdDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "'>" + autoLeftPad(date, 2) + "</option>";

            }

        }


        for(let date = 1; endLastDate.getDate() >= date; date++) {

            if((nullPointerException(getParameter("endDate")) == true) ? endFirstDate.getDate() : endLastDate.getDate() == date) {

                endDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "' selected>" + autoLeftPad(date, 2) + "</option>";

            } else {

                endDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "'>" + autoLeftPad(date, 2) + "</option>";

            }

        }

    });



    /**

     * @brief   시작일( 년, 월 ), 종료일 ( 년, 월 )의 값이 변경된 경우 실행

     * @details 시작일( 년, 월 ), 종료일 ( 년, 월 )의 값이 변경에 따른 해당 년월의 전체일수를 다시 체크,

     *             변경된 년월의 일보다 선택된 일이 큰경우 마지막 날로 선택일 자동 변경

     */

    function changeConditionPeriod(cal) {


        const searchDays = cal.parentNode;

        const checkDate = searchDays.querySelector("select:nth-child(3)").value;


        // @param 다음달의 첫째날을 지정

        let lastDate = new Date(

              searchDays.querySelector("select:nth-child(1)").value

            , searchDays.querySelector("select:nth-child(2)").value

            , 1

        );

        lastDate.setDate(0);    // @param 하루를 뺌


        // @param 일정보 초기화

        searchDays.querySelector("select:nth-child(3)").innerHTML = "";


        // @param 일정보 재삽입

        for(let date = 1; lastDate.getDate() >= date; date++) {

            if(checkDate == date) {

                searchDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "' selected>" + autoLeftPad(date, 2) + "</option>";

            } else {

                searchDays.querySelector("select:nth-child(3)").innerHTML

                    += "<option value='" + date + "'>" + autoLeftPad(date, 2) + "</option>";

            }

        }


        // @param 일정보 예외처리 선택되어있는 마지막 날이 바꾼 월의 날보다 큰경우의 예외처리

        if(checkDate > lastDate.getDate()) {

            searchDays.querySelector("select:nth-child(3) > option:last-child").selected = true;

        }

    }


    /**

     * @brief   강제로 앞에 0을 붙여서 두자릿수 숫자로 변경한다.

     * @param   num     앞에 0을 붙일 숫자 값

     * @param   digit   자릿수를 지정

     */

    function autoLeftPad(num, digit) {

        if(String(num).length < digit) { num = new Array(digit - String(num).length + 1).join("0") + num; }

        return num;

    }


    /**

     * @brief   GET으로 넘어오는 값의 존재 여부를 체크

     */

    function nullPointerException(worth) {

        if(

               worth == "" 

            || worth == null 

            || worth == undefined 

            || (worth != null && typeof worth == "object" && !Object.keys(worth).length == "")

        ) {

            return false;

        } else {

            return true;

        }

    }


    /**

     * @brief   GET방식으로 전달된 값을 추출

     * @param   param     추출할 key의 명칭

     */

    var getParameter = function(param) {

        let returnValue;

        let url = location.href;

        let parameters = (url.slice(url.indexOf("?") + 1, url.length)).split("&");

        for(let i = 0; i < parameters.length; i++) {

            let varName = parameters[i].split("=")[0];

            if (varName.toUpperCase() == param.toUpperCase()) {

                returnValue = parameters[i].split("=")[1];

                return decodeURIComponent(returnValue);

            }

        }

    };

</script>

</head>

<body>

    <h1>■ SelectBox를 이용한 캘린더</h1>

    <hr/>

    <!-- 년( Year )은 자동으로 생성시 너무 많이 늘어날 수 있어서 -->

    <!-- 월( Month )은 01 ~ 12라는 고정값을 알고 있기에 직접 값을 지정 -->

    <!-- 일( Day )은 마지막 일이 항상 변하기 때문에 자동 생성 한다. -->

    <div>

        <span>

            시작일&nbsp;:&nbsp;

            <select onChange="changeConditionPeriod(this);">

                <option value="2018">2018</option>

                <option value="2019">2019</option>

                <option value="2020">2020</option>

                <option value="2021">2021</option>

                <option value="2022">2022</option>

                <option value="2023">2023</option>

                <option value="2024">2024</option>

                <option value="2025">2025</option>

            </select>

            &nbsp;년&nbsp;&nbsp;

            <select onChange="changeConditionPeriod(this);">

                <option value="1">01</option>

                <option value="2">02</option>

                <option value="3">03</option>

                <option value="4">04</option>

                <option value="5">05</option>

                <option value="6">06</option>

                <option value="7">07</option>

                <option value="8">08</option>

                <option value="9">09</option>

                <option value="10">10</option>

                <option value="11">11</option>

                <option value="12">12</option>

            </select>

            &nbsp;월&nbsp;&nbsp;

            <select class="choiceDay"></select>

            &nbsp;일

        </span>

        &nbsp;&nbsp;~&nbsp;&nbsp;

        <span>

            종료일&nbsp;:&nbsp;

            <select onChange="changeConditionPeriod(this);">

                <option value="2018">2018</option>

                <option value="2019">2019</option>

                <option value="2020">2020</option>

                <option value="2021">2021</option>

                <option value="2022">2022</option>

                <option value="2023">2023</option>

                <option value="2024">2024</option>

                <option value="2025">2025</option>

            </select>

            &nbsp;년&nbsp;&nbsp;

            <select onChange="changeConditionPeriod(this);">

                <option value="1">01</option>

                <option value="2">02</option>

                <option value="3">03</option>

                <option value="4">04</option>

                <option value="5">05</option>

                <option value="6">06</option>

                <option value="7">07</option>

                <option value="8">08</option>

                <option value="9">09</option>

                <option value="10">10</option>

                <option value="11">11</option>

                <option value="12">12</option>

            </select>

            &nbsp;월&nbsp;&nbsp;

            <select class="choiceDay"></select>

            &nbsp;일

        </span>

    </div>

</body>

</html> 



이제 위 코드를 브라우저를 통해 실행시켜 보면




# 출력결과




위와 같이 예외처리해야할 사항들이 정상적으로 반영되어 기능하는 모습을 확인 할 수 있다.








Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[JavaScript] 매월의 월요일 기준, 주차 구하기[JavaScript] 매월의 월요일 기준, 주차 구하기

Posted at 2020. 6. 29. 18:14 | Posted in JavaScript & jQuery/JavaScript




참고 : https://blog.naver.com/z1004man/220936098651

참고 : https://matthew-jo.tistory.com/8





매월 월요일을 기준으로하는 주차 및 해당주차의 날짜 구하기


( 부제 : 일요일을 마지막으로 하는 주차 구하기 )




선택한 매월의 월요일을 기준으로하는 해당월의 주차수와 각 주차별 월요일, 일요일 날짜를 구하는 함수




통계 프로그램을 만들면서 JSP, PHP, Oracle, MySQL등 여러가지 개발 언어를 사용하여


매월의 주차를 구하는 프로그램을 구현해야 하는 일이 종종 발생 하였다.


그런데 저렇게 다양한 환경에서 매번 그 언어에 맞게 다시 조립 및 분해하는 과정이 생기는게 싫어서.


이번에 아예 한번 자바스크립트로 클라이언트 영역에서 넘겨줄 값을 미리 다 생성해서 넘겨주면


좀더 환경에 자유로울 수 있을 것 같아 순수 자바스크립트로 매월의 ( 월요일을 시작을 기준 )으로 함수를 만들어 정리해 보았다.





# 소스코드

<html>

<head>

<title>:: JavaScript 매월 주차 구하기 ::</title>

<script type="text/javascript">


    // 기준요일에 따른 주차구하는 함수.

    // 해당 주차 / 해당주차 시작날짜 / 해당주차 끝나는날짜를 리턴.

    function searchPeriodCalculation() {


        let cYear = document.getElementById("choiceYear").value;

        let cMonth = document.getElementById("choiceMonth").value.replace(/(^0+)/, "") - 1;


        // 날짜형으로 데이트 포맷

        let date = new Date(cYear, cMonth);


        // 월요일을 중심으로한 주차 구하기( JS기준 : 일요일 0 월요일 1 ~ 토요일 6 )

        let firstDay = new Date(date.getFullYear(), date.getMonth(), 1);

        let lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);


        let weekObj = null;

        let weekObjArray = new Array();

        let weekStand = 8;  // 월요일 고정

        let firstWeekEndDate = true;

        let thisMonthFirstWeek = firstDay.getDay();


        for(var num = 1; num <= 6; num++) {


            // 마지막월과 첫번째월이 다른경우 빠져나온다.

            if(lastDay.getMonth() != firstDay.getMonth()) {

                break;

            }


            weekObj = new Object();


            // 한주의 시작일은 월의 첫번째 월요일로 설정

            if(firstDay.getDay() <= 1) {


                // 한주의 시작일이 일요일이라면 날짜값을 하루 더해준다.

                if(firstDay.getDay() == 0) { firstDay.setDate(firstDay.getDate() + 1); }


                weekObj.weekStartDate =

                      firstDay.getFullYear().toString()

                    + "-"

                    + numberPad((firstDay.getMonth() + 1).toString(), 2)

                    + "-"

                    + numberPad(firstDay.getDate().toString() , 2);

            }


            if(weekStand > thisMonthFirstWeek) {

                if(firstWeekEndDate) {

                    if((weekStand - firstDay.getDay()) == 1) {

                        firstDay.setDate(firstDay.getDate() + (weekStand - firstDay.getDay()) - 1);

                    }

                    if((weekStand - firstDay.getDay()) > 1) {

                        firstDay.setDate(firstDay.getDate() + (weekStand - firstDay.getDay()) - 1)

                    }

                    firstWeekEndDate = false;

                } else {

                    firstDay.setDate(firstDay.getDate() + 6);

                }

            } else {

                firstDay.setDate(firstDay.getDate() + (6 - firstDay.getDay()) + weekStand);

            }


            // 월요일로 지정한 데이터가 존재하는 경우에만 마지막 일의 데이터를 담는다.

            if(typeof weekObj.weekStartDate !== "undefined") {


                weekObj.weekEndDate =

                      firstDay.getFullYear().toString()

                    + "-"

                    + numberPad((firstDay.getMonth() + 1).toString(), 2)

                    + "-"

                    + numberPad(firstDay.getDate().toString(), 2);

                    

                weekObjArray.push(weekObj);

            }


            firstDay.setDate(firstDay.getDate() + 1);

        }


        console.log( weekObjArray );

    }


    // 월, 일 날짜값 두자리( 00 )로 변경

    function numberPad(num, width) {

        num = String(num);

        return num.length >= width ? num : new Array(width - num.length + 1).join("0") + num;

    }

</script>

</head>

<body>

    <div>

        <input type="text" id="choiceYear" style="text-align:center;" value="2020"/>

        &nbsp;년&nbsp;&nbsp;

        <select id="choiceMonth">

            <option value="01">01</option>

            <option value="02">02</option>

            <option value="03">03</option>

            <option value="04">04</option>

            <option value="05">05</option>

            <option value="06">06</option>

            <option value="07">07</option>

            <option value="08">08</option>

            <option value="09">09</option>

            <option value="10">10</option>

            <option value="11">11</option>

            <option value="12">12</option>

        </select>

        &nbsp;월&nbsp;&nbsp;

        <button type="button" onClick="searchPeriodCalculation( );">검색</button>

    </div>

</body>

</html>




위 코드의 결과는 아래와 같다.



# 출력 결과















Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[JavaScript] Table을 Excel 처럼 즉시 수정가능하게 만들기[JavaScript] Table을 Excel 처럼 즉시 수정가능하게 만들기

Posted at 2020. 6. 24. 09:46 | Posted in JavaScript & jQuery/JavaScript




주의 : 해당 포스팅의 샘플 코드에서 사용하는 addEventListener( ) 메서드의 경우 Internet Explorer에서는 정상적으로 작동하지 않습니다.

           호환되지 않는 Internet Explorer에서의 사용의 경우는 addEventListener( ) 메서드를 attachEven( )로 변경하여 작업을 진행하셔야 합니다.






contenteditable 속성을 이용한 텍스트 내용 수정




기본적으로 웹 환경에서 값을 변경하기 위해서는 <input type="text" value="값">이 기본 형태였지만.


HTML5가 나오면서 contenteditable을 이용해 좀더 재미있는 사용자 경험을 제공할 수 있게 되었다.


해당 포스팅에서는 웹 페이지에 출력된 텍스트 문구를 contenteditable 속성을 이용하여 수정해 보려 한다.




# 소스코드

<html>
<head>
<title>:: JavaScript Contenteditable ::</title>
</head>
<body>
<p contenteditable="false">
    이 단락은 편집을 원하는 경우 더블 클릭해 주세요
</p>
</body>
</html>
<script type="text/javascript">

    // @breif contenteditable 속성을 가진경우
    var content = document.querySelector( "[contenteditable]" );
    var edtButton = document.querySelector( "button" );

    document.addEventListener("DOMContentLoaded", function() {

        // @breif 더블클릭시 실행
        content.addEventListener("dblclick", function(event) {

            // @details contenteditable 속성이 수정 불가인 경우 실행( false )
            if(content.isContentEditable == false) {

                // @details 편집 가능 상태로 변경
                content.contenteditable = true;

                // @details 텍스트 문구 변경
                content.textContent = "편집 가능한 상태로 변경되었습니다.";

                // @details CSS 효과 추가
                content.style.border = "1px solid #FE7F9C";

                // @details 포커스 지정
                content.focus();
            }

            // @details contenteditable 속성이 수정 가능인 경우 실행( true )
            else {

                // 편집 불가 상태로 변경
                content.contenteditable = false;
                content.style.border = "0px";
            }
        });
    });
</script>




# 출력결과












■ 테이블을 엑셀처럼 즉시 변경가능하게 만들기





이제 contenteditable 태그를 좀더 심도있게 활용을 해보자.


바로 테이블에 출력되어 있는 내용을 엑셀처럼 즉시 수정을 하고 해당내용을 적용하는 것이다.


즉각적으로 수정된 내용을 데이타 베이스( DataBase )에 적용하는 예제는 아니지만.


해당 내용의 결과물을 본다면 어떻게 사용하는것이 좋을지 바로 감이 올 거라 생각한다.



# 소스코드

<html>

<head>

<title>:: JavaScript Contenteditable ::</title>

<style type="text/css">

    table { border-collapse:collapse;border:1px gray solid; }

    .rowColumn { border-radius:5px;margin:5px; }

</style>

</head>

<body>

<table border="1">

    <thead>

        <tr>

            <th style="width:50px;">번호</th>

            <th style="width:120px;">가수</th>

            <th style="width:350px;">노래</th>

            <th style="width:100px;">발매일</th>

        </tr>

    </thead>

    <tbody>

        <tr>

            <td style="text-align:center;">1</td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="윤하">윤하</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="오렌지 첫사랑">오렌지 첫사랑</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="2007-10-23">2007-10-23</p>

            </td>

        </tr>

        <tr>

            <td style="text-align:center;">2</td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="도원경">도원경</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="다시 사랑한다면">다시 사랑한다면</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="2001-05-01">2001-05-01</p>

            </td>

        </tr>

        <tr>

            <td style="text-align:center;">3</td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="더더">더더</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="it's You">it's You</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="1998-11-01">1998-11-01</p>

            </td>

        </tr>

        <tr>

            <td style="text-align:center;">4</td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="박기영">박기영</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="시작">시작</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="1999-04-07">1999-04-07</p>

            </td>

        </tr>

        <tr>

            <td style="text-align:center;">5</td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="이소은">이소은</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="오래오래">오래오래</p>

            </td>

            <td>

                <p class="rowColumn" contenteditable="false" data-default="2002-12-21">2002-12-21</p>

            </td>

        </tr>

    </tboyd>

</talbe>

</body>

</html>

<script type="text/javascript">


    // @breif contenteditable 속성을 가진경우

    var contents = document.getElementsByClassName("rowColumn");


    document.addEventListener("DOMContentLoaded", function() {


        // @breif rowColumn 클래스의 갯수 만큼 반복문을 실행한다.

        Array.from(contents).forEach(function(content) {


            // @breif 마우스로 해당영역을 더블클릭 한경우

            content.addEventListener("dblclick", function(event) {


                // @breif 전체 테이블 컬럼( td > p )에서 현재 사용중인 값의 존재여부를 확인한다.

                Array.from(contents).forEach(function(defaultVal) {


                    /*
                    // @details 빈값( null )이 존재하는지 체크한다.
                    if(
                           defaultVal.textContent == ""
                        || defaultVal.textContent == null
                        || defaultVal.textContent == undefined
                        || (defaultVal.textContent != null
                        && typeof defaultVal.textContent == "object"
                        && !Object.keys(defaultVal.textContent).length == ""))
                    {

                        // @details 내용이 존재하지 않다면 data 태그의 기본값으로 되돌린다.
                        defaultVal.textContent = defaultVal.dataset.default;
                    }
                    */

                    // @details 저장하지 않은 내용이라고 판단하여 data 태그의 기본값으로 되돌린다.
                    defaultVal.textContent = defaultVal.dataset.default;

                    // @breif 수정 불가 상태로 되돌린다.

                    defaultVal.contenteditable = false;

                    defaultVal.style.border = "0px";

                });


                if(content.isContentEditable == false) {


                    // @details 편집 가능 상태로 변경

                    content.contenteditable = true;


                    // @details 텍스트 문구 변경

                    // content.textContent = "";


                    // @details CSS 효과 추가

                    content.style.border = "1px solid #FFB6C1";


                    // @details 포커스 지정

                    content.focus();

                }

            });


            // @breif 키보드 입력이 방생한 경우 실행

            content.addEventListener("keypress", function(event) {


                // @breif Enter키 입력시 실행

                if(event.key === "Enter") {


                    // @details 입력된 값이 빈값( null )인지 체크한다.

                    if(

                           content.textContent == ""

                        || content.textContent == null

                        || content.textContent == undefined

                        || (content.textContent != null

                        && typeof content.textContent == "object"

                        && !Object.keys(content.textContent).length == ""))

                    {


                        // @details 내용이 존재하지 않다면 data 태그의 기본값으로 되돌린다.

                        content.textContent = content.dataset.default;

                    } else {


                        // @details 내용의 수정이 완료되었다면 data 태그의 기본값도 바꿔준다.

                        content.dataset.default = content.textContent;

                    }


                    // @breif 수정 불가 상태로 되돌린다.

                    content.contenteditable = false;

                    content.style.border = "0px";

                }

            });

        });

    });

</script>




위 코드의 결과는 아래와 같이 직접 확인을 진행해 보자.




# 출력결과

번호

가수

노래

발매일

1

윤하

오렌지 첫사랑

2007-10-23

2

도원경

다시 사랑한다면

2001-05-01

3

더더

it's You

1998-11-01

4

박기영

시작

1999-04-07

5

이소은

오래오래

2002-12-21

주의해당 출력결과Internet Explorer에서는 정상적으로 작동하지 않습니다.



위에 가수, 노래, 발매일 항목을 마우스로 더블클릭( dblclick )하면 내용을 수정하고, 키보드 Enter 키를 눌러 변경한 내용을 반영할 수 있다.

( 단 공란( null )은 사용할 수 없게 처리되어 있다. )



또한 하나의 컬럼을 수정하던중 다른 컬럼을 더블클릭( dblclick )한 경우 수정중이던 컬럼영역은


data-default에 저장되어있는 기존값으로 다시 돌아가게 설계되어 있다.


이와같은 예외처리는 용도에 맞게 코드를 조금 손보면 바로 적용할 수 있을 것이다.





자체과제 : 심화코드 만들어보기






Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

퍼펫티어 request, response ( 작성중 )퍼펫티어 request, response ( 작성중 )

Posted at 2020. 5. 29. 22:38 | Posted in Node.js/Puppeteer






    await page.on("response", async(response) => {
        console.log(await response.url());
        console.log(await response.json());
    });




    await  page.on("request", request => {
        console.log("123");
        request.continue({
              url:"사이트 URL"
            , method:"GET"
            , postData: JSON.stringify({ test:"test" })
            , headers: { "Content-Type":"application/json" },
        });
    }); 





Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[Puppeteer] 퍼펫티어를 이용해 File Upload 하기[Puppeteer] 퍼펫티어를 이용해 File Upload 하기

Posted at 2020. 5. 29. 21:26 | Posted in Node.js/Puppeteer





■ 퍼펫티어를 이용해 파일 업로드하기






퍼펫티어를 통한 사무자동화 작업중 사이트의 파일 업로드를 해야하는 경우가 발생하였다.


문제는 헤드리스 모드( 작업화면이 사용자에게 보이지않는 ) 상황으로 완성품을 만들기위해서는.


업로드 버튼을 누르고 → 파일을 찾고 → 이미지를 업로드 하는 과정을


컴퓨터 화면을 보지 않고 진행을 해야한다는 것이다.









다행이 퍼펫티어에서는 waitForFileChooser( ) 메서드를 통해


해결할 수 있는 방법을 제공한다.


아래 코드를 통해 관련 작업을 진행해 보자.




# 소스코드 - node.js

 fileUpload.php

const puppeteer = require( "puppeteer" );


// 사용시 인위적인 딜레이를 주기위한 함수

function delay( timeout ) {

    return new Promise(( resolve ) => {

      setTimeout( resolve, timeout );

    });

}


puppeteer.launch({

      headless : false          // 헤드리스모드의 사용여부를 묻는다.

    , devtools : false          // 개발자 모드의 사용여부를 묻는다.

    , slowMo : 50          // 지정된 시간(ms)만큼 Puppeteer 작업 속도를 낮춘다.

}).then(async browser => {


    const page = await browser.newPage();

    await page.goto( "http://localhost/fileUpload.php", { waitUntil : "networkidle2" } );


    const [fileChooser] = await Promise.all( [

        page.waitForFileChooser( ),

        page.click( "#upFile" )         // 파일을 올릴 input type = "file" 태그를 지정

    ] );


    await fileChooser.accept( [ "puppeteer.png" ] );


    // 3초간딜레이를 준다.

    await delay( 3000 );


    page.waitForNavigation( ),page.click( "#upBtn" );


    // await browser.close( );

});




해당 포스팅은 운영중인 어느 사이트가 아닌


업로드 기능을 가진 페이지를 기준으로 테스트를 진행하려 한다.


아래 파일을 업로드할 웹 페이지를 간단히 제작하자.




# 소스코드 - PHP

 fileUpload.php

<?php

    echo "<pre>";

    print_r( $_FILES );

    echo "</pre>";

?>

<html>

<head>

<title>:: Puppeteer 업로드 속성 ::</title>

<script type="text/javascript">

    function fileUpload() {

        document.getElementById("upFrm").action = "./fileUpload.php";

        document.getElementById("upFrm").submit();

    }

</script>

</head>

<body>

    <form enctype="multipart/form-data" id="upFrm" method="post">

        <input type="file" id="upFile" name="upFile" accept="image/*">

        <br/><br/>

        <input type="button" onClick="fileUpload();" id="upBtn" value="업로드">

    </form>

</body>

</html>





# 출력결과




그럼 위의 이미지와 같이, 지정한 이미지를 자동으로 업로드 해주는 모습을 확인 할 수 있다.







Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[Flow.Txt] 언젠간 내 기술이 시대 흐름에 뒤쳐질지 모른다.(2020.05.29)[Flow.Txt] 언젠간 내 기술이 시대 흐름에 뒤쳐질지 모른다.(2020.05.29)

Posted at 2020. 5. 29. 15:39 | Posted in Flow.Txt





금일에서야 대충 마무리 단계에 들어간 프로젝트의 프로그램 코드중에서


다음에도 다시 재활용하게될 코드들이 있을것 같아 정리하던중


문득 그 내용에대해 관련 기사나 포스팅등을 검색을 해보던중


"Puppeteer vs Playwright. : 어느 쪽을 택할 것인가?"


라는 포스팅을 보게 되어서 드는 생각을 좀 정리해 보았다.






자동화( 브라우저 제어 ) 업무가.


어쩌다 보니 근1년가까이 주된 업무가 되었다.


지금은 내가 웹 개발자인지, node.js 개발자인지 좀 햇갈릴 정도로, 해당 업무비중이 높아진 상태다.








일단 파이썬은 내가 다시 배워야 했지만, node.js의 경우 이미 JavaScript는 자칭( ? ) 귀신의 레벨이기에... 꽤 자신 있었기에....






그렇게 노드를 혼자서 열심히 파다가...


디노( Deno )라는 노드의 단점을 보완하기 위한것이 나왔다는 것을 알게 되어서...


아 앞으로는 이것도 해야 하나 라는 함숨이 절로 나올때...


아래의 포스팅을 보게되었다.





원문 : https://blog.logrocket.com/playwright-vs-puppeteer/


검색결과

사이트 링크가 있는 웹 검색결과

내가 Node.js의 작업중 1/3에서 2/3정도 사이의 작업 비중이 퍼펫티어 라이브러리를 사용하는


자동화와 스크랩핑이다, 그런데 그 퍼펫티어의 개발자들이 구글(Google)을 나와서 마이크로 소프트( MicroSoft )로 옮겨


플레이라이트라는 새로운걸 만들었다니...




물론 관련내용을 읽어보니 MySQL ↔ MariaDB 처럼...


바로 사용할 수 있는 레벨이지만...

( 위 포스팅에는 아직 Ver1.0 버전때지만 버전이 높아지면서 점점 차이가 두드럴 질 수 있다는 내용이 포함되어 있다. )


그래도 좀 괜히 힘빠지고 귀찮아 지는 것은 사실이다.





코로나19 사태로 많은일자리가 줄어들고 있기에 반대로.


이런 사무자동화 프로그램을 짤 수 있는건 큰 장점이 되기에...


"아 그래도 나는 이 사태가 내 밥줄에 크게 영향을 끼치지 않는다." 라는 생각을 가지게 되면서 잊고있었다.


기술의 패러다임이 변화하는 싸이클은 점점더 가속화 되고 있다는걸...


ㅠ.ㅠ





그닥 공부머리는 아니여서, 그냥 끈기하나로 붙잡고 늘어져서 코딩하는 나이기에...


관련내용들에 대해서는 블로그에 또 포스팅으로 남기겠지만.


언젠간 내가 가지고 있는 기술이 어쩌면 무쓸모 해질지 모른다 라는 생각이 드는건 역시 조금 서글프지만...









PS. 글을 다 쓰고 나서 좀 너무 부정적으로 끝나는것 같아 좀 더 써본다.


세상에 무의미한 기술은 없다, 아직 엄청나게 활성화 되거나 그런것도 아니고, 당장 내 기술이 사장된것도 아니다.


개발자로서 망하진 않을곳까지 올라간다... 적어도 그거는 이미 달성하였는데, 그거면 되었지...








Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[PHP] 서버 접속 가능여부 확인[PHP] 서버 접속 가능여부 확인

Posted at 2020. 5. 29. 15:33 | Posted in PHP




참고 : https://www.php.net/fsockopen





■ 서버간 데이터 통신 가능여부 확인하기




# 소스코드

<?php


    $accessCheck = fsockopen( "120.0.0.1", 포트번호, $errno, $errstr, 10 );


    if( $accessCheck ) {


        echo "서버 ON - 접속 가능";


    } else {


        echo "서버 OFF - 접속 불가";

    }


    fclose( $accessCheck );


?> 



# 접속정보










Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[Puppeteer] 퍼펫티어 setViewport()를 사용한 창 크기 조절[Puppeteer] 퍼펫티어 setViewport()를 사용한 창 크기 조절

Posted at 2020. 5. 29. 15:23 | Posted in Node.js/Puppeteer




■ 퍼펫티어 창 크기 조절하기





퍼펫티어를 이용한 작업을 진행하다 보면


화면에 표시되는 영역의 크기가 작아서 답답한 경우가 발생한다.






브라우저의 크기를 아무리 늘려도 출력되는 페이지의 크기는 변화가 없다.


실제 서비스하는경우는 headless : true로 설정하여 보이지 않게 하고 작업을 진행하겠지만.


서비스 개발중에는 종종 답답한 경우가 발생한다. 


setViewport( ) 설정을 통해 이런 답답한 점을 해결해보자.




# 소스코드

 puppeteer.viewport.js

const puppeteer = require("puppeteer");


puppeteer.launch({

      headless : false // 헤드리스모드의 사용여부를 묻는다.

    , devtools : false // 개발자 모드의 사용여부를 묻는다.

    , slowMo : 0            // 지정된 시간(ms)까지 Puppeteer 작업속도를 느리게 한다.


}).then(async browser => {


    const page = await browser.newPage();


    await page.setViewport({

          width : 1280               // 페이지 너비

        , height : 720                // 페이지 높이

        , deviceScaleFactor : 1     // 기기 배율 요소를 지정 DPR( Device Pixel Resolution )

        , isMobile : false            // 모바일

        , hasTouch : false           // 터치 이벤트 발생여부

        , isLandscape : false        //

    });


    await page.goto( "https://github.com/puppeteer/puppeteer/blob/master/docs/api.md", { waitUntil : "networkidle2" } );

});





# 출력 결과

 $ node puppeteer.viewport.js



위와같이 출력화면이 브라우저 가득 커져서 나오는 것을 확인 할 수 있다.








Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[Puppeteer] evaluate() 페이지 데이터 추출[Puppeteer] evaluate() 페이지 데이터 추출

Posted at 2020. 5. 27. 13:54 | Posted in Node.js/Puppeteer





■ evaluate( )를 이용하여 페이지내의 데이터 가져오기




티스토리 블로그에 로그인을 하려면


로컬 스토리지의 tistoryFingerprint 값이 필요하다.






샘플로예제로 위 데이터를 가지고와 보자.




# 소스코드

 evaluate_get.js

const puppeteer = require("puppeteer");


// 사용시 인위적인 딜레이를 주기위한 함수

function delay( timeout ) {

  return new Promise(( resolve ) => {

    setTimeout( resolve, timeout );

  });

}


puppeteer.launch({

          headless : false // 헤드리스모드의 사용여부를 묻는다.

, devtools : true // 개발자 모드의 사용여부를 묻는다.


}).then(async browser => {


    const page = await browser.newPage();


    await page.goto( "https://www.tistory.com/auth/login", { waitUntil : "networkidle2" } );


    await page.setRequestInterception(true);


    const pageGetData = await page.evaluate(() => {

        const jsonData = {

            "fingerPrint" : localStorage.getItem( "tistoryFingerprint" )

        };

        return jsonData;

    });


    console.log( pageGetData.fingerPrint );

});



이제 실행해보면 tistoryFingerprint값을 가져올 수 있는 모습을 확인 할 수 있다.



# 출력결과

 $ node evaluate_get.js 











Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

[Flow.Txt] 新 직장인 꼰대 체크 리스트(2020.05.24)[Flow.Txt] 新 직장인 꼰대 체크 리스트(2020.05.24)

Posted at 2020. 5. 24. 20:09 | Posted in Flow.Txt





출처 : 90년생이 온다






新 직장인 꼰대 체크 리스트




#01. 9급 공무원을 준비하는 요즘 세대를 보면 참 도전정신이 부족하다는 생각이 든다.


#02. 헬조선이라고 말하는 요즘 세대는 참 한심하다.


#03. 회사에서 점심시간은 공적인 시간이다. 싫어도 팀원들과 함께해야 한다.


#04. 윗사람의 말에는 무조건 따르는 것이 회사 생활의 지혜이다.


#05. 처음 만나는 사람에게 먼저 나이나 학번을 물어보고 이야기를 풀어나가야 속이 편하다.


#06. '정시 퇴근 제도( 패밀리 데이 )'는 좋은 복지 혜택이다.


#07. 휴가를 다 쓰는 것은 눈치가 보이는 일이다.


#08. 1년가 '육아휴직'을 다녀온 동료 사원이 못마땅하다.


#09. 나보다 늦게 출근하는 후배 사원이 거슬린다.


#10. 회식 때 후배가 수저를 알아서 세팅하지 않거나, 눈앞의 고기를 굽지 않는 모습에 화가난다.


#11. '내가 왕년에'. '내가 너였을 때'와 같은 말을 자주 사용한다.


#12. 편의점이나 매장에서 어려 보이는 직원에게는 반말을 한다.


#13. 음식점이나 매장에서 '사장 나와'를 외친적이 있다.


#14. '어린 녀석이 뭘 알아?'라는 생각을 해본적이 있다.


#15. 촛불집회나 기타 정치 활동에 참여하는 학생들은 학생의 본분을 지키지 않는다고 생각한다.


#16. '나이가 들면 지혜로워진다'란 말에 동의한다.


#17. 낮선 방식으로 일하는 후배에게는 친히 제대로 일하는 법을 알려준다.


#18. 자유롭게 의견을 얘기하라고 해놓고 내가 먼저 답을 제시한다.


#19. 내가 한때 잘나가던 사람이었다는 사실을 알려주고 싶은 마음이 든다.


#20. 회사 생활 뿐만 아니라, 연애사와 자녀계획 같은 사생활의 영역도 인생선배로서 답을 제시해줄 수 있다고 믿는다.


#21. 회식이나 야유회에 개인 약속을 이유로 빠지는 사람을 이해햐기 어렵다.


#22. 내 의견에 반대한 후배에게 화가난다.


#23. 자기 개발은 입사 전에 끝내고 와야 하는 것이다.








[ 테스트 결과 ]



0개 : 대단합니다. 당신은 꼰대가 아닙니다.


1 ~ 8개 : 꼰대입니다. 심각하지 않지만 꼰대가 아닌것도 아닙니다.


9 ~ 16개 : 조금 심각한 꼰대입니다.


17 ~ 23개 : 중증 꼰대입니다.








내 꼰대력이 이렇게 낮을 줄이야...

좀더 훌륭한 꼰대가 되기위해 분발하자.











Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기