728x90

보통 node 로 api 백엔드를 구성할때 env 파일을 설정 하고 db.config.js 로 db 설정 파일을 별도로 두고 관리한다. 개발서버에서는 잘 되다가 실서버로 이전 했는데 소스는 동일한데 response 에 데이터가 모두 이진법으로 buffer type 으로 넘어 올때가 있다. 숫자 데이터는 정상적으로 나오는데 영문이나 한글 등 string 형태의 데이터는 모두 이렇게 나와서 처음에는 무척 당황했다. 

 

"mem_id": { "type": "Buffer", "data": [116, 101, 115, 116, 49, 49, 49] }

 

캐릭서셋이 안맞을 경우 이런 현상이 발생하는데 이때는 캐릭터셋을 수정해 볼수 있으면 그 방법이 가장 좋지만 이미 데이터가 모두 들어가 있는 상태에서 기존 db 데이터도 문제가 없어야 하고 신규 데이터 연동에 대해서도 문제가 없어야 하는 상황이라면 typeCast 를 설정해서 데이터를 읽어 올때 utf8 로 toString 으로 변환해 주는 방법이 있다. 숫자는 정상적으로 나오지만 string 데이터가 문제 이기 때문에 조건문에는 VAR_STRING 와 BLOB 데이터를 걸러 준다. 캐릭터셋도 맞는 형식으로 한번 더 정의를 해준다. 

charset: 'utf8mb4'
const connInfo = {
	host: process.env.REACT_APP_DB_HOST,
	port: process.env.REACT_APP_DB_PORT,
	user: process.env.REACT_APP_DB_USER,
	password: process.env.REACT_APP_DB_PASSWORD,
	database: process.env.REACT_APP_DB_NAME,
	// ... 기타 연결 정보

	// typeCast 함수 추가
	typeCast: function(field, next) {
		// field 객체는 현재 처리 중인 필드의 메타데이터를 담고 있습니다.
		// 예를 들어 field.type, field.length 등을 확인할 수 있습니다.
		// next 함수는 기본 타입 변환 로직을 수행합니다.

		// 예시: BLOB 또는 VARCHAR 타입이 Buffer로 넘어올 때 문자열로 변환
		if (field.type === 'BLOB' || field.type === 'VAR_STRING') { // 또는 다른 문자열 관련 타입
			if (field.length === 0) { // 빈 문자열 처리
				return '';
			}
			// Buffer 타입인 경우 toString('utf8')로 변환
			if (field.buffer()) {
				return field.buffer().toString('utf8');
			}
		}

		// 다른 타입의 경우 기본 변환 로직 사용
		return next();
	}
};

 

728x90
728x90
<div class="input_wrap input_item_area">
  <select class="select" onchange="num(this.value)">
    <option value="1">평수로 입력</option>
    <option value="2">가로 X 세로로 입력</option>
    <option value="3">㎡로 입력</option>
  </select>
  
  <!-- 평수로 입력 -->
  <div class="wrap" id="1">
    <div>
      <input type="text" class="input" placeholder="10" id="areaInput" oninput="convertToSquareMeters()"> <span class="txt">평</span>
    </div>
  </div>
  
  <!-- 가로세로로 입력 -->
  <div class="wrap" style="display: none;" id="2">
    <div>
      <span class="txt">가로</span>
      <input type="text" class="input" placeholder="6.8" id="widthInput" oninput="calculateArea()">
      <span class="txt">m</span>
    </div>
    <div class="divider">
      <span class="txt">세로</span>
      <input type="text" class="input" placeholder="7.15" id="heightInput" oninput="calculateArea()">
      <span class="txt">m</span>
    </div>
    <div>
      <span class="txt">총 면적</span>
      <input type="text" class="input" id="squareMeterInput" readonly> <span class="txt">㎡</span>
    </div>
  </div>
  
  <!-- ㎡로 입력 -->
  <div class="wrap" style="display:none" id="3">
    <div>
      <input type="text" class="input" placeholder="48.6" id="squareMeterInput2" oninput="convertToPyeong()"> <span class="txt">㎡</span>
    </div>
  </div>
</div>
function num(value) {
  // 모든 입력 필드를 숨김
  document.getElementById('1').style.display = 'none';
  document.getElementById('2').style.display = 'none';
  document.getElementById('3').style.display = 'none';

  // 선택한 값에 따라 해당 입력 필드 보이기
  document.getElementById(value).style.display = 'block';
}

function calculateArea() {
  const width = parseFloat(document.getElementById('widthInput').value) || 0;
  const height = parseFloat(document.getElementById('heightInput').value) || 0;
  const areaInSquareMeters = width * height;
  
  // 평으로 변환 (1평 = 3.3㎡)
  const areaInPyeong = areaInSquareMeters / 3.3; // 제곱미터를 평으로 변환
  
  // 평수 입력란에 결과 표시
  document.getElementById('areaInput').value = areaInPyeong.toFixed(2);
  
  // 제곱미터 입력란에 결과 표시
  document.getElementById('squareMeterInput').value = areaInSquareMeters.toFixed(2);
}

function convertToPyeong() {
  const squareMeters = parseFloat(document.getElementById('squareMeterInput2').value) || 0;
  
  // 평으로 변환 (1평 = 3.3㎡)
  const areaInPyeong = squareMeters / 3.3; // 제곱미터를 평으로 변환
  
  // 평수 입력란에 결과 표시
  document.getElementById('areaInput').value = areaInPyeong.toFixed(2);
}

function convertToSquareMeters() {
  const pyeong = parseFloat(document.getElementById('areaInput').value) || 0;
  
  // 제곱미터로 변환 (1평 = 3.3㎡)
  const areaInSquareMeters = pyeong * 3.3; // 평을 제곱미터로 변환
  
  // 제곱미터 입력란에 결과 표시
  document.getElementById('squareMeterInput').value = areaInSquareMeters.toFixed(2);
}
728x90
728x90

1. 비동기 처리

문제: 주어진 두 개의 비동기 함수 fetchData1과 fetchData2를 호출하여 그 결과를 배열로 반환하는 fetchAllData 함수를 작성하세요. 두 함수는 각각 1초 후에 값을 반환합니다.

 

function fetchData1() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('Data from fetchData1');
        }, 1000);
    });
}

function fetchData2() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('Data from fetchData2');
        }, 1000);
    });
}

async function fetchAllData() {
    // 여기에 코드를 작성하세요.
}

 

정답 

async function fetchAllData() {
    const results = await Promise.all([fetchData1(), fetchData2()]);
    return results;
}

 

2. 배열 및 객체조작

문제: 주어진 배열에서 중복된 값을 제거하고, 각 값의 출현 횟수를 객체 형태로 반환하는 countOccurrences 함수를 작성하세요.

function countOccurrences(arr) {
    // 여기에 코드를 작성하세요.
}

 

정답

function countOccurrences(arr) {
    return arr.reduce((acc, item) => {
        acc[item] = (acc[item] || 0) + 1;
        return acc;
    }, {});
}
728x90
728x90

이 예제에서는 버튼 요소에 data-action과 data-target 속성을 추가하여 버튼의 동작과 대상 폼을 지정했습니다. JavaScript에서는 이 data- 속성을 읽어 적절한 동작을 수행하도록 구현했습니다.

data- 속성은 이처럼 HTML과 JavaScript 간의 데이터 전달에 유용하게 사용될 수 있습니다.

<div id="myElement" data-color="blue" data-size="large">...</div>

const myElement = document.getElementById('myElement');
console.log(myElement.dataset.color); // "blue"
console.log(myElement.dataset.size); // "large"
<button id="myButton" data-action="submit" data-target="#myForm">Submit</button>
<form id="myForm">
  <!-- form fields -->
</form>
<script>
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', () => {
  const action = myButton.dataset.action;
  const target = myButton.dataset.target;

  if (action === 'submit') {
    const form = document.querySelector(target);
    form.submit();
  }
});

</script>
728x90
728x90

첨부파일을 드래그로 순서 변경 가능하게 구현한 스크립트 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Drag and Drop</title>
    <style>
        .file-list-box {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            justify-content: flex-start;
            align-items: center;
            border: 2px dashed #ccc;
            padding: 10px;
            min-height: 100px;
        }

        .file-list-box .img-box {
            width: 100px;
            height: 100px;
            border-radius: 8px;
            overflow: hidden;
        }

        .file-list-box .img-box img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .over {
            border: 2px dashed #000;
        }
    </style>
</head>
<body>
    <div class="file-box" data-max-files="5" id="file-box-1">
        <div class="file-s-tit">사진올리기</div>
        <div class="file-list-box attachBookingList" data-id="1"></div>
        <button class="btn-file" type="button" onclick="openFileInput(1)">사진추가</button>
        <input type="file" name="addFile" id="fileInput-1" class="file-input" style="display: none;" accept="image/*" multiple>
    </div>

    <div class="file-box" data-max-files="5" id="file-box-2">
        <div class="file-s-tit">사진올리기</div>
        <div class="file-list-box attachBookingList" data-id="2"></div>
        <button class="btn-file" type="button" onclick="openFileInput(2)">사진추가</button>
        <input type="file" name="addFile" id="fileInput-2" class="file-input" style="display: none;" accept="image/*" multiple>
    </div>

    <script>
        function openFileInput(id) {
            document.getElementById(`fileInput-${id}`).click();
        }

        function handleFiles(files, targetBox) {
            const maxFiles = parseInt(targetBox.closest('.file-box').dataset.maxFiles);
            const currentFiles = targetBox.querySelectorAll('.img-box').length;
            const totalFiles = currentFiles + files.length;

            if (totalFiles > maxFiles) {
                alert(`최대 ${maxFiles}개의 파일만 업로드할 수 있습니다.`);
                return;
            }

            Array.from(files).forEach(file => {
                if (!file.type.startsWith('image/')) return;

                const reader = new FileReader();
                reader.onload = function(e) {
                    const newImage = document.createElement('div');
                    newImage.classList.add('img-box');
                    newImage.innerHTML = `<img src="${e.target.result}" alt="이미지">`;
                    targetBox.appendChild(newImage);
                };
                reader.readAsDataURL(file);
            });
        }

        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('.file-input').forEach(input => {
                input.addEventListener('change', function(event) {
                    const targetBox = document.querySelector(`.file-list-box[data-id="${this.id.split('-')[1]}"]`);
                    handleFiles(event.target.files, targetBox);
                    this.value = '';
                });
            });

            document.querySelectorAll('.file-list-box').forEach(box => {
                box.addEventListener('dragover', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    this.classList.add('over');
                });

                box.addEventListener('dragleave', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    this.classList.remove('over');
                });

                box.addEventListener('drop', function(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    this.classList.remove('over');
                    const files = event.dataTransfer.files;
                    handleFiles(files, this);
                });
            });
        });
    </script>
</body>
</html>

 

// 파일 업로드 처리 함수
function handleFileUpload(event, listBoxSelector) {
  const files = event.target.files;
  const fileListBox = document.querySelector(listBoxSelector);

  // 파일 목록 표시
  for (let i = 0; i < files.length; i++) {
    const fileItem = document.createElement('div');
    fileItem.classList.add('file-item');
    fileItem.textContent = files[i].name;
    fileItem.draggable = true;
    fileItem.addEventListener('dragstart', handleDragStart);
    fileItem.addEventListener('dragover', handleDragOver);
    fileItem.addEventListener('drop', handleDrop);
    fileListBox.appendChild(fileItem);
  }
}

// 드래그 시작 이벤트 핸들러
function handleDragStart(event) {
  event.dataTransfer.setData('text/plain', null);
  event.currentTarget.classList.add('dragging');
}

// 드래그 오버 이벤트 핸들러
function handleDragOver(event) {
  event.preventDefault();
  event.currentTarget.classList.add('drag-over');
}

// 드롭 이벤트 핸들러
function handleDrop(event) {
  event.preventDefault();
  event.currentTarget.classList.remove('drag-over');

  const draggedItem = document.querySelector('.dragging');
  const dropTarget = event.currentTarget;
  const fileListBox = event.currentTarget.parentNode;

  // 파일 목록 순서 변경
  fileListBox.insertBefore(draggedItem, dropTarget);
}

// 파일 추가 버튼 클릭 이벤트 핸들러
document.querySelectorAll('.btn-file').forEach(button => {
  button.addEventListener('click', () => {
    const fileInput = button.parentNode.querySelector('.file-input');
    fileInput.click();
  });
});

// 파일 선택 이벤트 핸들러
document.querySelectorAll('.file-input').forEach(input => {
  input.addEventListener('change', (event) => {
    const listBoxSelector = event.target.parentNode.parentNode.querySelector('.file-list-box').classList[1];
    handleFileUpload(event, `.${listBoxSelector}`);
  });
});
728x90
728x90

canvas 를 이용해 낙서장 만들기 스크립트 및 html 태그 예시

 

html5 에 있는 canvas 태그를 이용하면 낙서장, 서명란 등을 간단하게 구현 할 수 있다. 스크립트를 적용하면 마우스나 터치팬에서도 잘 작동 한다. 처음 작업 했을때는 width:100px, height:100px 처럼 사이즈가 정의된 경우만 적용이 되어서 반응형을 못맞췄었는데 스크립트로 화면의 가로 세로 사이즈를 체크해서 가로 세로 값을 계산해서 canvas.width 에 적용해 주면 화면이 유동적이어도 적용이 잘 된다. 단 임의로 화면 사이즈를 줄였을때도 적응이 되게 하려면 리사이징 시에도 가로세로 값을 체크 하는 함수를 한번 더 호출해주어야 한다. 아래 예시는 부분은 작업 되어 있지 않다. 

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Drawing and Erasing</title>
    <style>
        canvas {
            border: 1px solid black;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <button id="toggleEraser">Toggle Eraser</button>
    <canvas id="drawCanvas"></canvas>
    <script src="script.js"></script>
</body>
</html>

 

const canvas = document.getElementById('drawCanvas');
const context = canvas.getContext('2d');
let isDrawing = false;
let isErasing = false;

function resizeCanvas() {
    canvas.width = canvas.offsetWidth;
    canvas.height = canvas.offsetHeight;
}

function getPosition(e) {
    if (e.touches) {
        return {
            x: e.touches[0].clientX - canvas.offsetLeft,
            y: e.touches[0].clientY - canvas.offsetTop
        };
    } else {
        return {
            x: e.clientX - canvas.offsetLeft,
            y: e.clientY - canvas.offsetTop
        };
    }
}

function startDrawing(e) {
    e.preventDefault();
    isDrawing = true;
    draw(e);
}

function stopDrawing() {
    isDrawing = false;
    context.beginPath();  // 경로 초기화
}

function draw(e) {
    if (!isDrawing) return;

    const pos = getPosition(e);

    context.lineWidth = 10;
    context.lineCap = 'round';

    if (isErasing) {
        context.globalCompositeOperation = 'destination-out';
        context.strokeStyle = 'rgba(0,0,0,1)';
    } else {
        context.globalCompositeOperation = 'source-over';
        context.strokeStyle = '#000';
    }

    context.lineTo(pos.x, pos.y);
    context.stroke();
    context.beginPath();  // 새로운 경로 시작
    context.moveTo(pos.x, pos.y);
}

function toggleEraser() {
    isErasing = !isErasing;
    const button = document.getElementById('toggleEraser');
    button.textContent = isErasing ? 'Switch to Draw' : 'Switch to Erase';
}

function clickDrawReset() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}

canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('touchmove', function (e) {
    e.preventDefault();
    draw(e);
});
canvas.addEventListener('touchend', stopDrawing);

document.getElementById('toggleEraser').addEventListener('click', toggleEraser);
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
728x90
728x90

특정 div 일부만 이미지로 저장하고 싶다면 html2cnvas  스크립트로 가능하다. pdf 로 다운로드는 구현이 조금 까다롭지만 스크립트로는 아래와 같이 코드 몇줄만 적으면 다운로드 파일 이름도 정의할 수 있고 jpg 파일로 저장 할수 있다.

<div class="com_btn_wrap right"> 
  <button class="com_btn l point btn_download" type="button" onclick="downloadImage()"><i class="icon svg_icon before icon_download white"></i>다운로드</button>
</div>

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script>
  function downloadImage() {
    // Get the target div
    var divToCapture = document.getElementById('mindScoreBody');

    // Use html2canvas to capture the div as an image
    html2canvas(divToCapture).then(function (canvas) {
      // Convert the canvas to a data URL
      var imageData = canvas.toDataURL('image/jpeg');

      // Create a link element and trigger a download
      var link = document.createElement('a');
      link.href = imageData;
      link.download = 'output.jpg';
      link.click();
    });
  }
</script>
728x90
728x90

영상이 종료 되는 시점을 체크 하는 스크립트 

 

if ($("#video").prop("ended") && !alertTriggered) 

 

video 태그에 아이디 값을 주고 ended 로 값이 매칭 될때 다른 영상을 플레이 할 수 있다. jquery 를 사용한 문법이라 jqeury 를 기본적으로 가지고 있어야 가능한 문법이다.

 

<div class="wrap">
	<div class="video-wrap">
		<video id="video" autoplay controls muted playsinline class="normal-video">
			<source id="normalloop1" src="영상주소" type='video/mp4'/>
		</video>
	</div>
	<div class="video-wrap">
		<video id="video2" controls muted playsinline class="normal-video">
			<source id="normalloop2" src="영상주소" type='video/mp4'/>
		</video>
	</div>
</div>

<style>
.wrap {position:absolute;top:0;left:0;width:100%;height:100%;}
.video-wrap {position:relative;height:50%;background:#000;overflow:hidden;}
.video-wrap video {position:absolute;top:0;left:50%;transform:translateX(-50%);height:100%;}
       
@media all and (max-width:1199px){
	.video-wrap video {width:100%;height:auto;top:50%;left:0;transform:translate(0,-50%);}	
}
</style>
<script src="/application/assets/common/js/jquery-1.12.4.min.js"></script>
<script>
$(window).on("load",function(){
	
    //윈도우 로드 시 비디오 플레이
    $("#video").get(0).play();
	
    //작동하는지 콘솔창에서 확인
    console.log("test");
})
    
var alertTriggered = false; // Flag variable to track whether the alert has been triggered

setInterval(function () {
    if ($("#video").prop("ended") && !alertTriggered) {

        //영상종료 후 어떤행동을 할건지 작성
        alert('영상종료');

        alertTriggered = true; // Set the flag to true to indicate that the alert has been triggered
    }
}, 200);
</script>
728x90
728x90

location.reload 함수는 새로고침에 많이 사용하지만 이렇게만 하면 ? 물음표 뒤에 파라메터값은 하나도 들어 오지 않는다. 그래서 가끔 당황하게 되는데 아래와 같이 params 를 달고 가는 방법을 메모해 본다. 

//location.href=location.reload();

var url = new URL(window.location.href);
var params = new URLSearchParams(url.search);
location.href = url.origin + url.pathname + '?' + params.toString() + url.hash;

 

 

728x90
728x90

selectableDays 배열 값으로 원하는 요일만 선택이 가능하게 설정 하는 스크립트 예제 

 

요일은 일요일이 0 부터 시작 되게 해서 작성 하면 됩니다. 

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
</head>
<body>

<input type="text" id="datepicker">

<script>
$(document).ready(function() {
  var selectableDays = [1, 2, 3]; // 월요일, 화요일, 수요일
  
  $("#datepicker").datepicker({
    beforeShowDay: function(date) {
      var day = date.getDay();
      
      if ($.inArray(day, selectableDays) >= 0) {
        return [true];
      }
      
      return [false];
    }
  });
});
</script>

</body>
</html>

beforeShowDay 함수에서 date 에 true, false 로 속성을 주어서 수정할 수 있는 부분으로 유용하게 사용 할수 있습니다. 

jquery 사용이 많이 줄어들고 있는 추세 지만 달력 스크립트는 간단하게 붙이고 수정이 용이해서 아직 까지는 장점도 있는거 같습니다. 

728x90

+ Recent posts